A language for agents to talk to each other.
Compact on the wire, readable by people, and precise about what nobody knows yet.
Quick start · Specification · Prompt cards · Examples · Status
We built agents that reason in high dimensions and then made them talk to each other in English paragraphs.
Prose is a superb interface between a machine and a person. Between two machines it is a lossy channel, and it loses the wrong things:
- Confidence disappears. "The cause is commit 9f2a" and "the cause is probably 9f2a" compress to the same downstream behaviour. Five hops later you have a confidently wrong answer built out of four honest guesses.
- Unknowns are unwritable. There is no natural place in a paragraph for "here is what I checked and still do not know." So it goes unsaid, and the next agent assumes it was covered.
- Nothing can be pointed at. To object to one sentence of a draft, an agent must re-paste the passage and hope you can tell which part it means.
- Nothing can be checked. No program can decide whether a reply answered the question without first understanding the answer.
- Half of every message is ceremony. Greetings, restatements, re-pasted context.
Each of these is fixable. None of them is fixable in prose.
A language with two planes over one address system.
a1.13 tell a1>a3 re=a3.12 #CKO.5xx
a cause = @commit:9f2a lowered http.timeout 30s>3s ~hi
fix = revert @commit:9f2a ~hi
eta = 12m ~lo
why @file:log/2f9c#L487 timeouts start 14:02 = deploy time +-40s
unk [who approved 9f2a, whether other services hit this]
risk reverting reintroduces the slow query in @issue:441 ~mid
Three facts, three different confidences, two named unknowns, evidence at a line number, no ceremony. The same AST renders for a person, deterministically, in code — never through a model:
a1 -> a3 · states · re a3.12 · #CKO.5xx
------------------------------
Answer
· cause: commit 9f2a lowered http.timeout 30s -> 3s (high confidence)
· fix: revert commit 9f2a (high confidence)
· eta: 12m (low confidence)
Evidence file log/2f9c, line 487: timeouts start 14:02 = deploy time +-40s
Unknown who approved 9f2a; whether other services hit this
Risk reverting reintroduces the slow query in issue 441 (moderate confidence)
And the second plane carries content — arbitrary text, byte-exact, addressable to the character, annotated without being touched:
txt tgt @md/en v=draft3
| The lighthouse keeper insisted that nothing unusual ever happened here.
|
| He was, by every account, the last man on the island you would ask.
mark tgt#L1.c4-19|q"lighthouse keeper" = must use the GLOSS name ~hi
tgt#L3|q"the last man" > "No one would have asked him." ~mid
Text tgt (en · md · v=draft3)
1 | The lighthouse keeper insisted that nothing unusual ever happened here.
^ L1.c4-19 "lighthouse keeper" must use the GLOSS name (high confidence)
2 |
3 | He was, by every account, the last man on the island you would ask.
> L3 -> "No one would have asked him." (moderate confidence)
A message. A field. Characters 5–19 of line 1. A region of an image. If you can address it, you can say how sure you are about it, where it came from, and what you still do not know.
That single rule is what makes the language cohere rather than accumulate. It is also checkable: §23 of the spec lists thirteen invariants and six registered exceptions, each one an assertion in the test suite.
git clone https://github.com/Birfy/AgentRosetta.git
cd AgentRosetta
python3 agentrosetta.py # self-test (119 assertions) + five demosNo dependencies. Python 3.9+.
1. Give your agents the language. Paste Card R into the system prompt of every agent in the mesh. About 1500 tokens, one card, the whole language.
2. Parse, validate and render what they emit.
from agentrosetta import parse, Session, render
session = Session()
for msg in parse(raw_text_from_the_agent):
for d in session.add(msg): # validate against the whole conversation
if d.level == "ERROR":
print(d) # e.g. S4: relayed claim upgraded to ~hi
print(render(msg, lang="en")) # deterministic human rendering
print(render(msg, lang="en", view="clean")) # just the deliverable, no annotations
for d in session.orphans(): # obligations opened and never discharged
print(d) # -> an agent is stuck; reassign3. Address content precisely.
msg.resolve_addr('tgt#L1.c4-19') # -> 'lighthouse keeper'
blk = msg.blocks()['tgt']
r = blk.resolve_full('#L3|q"the last man"')
r.status, r.line, r.conf # -> ('relocated', 4, ~mid)4. Measure it yourself.
python3 bench/token_compare.py # pip install tiktoken for real BPE countsSix decisions carry most of the weight. Each of them is a place where the obvious choice is wrong.
The tempting move is a dense private notation. It is a trap. An LLM's competence rests entirely on the distribution it was trained on; the more alien your symbols, the further off-distribution the model drifts, and the reasoning quality you lose exceeds the tokens you save.
So: keywords are ordinary English words, markers are ordinary punctuation, and the design constraint is falsifiable —
Show a Rosetta message to a model that has never seen the spec and ask it to paraphrase. If it cannot, the syntax is wrong. Not the model.
Models think by writing. Compress their output and you take away the scratchpad.
[reasoning] free prose, as long as it needs to be, never sent
[message] Rosetta wire form, structured, sent
Skip this and nothing else in the design helps.
An earlier version of this language had an axiom — coordination, not cargo — that pushed all content into blobs. It was wrong, and expensively so:
A passage that can only travel as an opaque blob is a passage nobody can point at. Unaddressable is undiscussable.
The rule now is a test, and it is machine-checkable: will any agent speak about one of its
lines? If yes, inline it as a txt block so it has addresses. If no, ship a hash.
One correction the benchmarks forced: referencing is not a token optimisation. A machine-readable address costs more than the phrase it replaces. It pays when it saves you from carrying the artifact, and not otherwise. Reference-over-copy earns its place by keeping content addressable and attention undiluted — not by being shorter.
And because carriage is orthogonal to addressing — win=L38-46 inlines nine lines of a
seventy-nine-line chapter with absolute numbering — getting that call wrong is not a
disaster. The next hop changes how the content travels; every address already written
stays valid.
Judgements about text hang off addresses; the text itself is never touched. Three consequences, and all three matter:
- Fidelity. Mix judgement into content and you can never cleanly separate them again.
- Concurrency. Three reviewers annotate one passage without conflict.
- Two artifacts from one AST. The
contentview is the annotated working copy; thecleanview is the deliverable. There is no "strip the comments before shipping" step — the step that always goes wrong.
#L3 after an edit points at whatever now sits on line 3. Silently. This is the leading
cause of death for annotation systems, and it is not solvable by being careful.
So an address may carry a quote anchor, and resolution returns a status, not a string:
| Status | Meaning | Confidence |
|---|---|---|
exact |
position and quote agree | ~hi |
relocated |
position failed, the quote found it | ~mid |
ambiguous |
the quote matched in several places | ~lo |
orphan |
points at nothing | ERROR |
Resolving an address is itself an epistemic act. Same rule as everything else.
There is no construct in this language that can alter a recipient's prompt, role or
permissions. Not a hardened one — no such construct exists, so the attack surface does
not either. Text inside a txt block is quoted material even when it is a perfectly formed
Rosetta message. The implementation asserts this.
Structure also buys a defence prose cannot offer: the only constructs meaning "do
something" are do and ask, so permission can be enforced at the protocol layer —
may this agent send me a do at all, and on which topics?
| spec/SPEC.md | The specification. 27 sections: grammar, address system, security model, distributed semantics, self-consistency invariants, a fourteen-domain coverage matrix, and an evaluation plan |
| spec/PROMPT.md | System prompt cards. Card R is ~1500 tokens and teaches the whole language |
| spec/EXAMPLES.md | Eleven worked domains — incident response, literature review, legal diligence, clinical triage, supply planning, financial diligence, data QA, creative work, adversarial review, translation — plus an anti-pattern table |
| agentrosetta.py | Reference implementation: parser, validator, bilingual renderer, 119 assertions, five demos. Zero dependencies, one file |
| spec/TOKENIZER.md | What we measured against three BPE tokenizers, what we changed, and what we tried and rejected — including why non-ASCII markers lose |
| bench/ | Four reproducible harnesses: per-message compression, long-conversation compression, mesh cost, and round-trip fidelity |
| samples/ | Runnable conversation transcripts |
acts ask tell do take part done fail stop propose accept reject revise def note
slots q a why ctx want unk assume risk opt sub on by · txt mark
marks @addr #topic ~hi|~mid|~lo|~? !commit !=negate |alt a>b = [] {}
head re= src= at= ttl= pri= sens= thd=
codes notfound denied timeout budget ambiguous unsafe unsupported
conflict upstream stuck malformed empty stale
addr @a1.7.tgt#L3.c5-9 @a1.7.a.cause @img:sha256:9c..#box=..
@a1.7.tgt#L3|q"quote anchor" <- survives edits, and says how it survived
ids obs7 letters = agent, digits = sequence, 2 tokens
a1.7 dotted form, always valid, 4 tokens
Frozen. Extension happens in profiles — versioned, content-addressed def packs — so
the core stays small enough to learn from one card. The bar for entering the core is
demonstrably necessary in at least three unrelated domains.
Because acts declare intent, a validator that understands nothing about your domain can still find:
| Orphans | work claimed and never finished |
| Stalls | obligations past their ttl |
| Livelocks | propose ↔ propose beyond N rounds |
| Contract violations | a reply that did not answer the question |
| Confidence laundering | a relayed guess promoted to a fact |
| Stale data | a claim used past its shelf life |
| Label leaks | a phi message forwarded as internal |
| Rotten anchors | an annotation that no longer points at anything |
The specification and the reference implementation are complete and tested. The claims are not all measured, and this README will not pretend otherwise.
Measured. Three harnesses, all reproducible, all in CI.
Compression, per message (bench/token_compare.py, bench/long_cases.py). Against
equal-information prose: ~21% on short exchanges, ~14% on a 24-message conversation.
Real, but modest — and the first long-case run was actually negative, which is what
produced 2.1 (see spec/TOKENIZER.md).
Compression, per mesh (bench/mesh_cost.py). This is the number that matters, and
counting wire bytes hides it completely. What a multi-agent system pays for is the total
tokens fed to every model in it, and the dominant term is the shared context each freshly
spawned agent must be handed before it can start.
Three reviewers on a 569-token diff:
| prose mesh | rosetta mesh | |
|---|---|---|
| the conversation itself | 799 | 788 |
| artifact handed to each agent | 1707 | — |
| spans each agent actually reads | — | 395 |
| total tokens billed | 2506 | 1183 (−53%) |
On the wire alone that difference is −2%. The saving is not in the notation.
It scales with how narrowly an agent can be addressed:
| shared context | agents | prose mesh | coarse addressing | precise addressing |
|---|---|---|---|---|
| 2,000 | 4 | 8,799 | 2,636 (70%) | 1,312 (85%) |
| 5,000 | 4 | 20,799 | 5,416 (73%) | 1,312 (93%) |
| 100,000 | 8 | 800,799 | 185,908 (76%) | 1,836 (99%) |
| 1,000,000 | 20 | 20,000,799 | 4,628,788 (76%) | 3,408 (99%) |
90% arrives at about 5,000 tokens of shared context across four agents — one source file and four reviewers. Coarse addressing plateaus in the seventies no matter how large the corpus gets, because reading a fixed fraction of something huge is still huge.
What buys it is that an address can name a span — @D:DIFF#L18|q"for attempt in range" —
that sub can hand one agent one span, and that want says what to bring back. A worker
can then be spawned against a million-token corpus and read six hundred tokens of it.
Fidelity (bench/fidelity.py). Thirteen deliberately hostile content lines — code fences,
a line shaped exactly like a Rosetta header, RTL script, a decomposed grapheme, trailing
whitespace — through ten parse/serialise cycles: byte-identical. An inventory of 55
information items across the four pairs, each with a predicate that reads the AST:
55 of 55 machine-extractable, against 0 of 55 from the prose baselines without an
NLP pass. The prose holds the same facts; only a reader can get at them.
That harness also decomposes the cost, which is the part worth staring at:
| wire | without epistemics | prose | |
|---|---|---|---|
| four pairs, 55 information items | 589 | 492 | 752 |
| 24-message conversation | 1501 | 1449 | 1669 |
The epistemic fields cost 97 tokens — 16% of the wire form. The 21% saving is net of them. That is the actual trade: fewer tokens while carrying confidence, unknowns and assumptions that prose has to spell out in clauses no program can read.
Against a chatty baseline the same comparison shows 3–4×. That number is meaningless and this project does not use it. Four pairs measure a format, not a system.
Not measured. The number that would actually matter is task success rate at a fixed token budget across a multi-domain suite. §25 of the spec specifies that evaluation in full. Until it runs, these are hypotheses:
- epistemic fields raise success rate and lower error propagation
- the content plane beats re-pasting on collaborative tasks
- the off-distribution penalty is near zero
The efficiency case, stated as the data supports it. Per message it is modest — about 21% short, 14% long, and negative if you ignore the conventions in §5.3 and §6.2.1, which is how the first measurement came out. Per mesh it is large — 90%+ once shared context outweighs the conversation — and that saving comes from addressing precision, not from terseness. A mesh whose agents each load the whole repository gets none of it.
The integrity case is the larger one — and it is still awaiting evidence. What the cost decomposition does establish is that the integrity machinery is not what you are paying for: confidence, unknowns and assumptions come to 2% of the wire form. Whether they earn even that has to come from the task-level evaluation, not from a token count.
Open problems, stated plainly in §26:
- A rewritten sentence still orphans its annotations, and no machine can tell whether that is correct — sometimes the note was adopted, sometimes the author merely rephrased.
- There is no hard ceiling on channel size. That belongs to the host at the transport layer.
- Profile ecosystem fragmentation is the largest long-term risk, and content addressing only partly mitigates it.
- References remain the expensive part — 28% of a long conversation after the 2.1 cleanup, and a machine-readable address is genuinely longer than the English it replaces. Shortening them without losing dereferenceability is unsolved.
| Source | Taken | Left behind |
|---|---|---|
| KQML / FIPA-ACL | Speech acts, conversation protocols, Contract Net | The mandatory global ontology — what killed them |
| Standoff annotation | Judgement separated from text, joined by address | Bespoke XML |
| YAML block scalars | A line prefix carrying byte-exact text | The rest of YAML |
| W3C Web Annotation | Redundant selectors resolved in order | The RDF apparatus |
| HTTP status codes | A frozen code table lets middleware act without understanding the business | Three-digit opacity |
| Event sourcing | The transcript is the log; state is a fold over messages | — |
| Unix diff | The > "becomes" semantics |
Line drift — which it never solved either, and neither have we |
In 1995, two agents had to agree on one global ontology before they could speak, because a parser could not understand natural language. That constraint is gone. The ontology can be three-tiered — a frozen core, loadable profiles, symbols coined in session — with prose as a permanent escape hatch, and content can live inside the language while staying addressable instead of being flattened into an opaque blob first.
That is the case for building this again, thirty years later.
See CONTRIBUTING.md. Two things worth knowing before you open a PR:
- Any change to the core vocabulary must re-run the fourteen-domain matrix (§24). A proposal that helps one domain and nothing else is a profile.
- Every "must" in the spec needs a diagnostic code, and every invariant in §23 needs an assertion. If your change cannot be checked, it is not finished.
The most valuable contribution right now is not a feature. It is running the evaluation in §25 and reporting what it says — including, especially, if it says this was a bad idea.