diff --git a/11-Features.md b/11-Features.md index f2f83ef..beeb0dd 100644 --- a/11-Features.md +++ b/11-Features.md @@ -57,6 +57,8 @@ six weeks, and the table alone will not carry it. | [Answer a question on an async delegation](#answer-a-question-on-an-async-delegation) | automatic | CB-574 | `msg/MessageService` | | [Learn why a delegation died](#learn-why-a-delegation-died) | automatic | CB-568 | `inject/CompletionResolver` | | [Watch the fleet's health](#watch-the-fleets-health) | `health:` | CB-573 | `health/FleetHealthMonitor` | +| [Tell a usage-limit refusal from a real reply](#tell-a-usage-limit-refusal-from-a-real-reply) | profile `exhaustedPattern:` | CB-578 | `inject/CompletionResolver` | +| [See which charter a member got](#see-which-charter-a-member-got) | automatic | CB-571 | `peer/CharterReceipt` | Nearly every knob above lives in one file, on one profile: @@ -1020,6 +1022,55 @@ and has its own test — it is a known edge, not an oversight. The retries also --- +## Tell a usage-limit refusal from a real reply + +**What.** A member can end its turn without calling `bridge_reply`. The bridge then scrapes the pane +and hands that text back as the answer. Sometimes that text is not an answer at all — it is the +backend refusing, because the account hit its usage limit. With this on, the bridge matches the scrape +against a pattern you configure. On a match it resolves the send as `BACKEND_EXHAUSTED` and carries the +matched line as the reason, instead of passing a refusal off as a completed reply. + +**On.** Per profile, `exhaustedPattern:` — a regex. Opt-in: leave it out and that profile's completion +fallback behaves exactly as before. Startup logs one line naming which profiles have a pattern and +which do not, so you can see the coverage without reading the config by hand. + +**Why.** The old behaviour lied in the worst direction. A lead asked for work, got back a block of +text, and had no way to tell "here is your answer" from "my account is refusing to run". The lead +would then treat a refusal as a result. Keeping the pattern in config, never in Java, is deliberate: +every backend words its refusal differently, so a sentence baked into the code would only ever match +one vendor. + +**Gotcha.** The pattern map is built once at startup from the config snapshot, so `exhaustedPattern` +is a **deferred** key — adding one to a profile does nothing until the daemon restarts. +`bridged.example.yaml` does not say this yet. Also, this stage only *classifies*. Nothing yet stops +the fleet spawning another member onto the same exhausted account, and nothing yet saves the work that +member was doing — those are stages B and C of CB-578. + +--- + +## See which charter a member got + +**What.** Every member launch records a `CharterReceipt`: the role, where the charter came from, a +sha-256 digest of it, and its size in bytes. It is stored on the session and shown in the roster +(`bridge_list` and `GET /members`). The charter text itself is never recorded. + +**On.** Automatic. + +**Why.** Charters are per-role config and are re-read on every spawn, so two members of the same role +can get different text without anyone noticing. The digest answers "did this member actually get the +charter I think it got?" without printing prompt text into logs an operator may not be allowed to +keep. It also closed a real leak: the older pane-placement spawn log printed the whole `argv`, and the +charter travels inside `argv`. That argument is now replaced by its digest. + +**Gotcha.** `PeerHandle.charterReceipt()` is deliberately **not** a `default` method. It used to be, +and `OpenCodeLauncher`'s wrapping handle forgot to override it — so it answered `null` while the real +receipt sat on its delegate, and `sol` and `terra` silently showed no receipt at all while Claude Code +members showed one. Nothing failed; the roster field was just quietly missing. Removing the `default` +makes the compiler catch that, and any new adapter must now answer the question on purpose. If you add +a `PeerHandle` implementation, this is the line that will not let you skip it. + +--- + ## Backfill status This page was started after the fact, so it is **not yet complete**. Entries above are written from