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.
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:
re-sent every turn
zero prompt tokens per turn
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.
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:
- Tool results are truncated to 2,000 characters during the serialization that feeds the
summarizer, with a marker recording how much was dropped — because, in the docs' own words, tool
results "especially from
ipythonand optionalbash, are typically the largest contributors to context size." The REPL design makes compaction harder, and this is the mitigation. - Split turns get two summaries. Normally the cut lands on a turn boundary. When one turn is
itself bigger than
keepRecentTokens— which is exactly what a long autonomous stretch of kernel work produces — the cut lands mid-turn on an assistant message, and Prime Agent summarizes the history and the turn prefix separately, then merges them. Never at a tool result: those must stay attached to their call. - Compaction is explicitly not a stopping condition. It "does not stop goals, autonomous continuations, heartbeats, or existing child sessions." A harness that treated a full context as the end of a task would quietly cap every long-running job at one context window.
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.
- Heartbeats, in two flavours.
/heartbeat every 10m ...is the user's single visible recurring instruction;rlm_heartbeat.create(...)is the agent's own, plural and programmatic. The Python skill deliberately cannot clear or replace the user-owned one. - Schedules —
prime-agent schedule add worker "0 9 * * 1-5" -- "Review open work"— persisted per session, surviving detach. The reliability detail is good: due ticks are claimed before delivery so a crash cannot replay an uncertain prompt, and missed ticks are coalesced rather than accumulating into a backlog. - Goals, which store a durable objective plus token usage, elapsed time, continuation count and
an optional budget. Only
await goal.complete()marks one done. The docs are careful that a goal is "an explicit user or host action, not something the agent should infer from every task." - Autonomous mode, which is the policy that decides whether to inject another continuation. It
is bounded on four axes at once — continuations, assistant turns, tokens, wall clock — and gated
on shell commands (
--autonomous-gate "npm run check") that must pass before the session may finish, with a failed gate's bounded output returned to the agent for another attempt.
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.
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.