Repository navigation
program: declare per-block shimmer by stable content-id (spec 0053 + implementation) - #567
Conversation
Shimmer was inferred from block-content changes: a block shimmered from
Run start until its text changed. That can't express "settled but text
unchanged", so no-work blocks (which never change) stayed shimmering for
the whole turn while actively-worked blocks settled — the exact inverse
of intent reported on a live program.
Add spec 0051: shimmer is a declared per-block state addressed by a
stable, content-derived block id (not a positional index, not an inferred
diff). get returns an ordered per-block projection (id + text + shimmer);
edit takes an optional partial {id, shimmer} list that can target any
block; update requires a complete declaration; execute stays optimistic.
Content-id addressing fails closed under concurrent human edits (drops the
stale declaration, never retargets a neighbor) — same semantics anchored
content edits already have. Writes echo the fresh projection.
Reconcile 0042 (narrow rule + published run facts now per-block projection)
and 0048 (planning pass declares the pending set without touching settled
blocks; fix the false "daemon clears any edit without shimmer:true" claim).
Spec-only; implementation to follow.
…-id (0051)
Replace the inferred shimmer mechanism (a block shimmered until its text
changed, which could never clear an unchanged no-work block — the reported
inversion) with the declared model from spec 0051.
protocol: add program_block_id (FNV-1a, dependency-free, shared by daemon
and clients) and program_block_spans (one parser for block identity); add
ProgramBlockSpan / ProgramBlockView / ProgramShimmerDecl. Rename
ProgramRunProgress.pending_block_signatures -> pending_block_ids (serde
alias keeps the old wire name). Drop ProgramEdit.shimmer; add shimmer to
edit (partial {id,shimmer} list), update (required complete Vec<bool>) and
execute (optional initial Vec<bool>); echo `blocks` from get/edit/update/
execute results.
daemon: narrow_program_run now applies the per-block declarations against
the post-edit document (changed ids drop, declared ids set, unknown ids
ignored — fail closed); set_program_run_pending for update's authoritative
declaration; program_blocks_projection builds the get/echo projection;
start_program_run accepts an optional initial set. Run prompts rewritten to
the declared-id planning pass.
mcp: tool schemas + dispatch for the new shimmer params across edit/update/
execute, and get now documents the per-block projection.
cli: render and re-run logic key on block id via the shared parser; param
sites pass shimmer; remove the now-unused signature field.
Tests: protocol id invariants; daemon planning-pass clears inert blocks
without text change (the bug), update complete-declaration + length check,
edit unknown-id fail-closed.
Implementation added (commit 2a46c7c)This PR now carries the full implementation, not just the spec. protocol — daemon — mcp — schemas + dispatch for the new shimmer params across the three writers; cli — render + re-run logic key on block id via the shared parser; param sites updated. Tests
Relevant binary (debug): |
…053, break pending_block_signatures compat main advanced with #569 (keep run shimmer alive past owning-session idle), which reworked the same shimmer lifecycle and added agent_managed to ProgramRunProgress, plus #563/#568 (specs 0051 custom-templates, 0052 session-removal-cascades). Reconcile: - protocol: keep pending_block_ids rename + the declared-shimmer types alongside #569's agent_managed field; DROP the pending_block_signatures serde alias (clean break, per request) and remove the legacy-compat test. - daemon: fold #569's agent_managed + inactivity-backstop refresh into the declaration-based narrow_program_run and set_program_run_pending so a complete/partial declaration also marks the run agent-managed; #569's AwaitingInput/terminal stop-signal split is preserved. Adapt #569's two lifecycle tests to the id-based API. - specs 0042/0048: combine #569's stop-signal model with the declared per-block-id model; my new spec collides with main's 0051/0052, renumbered to specs/0053-program-shimmer-block-addressing and all references updated. cargo test --workspace green.
Rebased on latest main (merge fe2d0c9)
|
…g (0053) (#576) Diagnosed from the 'construct improvements' session: after the planning pass declared the task pending, dispatching a subagent moved the task into the "In progress" section and appended a @{session} clip — which changes the block's text and therefore its id. The old id dropped from the pending set before the new id was declared, so the set transiently emptied; the daemon reaped the whole run on that empty, and the agent's follow-up re-declaration of the new id hit a removed run and was a silent no-op. All shimmer vanished. The agent's own narration: "the system won't re-light a settled/new block". Fix #1 — survive transient empty. narrow_program_run / set_program_run_pending no longer remove a run when its pending set empties; program_run_snapshot reports no active run on empty but KEEPS the record so a later declaration revives it. An empty run is reaped only on a terminal owning-session state, the owning session going idle with nothing pending, or the inactivity backstop (note_session_state handles the idle case). Fix #2 — atomic keep_pending. Re-introduce a per-edit ProgramEdit.keep_pending flag: an edit that changes a still-pending block re-adds the resulting block's new id in the SAME narrowing call (anchored on the edit's new_string), so a move/annotate never transiently empties the set and the agent needn't know the post-edit id. The call-level shimmer id-list still handles declaring arbitrary existing blocks (planning pass). Specs 0042 (stop lifecycle: transient empty is not a stop), 0048 (contract: use keep_pending when editing an in-flight block), 0053 (both mechanisms + transient-empty survival). Three new daemon tests; #567/#569 tests unaffected.
Why
A live program showed the shimmer animation inverted: the inert sections (
# Rule,## TODO,## Done) shimmered for the whole turn while the two actively-worked in-progress items went calm — the opposite of "pending work shimmers."Root cause: shimmer is inferred from block-content changes. A block shimmers from Run start until its text changes (
narrow_program_runretains a block's signature while its text is unchanged; the agent contract's "any edit withoutshimmer:trueclears the block" is not what the daemon actually does). That model cannot express "this block is settled but its text is unchanged" — so no-work blocks, which by definition never change, can never be cleared by the planning pass, while worked blocks settle the moment the agent rewrites them. Net: the inverse.What
Spec-only change establishing the fix design (implementation to follow):
0051-program-shimmer-block-addressing— shimmer becomes a declared per-block state addressed by a stable, content-derived block id:id+ text +shimmer), derived (never persisted).{id, shimmer}list that can target any block, not only edited ones — so the planning pass clears# Rule/## TODO/## Donewithout touching their text.0042— narrow rule and published run facts are now the per-block projection rather than opaque pending signatures.0048— planning pass declares the pending set (no need to textually touch settled blocks); removed the false "daemon clears any edit withoutshimmer:true" claim and pointed the mechanism at 0051.Design decisions (locked in discussion)
Scope
Docs only (
specs/). No code; nothing to build or run. Implementation acrosscrates/{protocol,daemon,mcp,cli}is a follow-up PR.