Skip to content

fix(program): keep shimmer alive across transient-empty + add keep_pending - #576

Merged
edwin-zvs merged 1 commit into
mainfrom
fix/program-shimmer-transient-empty
Jun 29, 2026
Merged

edwin-zvs merged 1 commit into
mainfrom
fix/program-shimmer-transient-empty

Conversation

@edwin-zvs

Copy link
Copy Markdown
Contributor

The bug (found in the 'construct improvements' session)

After the planning pass declared a task pending, the agent dispatched a subagent, which moved the task into "In progress" and appended a @{session} clip — changing the block's text, hence its id (50eca… → 74c9…). 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; the agent's follow-up re-declaration of the new id hit a removed run and was a silent no-op. All shimmer vanished — exactly opposite the expectation (In-progress items should animate while subagents work).

The agent even diagnosed it in its own words:

"The move reset the block's shimmer to settled and the system won't re-light a settled/new block … That's a known constraint."

Fix #1 — survive a 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 (nothing shimmers) 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.

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 declares arbitrary existing blocks (the planning pass). Tool description + run prompts updated to steer agents to keep_pending for move/clip edits.

Specs

  • 0042 — stop lifecycle: a transient empty mid-turn is not a stop; empty clears only on terminal/idle/backstop.
  • 0048 — agent contract: use keep_pending (or re-declare) when editing an in-flight block.
  • 0053 — both mechanisms (id-list vs keep_pending) and transient-empty survival.

Tests

Three new daemon tests — transient-empty survival + revival, keep_pending atomicity, empty-clears-on-idle — plus the #567/#569 tests unaffected. cargo test --workspace green.

Deploy note

The running daemon needs a /construct restart to pick this up (same as #567).

Relevant binary: construct (daemon + cli + mcp).

…g (0053)

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.
@edwin-zvs
edwin-zvs merged commit 5fe8fb1 into main Jun 29, 2026
1 check passed
@edwin-zvs
edwin-zvs deleted the fix/program-shimmer-transient-empty branch June 29, 2026 01:36
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