~/satyajit

Prime Agent: the interface is a Python REPL, not a tool-call schema

mdjsonmcp

2026-08-06 · 30 min · agents · coding-agent · harness · open-source · prime-intellect · explainer

Most agent harnesses give the model a menu. You define grep(pattern, path), read_file(path), run_tests(), each with a JSON schema, and the model picks one, the harness parses the call, runs it, and hands the result back as another message in the transcript. Composition — loop over these results, retry that one, spawn three of these in parallel — happens in the conversation, one role-tagged message at a time, because the schema has no concept of control flow.

Prime Agent, Prime Intellect's open-source coding and research agent, makes a different bet. It gives the model one tool: a persistent Python interpreter. Composition is not a transcript pattern the harness has to support — it is just Python. This piece is a tour of that decision, built from reading the actual TypeScript host and Python runtime in the repo (not just the README), plus the second idea Prime Agent ships alongside it: a harness that edits its own supplemental state through /refine, with one part of itself — the base system prompt — locked out of the edit path in code, not just in the prompt.

This continues two threads already on this site. Agent harnesses argued the loop wrapped around a model matters as much as the model itself, and the harness effect showed orchestration — not the model — sets an agent's token bill. Prime Agent is a concrete instance of both claims taken further: the orchestration layer here is not a fixed loop around a fixed tool menu, it is a programming environment, and the harness state that shapes behavior is itself something the agent is allowed to edit.

One tool, not a menu

Here is the entire tool surface Prime Agent gives the model, from packages/coding-agent/src/core/tools/ipython.ts:

const ipythonSchema = Type.Object({
  code: Type.String({
    description:
      "Python scratchpad code or `%%bash` shell cells to execute in the agent kernel. Use the target project's own environment for project imports, tests, scripts, CLIs, and dependency checks instead of direct kernel imports.",
  }),
})

That is one parameter: code, a string. Compare that to a typical schema-based agent, which carries a dozen or more tool definitions — read_file, write_file, bash, grep, glob, maybe a bespoke one per integration — each with its own JSON schema, each repeated in every request the provider sees. Prime Agent's own CHANGELOG.md records the direction of travel: an early entry reads "Removed the interactive ! / !! bash shortcuts; use IPython for shell commands." Shell access isn't a separate tool bolted on next to Python. It is a magic cell (%%bash) inside the same interpreter.

The kernel is genuinely persistent. Variables, imports, and open file handles survive across tool calls — and across context compaction — because they live in the interpreter's process, not in the token transcript the model rereads every turn:

from pathlib import Path
 
config_files = list(Path(".").rglob("*.toml"))
large_files = [path for path in config_files if path.stat().st_size > 10_000]

config_files is still there three turns later. Nothing re-lists the directory, and nothing re-sends the file list back through the model's context to remind it what it found — the model holds a reference to the data, not a copy of it in its own working memory. That is the "prompt-as-a-variable" half of the Recursive Language Model idea Prime Agent is built on: context becomes something you slice with Python, not a transcript you re-read.

Context as variables, made concrete

Take a task like "grep across 60 files for a pattern and summarize the hits." A schema-based agent issues one grep call, gets a list of matches back, and then — if it wants to actually look at what it found rather than trust the grep output blind — issues one more round trip per file it wants to inspect, and a final call to write the summary. The loop lives in the conversation: every iteration is a full model turn, with the tool schemas and message envelope repeated each time.

An RLM turn writes the loop instead of living inside one:

import subprocess
 
hits = subprocess.run(
    ["grep", "-rl", "TODO(perf)", "src/"], capture_output=True, text=True
).stdout.splitlines()
 
summaries = []
for path in hits:
    text = Path(path).read_text()
    summaries.append(f"{path}: {text.count('TODO(perf)')} occurrences")
 
print("\n".join(summaries[:10]))
print(f"... {len(summaries)} files total")

The for loop, the file reads, and the counting all happen inside one ipython call. The model sees one printed summary, not sixty round trips of tool call and tool result. summaries stays a Python list the model can filter, sort, or hand to another cell — it does not have to be re-stated in the transcript to stay usable.

grep 60 files and summarize the hits · round tripsillustrative
tool-call agent1 grep call + 60 per-file round trips + 1 summarize = 62 callsRLM turngrep + read + aggregate run as Python inside one cell = 3 calls, any 60
files matched60
tool-call agent
62 round trips
~32.2k tok
RLM turn
3 round trips
~1.7k tok

Drag the slider. A schema-based agent that grep's then inspects each hit pays one round trip per file — the loop lives in the conversation, so the transcript grows with n. An RLM turn writes the loop instead of living inside one: grep, read, and aggregation run as Python in a single cell, so the round-trip count stays flat and only the printed summary grows. The token and call counts here are a cost model to make the shape visible, not a measured benchmark.

Drag the slider above. The gap is not a fixed multiplier — it is linear-versus-flat, so it gets more dramatic exactly where it matters most: large fan-out tasks. The numbers there are a cost model built to make the shape of the tradeoff visible, not a benchmark; Prime Agent doesn't publish one, so neither do I.

This is also where the honesty has to cut both ways. Working in a persistent kernel does not make the model's own attention free — if summaries is genuinely large, someone (or something) still has to look at it, and dumping ten thousand lines of print() output into the transcript defeats the entire point. The advantage is that the decision about how much of the data to surface is a line of Python (summaries[:10]) instead of a constraint baked into the tool schema. It is a better failure mode, not an absent one.

The schema didn't disappear — it moved

Here is the thing I got least precise about the first time, and it is the most interesting mechanism in the codebase. "One tool" is true of what the provider sees. It is not true of what the model can reach.

When Python in the kernel calls rlm(...), or goal.get(), or agent_message.send(...), it is not doing the work locally. It opens a Jupyter comm target named host.request and sends a typed request back across the ZeroMQ boundary to the TypeScript AgentSession, which does the work and replies. rlm-runtime.md is blunt about the division: the Python rlm package "is a model-facing shim; the TypeScript host owns child execution, persistence, usage accounting, and lifecycle," and "the Python side does not call providers or implement an agent loop."

The dispatch table is built in _createKernelHostHandlers() in agent-session.ts. I counted twenty-one entries:

agent-session.ts · _createKernelHostHandlers()21 of 21 reachable
what the provider sees
ipython
code: string
1 tool · 1 parameter
re-sent every turn
everything on the right costs
zero prompt tokens per turn
what the kernel can reach — click a gate to close it
rlm.runspawn a child agent; returns at admission
rlm.find_modelsresolve a model for a child
rlm.list_subagentsrecover direct child handles
rlm.delete_subagentdrop a retained child
model.infocurrent model id, provider, modalities
goal.getread the persistent objective
goal.createopen one, with optional token budget
goal.completethe only way a goal ends successfully
compact.statushow close context is to the threshold
compact.runcompact now, with optional instructions
refine.statuscurrent continual-harness state
refine.runpropose evidence-backed harness edits
rlm_heartbeat.listagent-owned recurring prompts
rlm_heartbeat.createadd one, with an interval and label
rlm_heartbeat.updatepause, resume, or edit
rlm_heartbeat.deleteremove one
agent_message.list_agentsparent, siblings, children
agent_message.sendrole-addressed; validated host-side
agent_observe.listfamily sessions you may watch
agent_observe.getread another session's transcript
agent_observe.recentits latest activity

The schema did not go away — it moved out of the provider payload and into a private bridge. Model-generated Python reaches these through a Jupyter comm target named host.request; each one is a typed handler that validates its own arguments in TypeScript and throws on anything malformed. Two consequences worth separating. The cheap one: a twenty-one-entry surface that costs nothing per turn, because it is never serialized into the prompt. The load-bearing one: most of these are registered conditionally. Close a gate above and the handler is not merely discouraged — it is absent from the dispatch table, so the model can write the call and get an error back from the host. That is capability gating in code, not an instruction it could argue with.

Two separate things fall out of that, and they are worth not conflating.

The cheap one is token economics. A conventional harness pays for its tool surface in every single request — twenty-one JSON schemas re-serialized into the prompt on every turn, forever. Prime Agent pays for its surface once, in the kernel bootstrap and the skills' SKILL.md files, and the per-turn cost of the whole bridge is zero. That is the same argument as the turn-count one above, pointed at a different axis.

The load-bearing one is that most of these handlers are registered conditionally. Goals are wired up only if (this._includeGoals). Compaction only if (this._includeCompactSkill). Refinement only if (this._autoRefineAllowedForSession()). Messaging requires both a controller and that the agent-message skill be in the model-visible set. In a session where those flags are off, the handler is not in the map at all — the model can write the exact right Python and get an error from the host, because there is nothing on the other end of the comm to answer it.

That reframes the security story in a way the README's "not a security sandbox" warning does not, and it is a genuine tension inside the design rather than a resolution of it. Arbitrary Python against the filesystem really is unbounded: the kernel runs with the user's permissions and can do whatever Python can do. But the agent-control surface — spawn a child, open a goal, edit the harness, message a sibling, read another session's transcript — is not open. It is a typed, argument-validated, conditionally-registered table in the host, which is exactly the property a tool schema is supposed to give you. Prime Agent kept the schema and moved it somewhere the model cannot see or enumerate, then gave the model a general-purpose language for calling into it.

Subagents are function calls

The same move applies to delegation. rlm is preloaded in the kernel as a callable:

handle = await rlm("Review the authentication flow for security issues", name="auth-reviewer")
print(handle.rlm_child_id, handle.name, handle.session_dir, handle.model)

rlm(...) is admission, not completion — it returns as soon as the TypeScript host has created a real child AgentSession with its own context and session directory, and it never blocks waiting for the child's answer. That is a real API design decision, not an implementation detail: the CHANGELOG.md for 0.6.0 records changing rlm(...) from waiting for the child to finish to returning a spawn handle at admission, specifically because treating asyncio.gather() over several rlm() calls as fan-in was the wrong mental model — spawning three reviewers is three independent calls, not a scatter-gather:

api_review = await rlm("Review the public API", name="api-reviewer")
test_review = await rlm("Review the test coverage", name="test-reviewer")
integration_audit = await rlm("Run the slow integration audit", name="integration-audit")

A child reports back only through an explicit message, never through the rlm() return value:

await agent_message.send(message, receiver_role="parent")

the same daemon-routed messaging prime-agent send <agent> "..." uses from the shell. Reach is deliberately narrow — an agent may message or observe only its parent, siblings, and direct children (the 0.6.0 changelog calls this "the nuclear family"); reaching a grandchild means relaying through the intermediate child. That is a real constraint on the "agents can message each other and orchestrate without routing through the user" claim: it is true, but bounded, not an open mesh.

Skills are Python you can call, not prompts you re-paste

Skills follow the standard Agent Skills markdown format (a SKILL.md with frontmatter Prime Agent loads lazily), extended with a Python-backed variant: a skill directory with a pyproject.toml gets installed into the kernel's virtualenv and exposed by import name.

report = await release_audit(repository=".", target_version="0.4.0")

That's a real callable, not a re-explained prompt — Prime Agent's built-in skill-creator skill turns a described workflow into exactly this shape: SKILL.md plus src/<import_name>/__init__.py plus a documented run(). Worth being precise about a distinction the docs themselves flag: an installed Python skill is a package on disk; a continual-harness skill entry (below) is a persisted description of a reusable call. /refine can create or update the description after it sees a repeated pattern, but it never packages the executable capability itself — that stays skill-creator's job.

