Keep the Planner while its workflows run, with Stop/Resume and a run card #230
- LA
Stacked on #215. Partly implements #221.
An atomic Planner session that starts an Atomic workflow now outlives its turn. Until now, Chopin destroyed the session when the turn ended, which left the run orphaned seconds after launch.
## What changes
- **The session stays while it owns runs.** An atomic Planner session is kept, with its owner binding and the loaded document, as long as it has live or paused workflow runs. Chopin tracks those runs through Atomic's workflow activity stream. The next turn reuses the session, so the Planner can still steer its runs over Intercom. The session is released when its runs end, when the owner binding ends, or when the document closes. - **Stop and Resume.** Stop Planner pauses the session's live runs through the session's own `workflow` tool, which returns a structured result instead of a dropped UI notice. Atomic can't pause a run whose only live stage is waiting on a question, so that run is quit instead. It stays resumable and its open question is withdrawn from Decisions. The new `chat:resume` message and the Resume Planner button resume paused runs. - **Run card in Chat.** Each run gets a card showing its name, status (running, waiting on Decisions, paused, finished, blocked, failed, or stopped), elapsed time, and each stage with its status and duration. A pulsing dot marks live work and stays still under reduced motion. Ended runs collapse to one line with the stages behind a toggle, and the transcript records how each run ended. A design-audit specimen covers the running, waiting, paused, blocked and finished states. - **`create_document` outcomes.** It now reports `title-taken`, and declares its `idempotency-conflict`, `document-unavailable` and validation-issue outcomes in its output schema. Before, strict MCP clients rejected those responses as schema violations. - **Working directory.** Without a checkout, the Planner's working directory moves from the shared tmp directory to a per-channel directory under Chopin's per-user state directory.
## Verification
- `bun test`: 1755 pass, 2 PostgreSQL skips, 0 fail. - `bun run types` and `bun run ci` pass, with no new Impeccable findings. - Manual agent-browser run against a local server, with the full atomic Planner running a planning workflow on a private demo repository: - the run card tracked the stages; - Stop paused the run and Resume continued it; - two Decisions questions were answered in the browser; - the workflow wrote its formal specification and plan, and the document reached revision 12.
## Known gaps
- The card's "Waiting on N Decisions" link doesn't appear, because the waiting count stays 0 for questions that come from `HostInput`. - The Planner isn't prompted to report a run's outcome when the run ends. - Pause and resume should move to typed SDK primitives once bastani-inc/atomic#3377 lands.
Assistant-workflow: inline Assistant-verification: bun test passed: 1755 pass, 2 skip, 0 fail Assistant-verification: bun run types and bun run ci passed Assistant-verification: agent-browser E2E passed: run card, Stop/Resume, Decisions answered, workflow wrote its spec and plan Co-authored-by: Alex Lavaee <lavaman131@github.com>
lavaman131added-to-stacklast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131head-ref-force-pushed 0931da62343flast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131head-ref-force-pushed 1b766471437flast month on GitHub
lavaman131head-ref-force-pushed 1b766471437flast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131head-ref-force-pushed 24659d0f7360last month on GitHub
lavaman131referenced this in Load operator extension paths in atomic Planner sessions #238last month on GitHub
- MAapps/server/src/chat/service.ts:963–96524659d0
This guard goes with the cleanup change above. Closing an old helper can take long enough for a new one to start. In a regression test, the old cleanup changed the new helper's running card to ‘stopped’. Let's only clear the owner and cards if this is still the helper being closed.
```suggestion if (chat.agent === opened.session) { chat.agent = undefined; if (!chat.busy) chat.owner = undefined; publishRuns(context, undefined); } ```
- MAapps/server/src/chat/service.ts:923–92524659d0
Let's save the new card and its finish message before showing either to people. Right now the message is sent by `say()` above, then the card is sent here, and only afterwards do we try to save them. If saving fails, people can see a job finish and then lose that update after a reload.
Please handle these updates in order, save the card and message together, then send both. Moving only `state()` would still send the message too early. Please add a test where saving fails and check that neither update was announced. This needs a coordinated change, so a one-line suggestion would leave part of the bug in place.
- MAapps/server/src/chat/service.ts:91424659d0
This is the other half of the quick-job fix: a job we first hear about after it has ended should still get a finish message. The current check skips it because there is no earlier card. This suggestion allows that first message and still avoids repeating one for a job already recorded as finished.
```suggestion if (!ended || (previous && ENDED_RUN[previous])) continue; ```
- MAapps/server/src/chat/service.ts:1048–10629c1047f
If the last job finishes while the Planner's reply is being saved, `chat.busy` is still true, so cleanup gets skipped and never retried. I reproduced the helper and document staying loaded after the job was done.
Let's check again once the reply and any queued replies have finished. Only close the same helper, and only if it still has no work. Please apply this with the cleanup guard below, so a slow close cannot overwrite a newer helper's progress.
```suggestion let stopWatching = opened.session.watchRuns?.(next => { if (chat.retained?.session !== opened.session) return; publishRuns(context, next); if (next.active.length || next.paused.length) return; let releaseWhenIdle = async () => { if (chat.busy) await chat.running; if (chat.busy || chat.retained?.session !== opened.session) return; let current = opened.session.runs?.(); if (!current || current.active.length || current.paused.length) return; await chat.retained.release(); }; void releaseWhenIdle().catch(err => console.error("[chat] releasing completed workflow session failed:", err) ); }); ```
- MAapps/server/src/chat/service.ts:941–94324659d0
A quick job can finish before the Planner's reply is done. We return here without keeping its card, so people never see that it finished or failed. I reproduced the missing card. Let's record its last state even when there is no helper left to keep running. Please apply this with the companion finish-message suggestion above.
```suggestion if (!runs || opened.binding.signal.aborted) return false; if (!runs.active.length && !runs.paused.length) { publishRuns(context, runs); return false; } ```
- MA
I'd fix three things before merging:
- Save progress cards and finish messages before showing them, so a failed save cannot leave people seeing progress that disappears after a reload. - Keep the completion card and message when a quick job finishes before the Planner's reply ends. - Recheck cleanup after a reply finishes, and keep old cleanup from overwriting a newer helper's progress.
The small code suggestions passed focused regression tests. The save-ordering fix needs the card and message paths changed together; I've described the behavior and test needed in its comment.
lavaman131head-ref-force-pushed c29f1014c2e1last month on GitHub
- LA
No review summary.
- LAapps/server/src/chat/service.ts:923–92524659d0
Fixed in c29f101. A run report is now built as a candidate outside shared chat state and saved first: the save stores the candidate card and transcript together, and only when it succeeds are the card and finish message added to the chat and announced together. While a save is pending, or after it fails, nothing else that broadcasts chat state or greets a joining client can show them. Reports are handled one at a time, so a report that arrives during a pending save is computed from the state after it settles.
The new test fails the save and checks that neither update was announced; others cover overlapping reports, a state broadcast during a pending save, and a failed save of a run's final report, which is replayed on release so the run still ends as finished rather than "was stopped".
Assistant-verification: fail-before/pass-after passed: save-failure, overlapping-report, state-leak and failed-final-save tests; bun test 1928 pass, 0 fail on the rebased branch
Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Co-authored-by: Alex Lavaee <lavaman131@github.com>
- LA
No review summary.
- LAapps/server/src/chat/service.ts:941–94324659d0
Applied in c29f101, together with the finish-message change: a quick job that ended before the Planner's reply finished keeps its last card and gets its finish message. The quick-job test fails without it.
Assistant-verification: fail-before/pass-after passed: quick-job test
Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Co-authored-by: Alex Lavaee <lavaman131@github.com>
- LAapps/server/src/chat/service.ts:91424659d0
Applied in c29f101: a run first reported as already ended gets one finish message, and a run already recorded as ended never gets another. The first-seen-ended test fails without it.
Assistant-verification: fail-before/pass-after passed: first-seen-ended test
Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Co-authored-by: Alex Lavaee <lavaman131@github.com>
- LA
No review summary.
- LAapps/server/src/chat/service.ts:1048–10629c1047f
Applied in c29f101. When the last job ends while a reply, or a queued reply, is still running, cleanup is checked again once it finishes, and it releases only the same helper, only if it still has no active or paused runs. The deferred-cleanup test fails without it.
Assistant-verification: fail-before/pass-after passed: deferred-cleanup test
Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Co-authored-by: Alex Lavaee <lavaman131@github.com>
- LA
No review summary.
- LA
No review summary.
- LAapps/server/src/chat/service.ts:963–96524659d0
Fixed in c29f101, generalizing your guard into one rule: every run card belongs to the Planner session that reported it. A report replaces or updates only that session's cards, and releasing a session marks stopped exactly the unended cards it owns, with one "was stopped" message each, saved and announced together. A release never touches another session's cards, and it clears the owner and agent only when the released session is still the current agent and no turn is running. So a slow release of an old helper can no longer overwrite a newer helper's progress, and it still stops its own cards when a newer helper has taken over.
A table test runs four cases through the real invoke flow: no newer helper, a newer helper's run-less turn, a newer helper that reported its own runs, and a run-less turn after a failed save. Each asserts that the old helper's cards end stopped with one message and that the newer helper's cards and owner are untouched.
One known gap remains: if the release's own final save also fails, nothing is announced, and when a newer helper then reports a new run, the old helper's orphaned card is dropped without a stop message. It needs two failures in a row; I can follow up if you'd like it covered here.
Assistant-verification: fail-before/pass-after passed: the four-case release table test (the newer-helper-reported-runs case failed before) and the slow-release tests; bun test 1936 pass, 0 fail on #238 with Atomic 0.9.26-alpha.6 Assistant-workflow: goal (run 6091839b-867c-4965-aec9-b583f62d4b1f) Co-authored-by: Alex Lavaee <lavaman131@github.com>
lavaman131requested review from MaggieAppletonlast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131head-ref-force-pushed 3d884c71bf9dlast month on GitHub
lavaman131referenced adb413ed4b01last month on GitHub
lavaman131head-ref-force-pushed 4d1e12f1ad41last month on GitHub
- MA
No review summary.
MaggieAppletonautomatic-base-change-succeededlast month on GitHub
lavaman131head-ref-force-pushed 96407ebd0542last month on GitHub
lavaman131merged 0616e527d3f5last month on GitHub
lavaman131head-ref-deletedlast month on GitHub