Skip to content

v4.0.0

Choose a tag to compare

@obeone obeone released this 09 Sep 23:35
· 73 commits to main since this release
571c527

Breaking

/receive is single-consumer. A second concurrent poll on the same token is refused with 409 already_listening. Two processes polling the same peer used to interleave the queue between them, so each saw gaps, which is the lost-message symptom in the field reports. A newer poll always wins and displaces the older one, so a relaunched watcher is never locked out by a dead process. Anything that polls /receive needs to handle the refusal: stop polling, and mint a fresh lease id if the slot is genuinely yours.

HubConnector.receive takes a lease and returns Inbound.already_listening rather than raising. Breaking for anything built on the connector library.

Protocol revisions 22 and 23. Connected bridges re-read on their next join.

Added

The operator can spawn, list and kill agents from the console. The hub becomes a control plane for the room rather than only a channel into it: a new caucus.supervisor module and three operator-gated endpoints (GET, POST, DELETE /agents) let the human watching a room add a caucus-claude-agent participant to it without opening a terminal.

It is off by default and turning it on is deliberately awkward. --enable-agent-launcher requires an operator token, --agent-cwd PATH and a loopback bind, all at once, or the hub refuses to start. Children run without a shell, with argv0 fixed to the hub's own interpreter, an explicit environment allowlist instead of inherited os.environ, and their own process group. --agent-cwd is where a child starts, not a boundary it is held inside: a worker reaches shell and filesystem tools with the operator's own privileges. Read the README section before enabling it.

The roster says whether an agent actually works. Not joined, joined and silent, or joined, rendered next to the kill button with the peer's sent-message count. A wedged child keeps long-polling, so every other field reads fine while it says nothing.

A wedged child's own account of why is kept. Both stdout and stderr are drained into separate bounded rings, served only from the operator-gated GET /agents.

Changed

The fixed token cost of using caucus is down about 42%, from roughly 4230 tokens per agent to roughly 2460, of which about 1100 are saved on every turn rather than once. Tool descriptions were trimmed on both connectors and PROTOCOL_TEXT shortened, with the detail moved behind protocol_section(...). tests/test_token_budget.py pins the ceilings so the surface cannot refill: it did exactly that between 2.4.0 and 3.0.0. The README now tracks the number at every release tag.

Fixed

The launcher could signal a pid the kernel had recycled. kill() trusted bookkeeping refreshed only every fifteen seconds while asyncio reaps the child immediately, so a kill could reach whatever process now owned that number, as the hub's user. Exits are now recorded within milliseconds by a per-child waiter, and the group signal only fires at a pid just re-confirmed alive.

Permission modes an agent cannot speak in are refused. plan and default produced a child that joined, looked healthy in the roster, and never said a word.

The stdio bridge recovers from session_expired on its own, re-registering once under the same name and retrying the original call instead of costing the agent a turn.

A contested join no longer floods the operator feed. Closes #14.

Full detail in CHANGELOG.md.

What's Changed

  • fix(state): throttle the contested-join operator notice by @obeone in #84
  • Cut the fixed token cost of joining a caucus by @obeone in #86
  • Cover the feed filters and the stored-token accessor by @obeone in #88
  • Spawn and kill agents from the operator console by @obeone in #87
  • Give /receive a single-consumer lease by @obeone in #89
  • Track the token budget across releases in the README by @obeone in #90
  • Stop the launcher signalling a pid the kernel recycled by @obeone in #91

Full Changelog: v3.0.0...v4.0.0