# Integrate rogerwilco rogerwilco puts three checks, run by a program, around anything an AI agent does that cannot be undone: a payment, a change of bank details, an email to a customer, a deploy. The job travels as one typed line. The line is checked against the approved record before it is sent. The tool that acts states exactly what it will do, and that statement is checked before it runs. What it actually did is checked afterwards. This page is for the developer wiring it in, and for the coding agent doing it on their behalf. > **If you are a coding agent**, read [/llms-full.txt](https://rogerwilco.crox.io/llms-full.txt): this guide, the skill and the full protocol in one file. The prompt under [Hand it to your coding agent](#agent) is a complete brief. ## Install ```sh pip install rogerwilco ``` Python 3.10 or later, standard library only, no dependencies. The package is [rogerwilco on PyPI](https://pypi.org/project/rogerwilco/); the source is on [GitHub](https://github.com/afieldofdreams/rogerwilco). The same package gives you a command line, `rogerwilco`, and every function is also a hosted [HTTP endpoint](#http) for other languages. ## Where the checks go | Check | Runs | Catches | Function | |---|---|---|---| | 1. The line against the record | before the job is sent | a wrong digit, date, account or addressee typed by the sender | `check_ho` | | 2. The tool's statement against the plan | after the tool prepares, before it acts | a wrong account or value the receiver or the tool resolved | `check_rb_full` | | 3. What was done against the plan | after the act | a tool that did something other than it stated | `check_dn` | In our studies check 1 caught every mistake planted in the sender's line; check 2, on a tool that states what it will do, took wrong payments caused by the tool from 59 in 60 to none; check 3 flagged every fault injected at the moment of execution, after the fact. The [home page](https://rogerwilco.crox.io/) tells those tests one at a time. ## 1. Write the job as a line, and check it before it goes Build the line from the approved record with `rw.encode`, then compare the two with `rw.check_ho`. Under a profile that names execution slots for the ask, `PAY` here, the check also insists the line asks for an execution readback, `rb=exec`. ```python import rogerwilco as rw record = {"to": "payer", "item": "inv:MD-2026-057", "ask": "PAY", "sup": "marlow", "amt": "5400.00", "cur": "GBP", "kind": "GROSS", "acct": "40-12-88/61102938", "paid": "20261002", "ref": "MD-2026-057", "apref": "FD-0929"} # the approved record, from your database ho = rw.encode({"type": "HO", "id": "scheduler-1", "from": "scheduler", "to": "payer", "item": record["item"], "slots": {"p": "pay", "rb": ["exec"], **{k: v for k, v in record.items() if k not in ("to", "item")}}}, "pay") problems, notes = rw.check_ho(ho, record, "pay") if problems: raise RuntimeError(problems) # never send it ``` The line it builds: ``` RW1 HO scheduler-1 from=scheduler to=payer item=inv:MD-2026-057 ask=PAY p=pay rb=exec sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 ``` If a line was drafted by a model rather than built from the record, this is where a typo stops. With two digits swapped in the account: ``` ['record: acct mismatch: record 40-12-88/61102938 vs HO 40-12-88/61102983'] ``` ## 2. Make the tool prepare, and check what it says it will do Give the tool that acts two phases. `prepare` resolves every value it will execute, including anything it looks up, such as the account on file, freezes them under a token and returns them as a readback line, `RB`. Nothing has happened yet. The sender checks that statement against the plan with `rw.check_rb_full`. ```python # inside your payment tool: resolve what will actually run, freeze it under a token, state it as a line values = resolve_payment(ho) # supplier, amount, account looked up on file, date, references token = secrets.token_hex(4) pending[token] = values statement = rw.encode({"type": "RB", "id": "payer-1", "re": "scheduler-1", "from": "payer", "to": "scheduler", "item": "inv:MD-2026-057", "slots": {"p": "pay", "ask": "PAY", "h": rw.digest(ho, "pay"), **values}}, "pay") # back on the sender's side: check the statement against the plan problems, notes = rw.check_rb_full(ho, statement, "pay") ``` The tool's statement, carrying `h=`, a fingerprint of the line it received: ``` RW1 RB payer-1 re=scheduler-1 from=payer to=scheduler item=inv:MD-2026-057 ask=PAY p=pay h=a7a82de76607 sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 ``` If the tool resolved a lookalike account, the check refuses it and nothing is paid: ``` ['tier5 execution: acct mismatch: HO 40-12-88/61102938 vs RB 40-12-88/61102983'] ``` Read the statement from the tool itself. Do not have a model copy it across: a small model in our study altered the tool's line in 51 of 129 relays. ## 3. Commit only on a yes, then check what was done `commit` executes the frozen values and nothing else, and refuses without the sender's confirmation of that token. Its done report, `DN`, carries the values as executed, and `rw.check_dn` compares them with the plan. ```python def commit(token, confirmed): if not confirmed or token not in pending: raise PermissionError("no confirmation for this token: nothing is paid") values = pending.pop(token) bank.pay(**values) # exactly what was stated, nothing else return rw.encode({"type": "DN", "id": "payer-2", "re": "scheduler-1", "from": "payer", "to": "scheduler", "item": "inv:MD-2026-057", "slots": {"p": "pay", "out": "OK", **values}}, "pay") dn = commit(token, confirmed=not problems) problems, notes = rw.check_dn(ho, dn, "pay", rb_line=statement) ``` A tool that states one thing and does another at the last moment is caught here, after the fact, so this is where you alert and reverse: ``` ['executed: amt mismatch: plan 5400.00 vs DN 5100.00'] ``` ## The whole flow, runnable [examples/payment.py](https://github.com/afieldofdreams/rogerwilco/blob/main/examples/payment.py) puts the three steps together with a toy two-phase payment tool. Run it, then run it with the tool resolving the wrong account. ```python """A payment handed from one agent to another with rogerwilco's three checks around it. pip install rogerwilco python examples/payment.py # roger, wilco: the payment goes out python examples/payment.py --fault # the tool resolves the wrong account; the check stops it before any money moves python examples/payment.py --cancel # the approval is withdrawn before confirmation; the sender cancels, nothing is paid The scheduler hands the job over as a line; the line is checked against the approved record before it is sent. The payment tool prepares the payment and states, as a readback line, exactly what it will execute; the scheduler checks that statement and only then confirms. The tool commits what it stated and nothing else, and its done report is checked against the plan afterwards. """ import datetime as dt import secrets import sys import rogerwilco as rw PROFILE = "pay" # the accounts-payable profile: it names the values a payment is executed with # The approved record: what the finance director signed off. In your system this comes from your database. RECORD = {"to": "payer", "item": "inv:MD-2026-057", "ask": "PAY", "sup": "marlow", "amt": "5400.00", "cur": "GBP", "kind": "GROSS", "acct": "40-12-88/61102938", "paid": "20261002", "ref": "MD-2026-057", "apref": "FD-0929"} # ---------------------------------------------------------------- the sender def hand_over() -> str: """Write the job as a line, from the record, and check it before it goes (check 1).""" slots = {k: v for k, v in RECORD.items() if k not in ("to", "item")} ho = rw.encode({"type": "HO", "id": "scheduler-1", "from": "scheduler", "to": RECORD["to"], "item": RECORD["item"], "slots": {"p": PROFILE, "rb": ["exec"], **slots}}, PROFILE) problems, _notes = rw.check_ho(ho, RECORD, PROFILE) if problems: raise SystemExit(f"not sent: {problems}") return ho # ---------------------------------------------------------------- the receiver's tool: prepare, then commit class PaymentTool: """Your payment tool, given two phases. prepare() resolves the payment and states it as a readback line; commit() executes exactly what was stated, and only with the sender's confirmation.""" def __init__(self, fault: bool = False): self.pending: dict[str, dict] = {} self.fault = fault def prepare(self, ho_line: str) -> tuple[str, str]: ho, errors, _ = rw.parse(ho_line, PROFILE) if errors or ho["to"] != "payer": raise ValueError(f"refused: {errors or 'not addressed to the payer'}") values = {k: ho["slots"][k] for k in ("sup", "amt", "cur", "kind", "acct", "paid", "ref", "apref")} if self.fault: values["acct"] = "40-12-88/61102983" # the tool resolves a lookalike account token = secrets.token_hex(4) # the statement is good for five minutes; a confirmation after that finds a world that may have moved exp = (dt.datetime.now(dt.timezone.utc) + dt.timedelta(minutes=5)).strftime("%Y%m%dT%H%MZ") self.pending[token] = {"values": values, "exp": exp, "ho": ho["id"]} statement = rw.encode({"type": "RB", "id": "payer-1", "re": ho["id"], "from": "payer", "to": ho["from"], "item": ho["item"], "slots": {"p": PROFILE, "ask": "PAY", "h": rw.digest(ho_line, PROFILE), "exp": exp, **values}}, PROFILE) return token, statement def cancel(self, cx_line: str) -> str: """A CX from the sender: abandon anything prepared for that handoff, and acknowledge.""" cx, errors, _ = rw.parse(cx_line, PROFILE) if errors or cx["type"] != "CX": raise ValueError(f"refused: {errors or 'not a CX'}") for token in [t for t, p in self.pending.items() if p["ho"] == cx["re"]]: del self.pending[token] return rw.encode({"type": "AN", "id": "payer-3", "re": cx["id"], "from": "payer", "to": cx["from"], "slots": {"p": PROFILE, "val": "Y"}, "tail": "statement abandoned; nothing paid"}, PROFILE) def commit(self, token: str, confirmed: bool) -> str: if not confirmed or token not in self.pending: raise PermissionError("no confirmation for this token: nothing is paid") if rw.now_utc() >= self.pending[token]["exp"]: del self.pending[token] raise PermissionError("the statement expired before it was confirmed: prepare again") values = self.pending.pop(token)["values"] # ... call your bank here with `values`, and nothing else ... return rw.encode({"type": "DN", "id": "payer-2", "re": "scheduler-1", "from": "payer", "to": "scheduler", "item": RECORD["item"], "slots": {"p": PROFILE, "out": "OK", **values}}, PROFILE) # ---------------------------------------------------------------- the flow def main(fault: bool, cancel: bool = False) -> int: ho = hand_over() # check 1: the line against the record tool = PaymentTool(fault) token, statement = tool.prepare(ho) if cancel: # the approval is withdrawn before confirmation cx = rw.encode({"type": "CX", "id": "scheduler-2", "re": "scheduler-1", "from": "scheduler", "to": "payer", "item": RECORD["item"], "slots": {"p": PROFILE, "why": "REVOKED"}}, PROFILE) ack = tool.cancel(cx) problems, _notes = rw.check_thread([ho, statement, cx, ack], PROFILE) # every handoff closed print("cancelled:" if not problems else "open:", problems or ack) return 1 if problems else 0 problems, _notes = rw.check_rb_full(ho, statement, PROFILE) # check 2: the tool's statement against the plan if problems: print("held:", problems) return 1 dn = tool.commit(token, confirmed=True) problems, _notes = rw.check_dn(ho, dn, PROFILE, rb_line=statement) # check 3: what was done against the plan print("recall the payment:" if problems else "roger, wilco:", problems or dn) return 1 if problems else 0 if __name__ == "__main__": sys.exit(main("--fault" in sys.argv, "--cancel" in sys.argv)) ``` ```sh python examples/payment.py python examples/payment.py --fault ``` ``` roger, wilco: RW1 DN payer-2 re=scheduler-1 from=payer to=scheduler item=inv:MD-2026-057 out=OK p=pay sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 held: ['tier5 execution: acct mismatch: HO 40-12-88/61102938 vs RB 40-12-88/61102983'] ``` ## Cancel, expiry, and nothing left open A handoff must end. Every job closes exactly once: done, unable or failed from the receiver, or a cancel, `CX`, from the sender. If the approval is withdrawn before the tool commits, send a `CX`; the tool abandons what it prepared and acknowledges. If the act already ran, the receiver answers with the done report instead, and a mismatch is reversed with a new job, `ask=UNDO`. The tool's statement also carries `exp=`, the end of its window, and `commit` refuses after it, so a confirmation that arrives late cannot pay against a world that has moved. `rw.check_thread(lines, profile)` takes a conversation and reports any job left open past its time, closed twice, or acted on after a cancel. ```sh python examples/payment.py --cancel ``` ``` cancelled: RW1 AN payer-3 re=scheduler-2 from=payer to=scheduler val=Y p=pay // statement abandoned; nothing paid ``` ## Teach the receiving model the format A model that has to read or write lines needs about 400 tokens of legend in its prompt and nothing installed. Pass the profile so it gets the domain's codes. ```python system_prompt += rw.legend("pay") ``` ``` rogerwilco RW1. One line per message: RW1 [re=] from= to= [item=] key=value ... [// tail]. No spaces in values; lists join with +; dates YYYYMMDD; tail is free text, 300 chars max. HO handoff: requires from to item ask; optional re due pri conf keep art ctx rb acc keys exp sup po amt cur kind dec flag appr var n src net vat acct paid ref eff apref addr RB readback: requires re from to item ask; optional due eta h do acc not asm exp sup po amt cur kind dec flag appr var n src net vat acct paid ref eff apref addr prev ... ``` Where the receiver is a model that cannot run your two-phase tool, it can still read back the values it will act with: `rw.make_rb(ho, "payer-1", "pay")` builds a correct readback for a receiver that runs code, and the sender checks it the same way. On strong models this caught little, because they read well; on a weak model, the tool's statement did better than any readback the model wrote. ## Your own domain: profiles A profile adds the codes and slots for one kind of work and names, for each irreversible ask, the values it is executed with. That list is what checks 1 to 3 compare. Four ship with the package: `pay` (accounts payable), `clin` (clinical statements), `deploy` and `pform` (a knowledge-graph workflow). To make your own, copy `rogerwilco/profiles/pform.json`, list your asks and slots, and put the irreversible ones under `exec`. A profile may add codes; it may not redefine the core ones. [Profiles](https://rogerwilco.crox.io/profiles) shows the same line doing four kinds of work. ## Hand it to your coding agent Paste this into Claude Code, Cursor or any coding agent working in your repository. It is the whole brief. ```text Integrate rogerwilco (pip install rogerwilco) around every irreversible action in this codebase: payments, bank-detail changes, outbound emails, deletions, deploys. Read https://rogerwilco.crox.io/llms-full.txt first. 1. Find each place an agent hands another agent or a tool a job that cannot be undone. 2. Write that job as an RW1 HO line from the approved record with rw.encode, and refuse to send it unless rw.check_ho(ho, record, profile) returns no problems. 3. Split the tool that acts into prepare and commit. prepare resolves every value it will execute, freezes them under a token and returns them as an RB line; commit executes the frozen values only, and only after the sender's check. 4. The sender runs rw.check_rb_full(ho, statement, profile) on that statement and confirms the token only when it returns no problems. Read the statement from the tool, not from a model's retelling of it. 5. After commit, build a DN line from what the tool actually did and run rw.check_dn(ho, dn, profile, rb_line=statement); any problem means alert and reverse. 6. If the domain is not payments, copy rogerwilco/profiles/pform.json to a new profile and list the execution slots for each irreversible ask under "exec". Add tests that plant a wrong value at each of the three checks and assert it is caught. ``` ## Not Python: the HTTP API Every function is a JSON endpoint at this site, no key and no sign-up, for any language: `POST /check-ho`, `/check-rb`, `/check-dn`, `/make-rb`, `/validate`, `/digest`, `/to-json`, `/from-json`, `/gate`, and `GET /legend`. Check 1 with the swapped digits: ```sh curl -s https://rogerwilco.crox.io/check-ho -H 'content-type: application/json' -d '{ "profile": "pay", "ho": "RW1 HO scheduler-1 from=scheduler to=payer item=inv:MD-2026-057 ask=PAY p=pay rb=exec sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102983 paid=20261002 ref=MD-2026-057 apref=FD-0929", "record": {"to": "payer", "item": "inv:MD-2026-057", "ask": "PAY", "sup": "marlow", "amt": "5400.00", "cur": "GBP", "kind": "GROSS", "acct": "40-12-88/61102938", "paid": "20261002", "ref": "MD-2026-057", "apref": "FD-0929"} }' ``` ``` {"ok": false, "problems": ["record: acct mismatch: record 40-12-88/61102938 vs HO 40-12-88/61102983"], "notes": []} ``` The full reference, with every input and output, is at [/docs](https://rogerwilco.crox.io/docs). For production, run the checks in your own process with the library, or host this service yourself: it is one container, `docker build .` from the repository. ## Trying it inside a chat client The same checks are exposed as MCP tools, so you can try the format from Claude or another MCP client without writing code: `claude mcp add --transport http rogerwilco https://rogerwilco.crox.io/mcp`. This is for exploring, not for integration: checks that guard money belong in your own code path, where a model cannot skip them. ## The functions | Function | Does | Returns | |---|---|---| | `encode(msg, profile)` | JSON form to a line, validated | the line | | `parse(line, profile)` | a line to its JSON form | `(message, errors, warnings)` | | `check_ho(ho, record, profile)` | check 1: the line against the record | `(problems, notes)` | | `check_rb_full(ho, rb, profile)` | check 2: a readback or a tool's statement against the line | `(problems, notes)` | | `check_dn(ho, dn, profile, rb_line=)` | check 3: what was done against the plan | `(problems, notes)` | | `make_rb(ho, id, profile)` | a correct readback, for a receiver that runs code | the RB line | | `digest(line, profile)` | the fingerprint `h=` of a line | 12 hex characters | | `check_thread(lines, profile)` | every job closed: done, unable, failed or cancelled; none open past its time | `(problems, notes)` | | `legend(profile)` | the prompt text another model needs | text | No problems means go. Anything in problems means stop: re-send the job from the record, or, after the act, reverse it. Notes are assumptions worth logging. [What a line is](https://rogerwilco.crox.io/lines) explains every message type; the [spec](https://rogerwilco.crox.io/spec) is the rulebook; the [skill](https://rogerwilco.crox.io/skill) is the file an agent reads. --- --- name: rogerwilco description: Typed one-line messages for handing work between agents, reading a handoff back, asking bounded questions, and reporting done, unable or failed. Use whenever you hand work to another agent or subagent, receive a message starting with RW1, or need a receipt for what was agreed between agents. --- # rogerwilco Roger: received. Wilco: understood and will comply. Agents hand work to each other in one typed line instead of a paragraph, and the receiver proves both before it owns the work. Facts go in slots, reasoning goes in the tail, and the receiver reads the line back before it owns the work. Full spec: `references/SPEC.md`. ## The discipline comes first Seven rules carry most of the value, and a team can adopt them in plain English before it adopts the syntax. In a 28-run study, prose written under these rules cut inter-agent traffic to a third of free prose and the principal's reading to 40%; the line syntax matched that and added verification, and when the lines replaced the working files it cut the files the agents pass around from 22k tokens a run to under 2k. 1. **Facts once, in a fixed shape.** Each item on its own line with its identifiers, amounts and your decision. 2. **Reasoning by reference, facts by translation.** Anything longer than a short paragraph goes in a file; the message carries the path, not a paraphrase. A source document (an email, an invoice, a ticket) is translated once, at the edge, into one line that carries every fact in a slot and points back at the source (`src=email:7`); when the lines carry the facts, nobody writes a working file to restate them. 3. **State by delta.** Once an item has been reported, mention it again only when something about it changed. 4. **Acknowledge a handoff before you start.** Say what you received and the deadline. 5. **Escalate as a question with a closed list of options.** The item, the amounts, the decision needed, and the answers the human may give. Do not escalate what the policy leaves to you. 6. **Ask, answer, finish.** Bounded questions, direct answers, an explicit done. 7. **No preamble, no recap.** The lines below are those rules made checkable by code: a validator refuses a malformed message, a closed vocabulary refuses an invented option, a digest proves a handoff arrived intact. Use them wherever a smaller or mixed set of models is doing the work, before an irreversible step, wherever the log will be read by someone who has to trust it, and at the edge where source documents enter the system. ## When to use - You are delegating work to another agent, subagent or role. - You receive a line that starts with `RW1`. - You need to ask another agent a question with a bounded answer. - You are reporting that delegated work is done, that you cannot take it, or that it failed. - You are escalating a decision to a human with a fixed set of options. Do not use it for the work itself. Drafts, specs and analysis stay as normal content; the line points at them. ## The line ``` RW1 [re=] from= to= [item=] [key=value ...] [// tail] ``` - First three tokens are positional: version `RW1`, the type, your message id: `-`, unique in the conversation (`from=writer` sends `writer-1`, `writer-2`, ...; the validator refuses any other shape). - Everything else is `key=value`, any order. No spaces inside values. Lists join with `+`. Dates are `YYYYMMDD`. - ` // ` starts the tail: free text, 300 characters max. Longer content becomes an artifact you reference with `art=`. - `item` is a reference to the thing under discussion: `gh:owner/repo#42`, `pensive:4102`, `url:...`, `file:...`, `sha256:...`. - `to=human` escalates to the human principal. ## Nine types | Type | Use | Required | |---|---|---| | `HO` | hand work over | `from to item ask` | | `RB` | read a handoff back: what you received and what you understood | `re from to item ask`, `due` if the HO had one, every slot the HO names in `rb=`, `acc` when the HO says `rb=acc` or `rb=plan` (`do` too for `plan`), and the profile's execution slots for the ask when it says `rb=exec` | | `AQ` | ask a bounded question | `from to need` and `opts` when `need=ENUM` | | `AN` | answer | `re from to val` | | `ST` | report a state change, no handover | `from to item` | | `DN` | done | `re from to item out`, and after an act asked with `rb=exec` the execution slots with the values as executed | | `UN` | unable, will not take it | `re from to why` | | `FL` | took it and it failed | `re from to item why` | | `CX` | the sender withdraws a handoff | `re from to item`, and `why` (`REVOKED` `SUPERSEDED` `EXPIRED` `TIMEOUT`) | Optional slots: `rb` readback level on an HO: `echo` (default), `all`, `acc`, `plan`, `exec`, plus slot names to echo (`rb=acc+pri`) · `keys` on an HO: the constraint keys the sender will check (`keys=len+lang+must+no`), so the receiver reports those dimensions · `acc` acceptance criteria as `key:value` tokens (`len:120w+lang:uk+must:monday-problem+no:em-dash`) · `do` what you will produce · `not` what you exclude · `asm` assumptions you are making · `h` digest of the HO, code-side receivers only · `due` `by` (dates) · `exp` the latest moment to act or confirm, `YYYYMMDDTHHMMZ` UTC · `pri` 1–5, 1 highest · `conf` L/M/H · `keep` agents staying involved · `art` artifact refs · `ctx` earlier message ids · `eta` · `pct` 0–100 · `blk` blockers · `alt` alternative agent · `next` suggested next ask · `retry` Y/N. ## Codes - `ask`: `REVIEW` `DRAFT` `DECIDE` `RESEARCH` `BUILD` `FIX` `SUMM` `PLAN` `TEST` `EXTRACT` `UNDO` (reverse the act named in `ctx`) - `why`: `NOCAP` no capability · `NOCTX` missing context · `PERM` not permitted · `BUSY` · `CONFLICT` owned elsewhere · `DUP` · `VER` bad version · `UNK` unknown code · `SCOPE` · `ERR` · `TIMEOUT` · `REVOKED` approval withdrawn · `SUPERSEDED` · `EXPIRED` - `out`: `OK` `PARTIAL` `NOOP` - `need`: `ENUM` `TEXT` `NUM` `DATE` `YESNO`. For `TEXT`, reply `val=tail` and put the answer in the tail. - `acc` keys, a closed set: `len` (unit suffix: `120w` words, `800c` chars, `2p` pages, `10s` slides) · `fmt` (`md` `docx` `json` `csv` `html`) · `lang` (`uk` `us` `fr`) · `tone` (`plain` `formal` `casual` `technical`) · `voice` (`we` `i` `you` `third`) · `aud` · `out` (`copy` `spec` `dataset` `pr` `report`) · `by` (date) · `src` · `scope` · `must` · `no`. `must`, `no`, `scope` and `src` may repeat. For `must` and `no` values, use the brief's own words, hyphenated, no spaces (`must:monday-problem`, `no:em-dash`). Do not invent keys; a key outside the set is not compared. A profile adds domain codes and slots; a message using one carries `p=`. Profiles live in `profiles/`. Without `p=`, only the codes above are valid. A profile may name execution slots for an ask (the pay profile: `sup amt cur kind acct paid ref apref` for `PAY`, `sup acct eff apref prev` for `CHGBANK`, `sup addr amt cur paid ref` for `SEND`); those are what `rb=exec` reads back and what the DN reports. A profile may close the answer vocabulary with an `ans` enum (the pay profile has `APPROVE DECLINE HOLD REJECT CALLBACK`): then every `AQ need=ENUM` takes its `opts` from it and every `AN val` is one of them, so a human or a policy engine can answer without decoding invented words. ## Five rules 1. **No handover without readback.** Set the level per handoff, not per team: `rb=exec` before an irreversible act such as releasing a payment, changing a payee or sending anything external, where the profile names execution slots for the ask, and `rb=all` (every slot echoed) where it does not; the default envelope echo (`item`, `ask`, `due`) for everything else; `rb=acc` when the instruction lives in a brief. After you send an HO, you still own the item until the receiver's RB passes your check: it echoes `item`, `ask`, `due` and anything you named in `rb=`, with the same decoded values, and if you asked `rb=acc` its `acc=` matches the criteria you wrote down before sending. If it does not pass, re-send the HO with `re=` pointing at the bad RB. No RB by your deadline: re-send with `re=` pointing at the original; a receiver that already read it back replies `UN why=DUP`. When you receive an HO, send the RB before doing anything else, echoing the values exactly as you read them; if you have code, add `h=` from `make-rb`. For any receiver that has not shown it copies a line exactly, the readback is composed by code from the message as delivered (`make-rb` by id) and the model's part is to confirm or dispute its values, because the breaking-point study found a strong model's hand-written readback refused for form in 210 of 347 readbacks where the code-composed one was refused in 0 of 378. **Before an irreversible act, three checks.** Build the HO from the record by code, or check it against the record before you send it (`check-ho`): a value that differs from the record, a value the record holds that the line omits, or a missing `rb=exec` stops the line going out. When you receive an HO with `rb=exec`, read back the execution slots with the values you will actually use, resolved from your own records where the HO does not give them (the account on file; for a bank change, the account you are replacing in `prev=`); if a value you resolved contradicts the HO, ask instead of acting. As the sender, check a resolved value against your record, and clear the act with `AN val=Y` only when the readback passes. After the act, report what the tool did, not what you meant: the DN carries the same slots with the values from the tool's result, and the sender checks it against the plan (`check-dn`). That done check is the only loop that catches a tool doing something other than what it was told. Where the act tool can prepare before it commits, the readback is the tool's prepared statement, not your own values: relay the line the tool printed unchanged, compare it first with the call you made and abandon and ask if they differ, and commit only the token the sender confirmed. **A relay is a sender.** If you forward a handover you received, check the line you forward against the one you received with `check-ho`, the received HO's slots standing as the record, plus only what your role adds; and build the readback you send with `make-rb` from the message as delivered, never retyped, because on a small model a retyped line breaks the digest and holds a correct handover. **Readback is comprehension, not recall.** Echoing slots proves you saw them. When the HO says `rb=acc` or `rb=plan`, read the brief and any `art=` it points at, derive the acceptance criteria yourself, and send them as `acc=` (`len:120w+tone:plain+no:em-dash+must:monday-problem`); add `do=` for `plan`; put anything you are excluding in `not=` and anything the brief left open in `asm=`; paraphrase the task in the tail. The sender compares your `acc=` by key with criteria it kept to itself: a different value is a contradiction and a missing key is a gap, both fail the readback; an extra key is an assumption the sender will confirm. When you send an HO whose instruction lives in the tail or an artifact, ask for `rb=acc`, name the keys you will check with `keys=` (dimensions only, never the values), and write your criteria down first using the closed key set. 2. **Never guess.** Unknown version, type or code: reply `UN why=UNK` (or `why=VER`) and name the code in the tail. `UNK` describes the message; do not substitute `NOCAP` or `SCOPE`, which describe you. An AQ always gets an AN (or a UN); never leave a question unanswered because you are busy. 3. **Every reply carries `re=`.** RB, AN, DN, UN, FL, CX always. Reconstruct threads from ids, not from position. **Every HO closes exactly once**: DN, UN or FL from you as receiver, or a CX from the sender. On a CX, abandon anything prepared for it and answer `AN val=Y`; if the act already ran, send its DN instead. Never act on an HO, or confirm a tool's statement, past its `exp=`. As a sender, close a handoff you are holding with a CX rather than leaving it, and reverse a DN that did not match the plan with a new HO `ask=UNDO ctx=`. `check-thread` finds any handoff left open. 4. **Facts in slots, reasoning in the tail.** If a fact has a slot, it goes in the slot. Tail is 300 characters max. 5. **One message, two forms.** The line and its JSON twin are equivalent (see SPEC §6). Use whichever your side handles better; the codec maps between them. ## Worked exchange ``` RW1 HO research-01 from=research to=writer item=gh:crox/site#42 ask=DRAFT due=20261002 pri=2 conf=M keep=research // hero copy, 120 words, no em dashes RW1 RB writer-01 re=research-01 from=writer to=research item=gh:crox/site#42 ask=DRAFT due=20261002 h=c40bba4360dc RW1 AQ writer-02 re=research-01 from=writer to=research item=gh:crox/site#42 need=ENUM opts=formal+plain+punchy // tone? RW1 AN research-02 re=writer-02 from=research to=writer val=plain RW1 DN writer-03 re=research-01 from=writer to=research item=gh:crox/site#42 out=OK art=sha256:9f3e1c // v1, 118 words ``` Comprehension readback (sender asked `rb=acc` and kept `len:120w+tone:plain+no:em-dash+must:monday-problem`): ``` RW1 HO research-03 from=research to=writer item=gh:crox/site#42 ask=DRAFT due=20261002 rb=acc keys=len+tone+must+no art=url:https://example.com/brief // hero copy, 120 words, plain tone, no em dashes, must name the reader's Monday problem RW1 RB writer-05 re=research-03 from=writer to=research item=gh:crox/site#42 ask=DRAFT due=20261002 acc=len:120w+tone:plain+must:monday-problem+no:em-dash do=copy // landing hero, 120 words, plain, lead with the Monday problem ``` Before an irreversible act (pay profile; the HO was checked against the approval record before it went, the RB carries the execution slots, the DN carries the values the payment tool returned): ``` RW1 HO scheduler-1 p=pay from=scheduler to=payer item=inv:MD-2026-057 ask=PAY rb=exec sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 RW1 RB payer-1 re=scheduler-1 from=payer to=scheduler item=inv:MD-2026-057 ask=PAY p=pay h=a7a82de76607 sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 RW1 AN scheduler-2 re=payer-1 p=pay from=scheduler to=payer val=Y // readback matches the approved run; proceed RW1 DN payer-2 re=scheduler-1 p=pay from=payer to=scheduler item=inv:MD-2026-057 out=OK sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 // as executed, from the payment tool ``` Refusing: `RW1 UN writer-04 re=research-01 from=writer to=research item=gh:crox/site#42 why=NOCTX alt=copywriter // no style guide on file` Escalating: `RW1 AQ triage-09 from=triage to=human item=pensive:4664 need=ENUM opts=RETIRE+ACTIVATE+PARK by=20261005 // stale 15d; default PARK` ## Validate when you can If you have a shell, check every line you send or receive: ``` python3 scripts/rogerwilco.py validate "RW1 HO a-1 from=a to=b item=x:1 ask=DRAFT" python3 scripts/rogerwilco.py make-rb "" [--acc "k:v+k:v" --do copy] # readback with h= digest python3 scripts/rogerwilco.py check-rb "" "" [--profile pay] [--expect-acc "k:v+k:v"] # sender side: receipt, integrity, echo, comprehension, execution python3 scripts/rogerwilco.py check-ho "" '{"amt": "5400.00", ...}' --profile pay # before an irreversible act: the HO against the record; a relay checks the line it forwards against the HO it received python3 scripts/rogerwilco.py check-dn "" "" --profile pay [--rb ""] # after it: the done report against the plan python3 scripts/rogerwilco.py to-json "" | from-json '' python3 scripts/rogerwilco.py legend [--profile pay] [--lean] # legend for another agent's prompt; --lean drops the comprehension-readback keys (about 1,200 tokens with the pay profile) ``` If you have no shell, read the line against the table above and apply rule 2. ## Giving it to another agent Put the output of `legend` (or this file) in the other agent's system prompt. That is all the other side needs; it does not have to install anything. --- # rogerwilco protocol, version RW1 (v0.2) A one-line, typed, closed-vocabulary message format for handing work between agents and asking them bounded questions. Any agent that can read a line of text can use it. Facts go in slots. Reasoning goes in the tail. Status: draft v0.1, 28 September 2026. Author: Adam Field, Croxio Ltd. Licence: Apache 2.0. ## 1. Design goals 1. One line of text, so a message travels over any channel: a chat turn, an MCP tool result, an A2A message part, an HTTP body, a Slack post, a log file, a comment on a record. 2. Readable by a model from a short legend alone, with no library on the receiving side. 3. Validatable in code where code exists. 4. Closed vocabulary. Unknown codes are refused, never guessed. 5. Domain-agnostic core; domain vocabulary lives in profiles. ## 2. Wire format ``` RW1 [re=] from= to= [item=] [= ...] [// ] ``` Tokens are separated by single spaces. The first three are positional; everything after is keyed. Order of keyed slots is free; the recommended order is `re from to item` then type slots. | Element | Rule | |---|---| | `RW1` | Protocol version token. Required, always first. | | `TYPE` | One of the nine message types in section 3. Uppercase. | | `id` | Message id: `-`, the sending agent's name, a hyphen and a number, e.g. `writer-07`, unique within the conversation. The validator refuses any other shape, so a receiver can tell who a message came from and which of theirs it is from the id alone. | | `key=value` | Keys are lowercase `[a-z][a-z0-9_]*`. Values contain no spaces. Lists join with `+`. Dates are `YYYYMMDD`. Booleans are `Y` or `N`. | | `// tail` | Free text after ` // `. At most 300 characters. Anything longer becomes an artifact and is referenced with `art=`. | Extension keys begin with `x_` and may carry anything a profile has not defined. A receiver ignores keys it does not know unless the key is required by the type; it must refuse unknown enum values (section 5, rule 2). ### 2.1 References `item`, `art` and `ctx` carry references. A reference is any token without whitespace. The recommended shape is `:`: - `gh:owner/repo#42`, `jira:ABC-12`, `pensive:4102`, `notion:` - `url:https://example.com/x` - `file:path/to/thing.md` - `sha256:` for a content-addressed artifact - `msg:` for another rogerwilco message ### 2.2 Agents `from` and `to` are agent names: tokens matching `[A-Za-z0-9._-]+`. The name `human` is reserved for the human principal. A message to `human` is how an agent escalates. ## 3. Message types | Type | Meaning | Required slots | Optional slots | |---|---|---|---| | `HO` | Handoff: here is work, do this with it | `from to item ask` | `re due pri conf keep art ctx rb acc exp` | | `RB` | Readback: I received the handoff, here is what I understood, and I will act on it | `re from to item ask` (+ `due` if the HO had one, + anything the HO's `rb=` names, + `acc` when the HO asked `rb=acc` or `rb=plan`, + `do` for `rb=plan`, + the profile's execution slots for the ask when the HO asked `rb=exec`) | `h eta not asm exp` | | `AQ` | Ask: a bounded question | `from to need` (+ `opts` when `need=ENUM`) | `re item by` | | `AN` | Answer to an AQ | `re from to val` | `item` | | `ST` | Status: state changed on an item, no handover | `from to item` | `re pct eta blk` (+ profile state slots) | | `DN` | Done: the handed-off work is complete | `re from to item out` (+ the profile's execution slots, with the values as executed, after an act the HO asked `rb=exec` for) | `art next` | | `UN` | Unable: I will not or cannot take this | `re from to why` | `item alt need` | | `FL` | Fail: I took it and it failed | `re from to item why` | `art retry` | | `CX` | Cancel: the sender withdraws a handoff | `re from to item` | `why` | ### 3.1 Slot definitions | Slot | Type | Meaning | |---|---|---| | `re` | msg id | The message this replies to. Threads are rebuilt from `re`, never from position. | | `item` | ref | The thing under discussion. | | `ask` | enum `ask` | What the receiver should do. Core set in section 4; profiles extend it. | | `due` | date | When the work is needed. | | `by` | date | When an answer is needed. | | `exp` | time | The latest moment to act, or to confirm a prepared statement: `YYYYMMDDTHHMMZ` in UTC (`20261002T1405Z`). On an HO, nothing may be done on it after; on an RB, the statement cannot be confirmed after, and it may not outlive the HO's. Never echoed, never part of the plan. | | `pri` | int 1–5 | Priority, 1 highest. | | `conf` | `L` `M` `H` | Sender's confidence in the brief and the state it describes. | | `keep` | list of agents | Agents that stay involved after the handoff. | | `art` | list of refs | Artifacts: context on a HO, output on a DN, logs on a FL. | | `ctx` | list of msg ids | Earlier messages the receiver should read first. | | `eta` | date | Expected completion. | | `pct` | int 0–100 | Progress. | | `blk` | list of refs | Items or messages blocking this one. | | `need` | enum `need` | Answer shape for an AQ: `ENUM` `TEXT` `NUM` `DATE` `YESNO`. | | `opts` | list | Allowed values when `need=ENUM`. | | `val` | value | The answer. For `need=TEXT`, `val=tail` and the answer is the tail. | | `out` | enum `out` | Outcome of a DN: `OK` `PARTIAL` `NOOP`. | | `next` | enum `ask` | Suggested next ask. | | `why` | enum `why` | Reason code on UN, FL or CX. | | `alt` | agent | Suggested alternative receiver on UN. | | `retry` | `Y` `N` | Whether a retry is sensible on FL. | | `keys` | list of constraint keys | On an HO with `rb=acc` or `rb=plan`: the dimensions the sender will check (`keys=len+lang+must+no`). Never carries values. | | `rb` | list | On an HO: the readback level and any extra slots to echo. Levels: `echo` (default: `item ask due`), `all` (every slot), `acc` (derived acceptance criteria, section 5.2), `plan` (`acc` plus `do`), `exec` (the execution slots the profile names for the ask, with the values the receiver will use, section 5.3). Slot names add to the echo set: `rb=acc+pri`. | | `h` | 12 hex chars | On an RB: SHA-256 digest of the HO's canonical JSON form (section 5.1). Produced only by code. | | `acc` | list of constraints | Acceptance criteria as `key:value` tokens (section 5.2). On an HO: criteria the sender chooses to send. On an RB: criteria the receiver derived from the brief. | | `do` | token | On an RB: what the receiver will produce (`copy`, `spec`, `dataset`, `pr`). Required when the HO asked `rb=plan`. | | `not` | list of tokens | On an RB: what the receiver understands to be out of scope. | | `asm` | list of tokens | On an RB: assumptions the receiver is making that the brief did not settle. | ## 4. Core vocabulary Profiles may extend any of these lists. They may not redefine a core code. **ask**: `REVIEW` review against stated criteria · `DRAFT` produce a first version · `DECIDE` choose among options · `RESEARCH` find out · `BUILD` implement · `FIX` repair a named defect · `SUMM` summarise · `PLAN` produce a plan · `TEST` verify · `EXTRACT` pull structured data out · `UNDO` reverse a completed act named in `ctx` **why**: `NOCAP` no capability · `NOCTX` missing context · `PERM` not permitted · `BUSY` at capacity · `CONFLICT` item owned by another agent · `DUP` duplicate of an open handoff · `VER` unsupported protocol version · `UNK` unknown code in the message · `SCOPE` out of scope · `ERR` runtime error · `TIMEOUT` ran out of time · `REVOKED` approval withdrawn · `SUPERSEDED` replaced by a newer handoff · `EXPIRED` past its expiry **out**: `OK` · `PARTIAL` · `NOOP` **need**: `ENUM` · `TEXT` · `NUM` · `DATE` · `YESNO` **conf**: `L` · `M` · `H` ## 5. Rules 1. **No handover without readback.** An HO transfers ownership only when the receiver sends an RB that passes the sender's check (section 5.1). Until then the sender owns the item. The sender sets the level per handoff with `rb=`: the default envelope echo (`item`, `ask`, `due`) where a misread is cheap to catch later; `rb=exec` where the receiver's next act is irreversible (a payment release, a payee change, anything external) and the profile names execution slots for the ask, `rb=all` where it does not, and in that regime every `ST` or `AN` the receiver will act on is echoed back before it acts; `rb=acc` where the instruction lives in a brief. Before an irreversible act the HO is built from the record by code, or checked against it before it is sent (`check-ho`), and after the act the receiver's DN carries the values as executed, which the sender checks against the plan (`check-dn`, section 5.3). The month-end study measured full readback at 1.6x the traffic of envelope echo with nothing caught in three clean runs, which is why the level belongs to the handoff and not to the team. An RB that does not pass is void; the sender re-issues the HO with `re=` pointing at the void RB. The owner of an item is the `to` of the most recent HO that received a passing RB. No RB within the sender's deadline: the sender re-issues the same HO with `re=` pointing at the original; a receiver that already read it back replies `UN why=DUP`, so a retry cannot create two owners. 2. **Never guess.** A receiver that meets an unknown version token, type, or enum value replies `UN why=UNK` (or `why=VER`) naming the offending code in the tail. It does not infer a meaning, and it does not substitute a nearby reason such as `NOCAP` or `SCOPE`: those describe the receiver, `UNK` describes the message. An AQ is answered with an AN or refused with UN; it is never left unanswered because the receiver is busy. 3. **Every reply carries `re=`.** RB, AN, DN, UN, FL and CX always do. Threads are reconstructed from ids, and every HO closes exactly once (section 5.4). 4. **Facts in slots, reasoning in the tail.** A fact that has a slot goes in the slot, never only in the tail. The tail is at most 300 characters; longer content is an artifact referenced by `art=`. 5. **Two forms, one message.** Every line has a canonical JSON form (section 6). An implementation is conformant when it round-trips the conformance suite in both directions and produces the expected replies. ### 5.1 What a readback proves, and how the sender checks it "Did you get it" means three different things, and the RB carries all three. In radio terms, tiers 1 and 2 are *roger* (received, intact) and tiers 3 to 5 are *wilco* (understood, will comply); a handoff whose readback passes every tier the sender asked for is wilco, and only then does ownership transfer. The sender verifies them in code (`scripts/rogerwilco.py check-rb`); a model-only sender applies the same checks by reading. | Tier | Proves | Carried by | Who can produce it | |---|---|---|---| | 1 Receipt | A message with this id arrived, and the reply is from the right agent | `re=`, `from`, `to` | anyone | | 2 Integrity | The receiver holds exactly the message that was sent | `h=` digest of the HO's canonical form | code only | | 3 Echo | The receiver decoded the envelope the same way the sender encoded it | echo of the readback set, as decoded values | anyone, including model-only receivers | | 4 Comprehension | The receiver's working picture of the task matches the sender's | `acc=` derived by the receiver from the brief, compared by key with the sender's own criteria | anyone; the check is code on the sender's side | | 5 Execution | The values the receiver will act with are the ones the sender approved | the profile's execution slots for the ask, with the values the receiver will use, resolved from its own records where the HO does not give them | anyone; the check is code on the sender's side, against the HO and the sender's record | Tier 3 catches a misread of the envelope: a receiver can hold the right bytes and still read the wrong column. Tier 2 is optional and only meaningful where the receiver runs code, since a model cannot compute a hash. Tier 3 alone is recall, not comprehension. In ATC the numbers are the instruction, so echoing them verifies the instruction. Between agents the envelope is the address and the instruction lives in the tail and the artifacts, so an echo verifies the wrong thing. Tier 4 is teach-back: the receiver states what it understood in typed form, and the sender checks it against what it meant. The readback level is set by the sender with `rb=` on the HO: `echo` (default) echoes `item ask due`; `all` echoes every slot, an equality of two decoded messages; `acc` requires the receiver to derive the acceptance criteria and send them as `acc=`; `plan` requires `acc` plus `do`; `exec` requires the execution slots the profile names for the ask, with the values the receiver will act with. Slot names add to the echo set (`rb=acc+pri`). Use `exec` before an irreversible act under a profile that names execution slots, `all` when the envelope carries the stakes and the profile names none, `acc` or `plan` whenever the instruction lives in prose. ### 5.2 The comprehension check Before sending an HO with `rb=acc` or `rb=plan`, the sender writes its acceptance criteria as typed constraints and keeps them. That is the challenge. The receiver reads the brief and the artifacts, derives its own criteria, and sends them as `acc=`. The sender compares the two sets by key (`scripts/rogerwilco.py check-rb --expect-acc`): | Finding | Meaning | Result | |---|---|---| | same key, different value (`len:150w` vs `len:120w`) | contradiction: the receiver understood a constraint wrongly | readback fails | | key in the sender's set, absent from the receiver's | gap: the receiver did not extract a constraint the sender holds | readback fails | | key only in the receiver's set | assumption: the receiver filled in something the brief left open | noted for the sender to confirm or correct | | a `not=` token matching a sender `must:` | the receiver excluded something required | readback fails | The sender may also send `acc=` on the HO. Then the receiver's `acc=` is an echo of typed criteria, which verifies less but is still stronger than an envelope echo, because the receiver has to map each criterion onto the brief to know it is complete. Withholding is the stronger form and costs nothing. Constraint grammar: `key:value`, no spaces, joined with `+`, compared case-insensitively. Keys are a closed set, because two agents reading the same brief will otherwise name the same fact differently (`lang:uk`, `spelling:uk`, `lang:en-gb` were all produced in testing) and the comparison turns naming variance into false gaps. The core set: `len` (unit suffix: `120w` words, `800c` characters, `2p` pages, `10s` slides), `fmt`, `lang`, `tone`, `voice`, `aud`, `out`, `by`, `src`, `scope`, `must`, `no`. `must`, `no`, `scope` and `src` are set-valued and may repeat; the others are single-valued. A key outside the set is noted and not compared. Profiles may extend the set. The sender names the dimensions it will check with `keys=` on the HO (`keys=len+lang+must+no`). Dimensions only, never values, so nothing leaks; the receiver then knows which keys to report and a key the sender asked for that the receiver omits is a gap. Values are compared after normalisation, not as strings: lowercase, punctuation stripped, an alias table (`uk` = `en-gb` = `british`, `md` = `markdown`, `we` = `first-person-plural`). Two values name the same fact when they are equal, one is a prefix of the other, or one's word set contains the other's. Set-valued keys additionally accept a loose match on a shared distinctive word (`regulated-proof` and `regulated-ai`) and report it as a note; single-valued keys never do, so `len:120w` and `len:150w` always contradict. For `must` and `no`, the receiver should use the brief's own words, which is what makes the match reliable. A receiver's `not=` list is compared as `no:` values. The check is lossless to the resolution of the criteria the sender typed, and no further. That is the intended limit: it verifies that the two agents hold the same structured picture of the task before work starts, which is the failure that matters between agents. The paraphrase in the RB tail is for a human or model hearback and is not checked by code. Translation between profiles is the same check in a different vocabulary. A receiver on another profile reads back in its own codes; the sender maps them through a crosswalk and diffs. Where a mapping exists the check is exact; where none exists the diff names what the translation lost, so loss is visible rather than silent. Comparison is by decoded value, never by string: `pri=2` and `pri=02` are the same fact; `keep=a+b` and `keep=b+a` are not, because list order is significant. A receiver's own additions (`h=`, `eta=`, `asm=`) are not compared except as section 5.2 describes. Canonical form for the digest: the JSON form of section 6 serialised with keys sorted, no whitespace, UTF-8, lists in written order. `h=` is the first 12 hexadecimal characters of SHA-256 over that string. No third leg is needed. A passing RB transfers ownership; a failing one is answered by a re-issued HO; a ledger records which happened. That is the controller's silence after a correct readback, made explicit and checked by code rather than by attention. ### 5.3 Before and after an irreversible act A handoff before an irreversible act has three checks, and the readback is the middle one. **Before sending.** The HO is built from the approval record by code, or compared with it before it goes: `check-ho` takes the line and the record as slot values and refuses a line that differs from the record in any value, omits a value the record holds, or does not ask `rb=exec` where the profile names execution slots for the ask. The execution readback study found the sender's drafting to be the locus where readbacks catch faults, and a check before sending catches the same faults without the round trip. A relay, an agent that forwards a handover it received without acting on it, is a sender: it checks the line it forwards against the one it received with `check-ho`, the received HO's slots standing as the record, plus only what its role adds, and the readback it sends is built by `make-rb` from the message as delivered, never retyped, because on a small model a retyped line breaks the digest and holds a correct handover. The same rule applies to any receiver that has not shown it copies a line exactly: its readback is composed by code from the message as delivered and it confirms or disputes the values, since the breaking-point study found even a strong model's hand-written readback refused for form in 210 of 347 readbacks (no version token, an omitted echo) where the code-composed one was refused in 0 of 378. **The execution readback.** Under `rb=exec` the RB carries every execution slot the profile names for the ask, with the values the receiver will actually use. Where the HO carries a value, the RB's must equal it (tier 5). Where the HO does not, because the receiver resolves it from its own records (the account on file, the account a bank change replaces), the RB carries the resolved value and the sender checks it against its record in its own code. That is a readback of the plan, not an echo of the message: a receiver that resolves the account for itself and finds the handover's account is not the one on file stops and asks, and a sender that sees a resolved value its record contradicts refuses the readback. Only a passing readback, answered with `AN val=Y` where the receiver waits for one, clears the act. **The tool's statement.** Where the act tool can state its transaction before executing it, the execution readback is the tool's, not the receiver's. `prepare` resolves and validates the call, freezes it under a token, and prints the RB line with the execution slots as it will run them and `x_tok=`; the receiver relays that line unchanged, or the sender reads it from the tool's log; the sender checks it as above; `commit ` executes the statement and nothing else, and `abandon` discards it. The checked values are then the ones that run. Before relaying the statement the receiver compares it with the call it made, and abandons and asks when they differ. The prepare-and-confirm study found this the one control that stops a fault the tool introduces when it resolves the call: the receiver's own readback passed every such fault, and the tool's statement stopped every one, read first by the receiver that set it. **The done report.** After the act the DN carries the same execution slots with the values as executed, taken from the tool's result, not from the receiver's intention. The sender checks it against the plan with `check-dn`: the DN answers the HO (receipt), and every execution slot it carries equals the HO's value, or the readback's where the HO left the slot to the receiver and the readback passed; a slot missing from an `out=OK` report fails; `out=NOOP` carries none, since nothing was executed; `out=PARTIAL` carries the ones that were. This is the loop that catches what no readback can: a tool that did something other than what it was told. It only works when the receiver reports what the tool printed, so a legend or a brief says so in those words. The tool's statement carries `exp=`, the end of its window for confirmation, and `commit` refuses after it: a confirmation that arrives late finds the world it was checked against may have moved (an account closed, an invoice paid elsewhere), so the statement is prepared again instead. A CX for the handoff makes the tool abandon any statement prepared for it. The execution slots are a profile's: an `exec` map from ask to slot list (section 7). An ask the profile does not list under `exec` gets no tier 5 and no done check beyond the receipt, and `check-rb` says so in a note. ### 5.4 Every handoff closes A handoff that nobody closes is the protocol's black hole: the sender believes it is in hand, the receiver believes it is held, and a prepared statement waits for a confirmation that may come hours later. So every HO ends exactly once, with one of four messages: - `DN`, `UN` or `FL` from the receiver; or - `CX` from the sender, which withdraws the handoff (`why=REVOKED` when the approval was withdrawn, `SUPERSEDED` when a newer HO replaces it, `EXPIRED` when its window passed, `TIMEOUT` when no readback came). The receiver abandons anything prepared for it and answers `AN re= val=Y`. If the act had already run, it answers with the act's DN instead: a cancel cannot unsay an act, and the sender then decides whether to reverse it. A held handoff, one whose readback failed and was not re-sent, is closed by the sender with a CX, never left. An HO with `exp=` that is still open after it is orphaned, as is one open after its `due=` date. A DN whose values do not match the plan (section 5.3) is reversed with a new HO, `ask=UNDO ctx=`, which carries its own readback and checks. `check-thread` takes a conversation in the order it was sent and reports: an HO still open past its `exp=` or `due=`, an HO closed more than once, a DN reporting the act done after a CX (`out=NOOP`, nothing ran, is the correct answer to a cancel that arrived first), and a CX that does not come from the HO's sender to its receiver. A CX not yet acknowledged, or an HO open and in time, is a note. ``` RW1 HO scheduler-1 p=pay from=scheduler to=payer item=inv:MD-2026-057 ask=PAY rb=exec ... exp=20261002T1700Z RW1 CX scheduler-2 re=scheduler-1 p=pay from=scheduler to=payer item=inv:MD-2026-057 why=REVOKED // FD-0929 withdrawn RW1 AN payer-3 re=scheduler-2 p=pay from=payer to=scheduler val=Y // statement abandoned; nothing paid ``` The cancel, the expiry and the closing rule came from a reader's point after study 7 was published: the two-phase tool held its statement indefinitely and the protocol gave the sender no way to withdraw a handoff. They are design, with conformance cases; no study has measured them yet. ## 6. JSON form ```json {"v":"RW1","type":"HO","id":"research-01","re":null, "from":"research","to":"writer","item":"gh:crox/site#42", "slots":{"ask":"DRAFT","due":"20261002","pri":2,"conf":"M","keep":["research"]}, "tail":"hero copy, 120 words, no em dashes"} ``` Encoding rules: integers are JSON numbers; lists are JSON arrays; `Y`/`N` are JSON booleans; everything else is a string. `re` and `tail` are `null` when absent. Slot order in the line is not significant. ## 7. Profiles A profile is a JSON file that extends the core for a domain. It may add enum values to `ask` and `why`, add new enums, add slots (with the types they apply to and whether required), and name agents. It may not remove or redefine anything in the core. Slot types: `token` (no spaces), `ref` (an item reference), `enum:`, `list:` (joined with `+`), `int:-`, `date` (YYYYMMDD), `days`, `bool` (Y or N), `money` (two decimals, no separators), `num` (a number as written, or two numbers with a slash such as a blood pressure, units in their own slot), `msgid`, `agent`. ```json {"profile":"pform","version":"0.1","extends":"RW1", "enums":{"stage":{"DIA":"diagnose","REV":"review","OPT":"options","DEC":"decide","ACT":"action","DONE":"done"}}, "asks":{"SCORE4":"run 4-axis scoring","ENRICH":"fill missing fields","PRUNE":"retire or merge"}, "slots":[{"key":"stg","type":"enum:stage","on":["HO","ST"],"required":false}], "agents":["TRI","STR","ARC","BLD","QAR","CPY","GRD","DAT","MKT","SCT","IDE"]} ``` A message using a profile carries `p=` as a keyed slot, e.g. `p=pform`. Without `p=`, only core vocabulary is valid. A profile may also close the answer vocabulary. If it defines an enum `ans`, every `AQ need=ENUM` must draw its `opts` from `ans` and every `AN val` (other than `tail`, `Y`, `N`, a number or a date) must be one of them. Without it, agents invent option words (`OFFRUN`, `KEEPFILE`, `PAY-USD`) and the human, or a policy engine, has to decode each one; the month-end study saw seven different spellings of the same three answers in twelve runs. The pay profile's vocabulary is `APPROVE DECLINE HOLD REJECT CALLBACK`. A profile may list `asks_used`, the core asks its domain needs, so the lean legend omits the rest. Slots a profile adds may carry `desc`, which the legend prints. A profile may name execution slots: an `exec` object mapping an ask to the slots an act is done with. The pay profile names `sup amt cur kind acct paid ref apref` for `PAY`, `sup acct eff apref prev` for `CHGBANK` and `sup addr amt cur paid ref` for `SEND`, and adds `prev` (the account on file that a bank change replaces) on `RB` and `DN`, so that a readback before a bank change says which account is being replaced. `rb=exec`, tier 5 and `check-dn` are defined over this map (section 5.3). A profile may make the lines the record. The pay profile adds `src` (`list:token`: `email:7`, `bank:20260918`, `po:PO-1044`), `net` and `vat`, and a `translation` note that a channel enforces: every `ST` about a source document carries `src=`, an invoice line carries `amt cur kind`, and net plus VAT must equal the amount when all three are given. Under that rule the agent at the edge translates each source document once into one line, every fact in a slot and a pointer back to the evidence, and no agent writes a working file to restate what the lines already carry. In the month-end study this cut the files the agents pointed each other at from 22k tokens a run to under 2k, with the extractor's lines carrying every checked fact and the outcome unchanged. The `clin` profile applies the same rule to clinical documents: one `ST` line per statement, `item=:`, `act pol cert subj time` required, values in typed slots, one `src=doc:#l` and the verbatim quote as the tail, checked against the document by the channel. The translation study (research/translation) measures it: a strong model found 99.7% of the gold assertions with 99% citing the exact words; the record costs about 60 tokens an assertion, of which the per-line envelope is 28%, so a record form that states the envelope once per block is the next revision. The legend a model needs is generated from the core plus the profile (`scripts/rogerwilco.py legend --profile pay`). `--lean` drops the comprehension-readback keys and the unused core asks for teams that use envelope echo only: about 1,200 tokens with the pay profile against about 1,600 for the full legend. ## 8. Exchanges Handoff, question, completion: ``` RW1 HO research-01 from=research to=writer item=gh:crox/site#42 ask=DRAFT due=20261002 pri=2 conf=M keep=research // hero copy, 120 words, no em dashes RW1 RB writer-01 re=research-01 from=writer to=research item=gh:crox/site#42 ask=DRAFT due=20261002 h=c40bba4360dc RW1 AQ writer-02 re=research-01 from=writer to=research item=gh:crox/site#42 need=ENUM opts=formal+plain+punchy // tone? RW1 AN research-02 re=writer-02 from=research to=writer val=plain RW1 DN writer-03 re=research-01 from=writer to=research item=gh:crox/site#42 out=OK art=sha256:9f3e1c // v1, 118 words ``` Handoff with a comprehension readback (the sender kept `len:120w+tone:plain+no:em-dash+must:monday-problem` to itself): ``` RW1 HO research-01 from=research to=writer item=gh:crox/site#42 ask=DRAFT due=20261002 rb=acc keys=len+tone+must+no art=url:https://example.com/brief // hero copy for the landing page, 120 words, plain tone, no em dashes, must name the reader's Monday problem RW1 RB writer-01 re=research-01 from=writer to=research item=gh:crox/site#42 ask=DRAFT due=20261002 acc=len:120w+tone:plain+must:monday-problem+no:em-dash+fmt:md do=copy // landing hero, 120 words, plain, no em dashes, lead with the Monday problem ``` `check-rb --expect-acc` passes and notes `fmt:md` as an assumption to confirm. Had the receiver sent `len:150w`, the readback would fail on a contradiction before any copy was written. Before an irreversible act, under the pay profile (the HO was checked against the approval record with `check-ho` before it went; the readback carries the execution slots; the done report carries the values the payment tool returned): ``` RW1 HO scheduler-1 p=pay from=scheduler to=payer item=inv:MD-2026-057 ask=PAY rb=exec sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 RW1 RB payer-1 re=scheduler-1 from=payer to=scheduler item=inv:MD-2026-057 ask=PAY p=pay h=a7a82de76607 sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 RW1 AN scheduler-2 re=payer-1 p=pay from=scheduler to=payer val=Y // readback matches the approved run; proceed RW1 DN payer-2 re=scheduler-1 p=pay from=payer to=scheduler item=inv:MD-2026-057 out=OK sup=marlow amt=5400.00 cur=GBP kind=GROSS acct=40-12-88/61102938 paid=20261002 ref=MD-2026-057 apref=FD-0929 // as executed, from the payment tool ``` `check-rb --profile pay` passes all five tiers; had the payer read back `amt=5100.00`, tier 5 would fail on the amount, and had it left out `acct`, tier 5 would fail on the missing slot. `check-dn --profile pay` passes; had the tool paid 5,100.00 and the payer reported it, the done check would fail on the amount, which is the fault no readback can see. Refusal with an alternative: ``` RW1 UN writer-04 re=research-01 from=writer to=research item=gh:crox/site#42 why=NOCTX alt=copywriter // no style guide on file ``` Escalation to the human principal with an option set: ``` RW1 AQ triage-09 from=triage to=human item=pensive:4664 need=ENUM opts=RETIRE+ACTIVATE+PARK by=20261005 // stale 15d, no owner active; default PARK ``` Void readback and re-issue: ``` RW1 HO ops-03 from=ops to=builder item=jira:OPS-77 ask=FIX due=20261001 RW1 RB builder-05 re=ops-03 from=builder to=ops item=jira:OPS-77 ask=BUILD due=20261001 RW1 HO ops-04 re=builder-05 from=ops to=builder item=jira:OPS-77 ask=FIX due=20261001 // readback mismatch on ask RW1 RB builder-06 re=ops-04 from=builder to=ops item=jira:OPS-77 ask=FIX due=20261001 ``` ## 9. Where it sits rogerwilco is content, not transport. It rides inside an A2A message part, an MCP tool result, a chat turn or a queue message. It does not do discovery, authentication or task lifecycle; the transport or runtime does. It replaces the free text those carry with a line that can be validated, read back and audited. ## 10. Evidence The format choices come from a 240-message study (September 2026). Keyed form rather than positional because a small model miscounted positional columns in one run of two while reading keyed form at 90–100%. Three-letter codes rather than glyphs because glyph savings were tokenizer-dependent and the legend cost 1.7x more. Readback because the errors observed were misreads of a neighbouring field, which an echo catches at the boundary. The comprehension tier because an envelope echo is recall, and between agents the instruction lives in prose the envelope does not carry. The closed constraint key set and `keys=` because the first end-to-end test (two sender agents, four receiver agents, one brief) showed every receiver understanding the brief while the comparator reported four to ten false gaps from key naming alone; with the closed set and `keys=`, three of four pairs passed clean and the fourth reported one real omission (the receiver had dropped "without naming clients"). In the same test, receivers given a tail that said "short paragraph" and an artifact that said 150 words reported `len:150w`, so a sender who believed 120 words was caught by a contradiction before any work was done. Free text confined to a bounded tail because real agent logs were 50% process narration and 26% narrated field changes. The execution level, the check before sending and the done check come from the execution readback study (research/plan-readback, 2,762 trials, October 2026): against a correct handover and a context built to mislead, Sonnet 5.5 and Haiku 5.5 made no wrong irreversible act with or without a readback (0 of 140 induced trials per cell), so the readback at the receiver's locus is insurance with a measured price of one turn and no false alarms in 75 clean readbacks; against planted faults in the sender's draft, no readback let 17 of 48 on Sonnet and 14 of 48 on Haiku through to a wrong act, the echo and the execution readback caught 48 of 48 each, and a check before sending refused all 16 planted handovers; against a tool that executed something other than what it was told, the sender's check of a done report that carried the tool's values caught 45 of 45 per receiver. The tool's statement comes from the prepare-and-confirm study (research/tool-prepare, 794 trials on Haiku 5.5, October 2026): a fault the tool introduced when it resolved the call became a wrong act in 59 of 60 trials with a one-step tool, readback or not, and in 0 of 60 with a two-phase tool whose statement the receiver read before relaying it; no false alarm, no early commit and no altered relay in 255 checked statements; a fault at commit got through every arm and the done check flagged every one. On gpt-5.4-mini (1,014 trials), the receiver whose own readbacks fail, the echo let 14 wrong acts through and the typed execution readback was held in 52 of 129 trials, while the tool's statement with the commit enforced made 0 wrong acts, was held 7 times and had the old account and a lookalike address caught at the sender's check; the receiver altered the relayed statement in 51 of 129 trials and never committed early, which is why the sender reads the tool where the receiver has not shown it relays faithfully. The relay case comes from the relay study (research/chain, 792 chains, October 2026): a handover passed through one to five relays in four forms, and no value was changed by a relay in any form; on Haiku 5.5 the line checked at every hop ended in a correct act in 45 of 45 clean chains against 37 of 45 for free prose, whose losses were the address and the provenance rather than the facts, with 0 false alarms in 90 forwards; on Haiku 4.5 the retyped readback held 13 of 128 with every value right, which is why a relay's readback is built from the message as delivered. The composed readback for any receiver, and the floor of the form, come from the breaking-point study (research/breaking, 1,585 chains on Qwen2.5 1.5B to 14B and Llama 3.2 3B run locally without tools and on Haiku 4.5 and 5.5 through the same text-only runner, October 2026): below about 14B no form gave throughput and every one of 51 wrong acts was another item from the relay's inbox, the checked arms made 0 wrong acts in 647 chains, 0 of 390 injection chains complied with a planted payee account in any form, Qwen 14B reached 5 of 6 clean chains with 0 wrong acts under the code-composed readback against 5 of 6 with 1 in free prose and 0 of 6 with 2 on the unchecked line, and on Haiku 5.5 the readback the model wrote itself was refused for form in 210 of 347 readbacks and held 57 of 90 chains with every value right where the code-composed one was refused 0 times in 378.