Building QuECTO: From Minimal Agent Harness to Evaluable Coding Agent
— 24 min read
Part two of the QuECTO build series. Start with What Is an AI Agent Harness? if you want the conceptual map first.
The first post defined the harness as the software layer that turns a language model into an acting system: context, tools, state, policies, verification, recovery, and budgets. Since then, QuECTO has moved from a compact proof of concept into a real coding-agent runtime. The interesting part is not just the feature list. It is the order in which the system had to grow.
QuECTO started with a deliberately tiny question:
How much agent infrastructure can we build before the harness stops feeling minimal?
The answer, so far, is more than I expected: a 1.3 MB core, a 3.5 MB coding agent, no async runtime, first official releases, native reasoning controls, verification, persistence, OpenTelemetry, MCP, subagents, multimodal image input, and a native Rust evaluation harness.
That sounds like a lot. The design pressure was to keep each piece small enough to remain observable.
The build arc
There are three phases hiding inside that diagram:
- Transport: make model calls boring, synchronous, inspectable, and vendor-neutral.
- Agency: add tools, approvals, verification, state, and UX without hiding the loop.
- Measurement: record enough traces to make behavior comparable across models, providers, policies, and releases.
The third phase is where a harness becomes research infrastructure.
Technical architecture
QuECTO's current architecture is a deliberately layered Rust workspace. The core crate stays a synchronous JSON transport primitive; the agent crate owns the control loop, policy, tools, state, rendering, and provider normalization; the eval crate runs the agent as an instrumented subject under repeatable tasks.
At the implementation level, the technical shape is:
| Boundary | Concrete mechanism | Reason it exists |
|---|---|---|
| Model transport | quecto_raw(url, headers, body) and quecto_stream(...) over blocking ureq | The core can carry arbitrary provider payloads without learning agent semantics. |
| Provider normalization | provider.rs maps OpenAI-compatible chat completions and Anthropic Messages into the same assistant/tool-call shape | Provider swaps become a controlled variable instead of a rewrite of the agent loop. |
| Control loop | agent.rs builds messages, calls the model, parses assistant output, dispatches tools, appends observations, and enforces max_steps | The agent remains a sequential, inspectable state machine. |
| Tool ABI | Tool { name, description, schema, run } plus registry filtering from flavor allow-lists | Built-ins, MCP tools, and future custom tools share one dispatch surface. |
| Safety gate | policy.rs decides `allow | ask |
| Edit semantics | apply_patch uses exact search/replace blocks, ambiguity rejection, line-ending preservation, and prior-content recording | File mutations are replayable and undoable rather than vague text edits. |
| Persistence | session.rs records messages, file changes, reasoning settings, and session metadata in SQLite | Chat, resume, diff, and undo are runtime features, not terminal scrollback. |
| Observability | recorder.rs emits JSONL TraceEvents; optional OTEL spans cover runs, steps, tool dispatches, completions, and reasoning traces | Eval graders can reason over turns, tokens, latency, and tool usage, not just final text. |
| Evaluation | quecto-eval runs task workspaces from eval.yaml, injects trace output, grades with scripts/telemetry/rubrics, and stores results | Harness changes can be compared across versions, models, providers, and policies. |
The core loop is still synchronous on purpose. Tool-call turns use buffered model responses because the harness needs the complete tool_calls array before dispatch; streaming deltas are useful for final text output, but partial function-call assembly is the least portable part of OpenAI-compatible local serving stacks. The optional MCP feature can bring in an async runtime for MCP clients, but the primary agent loop does not become async as a side effect.
There is a second important separation: tool-call transport and tool execution are different layers. A model response can use native tool calls or a text-based fallback protocol, but both normalize into the same internal tool call structure. Only after that does the policy layer decide whether the call is allowed, the registry route it, the sandbox constrain it, the session recorder persist it, and the renderer summarize it to the user.
That is the technical version of "minimal but observable": the harness avoids a large framework core, but it does not avoid boundaries. It names them.
How the lean-core decision was made
The important architectural move was made before the first implementation task: define the maximal harness, then compress against it.
QuECTO's docs/superpowers notes include a full coding-agent harness reference: CLI session, model adapter, instruction loader, repository context engine, tool registry, coding tools, agent loop, edit engine, sandbox, verification loop, session state, renderer, MCP, observability, and evaluation. Instead of putting that whole list into the quecto crate, the design asks a stricter question:
Which of these responsibilities must live in the smallest possible core?
The answer was exactly one: model transport.
That is why the core implementation plan was so constrained:
- Four public functions:
quecto_raw,quecto_stream,quecto_to,quecto - Two source files:
src/lib.rsandsrc/main.rs - Two direct dependencies:
ureqandserde_json - No
tokio - No
reqwest - No CLI framework
- No custom error enum
- No typed provider abstraction in the core
- Public
serde_json::Valueinstead of a bespoke model-response struct
Those constraints are not arbitrary size golf. They are architectural pressure. serde_json::Value stays in the public API because quecto_raw is supposed to carry any provider-shaped JSON body and return the complete JSON response. If an agent wants tool calls, usage metadata, reasoning traces, or provider-specific fields, the core should preserve them rather than prematurely normalize them away.
Looking back through the specs, I can see the lean design came from a handful of repeated decisions:
| Lean-harness move | What it means in QuECTO |
|---|---|
| Compress against the maximal system | I first described the whole coding-agent harness, then let the core implement only the irreducible transport piece. |
| Preserve raw provider shape | quecto_raw returns serde_json::Value, so the core does not need typed abstractions for every provider feature. |
| Normalize one layer up | OpenAI vs. Anthropic differences belong in quecto-agent/src/provider.rs, where they can change without touching the core. |
| Make sync the default | The common path is blocking, sequential, and easy to inspect; async runtimes appear only behind boundaries that genuinely need them. |
| Quarantine dependencies | MCP, OTEL, rendering, SQLite, and eval machinery live outside the core or behind feature flags, so they do not tax the smallest build. |
| Treat execution as policy | Tools, approval, sandboxing, verification, trust, and persistence are runtime decisions, not model-transport concerns. |
| Evaluate from outside | quecto-eval drives quecto-agent as a subject and consumes traces, instead of mixing benchmark logic into the agent loop. |
That is how the harness stays lean while still becoming capable. It is not lean because features are missing; it is lean because every feature has to justify both its existence and the layer it enters.
The companion-crate boundary follows directly from that:
| Harness concern | Where it went | Why it stayed out of quecto core |
|---|---|---|
| Tool loop and step limits | quecto-agent | Requires state, policies, renderer, repeated-action guards, and task outcomes. |
| Tool schemas and dispatch | quecto-agent | The model transport can carry tools, but execution belongs to the runtime. |
| Approval and sandbox policy | quecto-agent | Security policy depends on repo paths, interactivity, denylist rules, and command execution. |
| Session persistence | quecto-agent | Resume, undo, diff, and chat history require SQLite and file-change recording. |
| Flavors and trust | quecto-agent | Project config can declare commands and loosen approvals, so it needs TOFU and explicit precedence. |
| MCP | quecto-mcp / optional agent feature | MCP needs transports, JSON-RPC, server trust, and sometimes async internals; none belong in the default core. |
| OpenTelemetry | optional otel feature in quecto-agent | Tracing is valuable, but the default build should not pay for exporter/runtime machinery. |
| Native evals | quecto-eval | Evaluation should run the agent as a subject and consume traces, not become part of runtime transport. |
The decision trail is visible in the docs/superpowers folder itself:
| Decision doc | Architectural conclusion |
|---|---|
2026-07-09-full-harness-reference.md | Capture the maximal coding-agent harness first, so QuECTO can choose what to compress and what to exclude. |
2026-07-09-quecto-harness-design.md | Define quecto as the model adapter only: arbitrary JSON in, arbitrary JSON out, with convenience helpers layered above the raw primitive. |
2026-07-10-quecto-core.md | Lock the core implementation constraints: two source files, two dependencies, four public functions, no async runtime, no framework. |
2026-07-10-quecto-agent-architecture.md | Move the loop, tools, sandbox, context, verification, renderer, and SQLite sessions into quecto-agent. |
2026-07-10-quecto-agent-flavors-design.md | Keep extensibility above the core by making flavors configure the agent library and default binary. |
2026-07-15-quecto-mcp-design.md | Put MCP in a separate library and optional agent feature so tokio, JSON-RPC transports, and server trust never enter the default core. |
2026-07-14-quecto-agent-otel-design.md | Keep tracing behind an optional feature, with exporter machinery outside the synchronous default path. |
2026-07-19-quecto-evals-sota-design.md | Run evaluation as an external harness around quecto-agent, using trace files and graders instead of bloating runtime transport. |
This is also why later specs keep repeating the same pattern. Reasoning modes live at request construction, not in the agent loop. Provider-specific Anthropic details live in provider.rs, not in the core. Markdown, Mermaid, and spinner behavior stay in the renderer, not in the model layer. MCP isolates async transport behind a synchronous public API and a feature flag. OTEL can spawn a small background runtime only when tracing is enabled.
So QuECTO's architecture was not "start tiny, then bolt everything on." It was closer to:
- Draw the full harness.
- Assign each responsibility a home.
- Let the core own only the JSON transport primitive.
- Let the agent own policy-rich execution.
- Let evals observe the agent from outside.
That is the real lean-core decision: the core remains small because the system already knows where complexity is allowed to live.
1. Keep the core boring
The quecto crate is intentionally narrow: send a prompt to an OpenAI-compatible endpoint and return a response, buffered or streamed. It can talk to local systems like Ollama, LM Studio, and vLLM, or to cloud endpoints with the same API shape.
That core is not where the agent magic lives. That is the point.
By keeping the transport layer tiny, the rest of the system has fewer places to hide behavior. If a task succeeds or fails, I want the explanation to live in the model, the prompt, the tools, the runtime policy, the verifier, or the budget, not in a thick framework layer whose choices are hard to inspect.
This is why QuECTO's minimalism is not only aesthetic. It is an experimental control.
2. Add agency as explicit policy
quecto-agent turns the core into a coding agent by adding the familiar pieces: file tools, search, patching, shell commands, git inspection, approval presets, a hard denylist sandbox, verification commands, and SQLite-backed sessions.
The important design choice is that those features are treated as policies, not ambient powers. Writes and commands are gated. Dangerous commands are denied even under permissive approval. Project-local flavor manifests use trust-on-first-use. Session state can be resumed, diffed, or undone. Verification is a first-class stop condition, not something remembered after the final answer.
The UAT pass was a good sign: 41 black-box scenarios, 41 passes, no blocking defects. But the more useful result was qualitative. The harness had become legible enough that testers could isolate behavior by area: CLI, tools and safety, persistence, and flavors.
That is what I want from an agent harness. Not just "it works," but "when it fails, the failure has an address."
3. Make the chat loop useful enough to live in
After the first usable agent landed, a surprising amount of work went into the terminal experience: bracketed paste, clearer activity summaries, slash commands, session-scoped /reasoning, markdown rendering, inline Mermaid rendering, better /status and /context, and quieter non-TTY output.
This sounds like polish, but for coding agents it is closer to instrumentation. If the user cannot see what the agent did, or cannot tell whether a tool call created 1 line or changed 449 lines, the harness is less trustworthy.
The chat UI now exposes more of the loop:
- Which tool ran
- What changed
- Whether verification passed
- Which reasoning mode is active
- Whether a session can be resumed or undone
- Whether a project flavor is trusted
The user interface is part of the harness because it shapes supervision.
4. Support provider differences without pretending they are the same
QuECTO started with OpenAI-compatible endpoints because that API shape is common across local and hosted runtimes. The next provider step was native Anthropic Messages API support.
That was not just a URL change. Anthropic has different wire concepts: system extraction, tool_use and tool_result content blocks, x-api-key auth, required max_tokens, and optional thinking budgets. QuECTO maps its reasoning_mode onto Anthropic's thinking.budget_tokens, while keeping the rest of the agent loop intact.
This is exactly the kind of boundary I care about for AgentABI-style questions. If the model changes, the provider changes, or the reasoning control changes, what behavior stays stable? What breaks? What becomes more expensive? What takes more turns?
Provider support is not only compatibility work. It creates more controlled substitutions to measure.
5. Add subagents without losing observability
The first subagent tool was synchronous: invoke_subagent runs another agent and waits. The newer concurrent path is more interesting. spawn_subagent starts a background agent and returns an id. monitor_subagents reports status, elapsed time, and recent activity. cancel_subagent stops one early. A capped buffer records progress, and background runs render to a null renderer so their output does not corrupt the foreground session.
The constraint matters: concurrency is useful only if the supervisor can still tell what is happening.
So the subagent design exposes monitoring as a read-only tool, caps concurrency, and keeps cancellation explicit. In a larger agent system, this becomes the difference between "the agent delegated work" and "the agent lost control of the runtime."
6. Treat multimodal input as another harness boundary
The new --image flag lets quecto-agent send base64-encoded images into a vision-capable model. It was verified end-to-end against a local vision model.
Again, the interesting thing is the boundary. Image support is not just a model feature. The harness must decide:
- How the image is represented
- How it enters the request payload
- Which providers can receive it
- Whether the rest of the tool loop still behaves normally
- How eval tasks can grade visual understanding
This gives QuECTO a way to test coding-adjacent visual tasks: reading screenshots, identifying UI states, inspecting rendered assets, or grounding a bug report in an image.
7. Move from demos to evals
The biggest shift since the first post is quecto-eval, the native Rust evaluation harness.
The earlier eval layer included a 10-task TerminalBench-style smoke suite and a Harbor adapter for Terminal-Bench 2.x. The native harness makes the system more self-contained: tasks are described with eval.yaml, setup runs in isolated workspaces, graders are composable, telemetry is parsed from JSONL trace events, and SQLite stores historical run data.
This changes the development loop. A harness feature is no longer done when a demo passes. It is done when it can be run again, graded, traced, and compared.
The smoke suite already showed 10/10 deterministic passes on tasks like git conflict resolution, package refactoring, advanced shell text processing, OpenSSL workflows, Docker builds, debugging a C crash, SQLite queries, and fixing a Rust build. The next step is less about adding more tasks randomly and more about sorting tasks by purpose:
- Regression tasks: things the harness should keep passing at near 100%.
- Capability tasks: harder tasks for hill-climbing.
- Compatibility tasks: same prompt, same workspace, different runtime or provider.
- Telemetry tasks: tasks where the pass/fail result is not enough without turns, tokens, latency, and tool usage.
This is where QuECTO connects back to the research question from the first post: agent performance is an emergent systems property. The eval harness is how that property becomes observable.
What has shipped so far
Here is the compressed changelog view:
| Date | Milestone | Why it matters |
|---|---|---|
| 2026-07-10 | Core crate and size-optimized build | Proved the tiny synchronous transport layer and release profile. |
| 2026-07-12 | Full quecto-agent M1-M7b | Added tools, editing, sandbox policy, verification, persistence, and flavors. |
| 2026-07-14 | UAT and bug-fix pass | Turned rough edges into clean behavior across 41 scenarios. |
| 2026-07-15 | Smoke evals, Harbor adapter, OTEL | Started measuring behavior through tests and traces. |
| 2026-07-16 | Chat and delegation improvements | Made the live loop easier to supervise. |
| 2026-07-18 | Anthropic provider, reasoning, Mermaid, subagents | Expanded provider/runtime substitution and concurrent delegation. |
| 2026-07-19 | Native quecto-eval and image support | Made evaluation and multimodal inputs first-class. |
| 2026-07-22 | quecto and quecto-agent v0.1.0 | Published the first official releases with macOS arm64 binaries. |
The through-line is not "add every feature." It is "make each harness boundary explicit enough to test."
The lesson so far
I went into QuECTO thinking the hard part would be making a small agent feel capable.
That was only half true. The harder part is making the capability inspectable. Tool calls need summaries. State needs a database. Trust needs hashes. Provider support needs a stable internal shape. Subagents need monitors. Images need a request representation. Reasoning needs controls and traces. Evals need graders and telemetry.
Minimalism did not mean avoiding those pieces. It meant adding them in a way that keeps the system understandable.
That is the build principle for the next phase:
A harness is not complete when it can act. It is complete when its actions can be replayed, measured, compared, and trusted.
The next posts in this series will go deeper into the individual layers: reasoning controls, subagent orchestration, multimodal evals, and behavioral compatibility across providers and harness versions.
Related reading
- What Is an AI Agent Harness? — the foundation for this build series
- QuECTO on GitHub — source code, changelog, releases, and eval docs
- AI Research Explained — the broader section this series lives in