What Makes a Coding Agent?
I spend a lot of time in coding-agent terminals nowadays. I wanted one that was fast and limited to features I use, so I started working on Tact, a terminal interface for Nanocodex.
Why Nanocodex?
Working with Pi convinced me that I wanted a thinner, library-like harness. I spent some time trimming it down and tuning it, but ran into performance issues as I pushed it toward the workflow I wanted. I also did not like replacing the harness that the model was trained to use. Maintaining a direct Codex fork solved that part, but keeping a smaller fork current quickly became rebase hell. Nanocodex kept the relevant Codex behavior and exposed it through a small Rust library.
Nanocodex handles the model conversation and turn lifecycle. Tact presents and controls it through typed events rather than parsed CLI output. It can steer or cancel an active turn, fork from a safe checkpoint, install extra tools, and save a session snapshot. Rust tools and MCP tools appear to the model as typed JavaScript functions, while their implementations, credentials, retry policy, and mutable state stay in the application.
Most agent harnesses put a model behind a generic loop, give it tools, and ask for actions until it stops. That abstraction makes tools reusable, but hides details that change model behavior, including instructions, tool contracts, response shapes, history rules, and failure semantics. Tool-result ordering matters. Killing a shell command's subprocesses matters. Preserving the prompt cache across continuations matters. Replaying a transcript and continuing from a stored response produce different requests.
Nanocodex describes its thesis as the model and harness are one system.1 It starts from the behavior Codex expects and keeps the parts which affect the model, including typed response items, history ordering, continuation, tool-call ordering, caching, cancellation, and recovery. Nanocodex packages those pieces as a Rust library with one owned driver and typed history in the caller's process. It does not require an app server or a separate control plane.
independently optimized
learned orchestration
native execution substrate
Code Mode
With flat tool calling, each operation returns to the model before the next one begins.
model -> search -> model -> read -> model -> read -> model -> synthesizeThe model needs several round trips to express a loop. With Code Mode, it can write the loop directly. A simple example:
const files = await tools.search({ query: "TODO|FIXME", path: "src" });
const reports = await Promise.all(
files.map((path) => tools.inspect({ path }))
);
text(reports.filter((report) => report.actionable));The model generates a program which can branch, filter output, retry an operation, fan out work, and join the results. Nanocodex runs cells in embedded QuickJS on a prewarmed thread. Nested tool requests become independent Rust futures, and long-running cells can yield and resume.
Dynamic Sub-agents
Tact exposes lifecycle primitives through tools installed into Nanocodex. A spawn starts a reusable clean-room agent with an explicit output schema. Agents can be awaited, interrupted, or closed. Children may delegate again, producing a live task tree.2
Scheduling remains a model decision rather than a fixed reviewer or researcher pipeline. The parent can write a schedule in Code Mode.
const reportSchema = {
type: "object",
properties: { report: { type: "string" } },
required: ["report"],
};
const [tests, history, design] = await Promise.all([
tools.spawn_agent({
role: "tester",
task: "Reproduce the failure.",
output_schema: reportSchema,
}),
tools.spawn_agent({
role: "historian",
task: "Find the introducing change.",
output_schema: reportSchema,
}),
tools.spawn_agent({
role: "designer",
task: "Challenge my current fix.",
output_schema: reportSchema,
}),
]);
const first = await tools.wait_agent({
agent_ids: [tests.agent_id, history.agent_id, design.agent_id],
});
const finished = first.agents.find(
(agent) => agent.status.state === "completed"
);
if (finished?.status.output.report.includes("race")) {
await tools.send_agent_message({
agent_id: finished.agent_id,
purpose: "delegate",
message: "Stress the suspected race under cancellation.",
});
}The generated JavaScript is the scheduler. It can launch independent tasks concurrently and add follow-up work after seeing an intermediate result. Tact provides a concurrency ceiling, scoped agent IDs, status updates, cancellation propagation, retained child sessions, and cleanup. The ceiling defaults to 32 active child turns. When it is full, new child turns receive a recoverable retry-later error, providing backpressure.
Tact renders this as a live tree. I can open a child's transcript, see whether it is pending, running, or failed, and change the concurrency limit from the same interface. I can also interrupt one subtree while leaving the root and its other children running. In practice, this makes it easier to follow long-running parallel work, inspect a stalled child directly, and stop unnecessary branches without canceling the rest of the task.
Point-to-Point Messaging
The tree is useful for ownership and cancellation, but it should not force every finding to travel up to the root and back down another branch. Tact lets a sub-agent discover the other agents in its task tree and send a bounded, directed message to any available peer.
const directory = await tools.list_agents({});
const tester = directory.agents.find(
(agent) => agent.role === "tester" && agent.can_message
);
if (tester) {
await tools.send_agent_message({
agent_id: tester.agent_id,
purpose: "finding",
message: "The regression begins after the cache key includes tool order.",
});
}Messages can convey a finding, a question, a reply, or delegation. A reply references the message it answers, so the runtime can retain a two-party thread rather than asking either model to reconstruct one from unrelated prompts. Ordinary messages add context without changing the recipient's task. Only an authorized manager can send a delegation which replaces it.
A deferred message starts an idle agent or waits for its active turn to finish, and an urgent message steers a running agent at its next safe model boundary. This gives agents a way to share useful context without interrupting each other by default, while still allowing one branch to warn another before it commits to stale work. Tact projects these exchanges into the relevant transcripts with their sender, recipient, purpose, thread, and delivery state.
A0 is the parent of A1, A2, and A3. A1 sends a deferred finding to A2, and A3 sends an urgent question to A2.
root
owns the task tree
historian
find the regression
tester
reproduce the failure
reviewer
challenge the fix
root
owns the task tree
children of A0
historian
find the regression
tester
reproduce the failure
reviewer
challenge the fix
The result is an authority tree with a communication graph layered over it. Parents still define work and own their descendants, but peer agents can coordinate shared dependencies, flag overlapping files, and pass discoveries directly.
Why not broadcast every message to every agent? Work on LLM multi-agent systems suggests that fully connected communication often spends tokens on edges which do not help the task. AgentPrune evaluates six benchmarks and, when applied to two existing systems on three of them, reports reducing token use by 28.1% to 72.8% while generally maintaining performance.3 G-Designer learns a task-specific communication graph and reports reducing token use by up to 95.33% on HumanEval.4 Li et al. also find that sparse debate topologies can match or outperform all-to-all communication at lower computational cost.5
These systems are not general-purpose repository coding agents like Tact, so they do not establish that Tact's topology is optimal. They do, however, support making communication addressed and selective by default.
The Recursive Agent Harnesses paper calls this pattern harness recursion. The recursive call gets a full harness with a filesystem, shell, tools, and planning.6 On a 199-sample slice of Oolong-Synthetic, a long-context reasoning benchmark, the authors report an Oolong Score of 81.36% with GPT-5, compared with 71.75% for a published Codex coding-agent baseline. The baseline was not rerun and the authors did not have its per-instance scores, so I see this as indicative rather than a controlled head-to-head.
Tact applies the same code-first pattern to general coding work. Each child can inspect files, run code, use tools, and delegate again.
Children reread context, parallel branches can duplicate work, and every report gives the parent more material to evaluate. Delegation works best when the task has separable parts that benefit from their own context.
Evaluating It
I do not yet have a reliable measure of how much these choices improve Tact. My early loop mostly looked like:
- Give Tact a nasty task from a real repository.
- Watch what it does.
- Change the prompt, tools, scheduler, or model.
- Try to remember whether the last run felt better.
These "vibe-evals" reveal qualitative aspects of model performance that a task verifier usually misses. They show whether the model decomposes a problem sensibly, uses its tools well, recovers from a dead end, and synthesizes sub-agent reports coherently. They are much less useful for separating luck from progress. The tasks vary, model snapshots change, and memorable successful runs are easy to overweight.
I've also hooked Tact up to Terminal-Bench 2.1. At this stage, I have
focused on several of its harder cases rather than running the full suite, and Tact passes those targeted tasks on xhigh
reasoning (e.g. make-doom-for-mips scored 3/5, extract-elf scored 4/5) with subagents enabled.
However, a few good runs cannot show whether a third-party harness has improved on the model's first-party environment. The model developer can tune and evaluate the model and harness together at a scale I cannot reproduce. Nanocodex starts from the existing Codex behavior, preserves its model-facing contract, and measures changes to the complete model-harness pair. That approach also sets a useful standard for evaluating harness extensions.
Nanoeval
Nanoeval is a sibling project focused on evaluating Nanocodex.7 For each attempt, it creates a new agent session and disposable workspace, runs the task's canonical verifier, and retains the typed event stream and result. An evaluation combines one typed agent recipe, one immutable task, and one fresh execution environment.
vibe-evals
discover behavior
Nanoeval
measure precisely
typed events
expose decisions
verifier
attribute failures
harness extensions
encode the fix
quality + regressions
events + verifier traces
extend the harness
Real repositories are messy, verifiers are incomplete, and benchmarks can reward changes a maintainer would not merge. Nanoeval makes evaluating extensions repeatable. Include a tool, sub-agent policy, instruction set, or Code Mode extension in the recipe, then compare repeated runs. Typed event streams can show whether a result came from fewer model round trips, better tool selection, duplicated delegation, or a lucky final answer.
A useful harness extension would arrive with an eval recipe, a public task set (or a pinned slice of one), its canonical verifier, and several retained attempts. A reviewer could rerun the baseline and extended recipe, inspect the event streams in the same schema, and add an adversarial task when an improvement turns out to be benchmark-shaped. This creates a promising workflow for community-developed harness extensions.
Tact's immediate goal is to make Nanocodex pleasant to use in a terminal, and to make sub-agent orchestration visible, automated, and powerful. Nanoeval can test whether changes generalize beyond the repositories I normally use. My bet is that extending a model's existing harness will be more productive than trying to outsmart its developers with a system prompt or a wholesale replacement. Better evaluations will tell us whether that is true.
Footnotes
-
Konstantopoulos, G. (2026). Nanocodex: Model and harness co-design. ↩
-
Clabby, B. (2026). Tact: A terminal interface for Nanocodex. ↩
-
Zhang, G., Yue, Y., Li, Z., et al. (2024). Cut the Crap: An Economical Communication Pipeline for LLM-based Multi-Agent Systems. ↩
-
Zhang, G., Yue, Y., Sun, X., et al. (2024). G-Designer: Architecting Multi-agent Communication Topologies via Graph Neural Networks. ↩
-
Li, Y., Du, Y., Zhang, J., et al. (2024). Improving Multi-Agent Debate with Sparse Communication Topology. ↩
-
Lumer, E., Sen, S., Paul, K., & Subbiah, V. K. (2026). Recursive Agent Harnesses. ↩
-
Konstantopoulos, G. (2026). Nanoeval: Docker-free, library-first evaluations for coding agents. ↩