Inside Harness Engineering: Core Architecture for AI Coding Agents
This article breaks down Harness engineering, the framework that turns a capable language model into an AI coding agent that can work inside real projects. It explains why a model alone is not enough, why Harness behaves like an operating-system-like layer, and how production-oriented coding agents combine a query engine, agentic loop, context management, streaming execution, hooks, permissions, multi-agent coordination, observability, and evaluation. The goal is to show how to build agent systems that are stable, safe, and measurable rather than simply waiting for a stronger model.
1. Introduction: From “a model can write code” to “an agent can deliver code”
Over the past two years, the capability boundary of large language models has continued to expand. Models can now read complex code, write usable functions, and produce seemingly reasonable implementation plans. However, when a model is placed directly into a real project, several structural problems still appear:
- One-shot code generation often stops at “it runs,” without test validation or iterative correction.
- The context window cannot hold an entire codebase, so the model may guess when it cannot see the relevant files.
- Once a multi-step task goes wrong, the model often struggles to recover on its own.
- Executing commands and modifying files lack clear boundary controls, making the risk unacceptable.
The way to solve these problems is not to wait on the model side for a stronger version. Instead, it is to build an engineering framework around the model. This framework is what the industry calls a Harness. The model is responsible for “judgment and thinking,” while the Harness is responsible for “constraint, orchestration, and execution.” Only when the two are combined can long-chain, high-complexity tasks be completed.
Public technical breakdowns show that mainstream AI coding agents are far more complex internally than “a prompt plus a loop.” They typically include a query engine, a tool system, a command system, external protocol integration, permission governance, session bridging, and a plugin and skill system. There are many engineering decisions worth studying in these implementations. Using the widely recognized design philosophy of Harness as the framework, this article systematically breaks down the core components, key designs, and implementation methods of Harness engineering, with reference to the architectural characteristics of this class of AI coding agents.
2. What Is Harness: Equipment That Puts the Model into a Working State
The original meaning of “Harness” is horse gear: the complete set of equipment needed to put a horse into a working state. That equipment includes several parts:
- Reins: transmit the rider’s instructions and control direction.
- Bit: allows instructions to be perceived accurately by the horse.
- Harness straps: transfer the horse’s pulling force to the carriage.
- Saddle and girth: secure the equipment and distribute load.
Applied to AI, Harness engineering means giving the model a set of rules and a framework, constraining model behavior so that it works according to the logic and policies defined by developers. The purpose is to complete tasks that the model alone cannot complete when “running naked.”
A useful analogy is an operating system. A bare model is like a high-performance CPU: it has strong compute capability, but without memory management it cannot run programs for long, without a file system it cannot read or write data, and without a permission mechanism it cannot control risk. Harness takes on several responsibilities similar to an operating-system kernel:
| Operating-system responsibility | Corresponding design in Harness |
|---|---|
| Process scheduling | Agentic main loop and parallel tool scheduling |
| Virtual memory and paging | Context compression and layered memory |
| System calls | Tool calls |
| Permission control | Tool-level permissions and approval |
| Hooks / interceptors | Hook mechanism |
| Multitasking | Subagents and background tasks |
The evolution from prompt → skills → Harness engineering makes the concept clearer:
- Prompt stage: The model output is constrained through a carefully designed instruction. This approach is lightweight, but its constraint power is limited and it struggles with multi-step tasks.
- Skills stage: Domain knowledge, workflows, and best practices are packaged into reusable “skill packs.” The model loads and follows them when needed, such as code style rules, test-case templates, or release checklists. Skills address “how to do it more professionally.”
- Harness engineering stage: The model, tools, state, permissions, context governance, and execution scheduling are integrated into a sustainable engineering system. It addresses “how to complete tasks in a stable, safe, and measurable way.”
In terms of capability layers, the three are nested: Harness includes the skill system, and the skill system depends on prompts. They are not mutually exclusive; together they form the complete structure of agent capability.
3. Overall Architecture: Multi-Module Collaboration Beyond a Single Agent
On the surface, an AI coding agent looks like a single intelligent agent running in a command line. Internally, however, it is a system composed of multiple collaborating modules. The main modules and their responsibilities are as follows:
| Module | Core responsibility | Design considerations |
|---|---|---|
| Query engine | Handles user input, calls the model, and runs the main loop for tool-call execution | Streaming event-driven design, state management, termination conditions |
| Tool system | Encapsulates file reading/writing, code editing, terminal execution, code search, web retrieval, subtask dispatch, task-list management, and other capabilities | Unified schema definitions and independent permission checks |
| Command system | Provides common operations through slash commands, such as viewing context, switching models, or configuring memory | Commands are product interaction entry points and must be discoverable and composable |
| External protocol integration | Connects to external data sources and tools through a standardized protocol | Client management, tool-name conflict handling, permission inheritance |
| Permission system | Authorizes, approves, and records tool calls at different granularities | Default deny, risk classification, auditability |
| Session bridging | Handles communication and synchronization between local sessions and remote/managed sessions | Session-state consistency and resume capability |
| Plugin and skill system | Supports third-party extensions and reusable professional workflows | Sandbox isolation, signature verification, version management |
| Subagents and background tasks | Dispatches sub-tasks with independent contexts and runs long-running background tasks | Context isolation and result aggregation |
One detail worth noting is that when the number of tools reaches dozens, tools are no longer just a “function list.” They become a system that requires unified governance, including schema definitions, description text, permission declarations, and error conventions. Similarly, when the number of commands reaches hundreds, the command system itself becomes a subsystem that requires version management and interaction design. This shows that the complexity of Harness engineering grows rapidly as capabilities expand. Architecture design must consider extensibility in advance.
4. Core Design 1: A Query Engine Driven by Async Generators
4.1 Why “streaming” Instead of “returning at once”
A complete interaction between an agent and a model is not a single request-response round trip. It is a sequence of events:
Receive user input → model outputs text token by token → model decides to call a tool → tool executes and returns a result → result is written back into context → model continues output → may call another tool → finally provides a conclusion
If this were implemented using the traditional “wait for the complete response” approach, there would be three problems:
- Users cannot see intermediate progress, so perceived latency is high.
- Operations cannot be executed while the model is still generating output.
- Once any stage fails, the entire flow must restart.
Therefore, this type of system commonly uses an async generator as the core abstraction of the query engine. The main loop is no longer “call a function and get a return value.” It is a stream that can yield events one by one. Upper layers can consume and process events as they arrive.
The following is illustrative code simplified to explain the core mechanism:
// Illustrative code: event-stream skeleton for the query enginetype StreamEvent =| { type: 'text'; delta: string } // Text delta from the model| { type: 'thinking'; delta: string } // Reasoning delta| { type: 'tool_use_start'; id: string; name: string }| { type: 'tool_use_delta'; id: string; partialJson: string }| { type: 'tool_result'; id: string; content: unknown; isError?: boolean }| { type: 'compaction'; summary: string } // Context compaction event| { type: 'done'; result: string };async function* runQuery(input: string,ctx: AgentContext,signal: AbortSignal,): AsyncGenerator<StreamEvent> {ctx.state.appendUserMessage(input);while (true) {if (signal.aborted || shouldStop(ctx)) break; // Unified termination check// Stream model output and yield events as they arriveconst step = yield* streamModelTurn(ctx, signal);// No tool calls means the task is completeif (step.toolCalls.length === 0) {yield { type: 'done', result: step.text };break;}// Tool calls exist: schedule execution and feed results back into contextconst results = yield* scheduleToolCalls(step.toolCalls, ctx, signal);ctx.state.appendToolResults(results);// Trigger a compaction strategy when context usage approaches the limitif (ctx.memoryUsage().ratio > ctx.config.compactThreshold) {yield* compactContext(ctx);}}}
4.2 Key design points
- Unified event types: All intermediate states, including text, reasoning, tool parameters, tool results, compaction, and completion, are exposed through a unified event structure. Whether the consumer is a terminal UI, a graphical UI, or an automation script, it can consume the event stream through the same protocol. This decouples the presentation layer from the engine layer.
- Incremental rather than snapshot-based: Text and reasoning are pushed as incremental events, and tool parameters are also parsed incrementally, as described later in this article. This allows the UI to show in real time what the model is doing.
- Interruptible: A unified cancellation signal, such as
AbortSignal, propagates interruption semantics across the entire call chain. When a user presses Esc, model connections, tool processes, and subtasks can also be terminated, avoiding resource leaks. - Resumable: The event stream itself can be persisted to disk, forming a replayable execution trace. This provides a foundation for debugging, auditing, and session recovery.
- Separation of state and stream: The async generator is responsible for producing events, while session state is maintained by a separate state object. This supports multi-turn interaction and allows state reconstruction after abnormal exits.
4.3 Engineering value
- Lower perceived latency: Users can immediately see the model begin responding instead of waiting through tens of seconds of silence.
- Strong composability: The same engine can drive interactive terminals, server-side interfaces, and batch jobs without rewriting logic for each form.
- Easier policy injection: The event stream is naturally suitable for inserting cross-cutting logic such as compression, hooks, and logging in intermediate layers, without intruding into the core loop.
5. Core Design 2: Agentic Loop and Explicit State Management
5.1 The Loop Structure Behind while(true)
The main loop of the query engine can be summarized as while(true), but this does not mean unrestricted execution. Its typical form is an “observe-decide-act” loop:
+---------------------------------------------+| Read current context || v || Call model and get a decision (text / tools)|| v || Execute tool calls (under permissions/Hooks)|| v || Write tool results back into context || v || Need to compress context? --yes--> Compact || v || Termination condition met? --yes--> Return |+---------------------------------------------+no|+------> Go back to the top
This structure borrows from the ReAct paradigm: Reasoning + Acting. The model first “thinks,” then “acts,” and adjusts its next plan based on feedback from the environment. Compared with traditional one-shot generation, its advantage is that it can handle tasks requiring multiple iterations, such as “first locate relevant files, then modify code, then run tests, and continue fixing based on failure information.”
5.2 Termination Conditions: Brakes That Prevent Runaway Behavior
The biggest risk of while(true) is that it may never exit. A mature Harness must define complete termination conditions, usually including:
| Termination type | Trigger condition | Handling |
|---|---|---|
| Normal completion | The model no longer initiates tool calls and outputs a final answer | Return the result |
| User interruption | The user actively stops the task | Cancel all ongoing operations |
| Error termination | Tool execution encounters an unrecoverable error and still fails after retries | Report the error and provide suggestions |
| Resource limit | The maximum number of turns, maximum tokens, or maximum runtime is reached | Preserve state and notify the user |
| Loop detection | The model repeatedly initiates the same or highly similar tool calls | Interrupt the loop and ask the model to change strategy |
| Context overflow | The context still exceeds the model’s processing capacity after compression | Suggest splitting the task or handing it off |
Loop detection is especially important. When the model encounters difficulty, it may repeatedly execute the same failing operation, such as repeatedly reading the same file. Without intervention, the system will continue to consume compute and cost. In engineering practice, tool-call sequences are often compared using fingerprints. If many duplicate fingerprints appear in a short period, the system proactively interrupts and asks the model to adjust its strategy.
5.3 State Management: Making Memory Explicit
The state during agent execution at least includes the following categories:
- Session state: the sequence of messages between user and assistant, and the pairing of tool calls with their results.
- Task state: the current goal, completed steps, and pending items.
- Execution state: running tools and background processes, temporary files, and the working directory.
- Configuration state: model selection, permission level, hook configuration, and similar settings.
Explicit state management brings three benefits:
- Traceability: At any moment, the system can answer “What is the agent doing, and why is it doing it?”
- Recoverability: After a session is interrupted, execution can be resumed from persisted state.
- Constraintability: A state machine or rule set can restrict illegal transitions, such as preventing the model from reasoning further from a missing tool result.
A common practice is to introduce a task-list, or Todo, mechanism. Before starting a complex task, the model first splits the goal into several verifiable subtasks and records their status. Each time a step is completed, the list is updated. This reduces the chance of the model going off track and allows users to see progress in real time.
6. Core Design 3: Context Management with a Five-Layer Compression Strategy
6.1 Why Context Must Be Managed
The context window is the most scarce resource for an agent. There are three reasons:
- Relevant files in a code repository often exceed the window capacity.
- Tool outputs, such as terminal logs, search results, and test reports, can be very long.
- Historical messages accumulated from multiple iterations keep growing.
When context approaches the limit, three consequences occur:
- The model must be given a truncated input, causing loss of critical information.
- Each request carries more input tokens, increasing cost and latency.
- In overly long context, the model is prone to “middle forgetting,” meaning it mainly remembers the beginning and the end.
Therefore, context management is not about “filling the window as much as possible.” It is about “retaining the most valuable information within a limited budget.” These systems commonly adopt a layered compression strategy, escalating level by level according to information loss, from low to high. It can be summarized as five layers.
6.2 Five-Layer Compression Strategy
Layer 1: Tool-output trimming and pagination
- Apply line-count or byte-count limits to overly long tool outputs.
- Return only the parts relevant to the current task, such as relevant search snippets rather than an entire file.
- For terminal command output, keep the head and tail and omit the middle.
Example:
[Output too long and has been truncated: 2000 lines total; showing first 50 and last 50 lines]...
Layer 2: Downgrade old tool results into summaries
- When a tool result has already been “consumed,” meaning the model has already made a decision based on it, replace it with a short summary.
- For example, replace the full content of “read a file” with “Read src/foo.ts (300 lines, containing 3 exported functions).”
- The original content remains stored on disk or in session storage and can be loaded on demand if needed.
Layer 3: Reasoning-process folding
- The model produces a large amount of intermediate reasoning during thinking, such as “possible causes are A, B, and C; first rule out A…”
- Once this reasoning has reached a clear conclusion, the entire reasoning segment can be compressed into a single conclusion.
- Keep the conclusion and omit the derivation process.
Layer 4: Subtask delegation (forked agent)
- For tasks requiring extensive information gathering, such as “find all locations that call a certain function,” dispatch a subagent to complete the work.
- The subagent has an independent context and can freely search and read files within it.
- After completion, only the conclusion is returned to the main agent, such as a list of involved files and line numbers.
- This way, the main agent’s context gains only the conclusion instead of being overwhelmed by the exploration process.
Layer 5: Session-level compaction
- When context approaches the limit, rewrite the entire session history.
- This is usually done by a dedicated compaction agent: it reads the full history and outputs a structured summary.
- The summary usually includes the user’s core request, completed work, key decisions, involved files and code snippets, unfinished items, and encountered problems.
Example summary structure:
summary:goal: Fix session expiration in the login module under a specific browsercompleted:- Located cookie expiration logic in session.ts- Changed it to sliding expiration and added unit testskey_decisions:- Use sliding expiration instead of extending a fixed lifetime- Preserve the original field for compatibility with older clientsfiles_touched:- src/auth/session.ts (modified)- tests/auth/session.test.ts (added)pending:- Run the full test suite and check type definitionsopen_issues:- Browser compatibility needs to be verified in a pre-release environment
6.3 Key principles of compression strategies
- Progressive: Prefer lower-loss compression methods first and escalate to higher-loss methods only when necessary.
- Traceable: Compression must not lose recoverability. Summaries should retain key references such as file paths, line numbers, commands, and error messages.
- Idempotent: A compressed summary should be able to serve as the starting point for a new session and continue work, rather than becoming a dead end.
- Observable: Each compression should record the size before and after compression, the compression level, and the trigger reason, making it easier to evaluate strategy effectiveness.
6.4 Common mistakes
- Over-compression: Discarding key information to save tokens, causing the model to “lose memory” and repeat completed work.
- Improper timing: Compressing while the model is about to use a tool result, making that result unavailable.
- Uncontrolled summary quality: The compaction agent itself may produce unstable output and miss key decisions.
- Ignoring compression cost: Compression itself is a model call, so its cost and benefit should be evaluated.
7. Core Design 4: Streaming Concurrent Execution
7.1 “Tools start before the model finishes speaking”
In traditional implementations, tool execution must wait until the model has fully output its response, and the tool-call structure is then parsed before execution. In a streaming architecture, tool-call parameters are generated token by token. The Harness can receive parameters while parsing them incrementally. Once the parameters of a tool call are complete, for example when an incremental JSON parser determines that the parameter structure is closed, execution can start immediately without waiting for subsequent tool descriptions to finish.
The direct benefit is lower perceived latency. For common chains such as “read file → edit code → run tests,” tool execution and model output can overlap. The time at which users see progress is significantly advanced.
7.2 Concurrent scheduling strategies
Tool execution is not simply “run everything in parallel.” Reasonable scheduling must consider dependencies and resource conflicts:
| Scenario | Scheduling strategy | Reason |
|---|---|---|
| Multiple independent read operations | Parallel | No side effects and no conflicts |
| Writing a file and reading the same file | Sequential | Avoid reading intermediate state |
| Two write operations targeting the same file | Sequential with locking | Avoid overwrite conflicts |
| Terminal command and file modification | Depends on dependency | The command may depend on file state |
| Network requests | Parallel with timeouts | Improve throughput and avoid blocking |
Example scheduling logic:
// Illustrative code: schedule tool calls based on dependenciesfunction scheduleToolCalls(calls: ToolCall[], ctx: AgentContext) {const groups = groupByDependency(calls, ctx.fileLocks);return groups.map((group) =>group.independent? Promise.all(group.calls.map((c) => executeTool(c, ctx))) // parallel: executeSequentially(group.calls, ctx), // sequential);}
7.3 Engineering details of streaming parsing
- Incremental JSON parsing: Tool parameters generated by the model are gradually generated JSON strings. An incremental parser is needed to determine when a parameter structure is complete.
- Front-loaded parameter validation: After parsing completes, perform schema validation immediately, including types and required fields. If validation fails, reject execution and return an error to the model to avoid invalid calls.
- Cancellation propagation: When one tool fails, pending parallel calls that have not started should be canceled. This avoids the situation where an error has already occurred but more useless operations are still running.
- Timeout and retry: Long-running tools, such as build or test commands, should have timeout and retry policies to prevent a single tool from dragging down the entire session.
- Streaming output display: Tool execution output, such as test logs, should also be streamed incrementally to the user rather than returned all at once after execution completes.
7.4 Benefits and tradeoffs
- Benefits: Lower first-byte latency, higher parallel throughput, and better user experience.
- Tradeoffs: Increased scheduler complexity, the need to handle concurrent-write conflicts, more error-handling paths, and higher debugging difficulty.
- Recommendation: For scenarios with frequent read-write conflicts, prioritize correctness before considering higher parallelism.
8. Core Design 5: Hook Architecture
8.1 Why Hooks Are Needed
The model is nondeterministic, but many types of logic must execute deterministically, such as:
- Safety checks before executing dangerous commands.
- Format validation before writing a file.
- Environment initialization at session start.
- Logging and metric reporting after tool execution.
If these logics depend on the model “remembering to do them,” reliability cannot be guaranteed. The hook architecture is designed to solve this problem: it allows developers to inject deterministic code at key points in the agent lifecycle.
8.2 Lifecycle nodes
| Node | Trigger timing | Typical use |
|---|---|---|
| SessionStart | When the session starts | Load project configuration and initialize the environment |
| UserPromptSubmit | After user input is submitted | Mask sensitive information and validate intent |
| PreToolUse | Before tool execution | Permission checks, dangerous-operation interception, parameter completion |
| PostToolUse | After tool execution | Logging, formatting, result validation |
| PreCompact | Before compaction | Protect critical context from being compressed away |
| SubagentStart | Before a subagent starts | Inject subtask context |
| Stop | When the session ends | Aggregate statistics and clean up resources |
8.3 Design points
- Declarative configuration: Hooks are usually declared in a configuration file, such as “run a certain script at the PreToolUse node,” decoupling them from core logic.
- Deterministic return: Hook output should clearly express one of three decisions: allow, deny, or modify. For example, it may return
{ decision: 'deny', reason: '...' }. - Timeout and fault tolerance: Hook execution should have a timeout, and the behavior after timeout, whether to block or allow, must be clear.
- Order and idempotence: Multiple hooks execute in declaration order and should be idempotent to avoid side effects from repeated execution.
- Testability: Hooks should be independently testable and should not depend on the agent runtime.
Example:
# Illustrative configuration: block destructive operations before tool executionhooks:PreToolUse:- matcher: "Bash"command: "./scripts/guard-destructive.sh"timeout: 5000on_timeout: deny # Deny on timeout to preserve safety
9. Core Design 6: Multi-Agent Collaboration and Background Tasks
9.1 Subagents: A Key Method of Context Isolation
When a task requires extensive information gathering, the main agent’s context can quickly be filled by the “exploration process.” The subagent pattern solves this problem:
- The main agent dispatches a subtask, such as “find all locations that call
parseConfigand analyze their parameters.” - The subagent has an independent context window and can freely search, read, and analyze within it.
- After the subagent completes the task, it returns only the conclusion to the main agent.
- The main agent’s context gains only one conclusion, while the exploration process is isolated inside the subagent.
This is a typical “context isolation” design, consistent with the “process isolation” idea in operating systems.
9.2 Background Tasks: Making Long Operations Asynchronous
For long-running operations, such as running a full test suite, building a project, or executing deployment scripts, they can run in the background. The main agent can continue handling other tasks and retrieve results after the background task completes. This avoids one long task blocking the entire session.
9.3 Multi-Agent Collaboration Orchestration
In more complex scenarios, multiple agents can work together, such as:
- Division of labor: One agent writes code, another writes tests, and a third performs code review.
- Pipeline: The output of an upstream agent becomes the input of a downstream agent, forming a processing chain.
- Debate: Multiple agents analyze a problem from different perspectives, and a final decision is aggregated.
9.4 Tradeoffs and recommendations
- Context isolation vs. information-transfer cost: Subagents isolate context, but the main agent cannot directly see the subagent’s exploration details. Information transfer depends on the quality of the conclusion.
- Reliability of result aggregation: Conclusions from multiple agents may conflict, requiring a clear arbitration mechanism.
- Debugging complexity: Multi-agent systems have longer execution traces and require stronger observability.
- Recommendation: Use a single agent by default. Introduce a multi-agent structure only when a task truly requires large-scale parallel exploration or information isolation.
10. Core Design 7: Permission System and External Protocol Integration
10.1 Tool-Level Permission Control
The first security question facing a Harness is: which operations can be executed automatically, and which require human confirmation? The permission system is usually designed at the tool granularity:
| Risk level | Example operations | Default policy |
|---|---|---|
| Read-only | Read files, search code | Automatically allowed |
| Low-risk write | Modify files inside the project directory | Automatically allowed, configurable |
| Medium risk | Execute build or test commands | Automatically allowed or confirmation on demand |
| High risk | Delete files, modify system configuration, execute network requests | Require human confirmation |
| Extremely high risk | Modify permission configuration, execute scripts from unknown sources | Denied by default |
Key design points include:
- Default deny: Operations without explicit authorization are denied.
- Risk classification: Classify operations according to impact scope and reversibility.
- Approval support: High-risk operations should support human approval workflows.
- Auditability: All permission decisions should be recorded for later traceability.
10.2 External Protocol Integration
Through a standardized protocol, such as the Model Context Protocol (MCP), an agent can connect to external data sources and tools, such as databases, code-hosting platforms, and monitoring systems. Engineering considerations include:
- Tool-name conflict handling: Tools from different sources may have the same name, so namespace isolation is required.
- Permission inheritance: External tools should follow the same permission policies as built-in tools.
- Connection management: Long connections, timeouts, and reconnection mechanisms.
- Version compatibility: Backward compatibility when the protocol version is upgraded.
11. Engineering Decisions Driven by Operational Data
A notable characteristic of Harness engineering is that design decisions are not based on guesswork. They are continuously optimized using operational data.
11.1 Three observability pillars
- Trace: Record the complete trajectory of each agent run, including model calls, tool calls, context compaction, and hook execution.
- Log: Record key events and error information.
- Metric: Aggregate key indicators for trend analysis and alerting.
11.2 Key metrics
| Metric | Meaning | Use |
|---|---|---|
| Task success rate | Proportion of completed tasks | Evaluate overall capability |
| Average number of turns | Number of model calls required to complete a task | Evaluate efficiency |
| Token consumption distribution | Input/output token consumption | Evaluate cost |
| Context compaction trigger rate | Proportion of sessions that trigger compaction | Evaluate context pressure |
| Tool error rate | Proportion of failed tool calls | Evaluate tool quality |
| Human intervention rate | Proportion of tasks requiring manual intervention | Evaluate automation level |
| Latency distribution (P50/P95) | Percentile values of response latency | Evaluate user experience |
11.3 Data-driven decision loop
- Collect: Gather the above metrics from real runs.
- Analyze: Identify bottlenecks, such as “the compaction trigger rate is too high, indicating that the context management strategy needs adjustment.”
- Experiment: Design improvement plans, such as adjusting compaction thresholds or optimizing tool descriptions, and run A/B tests.
- Validate: Use an evaluation set to validate the effect of improvements and ensure no regressions are introduced.
- Roll out: After validation, release the change to all users and continue monitoring.
11.4 Importance of the evaluation set
An evaluation set is a set of standardized test cases used to measure agent performance on specific tasks. It usually includes:
- Inputs and expected outputs for typical tasks.
- Boundary cases and exceptional scenarios.
- Regression tests to ensure new changes do not break existing capabilities.
Without an evaluation set, Harness optimization can only rely on subjective judgment and is difficult to quantify.
12. Summary of Design Patterns Worth Borrowing
| Design pattern | Core idea | Applicable scenario |
|---|---|---|
| Event-stream driven | Use async generators to abstract agent execution as an event stream | Scenarios requiring streaming interaction |
| Explicit state machine | Explicitly model task state instead of implicitly relying on context | Complex multi-step tasks |
| Layered compression | Compress context level by level according to information loss | Long sessions and large codebases |
| Context isolation | Use subagents to isolate the exploration process and return only conclusions | Information-intensive tasks |
| Dependency-aware scheduling | Decide whether to execute in parallel or sequentially based on dependencies | Multi-tool collaboration |
| Declarative hooks | Inject deterministic logic at lifecycle nodes | Security, auditing, formatting |
| Permission classification | Assign authorization policies according to risk levels | Scenarios involving write operations |
| Data-driven iteration | Use metrics and evaluation sets to drive optimization decisions | Long-term evolving systems |
13. Adoption Path and Common Mistakes
13.1 Phased adoption
Harness engineering should not be implemented all at once. It is recommended to proceed in stages:
- Stage 1: Minimal viable loop
- Implement a basic agentic loop: model call → tool execution → result write-back.
- Implement a few core tools, such as read file, write file, and execute command.
- Implement basic permission control.
- Stage 2: Context governance
- Introduce a layered compression strategy.
- Establish session persistence and recovery mechanisms.
- Stage 3: Efficiency and extensibility
- Introduce streaming parsing and parallel scheduling.
- Implement a hook mechanism to support external extensions.
- Stage 4: Multi-agent and evaluation system
- Introduce subagents and background tasks.
- Establish an evaluation set and observability system.
13.2 Common mistakes
- Mistake 1: Equating Harness with prompts
- Prompts are only one part of Harness. True Harness is a complete system including tools, state, permissions, and context.
- Mistake 2: Over-pursuing automation
- High-risk operations should retain a human-confirmation step rather than aiming for “fully automatic.”
- Mistake 3: Ignoring context cost
- Context is not free. Every compression and every tool call has a cost and should be evaluated quantitatively.
- Mistake 4: Lacking an evaluation system
- Optimizing a Harness without an evaluation set is essentially blind tuning.
- Mistake 5: Introducing multi-agent systems too early
- Multi-agent systems increase complexity and should be introduced only when a single agent truly cannot meet the requirements.
14. Security, Compliance, and Cost
While Harness engineering brings efficiency improvements, it also introduces new risk surfaces:
- Security risk: Agents may execute dangerous operations, such as deleting files or modifying system configuration. Both the permission system and hooks are needed for defense.
- Compliance risk: Code and data may involve sensitive information. It must be ensured that they are not exposed to locations that should not access them.
- Cost risk: Long sessions, multi-turn iterations, and subagents all increase token consumption. Cost budgets and monitoring are required.
- Reliability risk: Model output uncertainty may cause an agent to fall into a loop or produce incorrect modifications. Termination conditions and rollback mechanisms are needed.
Recommendations during implementation:
- Establish clear permission policies and audit logs.
- Add human-confirmation steps for sensitive operations.
- Establish cost monitoring and alerting mechanisms.
- Provide rollback capability for critical operations.
- Conduct regular security assessments and drills.
15. Conclusion
Harness engineering is critical infrastructure for moving intelligent agents from “demo” to “production.” It determines whether model capabilities can be converted into actual output in a stable, safe, and controllable way.
From an async-generator-driven query engine, to a while(true) agentic loop, to a five-layer compression strategy, streaming concurrent execution, hook architecture, multi-agent collaboration, permission governance, and data-driven iteration, these designs together form a complete Harness. Behind them is a common principle:
Treat the model as an “uncertain, limited-capability component,” and use engineering methods to constrain its behavior, supplement its capabilities, and measure its performance.
For teams building AI coding agents or similar systems, understanding these design patterns has more long-term value than chasing improvements in a single model. Models will keep iterating, but the engineering principles of Harness are relatively stable.
