Skip to content

feat(intercom): mail rides the same queue as the operator's own typing - #17

Closed
lucy-wolfe wants to merge 12 commits into
tribes-protocol:mainfrom
lucy-wolfe:feat/mail-rides-steer
Closed

feat(intercom): mail rides the same queue as the operator's own typing#17
lucy-wolfe wants to merge 12 commits into
tribes-protocol:mainfrom
lucy-wolfe:feat/mail-rides-steer

Conversation

@lucy-wolfe

Copy link
Copy Markdown
Contributor

A busy person now reads an ordinary message at the next step boundary inside the running turn, instead of at the end of it.

The gap this closes

The operator typing mid-turn is submitted with streamingBehavior: "steer" and consumed within seconds. Ordinary organization mail rode the follow-up queue, which Pi consumes only once the agent has no more tool calls or steering messages — the end of the turn.

So somebody an hour into a piece of work did not see a teammate's message until the hour was over, while the identical words typed by the operator arrived immediately. Deliveries were already triggered mid-turn; only the consumption point deferred.

Interrupt mail already rode the steering lane. This widens a production-proven path rather than building one.

What does not change

  • The digest is kept. Batching is still the answer to twenty messages arriving in one turn; nothing about how a batch is built has moved, only when it is consumed.
  • The idle row is byte-identical. Mail arriving at somebody doing nothing still starts them.
  • The boot window is byte-identical. Mail arriving inside it is still parked.
  • The wake lease is untouched by construction. This is client-side delivery-mode selection; the lease's readers key on person_activity.operator_wake_at, which this change neither reads nor writes. Nothing in the converge, activity or settle paths is involved.

The blocker that was checked before anything was changed

The delivery receipt (deliveriesConsumedThisTurn) is what tells a sender their message was actually eaten, and if it only fired at turn end then consuming mid-turn would lose it.

It hangs off message_start, not turn_start — so it is written wherever the message is consumed. Interrupts have been consumed mid-turn in production all along and receipted through that same handler. A normal envelope now takes an identical path.

Had that not held, this would have stopped here.

Three call sites considered, two moved

site outcome
batch digest delivery → steering queue
single envelope delivery → steering queue
work-resume prompt kept — not a mailbox envelope; nobody is waiting on it, and it asks the person to pick their own work back up, so arriving at the end of the turn is the honest reading

The input-requeue rescue and the printed-tool-call corrective also keep follow-up. Both already carry written rationales, and both remain true — a submitter who did not know a turn was running, and a correction to a turn that has just ended.

An unreachable ternary went with it. Normal urgency is routed away three lines above the interrupt delivery, so isInterruptDelivery ? "steer" : "followUp" could never take its false arm, even before this change.

The change was unpinnable as written, so it was made pinnable

This is the part worth reviewing most carefully.

The delivery table was already covered by a test. But which mode the mailbox passed into that table was two string literals at two call sites — so the timing of every delivery in the product could change with every test still green. That is exactly what happened: the first implementation went green immediately, which is what prompted looking for what was missing rather than trusting it.

The mode is one named decision now, and the new test asserts the rule rather than the table. Verified by mutation: reverting the mode to follow-up fails it, where the pre-existing suite passed.

Harness note

The changed surface is the pure mode table, which is unit-pinned. Pi's steering semantics are upstream and proven by two paths already in production — the operator's own typing, and our interrupt mail. Mentioned because an earlier decision entry flagged the missing end-to-end harness and a reader will reasonably ask.

Checks

bun run typecheck, test, lint, lint:reactive, knip, all 98 guards, the tool-contract lanes by path (4 files, 31 tests run), and CI.

Lucy and others added 10 commits August 28, 2026 15:57
The word is a claim about whose fault the failure was, and the two invite
opposite recoveries: refused invites a corrected call, failed invites a retry.
Calling a crash a refusal is the worse direction, because it sends somebody to
fix a call that was never wrong.

The verb follows the classification rather than a list, so a refusal added next
month gets the right word without anyone remembering. One predicate in one
place: three renderers build a failure title, and two of them are bespoke
titles that would otherwise have kept saying failed for a refusal -- the
both-arms property again, found by sweeping for the word rather than trusting
the default path to be the only one.

One case was not what the classification said. A partial hire carries a status
so the card can name the people already hired, which a retry needs so it does
not double-hire them -- but the error it wraps may be a genuine crash. A status
carried for CONTEXT is not a claim about fault. That path now asks the wrapped
error's own type, and a producer in the same position marks itself rather than
being enumerated in the renderer, because a list is where the next case is
missed.

Pinned by a discriminating pair: a classified refusal is refused, an
unclassified failure is not, and a context-carried status with fault:true is
not. Verified by mutation -- making the predicate return true fails both
negative cases.

Checked the collapsed card: the verb is one character longer and the title is
not what gets truncated, so no card's summary window moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ed a default

The field was REQUIRED under a description opening 'DEFAULT: the department YOU
head'. An agent read that, reasoned correctly that it should omit the field,
met a schema that would not allow it, and improvised the most salient name in
context -- the company's. It obeyed the instrument over the claim, which is the
right thing for it to do.

The field is optional now and the default is resolved in execute using the
helper that already existed: the department the caller heads, or failing that
the one they sit in. That is word-for-word what the description promised, so
the promise was always implementable and simply was not implemented.

The worked example now shows the omitted form first. An example that keeps
passing the field re-teaches the habit the description is trying to correct.

The sweep asked the general question of every parameter in the file -- does the
schema permit what the prose promises -- across DEFAULT, omit, omitted,
optional and leave empty. Exactly one leaf field disagreed: this one. The only
other hit says 'never omit this field' and is required, so its prose and schema
agree. No mechanical guard was added: an instrument for a class of one is
furniture.

My first sweep reported sixteen mismatches. All but one were container objects
matching on a nested field's prose -- the detector measured a property adjacent
to the question. Refining it to leaf fields gave the real answer.

Also caught by an existing guard: the longer tool description exceeded the 1024
characters a strict provider accepts. Trimmed to 993 rather than expanded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ng past a lecture

What it is in two sentences, the animation, install, and the first company
now lead the page. Depth moved into short skimmable sections or linked
docs: the crate table went to docs/ARCHITECTURE.md, which already carries
it, and the long GIF caption and quick-start asides were trimmed.

New: an Everyday commands table, derived from the routed operator verbs in
chief-cli's OPERATOR_VERBS, and a Contributing section naming the two open
workstreams (finishing apps/web, and multi-machine hosting so one busy
company stops pegging a single box's CPU) plus the issue tracker and
CONTRIBUTING.md.

Every command documented was checked against install.sh and the routed
verb table. The Pi floor line keeps Pi and 0.80.10 on one line for
pi-floor-single-definition.test.mjs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
The verb moved to the fault marker and the tag did not. The tag still measured
only the ABSENCE of a status, so a partial batch carrying one for context while
wrapping a real crash said "failed" -- correctly -- and then dropped the crash
marker. Somebody debugging that would see a list of people already hired, no
fault marker, and reasonably conclude they had passed bad input.

One classification, two surfaces. The tag now reads the same marker, and a test
asserts the two are complementary rather than merely each correct: nothing may
be both a refusal and a system fault, which fails if either rule moves without
the other. Verified by mutation -- putting the tag back on the old instrument
fails the crash case and only that case.

This was the scope note I flagged rather than fixed in the first pass. Review
ruled it fix-here, and that was right: shipping the verb on the new instrument
and the tag on the old one is a divergence nobody would find again except by
hitting it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The installer already edits the shell profile and reports exactly which
file it touched, so the extra line was a second copy of work the script
does itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
The animation speaks for itself; the recording provenance note is not
needed on the front page.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Three steps a stranger can paste in order: Pi, chief, first company. The
Pi floor stays on one line with the name for the pi-floor guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Step 3 says what the first run is (Founder, two questions, then the CEO
boots) and step 4 says what every later run is (the same bare chief
starts and attaches the company). The transition is the part a stranger
cannot guess from the command alone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2
Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
@lucy-wolfe
lucy-wolfe requested a review from hishboy as a code owner August 28, 2026 16:41
…s Pi (#1)

The Pi step collapses into a single sentence: the installer installs a
missing Pi and asks before upgrading an old one. The version number
leaves the README entirely; the floor's single definition stays in
pi_floor.rs, and CONTRIBUTING.md remains the document that quotes it.

Depends on the installer change (PR tribes-protocol#18) riding in the same integration
branch; without it this page describes behavior install.sh does not have.


Claude-Session: https://claude.ai/code/session_01TMNbixeJo8rpeijUKyxhD2

Signed-off-by: Lucy Wolfe <lucy@zbox.sh>
Co-authored-by: Lucy Wolfe <lucy@zbox.sh>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A busy person now reads an ordinary message at the next step boundary inside
the running turn instead of at the end of it. The operator typing mid-turn has
always been submitted as a steering message and consumed within seconds;
ordinary mail rode the follow-up queue, which Pi consumes only once the agent
has no more tool calls or steering messages. So somebody an hour into a piece
of work did not see a teammate's message until the hour was over, while the
identical words typed by the operator arrived immediately.

Interrupt mail already rode the steering lane, so this widens a
production-proven path rather than building one. The digest is untouched:
batching is still the answer to twenty messages in one turn, and only the
moment of consumption moved.

Verified before changing anything, because it would have been a real blocker:
the delivery receipt hangs off message_start, not turn_start, so it is written
wherever the message is consumed. Interrupts have been consumed mid-turn in
production all along and receipted through that same handler, so a normal
envelope takes an identical path.

Three call sites were considered and two moved. The work-resume prompt keeps
follow-up and says why: nobody is waiting on it and it asks the person to pick
their own work back up. The input-requeue rescue and the printed-tool-call
corrective keep theirs, with rationales already written down that remain true.

An unreachable ternary went with it: normal urgency is routed away three lines
above the interrupt delivery, so its false arm could never be taken even before
this change.

The change was unpinnable as written, so it was made pinnable. The delivery
table was already covered, but the mode passed into it was two string literals
at two call sites -- so the timing of every delivery in the product could change
without one test noticing, which is exactly what happened when the first
implementation went green. It is one named decision now, with a test that
asserts the rule and fails when the mode is reverted.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@lucy-wolfe
lucy-wolfe force-pushed the feat/mail-rides-steer branch from 94e2bb0 to dfd6c62 Compare August 28, 2026 17:32
@lucy-wolfe

Copy link
Copy Markdown
Contributor Author

Merged into the v0.5.7 integration branch and shipping in #16. The commit is on that branch verbatim; GitHub could not retarget this pull request because a cross-fork PR's base must live in the base repository, and the integration branch lives on the fork. The review that happened here stands.

@lucy-wolfe lucy-wolfe closed this Aug 28, 2026
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