MCP, without adding a tool

The clearest test of whether a "one tool" design is a real commitment or a slogan is what happens the first time someone wants Linear and Notion in the agent. The default answer everywhere else is to mount the MCP server's tools into the model's tool list, which is how a clean six-tool agent becomes a forty-tool agent nobody planned.

Prime Agent's docs refuse the move in the first paragraph: "Consistent with Prime Agent's single-tool design, MCP integrations are not exposed as new agent tools." An integration is a Python skill whose module subclasses McpIntegration, and the MCP connection runs inside the kernel on the official mcp Python SDK. The host's only jobs are browser OAuth and keeping a token fresh in auth.json.

import linear
 
for tool in await linear.list_tools():
    print(tool["name"], "-", tool["description"])
 
help(linear.list_issues)                       # schema, after list_tools() has run
issues = await linear.list_issues(team="Engineering")

Every discovered tool is bound as an async method on the integration object; results come back as parsed Python rather than JSON to unpack; a tool whose name isn't a valid identifier (Notion's notion-search) falls back to await notion.call_tool("notion-search", {...}). Authoring your own is a pyproject.toml, an mcpServers entry in settings, and roughly ten lines subclassing the base.

Two details are more interesting than the API itself.

The first is that discovery moved into the turn. In a schema-mounted MCP integration, tool definitions are resolved when the harness connects and then frozen into the prompt; the model gets the server's surface whether it needs it or not, and a server that changes its tools mid-session is a stale-schema bug. Here the docs tell the model to list_tools() and help() before calling rather than hardcoding, because "tool names and argument schemas come from the server and can change." The model pays for the schema only in the turns where it actually looks it up.

The second is a small landmine that says a lot about how the kernel works. The reference integration's module-level __getattr__ forwards unknown attributes to the instance, but keeps a reserved list:

_RESERVED = {"run", "__wrapped__", "__call__"}

Forwarding run would make the kernel bootstrap, which probes modules for a callable entrypoint, mistake the whole integration module for a callable skill and break dispatch. That is the flavour of bug you only get when your tool boundary is Python's attribute protocol instead of a JSON schema — more expressive, and with sharper edges.

The Continual Harness: durable state the agent is allowed to edit

Everything so far is inside one turn. The Continual Harness (arXiv 2605.09998) is about state that outlives the turn — and the session, if you ask for it to. It has four editable kinds, defined in refinement.ts: prompt (supplemental behavioral notes), memory (durable facts and decisions), skill (a description of a reusable Python call), and subagent (a reusable delegation role). Each entry lives in one of two scopes — local, written to the current session's own harness/harness_state.json and gone with the session unless promoted, or global, written to ~/.prime/agent/harness/ and available to every future session.

continual harness · what /refine may touchillustrative
×/refine reviewbase system promptimmutable — never rewrittenprompt notessupplemental onlymemoriesfacts · decisionsskillspython call specsubagent specsdelegation roles
scope
<session_dir>/harness/harness_state.json
memories · version 3 of 3

updated: corrected a stale path

Click a kind to select it. Every arrow into prompt notes, memories, skills, and subagent specs is one /refine can draw — small, evidence-backed edits, each recorded as a new version. The dashed line to the base prompt is blocked in code, not just in the prompt: the edit validator rejects any edit whose kind is prompt and id is base_system_prompt before it ever reaches the model. Rollback moves a kind's pointer to an earlier recorded version; it does not undo history, it adds to it.

/refine is the mechanism that writes to this state. It reviews the current trajectory and, when it finds something worth persisting, emits small Create/Update/Delete edits — never a full rewrite. From the actual system prompt the host sends to the refiner model:

Use the trajectory, current continual harness state, and prior refinement history. Prefer
small evidence-backed edits. If prior refinements caused issues, rollback or replace the
faulty editable entries. Never edit source files directly.

The one thing /refine cannot touch

