Read-only: an opt-in status line with seat and usage, and a /fleet pane listing RUNNING seat records from the fleet script's -Json output

Keep One Repo, Unblock Sessions. A working method for running several AI coding sessions against one codebase without them colliding, losing work, or quietly agreeing with each other.
KORUS grew out of tooling for a single project and has been revised repeatedly under real use. Its properties were discovered, not designed -- most of what is known about it came from running it and measuring what broke.
This repository exists to change that. It uses Spec Kit for spec-driven development: the constitution first, then the spec, then the plan.
Nothing here is stable yet. Findings are still being consolidated from three days of measured operation, and some published guidance has already been shown wrong.
The KORUS Constitution is the document to read first. It holds the rules a session, a seat, a gate or a later spec may not break, and every article names the evidence behind it so a reader can check rather than trust.
Alongside it are the seat playbooks in roles/, the working guides in docs/, and the specs in specs/. The constitution outranks all of them: where a playbook and an article disagree, the article wins and the playbook is the bug.
Thirteen articles, in short:
| I | No session is the only reader of its own work |
| II | Publish readings, not conclusions |
| III | A gate that cannot check identity must make refusal legible |
| IV | Every claim names the condition it did not vary |
| V | No rule may manufacture its own evidence |
| VI | A number without its instrument is not a measurement |
| VII | Waiting is a design cost and it is measured |
| VIII | The account roster is assigned by the Owner, and no design may infer it |
| IX | Built for Claude Code, and no design may require a particular surface |
| X | A seat that cannot be measured cannot be steered |
| XI | A seat's job is to write something down |
| XII | The shared write surface is the boundary that binds, not the account |
| XIII | Work reaches the model through Claude Code, never through the API |
It is at v1.16.0 and it expects to be wrong in places. Most articles rest on a small number of observations, several from a single night of operation, and the document says so. Amendments require evidence, and retired text stays with the reason it was retired.
Run more than one AI session on one repository and four things go wrong:
KORUS is the set of roles, gates and instruments that address these.
Both on the repository this tooling was developed in. Over 30 days, 166 sessions ran with their working directory in the shared primary checkout. Both percentages below are shares of the Edit/Write calls those sessions made, not of every write on the machine:
This page is the record for both figures. Neither has been re-measured, and nothing in this repository can recompute them.
Work is divided among seats, each a session with one job:
| Seat | What it does | Lifetime |
|---|---|---|
| Manager | Plans, runs workers, holds the owner's attention | long-lived |
| Builder | Takes one brief, does the work, opens a pull request | one turn |
| Steward | Writes files other seats read | cron, no model calls |
| Lander | Decides merge order | long-lived |
Nine further seats were tried and retired: the Console on 2026-09-10, and another on 2026-09-12 that is deliberately left unnamed here, with nothing replacing it. Why each went is part of the record this repository holds.
.specify/memory/constitution.md the rules nothing may break
.specify/ Spec Kit scaffold: templates and scripts
specs/ one directory per feature: spec.md, plan.md, tasks.md
See LICENSE.
hooks/register.tsx 263 lines1import { atom, read, update } from 'claude-code'
2import type { Engine, PluginOptions, Register } from 'claude-code'
3
4import type { FleetBoard, FleetJson, FleetRow } from '../types'
5
6// Read-only. It reads one file and runs one script; it never writes to the repository, the
7// coordination directory, or any peer, and calls no file-write API.
8
9const PANE = 'korus-fleet'
10const STATUS_EVERY_MS = 60_000
11// The pilot timed one run of the MessageFoundry fleet script at about 26 s (not re-measured
12// here), so the board refreshes rarely and only while the pane is open.
13const BOARD_EVERY_MS = 5 * 60_000
14const FLEET_TIMEOUT_MS = 120_000
15
16// Where scripts/coord/seat.ps1 -Declare writes the seat. Git-ignored by `*.local.*`.
17const SEAT_MARKER = '.claude/seat.local.txt'
18// The userConfig default, repeated here so a load with no options still has a path.
19const DEFAULT_FLEET_SCRIPT = 'scripts/coord/fleet.ps1'
20
21const board = atom({ plugin: 'korus-fleet', key: 'board' } as const, null)
22const isRefreshing = atom({ plugin: 'korus-fleet', key: 'isRefreshing' } as const, false)
23
24type Api = Engine
25
26// The status line is opt-in (owner instruction 2026-10-02). The userConfig field `statusLine`
27// defaults to false, and only a stored true turns it on.
28function isStatusLineOn(options: PluginOptions): boolean {
29 return options['statusLine'] === true
30}
31
32function fleetScriptFrom(options: PluginOptions): string {
33 const value = options['fleetScript']
34 return typeof value === 'string' && value.trim() !== '' ? value.trim() : DEFAULT_FLEET_SCRIPT
35}
36
37async function readSeat($: Api): Promise<string> {
38 try {
39 const text = await $.fs.read(SEAT_MARKER)
40 return text.trim() || 'undeclared'
41 } catch {
42 return 'no seat marker'
43 }
44}
45
46// Short labels for the rate-limit kinds the session reports (owner instruction 2026-10-02).
47// Any other kind is shown as the session names it.
48const LIMIT_LABELS: ReadonlyMap<string, string> = new Map([
49 ['five_hour', '5h'],
50 ['seven_day', '7d'],
51])
52
53function limitLabel(kind: string): string {
54 return LIMIT_LABELS.get(kind) ?? kind
55}
56
57async function refreshStatus($: Api): Promise<void> {
58 const parts = [`seat ${await readSeat($)}`]
59 try {
60 const usage = await $.session.usage()
61 if (usage.context.percent !== undefined) {
62 parts.push(`ctx ${Math.round(usage.context.percent)}%`)
63 }
64 for (const limit of usage.rateLimits) {
65 parts.push(`${limitLabel(limit.kind)} ${Math.round(limit.percentUsed)}%`)
66 }
67 } catch (err) {
68 parts.push(`usage unread: ${err instanceof Error ? err.name : 'error'}`)
69 }
70 $.ui.status(parts.join(' | '))
71}
72
73// The board consumes the FleetJson contract (types/index.d.ts) and nothing else. Anything that
74// does not have that shape is reported as a contract mismatch rather than drawn half-read.
75function isNullableString(value: unknown): boolean {
76 return value === undefined || value === null || typeof value === 'string'
77}
78
79function isFleetJson(value: unknown): value is FleetJson {
80 if (typeof value !== 'object' || value === null) return false
81 const v = value as { receipt?: unknown; rows?: unknown }
82 if (typeof v.receipt !== 'object' || v.receipt === null || !Array.isArray(v.rows)) return false
83 const r = v.receipt as { renderedAtUtc?: unknown; liveSessionsInRepo?: unknown; stopConditions?: unknown }
84 if (typeof r.renderedAtUtc !== 'string' || typeof r.liveSessionsInRepo !== 'number') return false
85 const stops = r.stopConditions
86 if (!isNullableString(stops) && !(Array.isArray(stops) && stops.every(s => typeof s === 'string'))) {
87 return false
88 }
89 return v.rows.every(row => {
90 if (typeof row !== 'object' || row === null) return false
91 const x = row as { Seat?: unknown; Box?: unknown; Branch?: unknown; State?: unknown; AgeHours?: unknown }
92 return (
93 typeof x.Box === 'string' &&
94 typeof x.State === 'string' &&
95 isNullableString(x.Seat) &&
96 isNullableString(x.Branch) &&
97 (x.AgeHours === null || typeof x.AgeHours === 'number')
98 )
99 })
100}
101
102// A script may print its stop conditions as one string or as a list, and a healthy run's list is
103// empty. Both an empty list and an empty string mean "none".
104function stopText(stops: FleetJson['receipt']['stopConditions']): string | null {
105 const text = Array.isArray(stops) ? stops.join('; ') : (stops ?? '')
106 return text.trim() === '' ? null : text
107}
108
109async function loadBoard($: Api, script: string): Promise<FleetBoard> {
110 const failed = async (error: string): Promise<FleetBoard> => ({
111 renderedAt: new Date(await $.clock.now()).toISOString(),
112 liveSessions: 0,
113 stopConditions: null,
114 warning: null,
115 running: [],
116 error,
117 })
118 let stdout: string
119 let warning: string | null = null
120 try {
121 if (!(await $.fs.exists(script))) {
122 return failed(`fleet script not found: ${script}. Set the korus-fleet fleetScript option to its path.`)
123 }
124 const ran = await $.process.run(['pwsh', '-NoProfile', '-File', script, '-Json'], {
125 timeoutMs: FLEET_TIMEOUT_MS,
126 })
127 if (ran.exitCode !== 0 && ran.stdout.trim() === '') {
128 return failed(`${script} exited ${ran.exitCode} with no output`)
129 }
130 if (ran.exitCode !== 0) {
131 warning = `${script} exited ${ran.exitCode}; the rows below may be incomplete`
132 }
133 stdout = ran.stdout
134 } catch (err) {
135 return failed(`${script} did not run: ${err instanceof Error ? err.message : String(err)}`)
136 }
137 let parsed: unknown
138 try {
139 parsed = JSON.parse(stdout)
140 } catch {
141 return failed(`${script} output was not JSON`)
142 }
143 if (!isFleetJson(parsed)) {
144 return failed(`${script} output does not match the fleet JSON contract`)
145 }
146 // A row with no age sorts last rather than first, so it never reads as the newest.
147 const sortAge = (h: number | null): number => (h === null ? Number.POSITIVE_INFINITY : h)
148 const running: FleetRow[] = parsed.rows
149 .filter(row => row.State === 'RUNNING')
150 .sort((a, b) => sortAge(a.AgeHours) - sortAge(b.AgeHours))
151 .map(row => ({
152 seat: row.Seat ?? null,
153 box: row.Box,
154 branch: row.Branch ?? null,
155 ageHours: row.AgeHours,
156 }))
157 return {
158 renderedAt: parsed.receipt.renderedAtUtc,
159 liveSessions: parsed.receipt.liveSessionsInRepo,
160 stopConditions: stopText(parsed.receipt.stopConditions),
161 warning,
162 running,
163 error: null,
164 }
165}
166
167// The guard is module memory, set before the first await, so two presses close together cannot
168// both pass it. The isRefreshing atom only drives the button label. A module reload starts with
169// the guard clear, so a refresh cut off by a reload never blocks the next one.
170let inFlight = false
171
172async function refreshBoard($: Api, script: string): Promise<void> {
173 if (inFlight) return
174 inFlight = true
175 try {
176 await update($, isRefreshing, () => true)
177 const next = await loadBoard($, script)
178 await update($, board, () => next)
179 } finally {
180 inFlight = false
181 await update($, isRefreshing, () => false)
182 }
183}
184
185async function isPaneShown($: Api): Promise<boolean> {
186 const panes = await $.ui.panes()
187 return panes.some(pane => pane.id === PANE && pane.isShown)
188}
189
190function age(hours: number | null): string {
191 if (hours === null) return '?'
192 return hours < 1 ? `${Math.round(hours * 60)}m` : `${hours.toFixed(1)}h`
193}
194
195export const register: Register = (on, options) => {
196 const script = fleetScriptFrom(options)
197 const showStatus = isStatusLineOn(options)
198
199 on('session.start', async ($, e, next) => {
200 await $.command.register({
201 name: 'fleet',
202 description: 'Open the KORUS fleet board (read-only)',
203 })
204 if (showStatus) {
205 void refreshStatus($)
206 $.clock.every(STATUS_EVERY_MS, () => void refreshStatus($))
207 } else {
208 // Off: clear any line a load with the option on drew before this reload, and read no usage.
209 $.ui.status(undefined)
210 }
211 $.clock.every(BOARD_EVERY_MS, () => {
212 void isPaneShown($)
213 .then(shown => (shown ? refreshBoard($, script) : undefined))
214 .catch(() => undefined)
215 })
216 return next(e)
217 })
218
219 on('turn.complete', async ($, e, next) => {
220 const done = await next(e)
221 if (showStatus) void refreshStatus($)
222 return done
223 })
224
225 on('command.run', { command: 'fleet' }, async $ => {
226 await $.ui.open({ id: PANE, title: 'Fleet board' })
227 void refreshBoard($, script)
228 return { text: 'Fleet board opened. It refreshes every 5 minutes while open.' }
229 })
230
231 on('ui.render', { component: 'Pane', requestId: PANE }, async ($, e) => {
232 const { Box, Text, Button } = $.ui.resolve(e)
233 const current = await read($, board)
234 const busy = await read($, isRefreshing)
235
236 return (
237 <Box flexDirection="column">
238 <Box flexDirection="row">
239 <Button key="refresh" label={busy ? 'Refreshing' : 'Refresh'} onPress={() => void refreshBoard($, script)} />
240 </Box>
241 {current === null && <Text dimColor>{busy ? `Reading ${script} (about 30 s)...` : 'No reading yet.'}</Text>}
242 {current !== null && current.error !== null && <Text color="red">{current.error}</Text>}
243 {current !== null && current.error === null && (
244 <Box flexDirection="column">
245 <Text dimColor>
246 rendered {current.renderedAt} | live in repo {current.liveSessions} | RUNNING records {current.running.length}
247 </Text>
248 {current.warning !== null && <Text color="yellow">WARNING: {current.warning}</Text>}
249 {current.stopConditions !== null && (
250 <Text color="yellow">STOP CONDITION: {current.stopConditions}</Text>
251 )}
252 {current.running.map(row => (
253 <Text>
254 {(row.seat ?? 'UNDECLARED').padEnd(10)} {age(row.ageHours).padStart(6)} {row.box}
255 </Text>
256 ))}
257 </Box>
258 )}
259 </Box>
260 )
261 })
262}
263types/index.d.ts 48 lines1// The input contract: what the fleet script prints with -Json. The board reads these fields and
2// nothing else. docs/PLUGINS.md states the same contract for a reader without this file.
3export type FleetJsonRow = {
4 Seat: string | null
5 Box: string
6 Branch: string | null
7 State: string
8 // null where the script could not read the record's age.
9 AgeHours: number | null
10}
11
12export type FleetJson = {
13 receipt: {
14 renderedAtUtc: string
15 liveSessionsInRepo: number
16 // One string or a list of them; an empty list or string means none.
17 stopConditions?: string | string[] | null
18 }
19 rows: FleetJsonRow[]
20}
21
22// What the pane draws, derived from FleetJson.
23export type FleetRow = {
24 seat: string | null
25 box: string
26 branch: string | null
27 ageHours: number | null
28}
29
30export type FleetBoard = {
31 renderedAt: string
32 liveSessions: number
33 stopConditions: string | null
34 // Set when the script exited non-zero but still printed a board.
35 warning: string | null
36 running: FleetRow[]
37 error: string | null
38}
39
40declare module 'claude-code' {
41 interface PluginState {
42 'korus-fleet': {
43 board: FleetBoard | null
44 isRefreshing: boolean
45 }
46 }
47}
48