ground gc-toolkit bead-hosts in core (assigned-work wake reason) + wake-reason-aware teardown (tk-z130v.3)#213
Conversation
…live teardown (tk-z130v.3)
Ground gc-toolkit bead-hosts so an involuntary config-drift drain no longer
kills them. gc-bead-host.sh 'link' sets the work bead's assignee to the host's
session NAME — the assigned-work wake reason the reconciler honors across drains
(compute_awake_set has no Drained gate) — and 'unlink' / gc-helm 'takeaway
--release' clear it so a host can still be stopped. New 'backfill' grounds hosts
linked before this shipped; new/resumed hosts ground automatically via 'up'.
- tools/gc-bead-host.sh: link grounds, unlink ungrounds, + backfill migration verb
- agents/bead-host/{prompt.template.md,agent.toml}: warm-while-live model — a
grounded host stays warm while its bead has awake-demand; teardown clears the
wake reason (close/park/release) FIRST, then drains
- assets/scripts/gc-helm.sh: note that --release's assignee-clear also ungrounds
- formulas/mol-witness-patrol.toml: recover-orphaned-beads skips grounded host
beads (assignee==host_session_name) — safe-by-code, not judgment (cf #206/#210)
- tests: bead-host-binding-fixture.sh grounding assertions; host-bead-skip.test.sh
Guardrails verified (scale-check/keeper/pool/refinery/reconcile key on
gc.routed_to/branch/merge_result/own-identity, never a session-name assignee).
…v.3) Pre-open signoff (tk-50mw4) found that ungrounding clobbers a real owner: cmd_unlink always sent `--assignee=` and cmd_backfill always overwrote the assignee with the host name, so tearing down (or backfilling) a host stripped any newer non-host assignment and stranded that owner's work. Guard both writes on the witness filter's contract (host-bead-skip.test.sh): assignee == host_session_name => grounded (ours to clear); != => a real assignee (keep). - cmd_unlink: snapshot host_session_name + the current assignee before the metadata clear; clear the assignee only while it still equals the host's session name, else preserve it (and log). - cmd_backfill: read the current assignee (rig-pinned, same --db as the ground write) and skip+log a bead a non-host owner already holds. - fixture: add host_session_name != assignee preservation coverage for both unlink and backfill. Addresses the sole P1 from the pre-open codex review of polecat/tk-z130v.3.
…he forward cache is absent (tk-z130v.3) Codex pre-open signoff (review bead tk-knb1t) requested changes: cmd_unlink decided whether to clear the grounding assignee from the optional forward cache host_session_name ALONE. That cache may be absent (a partial link, a manually cleared cache, the perf-cache-only case), and on that no-forward-cache path unlink cleared host_session/hosts_bead but left assignee=<session_name> behind — so the grounded host kept its assigned-work wake reason and revived ~20s after every drain. Repro on the reviewed commit: before/after assignee both s-<session>, host_session and hosts_bead empty. Derive the grounding-owner name from the authoritative sources instead: the reverse link (each bound session bead's session_name, matched as the clear loop walks the candidates) and the explicit session arg (the caller names the exact host being torn down); the forward cache, when present, is now just one more signal. Clear the assignee iff the current assignee equals a grounding-owner name — a newer NON-host assignee (a real agent/operator that took the bead after grounding) matches none and is still preserved, so we never strand a live owner's work. Add a no-forward-cache unlink assertion to bead-host-binding-fixture.sh: with the forward cache cleared but the reverse link intact and the work bead grounded going in, unlink must clear the grounding assignee (derived via the bound session's session_name). Precondition + post-unlink assertion both added.
…fix (tk-0gnq9) rig_db_for_bead's trailing `[ -n "$path" ] && [ -d ... ] && printf` chain leaked exit 1 as the function's status when a hosted bead's id-prefix was absent from `gc rig list`. Under `set -euo pipefail`, cmd_backfill's `db="$(rig_db_for_bead "$work")"` then aborted the whole one-time grounding pass on the FIRST unresolvable host — stranding every later live host (the migration gates the tk-z130v.4 exemption drop). Honor the documented "empty if unresolved" contract: emit the path via an `if` so the function returns 0 on the unresolved path (caller falls back to the ambient bd context). Add a hermetic missing-prefix fixture proving backfill grounds a later host despite an earlier unresolvable one and exits 0. Addresses pre-open codex signoff (review tk-wc3gf) on branch polecat/tk-z130v.3.
|
Codex signoff (pre-open, comment-only — not an approval): Code Review: tk-q6zwfScope: pre-open branch review, Review team: local correctness/testing/project-standards/reliability/security pass. Multi-agent reviewers were not spawned because this session's sub-agent tool requires an explicit delegation request. FindingsNo blocking findings. CoverageReviewed Validation: Residual risk: the live LLM smoke/conversation-fidelity check remains operator-deferred as documented on the anchor; this review covered the shell metadata contract and hermetic regression surface. Verdict: Ready to merge. Actionable findings: none. |
Summary
Ground gc-toolkit bead-hosts in core (assigned-work wake reason) + wake-reason-aware teardown
Parent decision: tk-z130v (read its notes — the A/B evidence and the accepted trade
are there). Repo:
zookanalytics/gc-toolkit(INTERNAL fork; standard bead→PR→Codex→refinery flow — open a PR, do NOT self-merge). This IS an implementation bead.
Goal (one sentence)
A gc-toolkit bead-host must survive an INVOLUNTARY config-drift drain by being brought
back automatically by the reconciler with its conversation intact — achieved by making
the host the assignee of its hosted bead (the assigned-work wake reason survives the
drainedstate), NOT by any fork exemption.Proven facts you are building on (do not re-derive; verify only where noted)
awake-demand. A/B proven on this city: assignee set ⇒ revived ~20s, no operator
action; assignee cleared ⇒ stayed
drained. (Mechanism:compute_awake_set.goassigned-work loop has no
Drainedgate;workBeadHasAwakeDemand= in_progress, oropen+Ready.)
s-<id>), not the session id — passing asession id to
gc bd update <tk-bead> --assigneeflips the resolver to the HQ storeand fails "no issue found".
s-<id>is also what the awake-set matches on.drain-ackwhilethe assignment + awake-demand hold (revives ~20s). Teardown must clear the wake reason
FIRST.
The change (core of it)
The dual-link already written on host bring-up is the natural home. In
tools/gc-bead-host.sh:cmd_link(bring-up) — in addition to the existing reverse/forward/lineagewrites, set the work bead's
assigneeto the session name ($name, alreadyresolved in that function). This is the grounding. Keep it idempotent (re-linking the
same session must not thrash). Note the existing convention (see
gc-helm.shheadercomment: "a child bead's
assigneeis its OWNING session — the session_name"), soassignee=session_name is already an established meaning here — confirm the host bead
is consistent with it.
cmd_unlink(teardown / re-bind) — clear the work bead'sassignee(alongsidethe existing metadata clears) so the host can actually be stopped. Without this, a
re-bind or cleanup leaves the old session grounded and un-drainable.
gc-helm.sh takeaway --release— ALREADY clears assignee (--assignee=) as itsclosing step (verified). Confirm it still does after your change and that release =
ungrounded-then-stoppable. If any other operator-facing "done with this bead" path
exists, it must also clear the wake reason before drain.
The prompt/model change (do not skip — the old guidance is now wrong)
agents/bead-host/prompt.template.mdcurrently tells hosts togc runtime drain-ack"between visits" for token economy. For a GROUNDED host that is a no-op-with-restart
(revives ~20s). Update the model to ONE coherent story and make the prompt match it:
bead has awake-demand; it is put down by ENDING the work — close the bead, or park it
(defer/block so it is no longer
ready), ortakeaway --release. Replace the"drain-ack between visits" instruction accordingly: intentional teardown clears the
wake reason (release / close / park) FIRST, then drains.
bounded
gc session suspend/held_untilto sleep between visits — the hold beatsevery wake reason (spike claim,
compute_awake_set.go"Hold suppression — overrideseverything"). BEFORE proposing this, VERIFY EMPIRICALLY that a config-drift occurring
DURING the hold still results in eventual revival (not permanent death). This
interaction is UNVERIFIED. If you cannot verify it cleanly, do NOT adopt it — ship the
warm-while-live model.
Guardrails to verify (must not regress)
Confirm scale_check, keeper prime-sweep (assignee-based), witness reconcile, and any
pool router do NOT pick up a host bead just because it now has an assignee. (The
child-bead convention suggests safe, but a HOST bead is often a decision/open bead —
check it isn't treated as an unassigned handback or a routable root.)
ungrounded. Provide a one-time backfill (set assignee=session_name on every live host
bead from its hosts_bead link) OR document that they ground on next
open. Thismatters: the exemption drop (tk-z130v.4) is gated on all live hosts being grounded,
and THIS conversation's host (tk-z130v) is one of them.
upruns cross-rig withBEADS_DIRpinned; the assigneewrite must land in the bead's own rig ledger (same pinning as the existing link
writes).
Tests / verification (TDD where practical)
linksets assignee=session_name on the work bead;unlinkclearsit; both idempotent. (gc-bead-host.sh already has offline-testable link/unlink — extend
those.)
→ assignee set → drain/idle → auto-revive; release/unlink → assignee cleared → drain
sticks. Tear the throwaway down cleanly.
is the tested invariant and the idle-revive A/B already stands in for it.
Deliverable
A PR on
zookanalytics/gc-toolkitimplementing the above, with the prompt/model updatedto the shipped sleep story, guardrails verified, and a backfill (or a documented
ground-on-next-open) for live hosts. Report the PR number back on this bead. Do NOT open
any upstream (gastownhall) PR. Do NOT touch the gascity fork or gc-8yr6px — that is
tk-z130v.4, deliberately separate and blocked on this.
Implementation notes
Implemented (branch polecat/tk-z130v.3 @ ca40c4d, target main). Grounding + warm-while-live teardown.
CORE (tools/gc-bead-host.sh):
linksets work-bead assignee=session_name — the assigned-work wakereason compute_awake_set revives across an INVOLUNTARY drain (no Drained gate);
unlinkclears it(ungrounds → stoppable); new
backfillgrounds already-linked hosts city-wide, rig-pinned per bead.New/resumed hosts ground automatically via up→link. gc-helm
takeaway --releasealready clearsassignee — verified + noted it ungrounds.
MODEL (agents/bead-host/{prompt.template.md,agent.toml}): warm-while-live — a grounded host stays
warm while its bead has awake-demand; teardown clears the wake reason (close/park/release) FIRST,
then drains. hold-to-sleep NOT adopted (unverified; a polecat must not drift live config).
GUARDRAILS (verified, must-not-regress): scale-check/keeper/pool-router/refinery/reconcile never act
on a session-name assignee (they key on gc.routed_to/branch/merge_result/own-identity; grounding also
fails --unassigned). The witness recover-orphaned-beads scan newly sees grounded host beads, but
drained/asleep already classify not-orphaned (revival scenario safe); closed the dead-host edge
safe-by-code (cf #206/#210): recover-orphaned-beads drops beads grounded to their own host
(assignee==host_session_name).
TESTS: bead-host-binding-fixture.sh grounding/unground/idempotent/backfill (31/31 live);
host-bead-skip.test.sh (5/5, extracts the witness filter verbatim). bash -n + shellcheck clean.
Rebased onto origin/main incl. #205 — quiesce is orthogonal (keys gc.step_ref=mol-polecat-work.*,
never host beads).
DEFERRED (operator; needs a live LLM session — a polecat must not spawn/reset live sessions): live
smoke on a throwaway host (open→assignee set→drain→auto-revive; release/unlink→cleared→drain sticks).
Parent tk-z130v already A/B-proved revival; this ships the metadata contract that triggers it.
Scope: gc-toolkit only (no upstream/gascity; tk-z130v.4 separate). PR opens via the refinery pre-open
codex gate — PR number to be recorded then.
Rework tk-5bygy landed on polecat/tk-z130v.3 at 358e0b7; re-review tk-knb1t dispatched (one-anchor-per-PR, tk-ynz4b).
Rework tk-v369i landed on polecat/tk-z130v.3 at 9fd8e47; re-review tk-wc3gf dispatched (one-anchor-per-PR, tk-ynz4b).
Rework tk-0gnq9 landed on polecat/tk-z130v.3 at cc62091 (rebased onto origin/main 4b09bd5 — resolved the assets/scripts/gc-helm.sh cmd_clear conflict with landed tk-xypcy #211, preserving the codex signoff fixes); re-review tk-q6zwf dispatched (one-anchor-per-PR, tk-ynz4b). Gates at cc62091: bash -n clean; shellcheck clean at warning+ (info-only SC2015 x41 on the A && ok || bad assertion idiom, SC2016 x4).
Refinery handoff
tk-z130v.3(task, P1)polecat/tk-z130v.3maincc620913; PR opened codex-green.