Skip to content

feat(cli): the turn-start brief names the docs the code outran (BEA-202) - #208

Open
ssowonny wants to merge 2 commits into
mainfrom
bea-202-ph-idea-the-project-writes-the-agents-session-brief
Open

feat(cli): the turn-start brief names the docs the code outran (BEA-202)#208
ssowonny wants to merge 2 commits into
mainfrom
bea-202-ph-idea-the-project-writes-the-agents-session-brief

Conversation

@ssowonny

@ssowonny ssowonny commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

TL;DR

  • Agents now hear the fourth piece of the turn-start brief: which synced docs their own code has outrun. bdrive stale already computed it — only nobody who wasn't typing the command ever saw it.
  • Second sentence names the mount's AGENTS.md when it syncs. Codex never finds one off the root→cwd path, so this is the one place every platform with hooks gets told the rules exist.
  • All local. The journal dates the paths and the cycle's own cache is the synced set, so a hook that blocks the turn never waits on the network.
  • Known gap: the spec's gate criterion is not met here. BEA-203 (first turn after joining names every file as a teammate's change) is still in Triage. This lands safely alone, but hold the launch sentence until that merges — a fourth sentence under thirteen false ones is wasted.
  • "Hot" (read heat) is still out — it lives on the hub, and fetching it means a network call inside the blocking hook.

The shape of the change

Three of four pieces already shipped from emitHookContext every turn. This fills the hole.

flowchart TB
    B["the agent's turn-start brief"]
    B --> L["link formula<br/><b>shipped</b><br/>hooksync.go"]
    B --> C["changed-since<br/><b>shipped</b><br/>hookChanged"]
    B --> R["team rules<br/><b>this PR</b><br/>hookRules"]
    B --> S["hot / stale<br/><b>this PR, stale half</b><br/>hookStale"]
    style R fill:#dcfce7,stroke:#16a34a
    style S fill:#dcfce7,stroke:#16a34a
Loading

What an agent actually receives now, appended to the same single JSON object:

  beardrive: this folder syncs to https://hub/<project-id> (the project's hub page; …)
  Changed since your last turn by a teammate or another device — re-read before editing: `runbook.md`.
+ Docs here that their own code has outrun — re-read before trusting them:
+ `runbook.md` (2 files newer, oldest gap 13d), `onboarding.md` (1 file newer, oldest gap 4d).
+ This project's team rules are at `AGENTS.md` — read it before substantive work.

What can't break

bdrive stale itself. Step 1 is a pure refactor: runStale's doc loop moves into staleDocs, which the command calls with a zero deadline (no budget). Every line of its printing, its -l/-n flags and its exit status are untouched, and every existing TestStale* passes unchanged — that is the proof.

The turn. Every failure path — a journal read error, a missing materialization cache, an expired budget — is a missing sentence and an exit 0. Same silent-degrade posture hookChanged and hookSecrets already have. TestSyncHookModeStaleDegrades sets hookStaleBudget = -time.Second and asserts the sentence is gone, the links survive, and the JSON is still one valid object.

Sync invariants. Nothing here writes a journal, takes the volume flock, or touches the network. AllOps and LoadCache are reads on a store the session already holds.

What you're accepting

Choice Instead of Why
The cycle's materialization cache as the synced set syncer.SyncedFiles (a second walk) The cycle just refreshed it and it already respects the filter. Cost: a file created between the scan and this read is invisible for one turn, so the hook's list can lag bdrive stale's by a turn
One 250 ms budget across all mounts one budget each The turn pays for this once, however many projects sit under the run folder
3 docs, then +N more hookChangedMax's 20 Judgments, not facts
../-climbing path for the rules pointer hookAgentPath hookAgentPath drops anything outside the session's subpath — right for a sibling folder's doc, wrong for rules the agent should still open