The interesting design choice is not that the harness can improve — plenty of systems do prompt optimization. It's what's carved out of the edit surface, and how that carve-out is enforced. validateEdit() in refinement.ts runs before any edit is applied:

if (edit.kind === "prompt" && (edit.id === "base_system_prompt" || computedId === "base_system_prompt")) {
  return "base system prompt is not editable";
}

That is not a prompt instruction the model could talk itself out of — it's a function that runs on every proposed edit, in the host, outside the model's control. The base system prompt is compiled once from the harness's own instructions, and any attempt to create, update, or delete an entry with that id is rejected before the edit ever lands. Everything the harness learns goes into one of the four editable kinds instead, injected at the top of the compiled prompt as clearly subordinate material: "Use these continual harness prompt notes, memories, skills, and subagent specs when they are relevant. The base system prompt is immutable; prompt entries below are supplemental notes only."

That matters because it draws a hard line between two very different kinds of self-modification. The model can accumulate memories, refine delegation roles, and tighten behavioral notes — real, compounding change to how it behaves — but it can never touch the instructions that define what counts as a legitimate edit in the first place. Nothing in the four editable kinds can rewrite the rule that keeps them editable-only. It's the same shape as a constitution that can be amended but whose amendment procedure is (by design) not itself amendable through the ordinary amendment process.

Every applied edit is versioned and every refinement pass is appended to refinements.jsonl with before/after entry state, which is what makes rollback possible: refineHarness() accepts a rollbackId and, instead of running the LLM proposal pass again, replays a target refinement's prior state as the new edit. If a /refine pass turns out to have been wrong, the fix is pointing the entry back at an earlier recorded version — not trusting a second LLM call to undo the first one's mistake correctly.

Read next to Recursive Harness Self-Improvement, published today, the contrast is worth stating plainly. Sakana and Berkeley's method compares a harness against its own immediately-previous version and keeps the winner — a research method with a real information-theoretic argument for why pairwise beats population search, but no product around it. /refine is the shipped, product-side sibling of that same instinct: also self-vs-self in spirit (evidence from this trajectory, checked against this harness's own history), but with no comparison objective, no accept/reject criterion beyond "small and evidence-backed," and a rollback button instead of a formal proof. One is a method with a Bradley-Terry argument behind it; the other is a feature with a JSONL log behind it. Neither is a lesser idea for that — they're answering different questions — but they shouldn't be mistaken for the same rigor.

MemHarness, also published today, is a useful contrast in the other direction. MemHarness's argument is that retrieved memory should be reconstructed — critiqued and rewritten against the current state — every time it's used, because verbatim replay of a stale memory can hurt more than having none. The Continual Harness takes the opposite bet on when the work happens: refinement is a deliberate, evidence-gated event ("prefer small evidence-backed edits") that happens rarely, and once written, an entry is trusted and injected verbatim into every future compiled prompt until the next refinement touches it. MemHarness spends compute at read time, on every retrieval; Prime Agent spends it at write time, once, on /refine. Neither is obviously right — cheap reads with occasional expensive writes versus expensive reads with cheap storage — but it's worth knowing you're choosing between them, and Prime Agent has made the choice, not left it implicit.

Two memories, and only one of them forgets

A persistent kernel gives an agent two independent memory systems, and I don't think that gets said plainly enough. The transcript is one: bounded by the context window, and periodically summarized away. The kernel namespace is the other: bounded by RAM, and never summarized. Compaction only touches the first.

Auto-compaction fires when contextTokens > contextWindow - reserveTokens — 16,384 reserved by default — walks backwards from the newest message accumulating tokens until it has kept keepRecentTokens (20k by default), summarizes everything before that cut into a structured document, and reloads the session as summary-plus-recent. long-running-agents.md states the kernel's exemption directly: "The IPython kernel persists through compaction, so variables, imports, helper functions, and task state remain available."

