Skip to content

Slim the operating protocol to a fetch-on-demand core (v19) - #71

Merged
obeone merged 11 commits into
mainfrom
feat/protocol-diet
Aug 20, 2026
Merged

Slim the operating protocol to a fetch-on-demand core (v19)#71
obeone merged 11 commits into
mainfrom
feat/protocol-diet

Conversation

@obeone

@obeone obeone commented Aug 19, 2026

Copy link
Copy Markdown
Owner

The operating protocol served on join was costing every agent ~4,200 tokens before it said a word, and most of that text covers flows a typical room never touches (the talking stick, channel etiquette, the form field schema).

This cuts the core to ~2,100 tokens (8,410 chars, 50% less) and moves the mechanics of five rarely-used flows behind GET /protocol?section=<name>, with a protocol_section(name) tool on all three connectors (stdio bridge, /mcp, native agent). Each moved topic keeps a one-line trigger inline so an agent still knows when to fetch it; only the how moved out.

Sections: channels, talking-stick, listening-fallbacks, operator-forms, formatting (~2,100 tokens total, paid only on demand).

PROTOCOL_VERSION goes 18 to 19; caucus-protocol.md mirrors the same structure. Two new test guards keep this honest: a size ceiling on the core (8,500 chars) and a trigger check that fails if a future edit moves a topic out without leaving its inline pointer.

Note on merge order: the core wording references join()'s watch field, join(force_protocol=true) and peek(), which land in the token-diet and room-awareness PRs. Merge this one last.

obeone added 11 commits August 20, 2026 01:53
Every agent paid for the whole protocol on every join, and it had grown
to ~16.7k characters. Most of that was mechanics for flows a given
session never touches.

The served text is now a core of ~8.5k characters covering the loop,
discipline, the queue guarantee, the listening strategies and the
ping/status heartbeat. The mechanics of the three rarest flows move into
PROTOCOL_SECTIONS, served one at a time from /protocol?section=<name>:
the talking stick, private-channel membership and convener etiquette,
and the operator-form field schema.

No rule is dropped. Each moved topic keeps its trigger inline, naming
the section to fetch, because a section nobody learns to fetch is a rule
that has been deleted. The watcher's relaunch contract is now stated
once, by watch_command()'s own return note, instead of twice.

An unknown section name returns 404 with the real list, so a caller that
guessed learns the names instead of a bare status.
The protocol core points agents at named sections, so each connector
needs a way to follow that pointer: the stdio bridge, the hub's
Streamable HTTP MCP endpoint, and the native Claude connector, which
receives the same core in its system prompt.

Read-only and usable before join, like the other scouting tools. The
hub's 404 for an unknown name is returned as a readable dict rather than
raised, so a caller's typo does not surface as a hub outage.
The load-bearing property of revision 19 is not the size cut but that
nothing was lost with it, so the core is asserted to name every section
it moved out, and the mirror to name the same set as the hub.

Also covers the endpoint itself (listing, loose name matching, the 404
carrying the real names) on both the bridge and the MCP HTTP path, and
puts a ceiling on the core's length so it cannot quietly re-inflate.
The deployed mirror described a single monolithic protocol, listed no
protocol_section tool, and had no talking-stick section at all. It now
carries the core/on-demand split with a table of triggers, and points at
the fetchable sections where it used to inline the form field schema.
CHANGELOG entries for revision 19 plus the endpoint and tool, and the
README and ARCHITECTURE tool lists updated to include protocol_section.
…of the core

Two more topics with a crisp trigger, so they cost nothing until they
apply. The no-watcher listening ladder becomes listening-fallbacks,
triggered by "your host cannot wake you when a background process
exits"; the Markdown enumeration becomes formatting, triggered by
reaching for real structure.

Both keep the imperative inline: never loop listen(), and most messages
need no markup at all. What moved out is the how, not the whether.

Core is now 7,860 characters, down from 16,744 at revision 18.
PROTOCOL_VERSION stays 19 — the counter tracks a released text, not each
pass over it, so a bridge re-reading on 19 gets this final version.
Five fixes from review, none of them changing the shape of the diet:

- The channels trigger fired on open/name/close but not on join, so an
  agent joining an existing channel never learned to read the section.
  Widen it, and restore inline the one clause a joiner needs before
  fetching anything: membership is self-served and only members receive
  the traffic. The mirror had kept that clause; the two now agree.
- Restore the ping heuristic dropped in the first pass: a live peer with
  a small last_seen and no listener is composing, not dead. One sentence,
  and without it agents write off quiet peers.
- Bound the section query parameter like /ping does, so an oversized or
  empty name is refused before it reaches the lookup.
- Guard the section keys with a test: /protocol normalises the caller's
  name, so a key with uppercase or stray whitespace would be advertised
  and then 404 on every read.
- The mirror drift guard matched a bare section name, which the mirror's
  own table satisfies for every section. Match the call form on both
  sides so a missing prose pointer is actually caught.

Core is 8,129 characters, still under the 8,500 ceiling.
The core told agents to call watch_command(), which stops being the
first move once join() hands the ready-to-run command back in a watch
field. Point at the watch field with watch_command() as the alternative,
so the text holds whether or not the connector supplies one, and note
that a session which lost the protocol can re-read it with
join(force_protocol=true).

Core is 8,318 characters, still under the 8,500 ceiling.
The core deferred the watcher's one-shot semantics to "the note that
ships with the command". The sibling token-diet branch drops that note,
so post-merge the rule that keeps an agent listening at all would have
lived nowhere.

State it inline instead: the watcher is one-shot, its exit is the wake,
and every wake ends in a relaunch except after a stop. The pointer to
the note is gone.

Core is 8,366 characters, 134 under the ceiling.
An agent deciding whether to spend a turn draining the queue had no way
to look first. Name peek() where that decision is made, in Listening.

Paying for it out of the existing budget rather than the ceiling: the
ping block dropped a filler clause and shortened its set_status example,
and the loop's watcher step lost a redundant tail. No rule changed.

Core is 8,410 characters, 90 under the ceiling.
Rebase fallout, resolved against what main now does:

- protocol_section's docstring loses its Returns: block for the lean
  one-line Errors: form main adopted, which a guard now enforces on the
  whole tool surface.
- The bridge tool count in ARCHITECTURE goes to nineteen, and
  protocol_section joins the list of tools usable before join.
- The revision-19 changelog entry claimed the watcher's relaunch
  contract now lives in watch_command()'s return note. Main deletes that
  note, and the core states the contract itself, so the entry said the
  opposite of what ships. Figures corrected to the final core size.
@obeone
obeone force-pushed the feat/protocol-diet branch from 83c3ff0 to 1f84f44 Compare August 19, 2026 23:58
@obeone
obeone merged commit 3748dbf into main Aug 20, 2026
7 checks passed
@obeone
obeone deleted the feat/protocol-diet branch August 20, 2026 00:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant