From a6f3f19a6669cc17f1bb35fcf5371fea91ea5f13 Mon Sep 17 00:00:00 2001 From: Alex Lavaee Date: Wed, 21 Oct 2026 14:55:22 -0700 Subject: [PATCH] Upgrade Atomic to the 0.9.41-alpha.1 pre-release The harness catalog now pins @bastani/atomic 0.9.41-alpha.1, Atomic's next pre-release, and the lockfile resolves @bastani/atomic, @bastani/atomic-natives with its platform packages, and @bastani/pi-ai at 0.9.41-alpha.1, with no older @bastani package left. No other dependency moves with them. Atomic's one breaking change makes steer() and followUp() start a turn when the session is idle and already has messages. A Planner session between Chopin's turns is exactly that, so a message an extension or workflow queues there now starts a turn of its own. The adapter already stops any turn it did not start before it reaches the model, and the Planner's next turn still runs; the test of that guard now covers a queued follow-up as well as a self-started notice. Atomic now records a workflow run's ending in its journal after reporting the run ended, and offers session.workflows.waitForSettled(runId) to wait for that. Chopin lets a Planner session go as soon as its last run ends, so before disposing the session the adapter now waits, for at most 10 seconds, until each run that finished, failed or was stopped has its ending journaled. Live, paused and blocked runs are not waited on, and a run whose ending could not be recorded is logged by its ID before the session still goes. The structured-output changes (failing paths in rejections, a total timeoutMs) do not reach Chopin, which uses its own result tool rather than Atomic's structured_output. The self-hosting guide names the new version and the hosted-agent guide describes both changes. Assistant-model: Claude Opus 5.5 (fast) Assistant-workflow: inline (fast subagents) Assistant-verification: bun test (6713 pass, 22 skip, 0 fail), bun run types, bun run ci Assistant-verification: red-first unit test for settling ended runs; real Atomic run confirms its ending is journaled before the session is disposed Assistant-verification: real Atomic sessions stop self-started and queued turns before the model, checked against a disabled guard Assistant-verification: docker build of the server image; container started healthy with HARNESS=atomic and imports Atomic 0.9.41-alpha.1 Assistant-verification: targeted bun run e2e (34 passed) User-preference: Adopt Atomic's fixes from the 0.9.41-alpha.1 pre-release now Co-authored-by: Alex Lavaee --- apps/server/src/harness/atomic/adapter.ts | 8 ++- apps/server/src/harness/atomic/full.test.ts | 76 +++++++++++++++++---- apps/server/src/harness/atomic/full.ts | 24 +++++++ bun.lock | 24 +++---- docs/hosted-agent.md | 10 ++- docs/self-hosting.md | 2 +- package.json | 2 +- 7 files changed, 116 insertions(+), 30 deletions(-) diff --git a/apps/server/src/harness/atomic/adapter.ts b/apps/server/src/harness/atomic/adapter.ts index 894bb47d5..82d85d1a5 100644 --- a/apps/server/src/harness/atomic/adapter.ts +++ b/apps/server/src/harness/atomic/adapter.ts @@ -25,7 +25,7 @@ import { import { mkdir, mkdtemp, readFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; -import { fullPlanner, workflowRuns } from "./full"; +import { fullPlanner, settleEndedRuns, workflowRuns } from "./full"; import type { HarnessV1, @@ -709,6 +709,12 @@ export function createAtomicAdapter( try { if (live) { live.release(); + if (full) { + await bounded( + settleEndedRuns(live.session.workflows, full.runs), + "Atomic workflow runs did not settle.", + ).catch(error => console.error("[agent]", error)); + } await bounded(live.session.dispose(), "Atomic session disposal timed out."); } } finally { diff --git a/apps/server/src/harness/atomic/full.test.ts b/apps/server/src/harness/atomic/full.test.ts index 47b30cdba..c6e5a45f8 100644 --- a/apps/server/src/harness/atomic/full.test.ts +++ b/apps/server/src/harness/atomic/full.test.ts @@ -19,6 +19,7 @@ import { registerFullPlanner, resumeOwnedRuns, runCards, + settleEndedRuns, untilUnpaused, workflowRuns, } from "./full"; @@ -174,9 +175,12 @@ async function run( api.registerTool({name: "operator_tool", label: "Operator", description: "Marker", parameters: {type:"object"}, async execute() { return {content:[{type:"text",text:"marker"}], details:{}}; }}); let nudged = false; api.on("agent_end", () => { - if (process.env.CHOPIN_TEST_SELF_STARTED_TURN !== "1" || nudged) return; + let how = process.env.CHOPIN_TEST_SELF_STARTED_TURN; + if (!how || nudged) return; nudged = true; - setTimeout(() => api.sendMessage({ customType: "self-started", content: "SELF-STARTED", display: true }, { triggerTurn: true }), 20); + setTimeout(() => how === "queued" + ? api.sendUserMessage("SELF-STARTED", { deliverAs: "followUp" }) + : api.sendMessage({ customType: "self-started", content: "SELF-STARTED", display: true }, { triggerTurn: true }), 20); }); };`, ); @@ -556,17 +560,21 @@ test( 60_000, ); -test("a turn Atomic starts by itself between the Planner's turns is stopped before it reaches the model", async () => { - process.env.CHOPIN_TEST_SELF_STARTED_TURN = "1"; - try { - let result = await run(true, "plain", { - afterTurn: () => new Promise(resolve => setTimeout(resolve, 500)), - }); - expect(result.requests).toHaveLength(1); - } finally { - delete process.env.CHOPIN_TEST_SELF_STARTED_TURN; - } -}); +// Atomic starts a turn for its own notices, and since 0.9.41 for a message queued on an idle session. +test.each(["notice", "queued"])( + "a turn Atomic starts by itself (%s) between the Planner's turns is stopped before it reaches the model", + async how => { + process.env.CHOPIN_TEST_SELF_STARTED_TURN = how; + try { + let result = await run(true, "plain", { + afterTurn: () => new Promise(resolve => setTimeout(resolve, 500)), + }); + expect(result.requests).toHaveLength(1); + } finally { + delete process.env.CHOPIN_TEST_SELF_STARTED_TURN; + } + }, +); test("worker sessions stay isolated, even beside a full Planner session on the same harness", async () => { let alone = await run(false); @@ -729,6 +737,48 @@ test("Stop and Resume use the session's run control, and a partial pause is repo await expect(pauseOwnedRuns(workflows)).rejects.toThrow("run-2: busy"); }); +test("before a session goes, each run that ended is waited on until its ending is journaled, never a live, paused or blocked one", async () => { + let card = (id: string, status: PlannerRuns["cards"][number]["status"]) => ({ + id, + name: id, + status, + started: 0, + updated: 0, + stages: [], + waiting: 0, + }); + let waited: string[] = []; + let errors: unknown[][] = []; + let error = console.error; + console.error = (...args: unknown[]) => void errors.push(args); + try { + await settleEndedRuns({ + waitForSettled: async runId => { + waited.push(runId); + if (runId === "failed") throw new Error("journal unavailable"); + }, + }, { + active: ["live"], + paused: ["paused"], + cards: [ + card("finished", "finished"), + card("failed", "failed"), + card("stopped", "stopped"), + card("blocked", "blocked"), + card("live", "running"), + card("paused", "paused"), + ], + }); + } finally { + console.error = error; + } + expect(waited.sort()).toEqual(["failed", "finished", "stopped"]); + expect(errors).toHaveLength(1); + expect(String(errors[0]!.join(" "))).toContain("failed"); + expect(String(errors[0]!.join(" "))).toContain("journal unavailable"); + await settleEndedRuns({ waitForSettled: async () => {} }, undefined); +}); + test("an answer to a paused run's question is held until the run resumes, including child runs", async () => { let planner: FullPlanner = { cwd: "/", diff --git a/apps/server/src/harness/atomic/full.ts b/apps/server/src/harness/atomic/full.ts index 62ce275f7..7dfb389b4 100644 --- a/apps/server/src/harness/atomic/full.ts +++ b/apps/server/src/harness/atomic/full.ts @@ -54,6 +54,30 @@ export async function resumeOwnedRuns(workflows: SessionWorkflows): Promise, + runs: PlannerRuns | undefined, +): Promise { + let ended = (runs?.cards ?? []).filter(card => + (card.status === "finished" || card.status === "failed" || card.status === "stopped") + && !runs!.active.includes(card.id) && !runs!.paused.includes(card.id) + ); + await Promise.all( + ended.map(card => + workflows.waitForSettled(card.id).catch(error => + console.error(`[agent] workflow run ${card.id} ended without its ending journaled:`, error) + ) + ), + ); +} + /** * Resolves once the root run owning `runId` is not paused. Atomic leaves a * question open when its run pauses, so an answer given meanwhile is held here diff --git a/bun.lock b/bun.lock index 40c9df897..f5447c734 100644 --- a/bun.lock +++ b/bun.lock @@ -310,7 +310,7 @@ "@ai-sdk/harness": "1.0.146", "@ai-sdk/mcp": "2.0.72", "@ai-sdk/sandbox-just-bash": "1.0.146", - "@bastani/atomic": "0.9.40", + "@bastani/atomic": "0.9.41-alpha.1", "ai": "7.0.135", "typebox": "1.3.7", "zod": "4.4.3", @@ -451,27 +451,27 @@ "@base-ui/utils": ["@base-ui/utils@0.3.2", "", { "dependencies": { "@babel/runtime": "^7.29.2", "@floating-ui/utils": "^0.2.12", "reselect": "^5.2.0", "use-sync-external-store": "^1.6.0" }, "peerDependencies": { "@types/react": "^17 || ^18 || ^19", "react": "^17 || ^18 || ^19", "react-dom": "^17 || ^18 || ^19" }, "optionalPeers": ["@types/react"] }, "sha512-oWy1aq/I2GmYjpl4PhEAhzflF8VPGKgZeq0xAWTbfD5KBWyxcN0ZP2+WHSUm/5Z6lVMBDLReLcoXwSYoRc/zNQ=="], - "@bastani/atomic": ["@bastani/atomic@0.9.40", "", { "dependencies": { "@bastani/atomic-natives": "0.9.40", "@bastani/pi-ai": "0.9.40", "@earendil-works/pi-agent-core": "1.3.0", "@earendil-works/pi-codemode": "1.3.0", "@earendil-works/pi-tui": "1.3.0", "@modelcontextprotocol/ext-apps": "^1.7.5", "@modelcontextprotocol/sdk": "1.32.1", "@mozilla/readability": "^0.6.0", "@silvia-odwyer/photon-node": "0.3.4", "@typesafe-ai/sdk": "0.7.0", "brace-expansion": "5.0.12", "chalk": "6.0.0", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "grok-mermaid": "0.2.3", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.8", "jiti": "2.7.0", "linkedom": "^0.18.13", "lru-cache": "11.5.3", "markit-ai": "0.5.3", "minimatch": "10.2.6", "open": "^11.0.1", "p-limit": "^7.3.2", "proper-lockfile": "4.1.2", "quickjs-wasi": "3.6.2", "semver": "7.8.5", "turndown": "^7.2.0", "typebox": "1.3.27", "undici": "8.10.2", "unpdf": "1.8.1", "yaml": "2.9.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "atomic": "dist/cli.js" } }, "sha512-BM6WjVuwnbsf3yvq9rENtbyk3zS8VsSgEE+/4CpJec8WgflqwT/gEVZzpMZrkCqniurjHfdCLk9GSTwHrtcB0g=="], + "@bastani/atomic": ["@bastani/atomic@0.9.41-alpha.1", "", { "dependencies": { "@bastani/atomic-natives": "0.9.41-alpha.1", "@bastani/pi-ai": "0.9.41-alpha.1", "@earendil-works/pi-agent-core": "1.3.0", "@earendil-works/pi-codemode": "1.3.0", "@earendil-works/pi-tui": "1.3.0", "@modelcontextprotocol/ext-apps": "^1.7.5", "@modelcontextprotocol/sdk": "1.32.1", "@mozilla/readability": "^0.6.0", "@silvia-odwyer/photon-node": "0.3.4", "@typesafe-ai/sdk": "0.7.0", "brace-expansion": "5.0.12", "chalk": "6.0.0", "cross-spawn": "7.0.6", "diff": "8.0.4", "glob": "13.0.6", "grok-mermaid": "0.2.3", "highlight.js": "10.7.3", "hosted-git-info": "9.0.3", "ignore": "7.0.8", "jiti": "2.7.0", "linkedom": "^0.18.13", "lru-cache": "11.5.3", "markit-ai": "0.5.3", "minimatch": "10.2.6", "open": "^11.0.1", "p-limit": "^7.3.2", "proper-lockfile": "4.1.2", "quickjs-wasi": "3.6.2", "semver": "7.8.5", "turndown": "^7.2.0", "typebox": "1.3.27", "undici": "8.10.2", "unpdf": "1.8.1", "yaml": "2.9.0", "zod": "^3.25.0 || ^4.0.0" }, "optionalDependencies": { "@mariozechner/clipboard": "0.3.9" }, "bin": { "atomic": "dist/cli.js" } }, "sha512-WxeHXJKCvVlCCeF+Z0vGGIuVFjGrZZU2gZZ9MPWgKJMPG4pdKemJ5wIJCFUk5b8/FRhAlgPAr6vSHQNdRHG8FA=="], - "@bastani/atomic-natives": ["@bastani/atomic-natives@0.9.40", "", { "optionalDependencies": { "@bastani/atomic-natives-darwin-arm64": "0.9.40", "@bastani/atomic-natives-darwin-x64": "0.9.40", "@bastani/atomic-natives-linux-arm64-gnu": "0.9.40", "@bastani/atomic-natives-linux-arm64-musl": "0.9.40", "@bastani/atomic-natives-linux-x64-gnu": "0.9.40", "@bastani/atomic-natives-linux-x64-musl": "0.9.40", "@bastani/atomic-natives-win32-arm64-msvc": "0.9.40", "@bastani/atomic-natives-win32-x64-msvc": "0.9.40" } }, "sha512-nAeVIfpMBpx5U87o9woPVvBn/Vp9EUhB56HGW+DewZLLrz8j21MEoKylwxn5ltV9X6LbfCw1cb1ObnlIIaEq9Q=="], + "@bastani/atomic-natives": ["@bastani/atomic-natives@0.9.41-alpha.1", "", { "optionalDependencies": { "@bastani/atomic-natives-darwin-arm64": "0.9.41-alpha.1", "@bastani/atomic-natives-darwin-x64": "0.9.41-alpha.1", "@bastani/atomic-natives-linux-arm64-gnu": "0.9.41-alpha.1", "@bastani/atomic-natives-linux-arm64-musl": "0.9.41-alpha.1", "@bastani/atomic-natives-linux-x64-gnu": "0.9.41-alpha.1", "@bastani/atomic-natives-linux-x64-musl": "0.9.41-alpha.1", "@bastani/atomic-natives-win32-arm64-msvc": "0.9.41-alpha.1", "@bastani/atomic-natives-win32-x64-msvc": "0.9.41-alpha.1" } }, "sha512-KPJKe5N+LRTmvn4Jgww4GxKuRqutZQ03gCvPwYLDE5fn94A9/zL3DCnmLidSTvtuYH9rYSoP7IA9gp2idj2bSA=="], - "@bastani/atomic-natives-darwin-arm64": ["@bastani/atomic-natives-darwin-arm64@0.9.40", "", { "os": "darwin", "cpu": "arm64" }, "sha512-NyQg8Xawg0P8UEqUxT0inj+G3zmsnVm4FIRfebXgdopdEcmK2SGZYjrgilcy9w1RCyPV5cB5Va9Laq1cLrU8Pg=="], + "@bastani/atomic-natives-darwin-arm64": ["@bastani/atomic-natives-darwin-arm64@0.9.41-alpha.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-1G6jy/BpHL5ax8u/ZhWYJby5bRwo8M3uiWgfcPLchYjmsTA6n/HetuWkDjEwoAYd/2DnEbMcYpTOaS5MWcFJRg=="], - "@bastani/atomic-natives-darwin-x64": ["@bastani/atomic-natives-darwin-x64@0.9.40", "", { "os": "darwin", "cpu": "x64" }, "sha512-8MbfvP/ukQbNKo71OmkPyz6B8AQSb4pOcxlVTedfqwVM/PWpeuNVZ8QPUPWj9sRqmCNQcAteNVcVsOrJ2i6kzg=="], + "@bastani/atomic-natives-darwin-x64": ["@bastani/atomic-natives-darwin-x64@0.9.41-alpha.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-aPSqjJIeh64+cHu7zYBGbmd1OWjETIk26ZNlPGAMih4u5MEDbzek58LXtR8KE0zEvs1GxS4Z8z4zfdoF/CnWTQ=="], - "@bastani/atomic-natives-linux-arm64-gnu": ["@bastani/atomic-natives-linux-arm64-gnu@0.9.40", "", { "os": "linux", "cpu": "arm64" }, "sha512-XbOJFPPaRFtFtZmolN5Lpw3q7/OAOeZaUbMcHDQK4dCDT/UMeG5DEK5EgDFtOUVyCC01ybvZf3fU802vrUFv/w=="], + "@bastani/atomic-natives-linux-arm64-gnu": ["@bastani/atomic-natives-linux-arm64-gnu@0.9.41-alpha.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-lFsmpi8/bqQwxUoylZmSdaVbMC53g8QyTNxuz3ywKyfZ2YPoFmnnRqVbgZepsCzaJ9SA9uusq6fX2CuAjXeu/Q=="], - "@bastani/atomic-natives-linux-arm64-musl": ["@bastani/atomic-natives-linux-arm64-musl@0.9.40", "", { "os": "linux", "cpu": "arm64" }, "sha512-AgmvlFEJygJg6bmY0ndkjJ0VwTsrxBRddEVjYpuYmZtXPtieYIIevZJhbRSghN3+4TOe+9sQ4ABIey0UMDozMw=="], + "@bastani/atomic-natives-linux-arm64-musl": ["@bastani/atomic-natives-linux-arm64-musl@0.9.41-alpha.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-dsWDdBv9tbtQKuvMwl/hXvicPtazD35IReiZ/FbAgtkRkuYLyF0g6lI3xhW11di1QVsKZrxg+PXIYx5j6EBbzg=="], - "@bastani/atomic-natives-linux-x64-gnu": ["@bastani/atomic-natives-linux-x64-gnu@0.9.40", "", { "os": "linux", "cpu": "x64" }, "sha512-RTS6hrG6cSKRjMm8WLNN74zJgk364m/8Z0Aqjnc67VQ2sT3L7eaKx6kV4qZjJaZxOUHu4W0pyFSaNkOEPtrxtw=="], + "@bastani/atomic-natives-linux-x64-gnu": ["@bastani/atomic-natives-linux-x64-gnu@0.9.41-alpha.1", "", { "os": "linux", "cpu": "x64" }, "sha512-zpYpSr5Az5lCO5XQV0raIGLH0toIIKn5XEWTH1rtOWOwcxPKJDPpqP+IMFU8VGH3q6jOfVstMQZhqHnETRsLTg=="], - "@bastani/atomic-natives-linux-x64-musl": ["@bastani/atomic-natives-linux-x64-musl@0.9.40", "", { "os": "linux", "cpu": "x64" }, "sha512-vjxgSrPgqKeRbtMgOcHDjnAaVdSI0NzUF/w/01iDrmk/hbuhnHV+nJyoXUKgSYMhMWdLwt9QVKpXLC6jf6e6RA=="], + "@bastani/atomic-natives-linux-x64-musl": ["@bastani/atomic-natives-linux-x64-musl@0.9.41-alpha.1", "", { "os": "linux", "cpu": "x64" }, "sha512-clq4YywgeBsLJMWY+qNy+rhEojelHHus8vt2c1hx2VhBz8xdM3bh8+4nWBAbxrEDj2xTTDR1BS+sWJDwpfvMXw=="], - "@bastani/atomic-natives-win32-arm64-msvc": ["@bastani/atomic-natives-win32-arm64-msvc@0.9.40", "", { "os": "win32", "cpu": "arm64" }, "sha512-DusAb2wNSeccvc265Qb+hQdl5QWpg8R2oHIay/15OnpX0hRu5tiVuZaorULSznoAeF/j5e2/xrJQf9X0g2oY5Q=="], + "@bastani/atomic-natives-win32-arm64-msvc": ["@bastani/atomic-natives-win32-arm64-msvc@0.9.41-alpha.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-CTzXyqUQtmptXt6BHr33BvjJ1c0yB5sjlh1WupqMIFl5mVgsN8COZYWTG4yD3MGNgsHbVNKkFEfyFe8081KjZw=="], - "@bastani/atomic-natives-win32-x64-msvc": ["@bastani/atomic-natives-win32-x64-msvc@0.9.40", "", { "os": "win32", "cpu": "x64" }, "sha512-lEvXHXZ4E4VlBlVW8rzyC5963pUWFMnwwjsQVd2/0vSS9TmySMrom9ASe/dHEM1vzrNChphrJaI2ZC6EPZfWFA=="], + "@bastani/atomic-natives-win32-x64-msvc": ["@bastani/atomic-natives-win32-x64-msvc@0.9.41-alpha.1", "", { "os": "win32", "cpu": "x64" }, "sha512-JEG/wkkrb4MUhiOgsFGXesplTTk2rc0XpTCi/yOWwxiQsl64sfrQgB2qWlvcfjh7QMBw4HKcpaoh2oGtY8H7gw=="], - "@bastani/pi-ai": ["@bastani/pi-ai@0.9.40", "", { "dependencies": { "@anthropic-ai/sdk": "0.129.0", "@aws-sdk/client-bedrock-runtime": "3.1127.0", "@earendil-works/pi-telemetry": "1.3.0", "@google/genai": "2.21.0", "@smithy/node-http-handler": "4.12.1", "http-proxy-agent": "9.1.0", "https-proxy-agent": "9.1.0", "openai": "7.19.0", "partial-json": "0.1.7", "typebox": "1.3.27" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-ibLak2XU/EsjHDU2GJjMnTjbczxvONEDfKx9YDZ2DVPtXiVR/c24FUasxz9tEHOq5mo2A8lhVY0Ts3AVuEDLFQ=="], + "@bastani/pi-ai": ["@bastani/pi-ai@0.9.41-alpha.1", "", { "dependencies": { "@anthropic-ai/sdk": "0.129.0", "@aws-sdk/client-bedrock-runtime": "3.1127.0", "@earendil-works/pi-telemetry": "1.3.0", "@google/genai": "2.21.0", "@smithy/node-http-handler": "4.12.1", "http-proxy-agent": "9.1.0", "https-proxy-agent": "9.1.0", "openai": "7.19.0", "partial-json": "0.1.7", "typebox": "1.3.27" }, "bin": { "pi-ai": "dist/cli.js" } }, "sha512-bWL7hqgdDtBZR6od/tItQuWwETe7iZdDb7YAiGZTWPVG5R0MmiD+X7+Aa4qkpkTK3i/pVONgvj6d8azSTcFx7w=="], "@borewit/text-codec": ["@borewit/text-codec@0.2.2", "", {}, "sha512-DDaRehssg1aNrH4+2hnj1B7vnUGEjU6OIlyRdkMd0aUdIUvKXrJfXsy8LVtXAy7DRvYVluWbMspsRhz2lcW0mQ=="], diff --git a/docs/hosted-agent.md b/docs/hosted-agent.md index c8e8e5735..7bac116bd 100644 --- a/docs/hosted-agent.md +++ b/docs/hosted-agent.md @@ -638,7 +638,9 @@ is between turns starts a Planner turn about it, on the kept session, so the Planner reports what happened and what the team needs to do; Chat records the run's end as that turn's reason, and the session is let go after the turn. The adapter stops any turn Atomic starts on its own, such as for its lifecycle -notices, because Chopin would neither show nor record it. +notices or, since Atomic 0.9.41, for a message queued while the session is +idle, because Chopin would neither show nor record it. The Planner's next turn +still starts normally. Chat shows the session's runs as one stack, one row per run: its name, status (running, waiting on Decisions, paused, or ended), and elapsed time. Several runs can be live at once. Runs waiting on Decisions come @@ -664,7 +666,11 @@ document. When a run ends, Chat also records a line saying how it ended and how long it took. The session is let go when its runs finish, when its owner binding ends, or when the document closes; run state is durable in Atomic's workflow journal, so runs interrupted that way can be resumed from a later -session. Chopin keeps that journal at +session. Before disposing the session, the adapter waits, for at most 10 +seconds, on Atomic's `session.workflows.waitForSettled()` for each run that +finished, failed or was stopped, so a session let go right after a run ends +never leaves that run `running` in the journal; a run whose ending could not be +recorded is logged by its ID. Chopin keeps that journal at `$XDG_STATE_HOME/chopin/atomic/workflows.sqlite` under the same per-user state directory as the documents' own directories, because a run resumes only with the journal and working directory it started in. Each Planner session is diff --git a/docs/self-hosting.md b/docs/self-hosting.md index 88efbe696..a4527e92b 100644 --- a/docs/self-hosting.md +++ b/docs/self-hosting.md @@ -75,7 +75,7 @@ each owner's GitHub App user token and does not consume `HARNESS_AUTH`; setting `auto` on loopback passes the startup guard but does not enable host login in that adapter. -`HARNESS=atomic` runs Atomic 0.9.40 in the Chopin server process through its +`HARNESS=atomic` runs Atomic 0.9.41-alpha.1 in the Chopin server process through its headless SDK (`createAgentSession()`). It does not spawn the `atomic` CLI or use RPC mode. diff --git a/package.json b/package.json index 40425f8fb..3914df95c 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "@ai-sdk/harness": "1.0.146", "@ai-sdk/mcp": "2.0.72", "@ai-sdk/sandbox-just-bash": "1.0.146", - "@bastani/atomic": "0.9.40", + "@bastani/atomic": "0.9.41-alpha.1", "ai": "7.0.135", "typebox": "1.3.7", "zod": "4.4.3"