Three small deviations from the reviewed plan, all cheap to reverse. The gap format is normalized to (N files newer, oldest gap Xd) for every doc (the spec's example rendered the second one differently). The rules sentence has a second phrasing for the multi-mount case, since "this project's team rules" reads wrong with two paths in it. And hookStaleDocs sorts the cache keys before scanning, so the tie-break between equal gaps doesn't ride on Go's map order.

What was run

  • go test ./... — green, every package, at HEAD (cmd/bdrive 40s, internal/webapp 380s).
  • go vet ./... — clean. gofmt on the four touched files only; a blanket run drags six unrelated files in.
  • Mutation-checked the new tests: with the two emitHookContext appends removed, six of the seven new cases fail. They bite.
  • Measured, since the spec sets a budget: a 500-file / 200-doc project runs the whole --hook invocation in ~24 ms against the 250 ms budget.
  • web/docs builds (Astro + Pagefind, 20 pages).

No frontend change, so no npm run e2e and no UI screenshots — the diff is cmd/bdrive and four doc surfaces.

Tests, one per acceptance criterion

Criterion Test
Stale sentence, same JSON object, hub unreachable TestSyncHookModeReportsStaleDocs
Nothing outgrown → no sentence at all TestSyncHookModeNoStaleSaysNothing
No journal yet → no sentence, links intact TestSyncHookModeStaleNoJournal
>3 outgrown → 3 named + +N more TestSyncHookModeStaleCap
Mount below the run folder carries its prefix TestSyncHookModeStaleMultipleMounts
Session inside a mount: own subpath stripped, sibling folder's doc dropped TestSyncHookModeStaleInsideMount
Deadline → sentence dropped, exit 0, valid JSON TestSyncHookModeStaleDegrades
AGENTS.md synced → named once; absent → nothing TestSyncHookModeRulesPointer
Rules above a deep session are still reachable TestSyncHookModeRulesAboveSession

Docs

Four surfaces, all of which describe the hook's output and all of which went stale together: README.md's bdrive sync row, reference/cli.md's, manual/hooks.md's "Three hooks, three jobs", and guides/shared-agent-memory.md's "Your agent is told what moved" — which is also where the AGENTS.md pointer belongs, right under the platform-discovery table that motivates it.

Architecture changes

None. staleDocs is a new function, and hookLink/hookSync gained two fields each — none of them are types the architecture/ diagrams draw, and no seam between drawn packages changed. Hence # skip-diagram-check.

Open question

Is the multi-mount rules sentence worth the second phrasing at all, or should a run covering several projects just name the nearest AGENTS.md and stay silent about the others? One project per session is the overwhelmingly common case, and I built for the rare one.

Build session

cd $(git worktree list | grep bea-202 | awk '{print $1}') && claude --resume 2292c817-d424-4728-af20-69abbde5940a

(only works on this machine)

ssowonny and others added 2 commits September 6, 2026 09:10
Three of the brief's four pieces already shipped from emitHookContext every
turn: the gated-link formula, what teammates changed, credential-shaped files.
The fourth — which synced docs their own code has moved past — was computed by
`bdrive stale` and seen only by a human who typed the command. Agents do not
type commands.

stale.go and hooksync.go are the same package, so this is one shared helper and
two appended sentences, not an extraction:

- staleDocs lifts runStale's doc loop out unchanged, plus a deadline. The
  command passes the zero time (no budget); the hook passes one shared across
  every mount, because staleRefs opens every synced markdown file and a hook
  that blocks the turn must drop the sentence, never the turn.
- The hook takes its inputs from the session it already holds — AllOps to date
  the paths (mtime is wrong on a freshly synced machine) and the cycle's own
  materialization cache as the synced set, which is fresher than a second walk
  and free. Nothing here touches the network.
- A second sentence names the mount's AGENTS.md when it syncs. Codex never
  discovers one off the root->cwd path and the others find it only lazily, so
  this is where every platform with hooks at all gets told. It is the one place
  hookAgentPath is deliberately skipped: rules above the session are still the
  agent's to open, so the path climbs with ../ instead of being dropped.

Every failure — a journal read error, an expired budget, a missing cache — is a
missing sentence and an exit 0. Measured: a 500-file / 200-doc project runs the
whole hook in ~24ms against a 250ms budget.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runHookSync returns false for a non-hub remote, so everything computed before
that check is discarded. Move the scan after it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ssowonny ssowonny self-assigned this Sep 6, 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