Skip to content

fix(agents): finished workers, transcript reads and worktree guard - #3306

Merged
teemow merged 8 commits into
mainfrom
beekeeper-execution/e106d3c0-3f2e-4b1b-a96c-a78e83dcdab5/1
Nov 6, 2026
Merged

teemow merged 8 commits into
mainfrom
beekeeper-execution/e106d3c0-3f2e-4b1b-a96c-a78e83dcdab5/1

Conversation

@teemow

@teemow teemow commented Nov 6, 2026

Copy link
Copy Markdown
Member

Written by an agent.

One batch for the agents lifecycle, transcript reads and worktree guard issues.

Closes #633
Closes #2064
Closes #3219
Closes #3235
Closes #3282
Closes #3284

Refs #3283: this PR does the part that needs no live proof. It stays open, see below.

Folds in #2325 and #2616, which both merged cleanly with main and are closed in favour of this PR.

What changes

Guard (2064, 3219). From 2325: an embedded slash in a relative path no longer reads as an absolute path, and unshadowed du and df pass as metadata. New: the session's own $CLAUDE_JOB_DIR counts as its own. The refusal reproduced only with a literal path into a job directory inside a transcript store, where the session-id ownership check does not match. The exemption checks the resolved path too, so a link out of the job directory stays refused, and it is ignored for a directory holding a whole store. $CLAUDE_JOB_DIR is now also expanded, so $CLAUDE_JOB_DIR/../../other.jsonl is refused where it used to pass unresolved.

Worker rules (3235). The Files rule names <scratchpad> for artifacts. The rules have a 2500-byte budget, so the line was reworded rather than extended, and the test pinning the old phrase moved with it.

Worktree liveness (3282). An unarchived desktop session keeps its worktree live for seven days after its last activity, read from its record and its transcript, not forever. A record with no activity at all stays live. Processes, running sessions and unfinished roster agents keep a worktree live as before. The trim and the disk step judge a tree's work themselves. The hook's mv/rm refusal has no such check, so past the bound it still refuses a worktree with uncommitted or unpushed work, and one it could not read within ten seconds.

Archive agreement (633). From 2616: agents.archiveAgreement goes from the schema, the doctor and the wording; the steward route archives without it. This loosens a confirmation step: sessions a person started stay excluded, and archiving stays reversible.

Transcripts (3284). Beekeeper never writes <truncated N bytes>, so those markers are the harness's own and that text cannot be recovered. transcripts read now opens with a # lost for good: line counting harness cuts, clipped tool results and tool calls with no stored result, and --json carries the same as loss. Agy tool results are now stored: they were dropped by the parser. The store's own 4 KiB clip stays, but each clipped result now says how many bytes are missing. A grant says it covers the stored transcript and not the files that session wrote, and says when no stored transcript matches it yet.

Finished workers (part of 3283). agents wake and agents resume reach a worker off the roster by its exact name. It comes back on the roster with its last task, in its own conversation, under the same stop, starts-hold and usage checks. Only these two commands look there: a timer or a settled wait naming a finished worker still wakes nobody. An agy start now records its conversation id in state, because the link files go with the inbox; that is why a stored agy transcript lost its roster name, and why transcripts read <name> and a grant by name found nothing.

Stays open: 3283

Claude Code workers continue. An agy, codex or omp worker whose process ended is refused with a line saying beekeeper cannot continue it and naming its stored transcript, which is the issue's fallback. Those three harnesses do have resume options, so "every harness that can continue one" is not met yet: relaunching them needs a live proof that the harness really rejoins the same conversation. A wrong guess there would quietly start a new one.

An agy worker started before this change has no recorded conversation id, so its transcript keeps no roster name.

Not in this PR

#1193 stays open. #2426 replaces the steward with a configured command, but no such adapter exists, so it does not close the issue, and it conflicts with the agreement removal here.

Overlap

Shared files, no shared logic found: cmd/agentpark.go and cmd/agents.go with #3303 and #3305, internal/state/state.go with 3305, #2492 and #2340. The real conflict is 2426, above.