So the same object can be simultaneously forgotten and present. The model may no longer have the message where it built summaries, but summaries is still bound in the interpreter. Both halves of that are useful and both can bite: the good case is that fifteen minutes of expensive analysis survives a compaction intact; the bad case is that the model retains a variable whose provenance was summarized away, and has to re-derive what it means. The structured summary format is clearly designed against this — it carries explicit ## Critical Context, <read-files> and <modified-files> blocks precisely so the pointers outlive the prose.

Three implementation details reveal where the pressure actually is:

Both compaction and the /tree branch summarizer accumulate file operations cumulatively across passes, so the record of what was read and modified survives repeated compactions rather than being re-derived from a summary of a summary.

What runs when nobody is attached

The last piece, and the one easiest to miss from the README alone: Prime Agent is built for sessions with no human in front of them. Sessions live in resident daemon worker processes, so closing the terminal detaches a client rather than stopping the work, and there are four separate mechanisms for producing a prompt when no user is typing.

The division there is sharper than most agent products bother with: the goal holds what and how far along, autonomous mode decides whether to continue. And one line in the gate policy is worth stealing outright — Prime Agent "avoids rerunning the same failed gate when the workspace has not changed." An agent that reruns a two-minute test suite against a byte-identical tree is not verifying anything, it is billing you for a cached failure.

All of it funnels into the same queue. From the session queue onward, a prompt from a heartbeat, a cron schedule, a goal continuation, autonomous mode, or another agent takes exactly the same path as one typed by a person, which is why none of these features needed a parallel execution mode.

What programmatic execution costs

The tradeoff the whole design rests on: a persistent interpreter is more capable and much harder to bound than a fixed tool schema. The README says this plainly, not buried in a docs page:

Prime Agent executes model-generated Python and project commands with your user permissions. Its worker and kernel processes improve lifecycle isolation and recovery; they are not a security sandbox. Review changes and use trusted repositories, instructions, skills, and extensions only.

rlm.md's trust-model section says the same thing about the kernel specifically: it "runs model-generated Python and project commands with the worker's operating-system permissions. It is a durable control environment, not a security sandbox." A fixed tool schema at least gives you an enumerable attack surface — every action the model can take is one of N defined functions, each individually auditable and individually deniable. A REPL's attack surface is "anything Python (and %%bash) can do," which is a much larger set to reason about, and a much easier one for a malicious skill or a compromised MCP integration to abuse.

The host bridge splits that claim in two, and the split is the honest version. Against the machine — files, network, processes, credentials on disk — the REPL really is unbounded, and no amount of typed dispatch changes that. Against the agent system — spawning children, opening goals, editing harness state, steering a sibling, reading another session's transcript — the surface is exactly as enumerable as a tool schema, because it is one: twenty-one named handlers, arguments validated in TypeScript, most of them absent unless a session flag turned them on. When you read "not a security sandbox," read it as a statement about the filesystem, not about the agent graph.

Persistence has an operational cost too, separate from the security one: a kernel that gets stuck stays stuck. The host's own busy-kernel handling spells out the tradeoff directly — interrupting a runaway cell and it still hasn't stopped, the choices are "wait" (preserve state, keep waiting) or "kill" (lose every in-memory variable, import, and running task and restart clean). There is no third option where you get both a responsive kernel and the state back. A schema-based tool call that hangs just times out; a wedged interpreter is holding real, valuable state hostage to its own unresponsiveness.

The daemon-backed background sessions and inter-agent messaging compound this rather than replace it. Sessions run in resident worker processes that survive a detached terminal — genuinely useful for long tasks, and long-running-agents.md is honest that this is a lifecycle property, not a security one: "Daemon workers are process-isolated for lifecycle and failure containment, not security-sandboxed. They normally run with the same operating-system permissions as the client." Add agents that can message and steer each other's active work and the blast radius of one compromised or badly-instructed agent is no longer just its own kernel — it's whatever its parent, siblings, and children will act on without a human turn in between. The nuclear-family reach limit added in 0.6.0 is a real mitigation, but it bounds propagation, it doesn't remove the surface. And 0.7.0 moved in the other direction on the same axis: agent messages now always steer, injecting into a running turn, with the option to queue politely behind the current work removed from every API. That is almost certainly the right default for responsiveness, and it does mean an inbound message from a sibling always interrupts.

