How a crew is led — the persistent lead and the lead as a function
How a crew can be led two ways — by a lead session that stays running, or by a lead turn seated for each event — and how the app measures the difference. Covers the switch, what a function turn actually is, the seven numbers the comparison reports (and which of them a seeded run can honestly produce), the one command that runs a seeded comparison, and the rule that a number with no source says so instead of showing zero.
Two ways to lead the same crew
Every crew has a lead post: the seat that acts on the crew's record — closing tasks, moving promises, answering a member. How that seat is filled is a per-crew setting.
- Persistent lead (the default). One session stays running and holds the post. It keeps its context between events, so it can carry a plan across turns — and it keeps spending turns while it waits.
- Lead as a function. No always-running session. The post keeps its session id, its schedules and its scorecard identity, but the app retires the running process as each turn settles and starts it fresh on the next event. Every turn is seated by an event, acts on the crew's record and the event, and gives the post back.
The setting belongs to one crew and ships as persistent for everyone. A crew that never switches is unchanged.
Who can switch it
Only the person can. The switch is a privileged control-server call (PATCH /crew/lead-mode), and
an agent session's own credential is not admitted to it. The seeded comparison below never uses
that route — it sets the mode inside a throwaway database that no live crew can see.
Switching is allowed while a session is holding the post, because the shape keeps that session as the post: what changes is how its turns start, not who the lead is.
The seven numbers the gate reads
GET /crew/lead-mode-comparison answers per crew, and every number carries a status:
| Number | What it is | Where it comes from |
|---|---|---|
| moves per paid lead turn | real work the lead did, divided by the turns it was given | the crew scorecard |
| owner replies per lead-day | how often the person had to step in | the crew scorecard |
| promises proven | deliverables the crew actually delivered | the crew scorecard |
| spend per paid lead turn | what the lead's turns cost | the crew scorecard's spend columns |
| missed events | events raised at the post that never produced a turn | the crew's turn ledger |
| turn latency | how long an event waited before its turn started | the crew's turn ledger |
| refusals | events the post could not accept | the crew's turn ledger |
The three statuses matter as much as the numbers:
- measured — read from the live record.
- scripted — produced by a seeded run. A seeded run uses a stub turn, so its numbers prove the pipeline works end to end; they cannot rank the two shapes.
- unavailable — there is no source, and the report says why. A crew that has never recorded a
turn reads
unavailablefor the three ledger numbers, never0, because a zero would read as a perfect result and decide a gate.
Today the turn ledger is written only by the seeded comparison itself. Nothing on the live
delivery path records a turn yet, so on a real install the three ledger numbers — missed events,
turn latency and refusals — read unavailable for every crew, including a crew already switched to
the lead-as-a-function. They become measurable when the app records each attempt as it delivers an
event; that writer is tracked separately. The report telling you unavailable is the honest answer,
not a bug in the table.
Run a seeded comparison
npm run lead-mode:compare -- --seeded
The command copies the shipped starting database into a temporary folder, seeds two crews of the same shape that differ only in how they are led, and drives one identical scripted event stream — a member's report, a red success number, a stalled promise, the person's reply, a second report, an event that arrives while the post is stopped, and one that follows it — through both. Each event starts one turn, and the turn performs the same deterministic stub actions on both sides.
It prints the seven numbers for both shapes beside the live persistent-lead baseline, then deletes
its temporary database. Add --keep to inspect it, or --json for a machine-readable answer.
What it does not do. It spawns no session and spends no tokens, so its spend number is
unavailable. Its latency is the model's fixed turn-start cost. Both sides act on the same events,
so the table is equal by construction — that equality is the point: it shows the pipeline produces
every number, and it is not evidence that one shape is better.
Where the code lives
src/shared/crew-lead-mode-metrics.ts— the seven keys, the ratios and the three statuses. One definition, used by the live route and the harness alike.src/main/db/crew-lead-mode-sql.ts— the move query, the comparison query and the turn read, run by the app through its database layer and by the harness through its own handle.src/main/services/overseer/crew-lead-mode-seeded-run.ts— the seeding, the scripted events and the stub turn.scripts/crew/lead-mode-compare.ts— the command.- The table
agent_crew_lead_turns— one row per event-to-turn attempt, which is what makes missed events, latency and refusals measurable.
Last verified 2026-10-17