Validation

  • go build ./...
  • go test ./internal/guard ./internal/transcripts ./internal/worktrees ./internal/config ./plugin: pass.
  • go test ./cmd ./internal/tui -run 'TestRestarted|Ended|Transcript|Archiv|Steward|PickSteward|OwedArchive|PlanArchives|SeedArchives|SayChores|PeerSendDelivers|TestDoctor|Idle|Worktree|TestGone|Skill': pass.
  • go test ./cmd -run 'Prompt|Places|Scratch|Rules|Brief': one failure, TestScratchSecretBrokerLifecycle/vault-socket, bind: invalid argument on a Unix socket under a long temporary path on the development host. This PR touches no sandbox or secret file; CI runs it on a short path.
  • New tests: TestTranscriptOwnJobDir, TestIdleDesktopSessionKeepsNoWorktreeLive, TestAgyResultsAndLoss, and TestEnded* for both harness outcomes, the starts hold, the name-only lookup and the roster name.

Nothing here was proven against a live desktop or a live agy session. The agy result shape is the one the existing activity reader already relies on.

🤖 Generated with Claude Code

beekeeper and others added 6 commits November 6, 2026 10:02
…ries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unarchived session alone no longer keeps a worktree live past seven
idle days; the hook still refuses to move one holding unsaved work. The
transcript guard treats the session's own job directory as its own, and
the worker rules send files to publish to the scratchpad.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ipts lost

agents wake and agents resume reach a worker off the roster by its exact
name, with its last task. An agy start keeps its conversation id, so its
transcript keeps its roster name after the link files go. A read says
what the store does not hold, agy tool results are stored, and a grant
says what it covers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teemow

teemow commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Desk review of exact head aa684e7fa8b99cf1ba0fbd0b4354867be561d0f5.

Verdict: changes needed. Two closing issues are not complete; the worktree change has a data-loss risk.

  1. P1 — Idle sessions in subdirectories lose the unsaved-work guard. cmd/worktrees.go:153–155: worktrees.Linked(d) checks for .git directly inside Cwd/OriginCwd. When both point inside a linked worktree (for example <tree>/cmd), neither is inspected. After seven days, liveFolders also excludes that desktop session, so the hook can allow rm/mv of the worktree despite uncommitted or unpushed work. The previous live-directory guard walked up to the worktree root. Resolve the containing worktree before inspecting it and retain protection on lookup errors. This violates worktree guard: an old unarchived desktop session keeps its worktree live forever #3282.

  2. P2 — Existing ended agy transcripts never receive the parser repair. internal/transcripts/parse.go:460–463 adds results only when parsing occurs. Store.IngestFile still skips parsing when source path, size and mtime match (internal/transcripts/store.go:397–400). An ended transcript already indexed before this upgrade therefore keeps its result-less summaries even when the source contains the results; reads and explicit sync do not repair it. Add a parser-version migration or another deliberate reindex path. This leaves the reported existing-transcript case in transcripts: read cuts a stored turn's content, so a successor loses the opening and the final report #3284 unresolved.

  3. P2 — Recoverable omissions are reported as permanently lost. cmd/transcripts.go:245–246 puts all Loss categories under “lost for good.” A 4 KiB index clip does not remove the rest from the source transcript, and a missing cached agy result can still be in that source (finding 2). LossOf also treats a tool result with legitimately empty output as missing. Distinguish irreversible harness cuts from index omissions, empty results and pending calls; otherwise successors are incorrectly told recovery is impossible. This is part of transcripts: read cuts a stored turn's content, so a successor loses the opening and the final report #3284.

Closing-issue assessment:

Issue Truly done at this head?
#633 Yes: agreement field, checks and wording removed; steward archive path and eligibility safeguards remain. No archiveAgreement occurrences in source/docs.
#2064 Yes against the stated four-command acceptance criteria: relative-path false positives and metadata utilities addressed, with foreign-transcript and shadowed-command refusal tests. This is not a claim that every runtime-path heuristic limitation is solved.
#3219 Yes for the reported own-job script case, with resolved-path checks against links escaping the job directory.
#3235 Yes: publishing artifacts are explicitly directed to the session scratchpad.
#3282 No: bounded liveness is implemented, but finding 1 drops the required unsaved-work safety check for subdirectory sessions.
#3284 No: grant scope is explained, but findings 2–3 leave existing stored context unavailable and misclassify recoverable omissions.

The explicitly non-closing #3283 correctly remains open: ended non-Claude harnesses are refused rather than continued; no live continuation proof is supplied.

Validation: exact-head static review, linked issues and comments read. The original focused tests passed:

go test ./internal/guard ./internal/transcripts ./cmd -run 'TestTranscriptOwnJobDir|TestTranscriptOrdinaryWorktreeCommands|TestAgyResultsAndLoss|TestIdleDesktopSessionKeepsNoWorktreeLive|TestEnded' -count=1