None of this makes Prime Agent unusual among agent products with shell and code-execution access — it makes the tradeoff explicit and names it in the docs instead of marketing around it, which is more than most.

Maturity, honestly

This is the section the shallow clone got wrong, and the fix is more interesting than the error.

On that first pass I could not audit the commit history at all — the clone showed one commit — so I declined to claim anything about the project's age. That was the right call given the instrument, and the wrong instrument. A full clone answers the question completely, and the answer is not what either a shallow clone or the changelog suggests.

prime-agent · commits per month · full clone4,473 commits · 231 authors · 48 tags
Mario Zechner everyone else
aug 25
sep
oct
nov
dec
jan 26
feb
mar
apr
may
jun
jul
aug*
2026-01 1,224 commits · 853 Zechner · 371 everyone else

The repository is twelve months old, not three. Its first commit is Mario Zechner’s monorepo setup on 2025-08-09, and Zechner authored 3,099 of the 4,473 commits (69%) before his last one on 2026-05-08. Prime Intellect’s first commit lands 2026-05-21. So the grey mass is pi-mono and the blue tail is Prime Agent: 482 commits by 17 authors in the three months since the handover. Both readings of “how mature is this” are true, and they answer different questions. (*August 2026 is partial — measured through the 6th.)

4,473 commits, 231 distinct authors, 48 release tags, first commit 2025-08-09, latest 2026-08-06, with pull request numbers past #660. That is a year-old project with a real contributor base, not a three-month-old repo — and it is worth saying that my earlier hedge, read charitably, was still an underestimate by an order of magnitude.

But the interesting number is the split. Mario Zechner authored 3,099 of those commits — 69% — and his last one is 2026-05-08. Prime Intellect's first commit lands 2026-05-21. Everything before the gap is pi-mono under its own name; the clean up legacy pi artifacts commit lands 2026-05-19. In the three months since, 482 commits by 17 authors have turned it into Prime Agent.

So "how mature is this?" has two honest answers depending on what you're asking. The codebase is a year old, heavily iterated (December 2025 and January 2026 alone account for 2,096 commits), and was production software before Prime Intellect touched it. The product — the RLM framing, the continual harness, /refine, the daemon-backed agent tree, the MCP-as-skill design — is three months old and mostly the work of a small team. If you are evaluating engineering quality, use the first number. If you are evaluating how settled the agent architecture is, use the second, and note that 0.6.0 and 0.7.0 both shipped breaking API changes inside 24 hours of each other.

The changelog backs that up rather than contradicting it. 0.6.1 and 0.7.0 both landed in the three days before this was written, and 0.7.0's single breaking change is instructive: agent messages now "always use steering delivery," and the mode parameter is gone from the Python, CLI, RPC, and connection APIs. The three-mode design (auto, steer, follow_up) I would have described as a feature two days ago has been collapsed into one behaviour. long-running-agents.md still documents all three and still shows mode="auto" in its example; the actual send() in agent-message/src/agent_message/__init__.py no longer accepts it. Docs lagging source by one release is a normal cost of moving this fast, and a reason to read the Python, not the markdown.

The rest of what I can check from the repository holds up: version 0.7.0 across all four TypeScript workspaces (ai, agent, tui, coding-agent), a CHANGELOG.md per package that tracks breaking changes deliberately (a house rule bans touching already-released version sections), GitHub Actions for CI and for building versioned release binaries with SHA-256 checksums, and 414 TypeScript test files plus 4 Python test files under prime-agent-runtime/test/ across roughly 341,000 lines of TypeScript. The daemon protocol is explicitly versioned (DAEMON_PROTOCOL_VERSION, a schema revision — now at 13 — and compatibility maps for old-client/ new-daemon and new-client/old-daemon pairs) — the kind of care you only add after being burned by version-skew bugs.

One provenance correction while I'm here. I called Prime Agent "an acknowledged hard fork" of pi-mono, which is how the docs describe it and is fair as a statement about lineage. The git history says something more literal: this is not a copy of pi-mono's code, it is pi-mono's repository, history unbroken from Zechner's first commit through to today's. The license reflects it — MIT, copyright jointly held by Mario Zechner (2025) and Prime Intellect (2026) — and the README credits pi-mono in its header links. Second: assets/ in the repo has a brand logo (an SVG butterfly mark) and nothing else — I checked specifically for architecture or benchmark figures to embed as this site's house style asks for, and there aren't any. The only other images in the repository are TUI screenshots under packages/coding-agent/docs/images/, and they carry the pre-rename pi-mono branding from before the fork was productized, which would misrepresent the current product if reproduced here. So this article ships no cover image and no embedded repo figures — the four interactives above are original, built from reading the code and measuring the repository, not redrawn from anything Prime Intellect published.

Where this sits

Scaling agentic RL covered Prime Intellect's environments side — 23 agentic tasksets, roughly 365,000 tasks behind one taskset API, each with a graded, reproducible reward. Prime Agent is the natural agent-side counterpart to that stack: the same company building the environments an RL loop trains against is also shipping the agent architecture that would run inside them. I want to be careful about what that observation is and isn't — I did not find any published result training or evaluating Prime Agent against that taskset catalog, so this is a structural connection (same company, complementary halves of an agentic-RL stack), not a reported one. If that pairing produces a number, it belongs in a different article than this one.

What Prime Agent actually is, stripped of both the marketing framing and my own enthusiasm for the design: an open-source harness that replaces a tool-call schema with a programming environment, and a harness state that can accumulate evidence-backed edits without ever being allowed to rewrite the rule that makes those edits legitimate. Both are real, checkable design decisions. Neither comes with a number attached.

The revision changed my read on one of them. "Replaces a tool-call schema with a programming environment" is the marketing line and it is half right. What Prime Agent actually did is demote the schema — out of the provider payload, where it is re-billed every turn and every entry competes for the model's attention, and into a private typed bridge the model reaches through a general purpose language. Twenty-one operations, argument-validated, conditionally registered. That is a better idea than abolishing the schema would have been, and it is the part I would steal.


Sources: the prime-agent repository at commit fix(coding-agent): isolate kernel state tests (#661), 2026-08-06 — specifically the docs under packages/coding-agent/docs/ (architecture.md, rlm-runtime.md, rlm.md, compaction.md, mcp-integrations.md, long-running-agents.md, acp.md), the TypeScript host (agent-session.ts, refinement.ts, agent-messages.ts, tools/ipython.ts), the Python runtime under prime-agent-runtime/src/rlm/ and packages/coding-agent/skills/, and the per-package CHANGELOG.md files. All repository statistics — commit counts, author counts, per-month distribution, tag count, line counts — were measured with git against a full clone and are reproducible from the commands in the source of the commit-history figure. The Continual Harness paper is arXiv 2605.09998; the RLM framing is Prime Intellect's RLM post. The four interactives are mine.

Cite this article

For attribution, please use the following reference or BibTeX:

Satyajit Ghana, "Prime Agent: the interface is a Python REPL, not a tool-call schema", ai.thesatyajit.com, August 2026.

bibtex
@misc{ghana2026primeagent,
  author = {Satyajit Ghana},
  title  = {Prime Agent: the interface is a Python REPL, not a tool-call schema},
  url    = {https://ai.thesatyajit.com/articles/prime-agent},
  year   = {2026}
}
share