Temporary test overlays (no reviewed source changes) reproduced findings 1–2:

  • TestReviewIdleDesktopSubdirectory: using the existing idle-worktree fixture with desktop records pointing into each tree's sub directory yields unsaved = [] instead of the dirty and unpushed worktrees.
  • TestReviewExistingAgyCacheUpgrade: seed a previously indexed tool summary with matching source metadata, then ingest a source containing TOOL_RESPONSE; the stored result remains empty. No source edits, pushes or merges.

beekeeper and others added 2 commits November 6, 2026 11:13
…indexed

The hook's unsaved-work check resolves the worktree a session's folder
lies in, so a session in a subdirectory keeps its protection, and an
unreadable lookup keeps it too. An agy transcript indexed before results
were stored is read again once. A read tells what is gone for good from
what only the index misses, and an empty result from a missing one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@teemow

teemow commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Verification of exact head 348959fdadf30e2a5163a22ee1b1bf70eb01a093, limited to my previous findings and the issues this PR still closes.

Verdict: the three previous findings are fixed.

Previous finding Result Evidence
P1: idle desktop sessions in worktree subdirectories bypass unsaved-work protection Fixed unsavedSessionDirs now resolves the containing worktree before inspecting it. The root/subdirectory regression checks dirty and unpushed trees and the hook's actual LiveWorktreeUnder result.
P2: unchanged, previously indexed agy transcripts never receive parsed results Fixed The agy parser version invalidates the old cache once. My original cache-upgrade reproduction now passes; the new regression also verifies the following sync takes the cheap unchanged path.
P2: recoverable omissions and empty/pending results are called permanently lost Fixed Only harness cuts use lost for good; index omissions get a separate recovery notice. Answered distinguishes empty results, and running calls are counted separately. The focused loss regression passes.

The PR still has all six Closes lines. Against their stated acceptance criteria:

Issue Done?
#633 Yes — obsolete agreement removed; no source/docs occurrences; steward archive regression passes.
#2064 Yes — the specified ordinary-command cases pass, while foreign-transcript reads remain refused.
#3219 Yes — own-job script case passes; escaping links remain refused.
#3235 Yes — artifact guidance explicitly names the session scratchpad.
#3282 Yes — bounded desktop liveness now retains dirty/unpushed protection for subdirectory sessions.
#3284 Yes against the issue body's stated contract — harness cuts are identified, old agy indexes are repaired, and recoverable index omissions and grant/file scope are explained. This does not add an unabridged source-file reader or extend transcript grants to scratch files; the documented 4 KiB index cap remains.

#3283 remains explicitly open, as before.

Validation: focused guard, transcript, archive and root/subdirectory worktree tests, plus my original unchanged-cache reproduction in a scratch-only test overlay. All pass at the head above. Read only: no source edits, pushes or merges. This is verification of the earlier review, not a fresh review of unrelated changes.

@teemow
teemow merged commit 876ee03 into main Nov 6, 2026
13 checks passed
@teemow
teemow deleted the beekeeper-execution/e106d3c0-3f2e-4b1b-a96c-a78e83dcdab5/1 branch November 6, 2026 13:15
teemow added a commit that referenced this pull request Nov 6, 2026
* origin/main:
  feat(guard): send harness memory to the desk, let the guide write there (#3304)
  ci: stage batch gate and catch package lint before push (#3314)
  fix(agents): finished workers, transcript reads and worktree guard (#3306)
  feat(execution): sync policy per start, account for host leftovers (#3311)
teemow added a commit that referenced this pull request Nov 6, 2026
…0a00f80-86c3-479f-b27a-7f2192f5fb1b/1

* origin/main:
  feat(update): roll back unhealthy releases during probation (#3316)
  feat(disk): show what holds the disk; serialize Teleport renewals (#3312)
  chore(deps): update dependency ubuntu to v26 (#3321)
  chore(deps): update actions/github-script action to v9 (#3320)
  feat(guard): send harness memory to the desk, let the guide write there (#3304)
  ci: stage batch gate and catch package lint before push (#3314)
  fix(agents): finished workers, transcript reads and worktree guard (#3306)
  feat(execution): sync policy per start, account for host leftovers (#3311)
  fix(supervisor): skip closed, absorbed and never-owned work in dispatch (#3315)
  feat(hook): carry an answered note for a production deletion (#2492)
  ci: vendor the Kubernetes schema the chart schema check reads (#3310)
  test: wait for pass actions before a loop rig's state goes (#3313)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment