@bastani/atomic 0.9.19-alpha.6 → 0.9.19-alpha.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/README.md +2 -2
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +6 -0
- package/dist/builtin/web-access/index.bundle.mjs +22 -3
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +19 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/builtin/adversarial-verification.js +2 -2
- package/dist/builtin/workflows/builtin/{chunk-vhmnzdj9.js → chunk-1jth021m.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-p8jjwfg6.js → chunk-2377r57g.js} +4 -2
- package/dist/builtin/workflows/builtin/{chunk-hbnwv56f.js → chunk-31j2gs7h.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-n8zysf1t.js → chunk-ngkqkzej.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-1nbq96fh.js → chunk-nkrafh9s.js} +1 -1
- package/dist/builtin/workflows/builtin/{chunk-1kx4aggw.js → chunk-wf741bv3.js} +2 -2
- package/dist/builtin/workflows/builtin/{chunk-t6x2d78s.js → chunk-z29qbehr.js} +1 -1
- package/dist/builtin/workflows/builtin/generate-and-filter.js +2 -2
- package/dist/builtin/workflows/builtin/goal.js +3 -3
- package/dist/builtin/workflows/builtin/index.js +7 -7
- package/dist/builtin/workflows/builtin/ralph.js +3 -3
- package/dist/builtin/workflows/builtin/tournament.js +2 -2
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +590 -256
- package/dist/builtin/workflows/src/index.js +67 -19
- package/dist/builtin/workflows/src/shared/authoring-contract-stage.d.ts +2 -0
- package/dist/modes/interactive/components/task-live-transcript.d.ts +1 -1
- package/dist/modes/interactive/components/task-live-transcript.d.ts.map +1 -1
- package/dist/modes/interactive/components/task-live-transcript.js +19 -11
- package/dist/modes/interactive/components/task-live-transcript.js.map +1 -1
- package/docs/background-tasks.md +1 -1
- package/docs/build.md +51 -0
- package/docs/changelog.mdx +8 -8
- package/docs/compaction/reference.md +267 -0
- package/docs/compaction.md +36 -219
- package/docs/computer-use.md +35 -0
- package/docs/containerization.md +2 -0
- package/docs/custom-provider/api-reference.md +145 -0
- package/docs/custom-provider/oauth.md +131 -0
- package/docs/custom-provider/override.md +34 -0
- package/docs/custom-provider/registration.md +163 -0
- package/docs/custom-provider/streaming.md +214 -0
- package/docs/custom-provider.md +43 -607
- package/docs/development.md +76 -0
- package/docs/docs.json +199 -77
- package/docs/environment-variables.md +1 -1
- package/docs/extensions/api-reference.md +1001 -0
- package/docs/extensions/authoring.md +676 -0
- package/docs/extensions/events.md +793 -0
- package/docs/extensions/examples.md +94 -0
- package/docs/extensions/ui.md +470 -0
- package/docs/extensions.md +270 -2768
- package/docs/getting-started/authentication.md +55 -0
- package/docs/getting-started/first-session.md +172 -0
- package/docs/getting-started/installation.md +154 -0
- package/docs/getting-started/project-instructions.md +43 -0
- package/docs/guides/configuration.md +65 -0
- package/docs/guides/intercom.md +24 -0
- package/docs/guides/non-interactive.md +33 -0
- package/docs/guides/subagents.md +24 -0
- package/docs/guides/workflows.md +27 -0
- package/docs/guides.md +41 -0
- package/docs/index.md +13 -45
- package/docs/intercom/operations.md +178 -0
- package/docs/intercom/reference.md +146 -0
- package/docs/intercom.md +54 -259
- package/docs/json.md +17 -0
- package/docs/llama-cpp.md +5 -0
- package/docs/models/artificial-analysis-index.md +89 -0
- package/docs/models/evals.md +2 -2
- package/docs/models/model-selection.md +3 -3
- package/docs/models/pareto-efficiency.md +2 -2
- package/docs/models/reference.md +601 -0
- package/docs/models.md +45 -550
- package/docs/packages/authoring.md +88 -0
- package/docs/packages/reference.md +43 -0
- package/docs/packages.md +28 -107
- package/docs/programmatic.md +29 -0
- package/docs/prompt-templates.md +11 -0
- package/docs/providers/reference.md +23 -0
- package/docs/providers.md +10 -15
- package/docs/quickstart.md +44 -238
- package/docs/reference/cli.md +252 -0
- package/docs/reference.md +52 -0
- package/docs/rpc/examples.md +64 -0
- package/docs/rpc/extension-ui.md +198 -0
- package/docs/rpc/protocol.md +1341 -0
- package/docs/rpc.md +109 -1346
- package/docs/sdk/reference.md +1090 -0
- package/docs/sdk.md +56 -1002
- package/docs/session-format.md +2 -0
- package/docs/sessions.md +6 -10
- package/docs/settings.md +6 -45
- package/docs/skills/authoring.md +95 -0
- package/docs/skills/reference.md +60 -0
- package/docs/skills.md +20 -122
- package/docs/subagents/authoring.md +37 -0
- package/docs/subagents/reference.md +60 -0
- package/docs/subagents.md +39 -95
- package/docs/terminal-setup.md +1 -1
- package/docs/themes/reference.md +176 -0
- package/docs/themes.md +21 -135
- package/docs/tmux.md +5 -0
- package/docs/tools/edit.md +5 -0
- package/docs/tools.md +5 -0
- package/docs/tui/reference.md +343 -0
- package/docs/tui.md +23 -286
- package/docs/usage.md +26 -209
- package/docs/web-access.md +2 -0
- package/docs/windows.md +2 -1
- package/docs/workflows/api-reference.md +11 -4
- package/docs/workflows/authoring.md +4 -2
- package/docs/workflows/builtins.md +2 -2
- package/docs/workflows/operations.md +15 -10
- package/docs/workflows/reliable-design.md +21 -3
- package/docs/workflows/verification.md +8 -0
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
|
@@ -3156,6 +3156,7 @@ class StageUiBroker {
|
|
|
3156
3156
|
};
|
|
3157
3157
|
}
|
|
3158
3158
|
requestCustomUi(runId, stageId, factory, options, signal) {
|
|
3159
|
+
signal = options?.signal ? signal ? AbortSignal.any([signal, options.signal]) : options.signal : signal;
|
|
3159
3160
|
if (signal?.aborted) {
|
|
3160
3161
|
return Promise.reject(signal.reason ?? new Error("atomic-workflows: stage UI request aborted"));
|
|
3161
3162
|
}
|
|
@@ -13429,21 +13430,17 @@ function throwFinalizationError(error) {
|
|
|
13429
13430
|
function createTrackedStageCaller(input) {
|
|
13430
13431
|
const { runtime } = input;
|
|
13431
13432
|
const readinessGateEnabled = runtime.opts.confirmStageReadiness !== undefined || runtime.opts.usePromptNodesForUi === true;
|
|
13432
|
-
const confirmReadiness = async () => {
|
|
13433
|
-
|
|
13434
|
-
|
|
13435
|
-
|
|
13436
|
-
|
|
13437
|
-
|
|
13438
|
-
|
|
13439
|
-
|
|
13440
|
-
|
|
13441
|
-
|
|
13442
|
-
|
|
13443
|
-
return await askReadinessViaStageBroker(runtime.runId, runtime.stageId, runtime.signal);
|
|
13444
|
-
} catch {
|
|
13445
|
-
return { action: "advance" };
|
|
13446
|
-
}
|
|
13433
|
+
const confirmReadiness = async (signal) => {
|
|
13434
|
+
if (runtime.opts.confirmStageReadiness !== undefined) {
|
|
13435
|
+
const ready = await raceAbort(runtime.opts.confirmStageReadiness({
|
|
13436
|
+
runId: runtime.runId,
|
|
13437
|
+
stageId: runtime.stageId,
|
|
13438
|
+
stageName: runtime.name,
|
|
13439
|
+
signal
|
|
13440
|
+
}), signal);
|
|
13441
|
+
return ready ? { action: "advance" } : { action: "stay" };
|
|
13442
|
+
}
|
|
13443
|
+
return await askReadinessViaStageBroker(runtime.runId, runtime.stageId, signal);
|
|
13447
13444
|
};
|
|
13448
13445
|
const suppressReadinessForCurrentTurn = () => {
|
|
13449
13446
|
runtime.state.askUserQuestionObservedThisTurn = false;
|
|
@@ -13597,9 +13594,35 @@ function createTrackedStageCaller(input) {
|
|
|
13597
13594
|
});
|
|
13598
13595
|
try {
|
|
13599
13596
|
while (runtime.state.askUserQuestionObservedThisTurn || repeatReadinessAfterChatTurn) {
|
|
13600
|
-
|
|
13597
|
+
runtime.signal.throwIfAborted();
|
|
13598
|
+
if (runtime.stageSnapshot.status === "paused" || runtime.stageSnapshot.status === "blocked") {
|
|
13599
|
+
await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
|
|
13600
|
+
continue;
|
|
13601
|
+
}
|
|
13602
|
+
const controller = new AbortController;
|
|
13603
|
+
runtime.state.readinessController = controller;
|
|
13604
|
+
let decision;
|
|
13605
|
+
try {
|
|
13606
|
+
decision = await confirmReadiness(AbortSignal.any([runtime.signal, controller.signal]));
|
|
13607
|
+
if (decision.action === "advance" || decision.message !== undefined) {
|
|
13608
|
+
await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
|
|
13609
|
+
}
|
|
13610
|
+
runtime.signal.throwIfAborted();
|
|
13611
|
+
if (controller.signal.aborted) {
|
|
13612
|
+
await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
|
|
13613
|
+
continue;
|
|
13614
|
+
}
|
|
13615
|
+
} catch (error) {
|
|
13616
|
+
runtime.signal.throwIfAborted();
|
|
13617
|
+
if (controller.signal.aborted) {
|
|
13618
|
+
await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
|
|
13619
|
+
continue;
|
|
13620
|
+
}
|
|
13621
|
+
throw error;
|
|
13622
|
+
}
|
|
13601
13623
|
if (decision.action === "advance")
|
|
13602
13624
|
break;
|
|
13625
|
+
delete runtime.state.readinessController;
|
|
13603
13626
|
if (runtime.signal.aborted)
|
|
13604
13627
|
break;
|
|
13605
13628
|
runtime.state.askUserQuestionObservedThisTurn = false;
|
|
@@ -13742,9 +13765,17 @@ function createTrackedStageCaller(input) {
|
|
|
13742
13765
|
const failure = runtime.classifyExecutorFailure(durableCheckpointError.error);
|
|
13743
13766
|
applyTerminalStageState = () => applyFailureToStage(runtime.stageSnapshot, failure);
|
|
13744
13767
|
}
|
|
13745
|
-
if (!runtime.state.stageFinalized)
|
|
13746
|
-
applyTerminalStageState?.();
|
|
13747
13768
|
try {
|
|
13769
|
+
if (terminalStateIsSuccess && runtime.state.readinessController !== undefined) {
|
|
13770
|
+
let pauseVersion;
|
|
13771
|
+
do {
|
|
13772
|
+
pauseVersion = runtime.state.readinessPauseVersion;
|
|
13773
|
+
await runtime.scheduler.waitForStageRelease(runtime.stageId, runtime.releaseLiveHandle);
|
|
13774
|
+
} while (pauseVersion !== runtime.state.readinessPauseVersion);
|
|
13775
|
+
runtime.signal.throwIfAborted();
|
|
13776
|
+
}
|
|
13777
|
+
if (!runtime.state.stageFinalized)
|
|
13778
|
+
applyTerminalStageState?.();
|
|
13748
13779
|
await runtime.finalizeStageSnapshot();
|
|
13749
13780
|
} catch (err) {
|
|
13750
13781
|
finalizationError ??= { thrown: true, error: err };
|
|
@@ -13762,6 +13793,9 @@ function createTrackedStageCaller(input) {
|
|
|
13762
13793
|
input.limiter.release();
|
|
13763
13794
|
if (finalizationError !== undefined)
|
|
13764
13795
|
throwFinalizationError(finalizationError.error);
|
|
13796
|
+
delete runtime.state.readinessController;
|
|
13797
|
+
if (terminalStateIsSuccess && readinessGateEnabled)
|
|
13798
|
+
runtime.signal.throwIfAborted();
|
|
13765
13799
|
}
|
|
13766
13800
|
};
|
|
13767
13801
|
}
|
|
@@ -14122,12 +14156,26 @@ function createStageControlHandle(runtime) {
|
|
|
14122
14156
|
async pause() {
|
|
14123
14157
|
runtime.throwIfStageMutationBlocked();
|
|
14124
14158
|
const statusBeforePause = runtime.stageSnapshot.status;
|
|
14159
|
+
let readinessPauseVersion;
|
|
14160
|
+
if (runtime.state.readinessController !== undefined) {
|
|
14161
|
+
runtime.scheduler.ensureReleaseBarrier(runtime.stageId);
|
|
14162
|
+
readinessPauseVersion = (runtime.state.readinessPauseVersion ?? 0) + 1;
|
|
14163
|
+
runtime.state.readinessPauseVersion = readinessPauseVersion;
|
|
14164
|
+
}
|
|
14125
14165
|
if (statusBeforePause === "pending" || statusBeforePause === "running" || runtime.innerCtx.isStreaming) {
|
|
14126
|
-
|
|
14166
|
+
try {
|
|
14167
|
+
await runtime.innerCtx.__requestPause();
|
|
14168
|
+
} catch (error) {
|
|
14169
|
+
if (readinessPauseVersion !== undefined && runtime.state.readinessPauseVersion === readinessPauseVersion && runtime.stageSnapshot.status !== "paused" && runtime.stageSnapshot.status !== "blocked" && !runtime.scheduler.hasCascadePauseOwners(runtime.stageId)) {
|
|
14170
|
+
runtime.scheduler.releaseStageBarrier(runtime.stageId);
|
|
14171
|
+
}
|
|
14172
|
+
throw error;
|
|
14173
|
+
}
|
|
14127
14174
|
}
|
|
14128
14175
|
const changed = runtime.activeStore.recordStagePaused(runtime.runId, runtime.stageId);
|
|
14129
14176
|
if (changed) {
|
|
14130
14177
|
runtime.scheduler.ensureReleaseBarrier(runtime.stageId);
|
|
14178
|
+
runtime.state.readinessController?.abort(new Error("atomic-workflows: readiness paused"));
|
|
14131
14179
|
await runtime.scheduler.cascadePauseFrom(runtime.stageId);
|
|
14132
14180
|
const run = runtime.activeStore.runs().find((candidate) => candidate.id === runtime.runId);
|
|
14133
14181
|
const stillActive = run?.stages.some((stage) => stage.status === "running" && stage.id !== runtime.stageId) ?? false;
|
|
@@ -358,6 +358,8 @@ export interface WorkflowChainOptions extends WorkflowSharedTaskDefaults {
|
|
|
358
358
|
export interface WorkflowParallelOptions extends WorkflowSharedTaskDefaults {
|
|
359
359
|
readonly concurrency?: number;
|
|
360
360
|
readonly failFast?: boolean;
|
|
361
|
+
/** Static discovery only: literal conservative stage names/globs for an otherwise opaque step array. */
|
|
362
|
+
readonly possibleStageNames?: readonly string[];
|
|
361
363
|
}
|
|
362
364
|
export type WorkflowTaskSessionOptions = StageOptions & WorkflowTaskSessionFields;
|
|
363
365
|
export interface WorkflowRunChildOptions<TInputs extends WorkflowInputValues = WorkflowInputValues> {
|
|
@@ -6,7 +6,7 @@ export declare class TaskLiveTranscript implements Component {
|
|
|
6
6
|
private readonly entries;
|
|
7
7
|
private readonly live;
|
|
8
8
|
private readonly unsubscribe?;
|
|
9
|
-
private components
|
|
9
|
+
private components;
|
|
10
10
|
readonly source: TaskTranscriptSource;
|
|
11
11
|
private readonly requestRender;
|
|
12
12
|
constructor(source: TaskTranscriptSource, messages: AgentMessage[], requestRender: () => void);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-live-transcript.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-live-transcript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAQ9E,oFAAoF;AACpF,qBAAa,kBAAmB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAa;
|
|
1
|
+
{"version":3,"file":"task-live-transcript.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-live-transcript.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAClE,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mCAAmC,CAAC;AAQ9E,oFAAoF;AACpF,qBAAa,kBAAmB,YAAW,SAAS;IACnD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAA4B;IACjD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAa;IAG1C,OAAO,CAAC,UAAU,CAA8C;IAChE,QAAQ,CAAC,MAAM,EAAE,oBAAoB,CAAC;IACtC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAa;IAC3C,YAAY,MAAM,EAAE,oBAAoB,EAAE,QAAQ,EAAE,YAAY,EAAE,EAAE,aAAa,EAAE,MAAM,IAAI,EAU5F;IACD,UAAU,IAAI,IAAI,CAEjB;IACD,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAe9B;IACD,OAAO,IAAI,IAAI,CAEd;CACD"}
|
|
@@ -2,29 +2,37 @@ import { chatEntriesFromAgentMessages, LiveChatEntriesController, renderChatMess
|
|
|
2
2
|
/** A viewer subscription, not a second task runner or a task-activity publisher. */
|
|
3
3
|
export class TaskLiveTranscript {
|
|
4
4
|
constructor(source, messages, requestRender) {
|
|
5
|
+
// LiveChatEntriesController replaces changed entries, including in-place message deltas.
|
|
6
|
+
// Weak keys release superseded generations without retaining a cache of the stream.
|
|
7
|
+
this.components = new WeakMap();
|
|
5
8
|
this.source = source;
|
|
6
9
|
this.requestRender = requestRender;
|
|
7
10
|
this.entries = chatEntriesFromAgentMessages(messages);
|
|
8
11
|
this.live = new LiveChatEntriesController(this.entries);
|
|
9
12
|
this.live.hydrateStreamingAssistantMessage(source.getStreamingMessage?.());
|
|
10
13
|
this.unsubscribe = source.subscribe?.((event) => {
|
|
11
|
-
|
|
12
|
-
this.components = undefined;
|
|
14
|
+
this.live.applyEvent(event);
|
|
13
15
|
this.requestRender();
|
|
14
16
|
});
|
|
15
17
|
}
|
|
16
18
|
invalidate() {
|
|
17
|
-
this.components =
|
|
19
|
+
this.components = new WeakMap();
|
|
18
20
|
}
|
|
19
21
|
render(width) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
22
|
+
return this.entries.flatMap((entry) => {
|
|
23
|
+
let component = this.components.get(entry);
|
|
24
|
+
if (!component) {
|
|
25
|
+
component = renderChatMessageEntry(entry, {
|
|
26
|
+
ui: { requestRender: this.requestRender },
|
|
27
|
+
cwd: process.cwd(),
|
|
28
|
+
hideThinkingBlock: true,
|
|
29
|
+
toolOutputExpanded: true,
|
|
30
|
+
showImages: false,
|
|
31
|
+
});
|
|
32
|
+
this.components.set(entry, component);
|
|
33
|
+
}
|
|
34
|
+
return component.render(width);
|
|
35
|
+
});
|
|
28
36
|
}
|
|
29
37
|
dispose() {
|
|
30
38
|
this.unsubscribe?.();
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"task-live-transcript.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-live-transcript.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,oFAAoF;AACpF,MAAM,OAAO,kBAAkB;
|
|
1
|
+
{"version":3,"file":"task-live-transcript.js","sourceRoot":"","sources":["../../../../src/modes/interactive/components/task-live-transcript.ts"],"names":[],"mappings":"AAGA,OAAO,EAEN,4BAA4B,EAC5B,yBAAyB,EACzB,sBAAsB,GACtB,MAAM,4BAA4B,CAAC;AAEpC,oFAAoF;AACpF,MAAM,OAAO,kBAAkB;IAS9B,YAAY,MAA4B,EAAE,QAAwB,EAAE,aAAyB;QAL7F,yFAAyF;QACzF,oFAAoF;QAC5E,eAAU,GAAG,IAAI,OAAO,EAA+B,CAAC;QAI/D,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,4BAA4B,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,CAAC,IAAI,GAAG,IAAI,yBAAyB,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,CAAC,gCAAgC,CAAC,MAAM,CAAC,mBAAmB,EAAE,EAAE,CAAC,CAAC;QAC3E,IAAI,CAAC,WAAW,GAAG,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,KAAK,EAAE,EAAE;YAC/C,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC5B,IAAI,CAAC,aAAa,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;IACJ,CAAC;IACD,UAAU;QACT,IAAI,CAAC,UAAU,GAAG,IAAI,OAAO,EAAE,CAAC;IACjC,CAAC;IACD,MAAM,CAAC,KAAa;QACnB,OAAO,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,EAAE;YACrC,IAAI,SAAS,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAC3C,IAAI,CAAC,SAAS,EAAE,CAAC;gBAChB,SAAS,GAAG,sBAAsB,CAAC,KAAK,EAAE;oBACzC,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE;oBACzC,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE;oBAClB,iBAAiB,EAAE,IAAI;oBACvB,kBAAkB,EAAE,IAAI;oBACxB,UAAU,EAAE,KAAK;iBACjB,CAAC,CAAC;gBACH,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,SAAS,CAAC,CAAC;YACvC,CAAC;YACD,OAAO,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChC,CAAC,CAAC,CAAC;IACJ,CAAC;IACD,OAAO;QACN,IAAI,CAAC,WAAW,EAAE,EAAE,CAAC;IACtB,CAAC;CACD","sourcesContent":["import type { AgentMessage } from \"@earendil-works/pi-agent-core\";\nimport type { Component } from \"@earendil-works/pi-tui\";\nimport type { TaskTranscriptSource } from \"../../../core/tasks/supervisor.js\";\nimport {\n\ttype ChatMessageEntry,\n\tchatEntriesFromAgentMessages,\n\tLiveChatEntriesController,\n\trenderChatMessageEntry,\n} from \"./chat-message-renderer.ts\";\n\n/** A viewer subscription, not a second task runner or a task-activity publisher. */\nexport class TaskLiveTranscript implements Component {\n\tprivate readonly entries: ChatMessageEntry[];\n\tprivate readonly live: LiveChatEntriesController;\n\tprivate readonly unsubscribe?: () => void;\n\t// LiveChatEntriesController replaces changed entries, including in-place message deltas.\n\t// Weak keys release superseded generations without retaining a cache of the stream.\n\tprivate components = new WeakMap<ChatMessageEntry, Component>();\n\treadonly source: TaskTranscriptSource;\n\tprivate readonly requestRender: () => void;\n\tconstructor(source: TaskTranscriptSource, messages: AgentMessage[], requestRender: () => void) {\n\t\tthis.source = source;\n\t\tthis.requestRender = requestRender;\n\t\tthis.entries = chatEntriesFromAgentMessages(messages);\n\t\tthis.live = new LiveChatEntriesController(this.entries);\n\t\tthis.live.hydrateStreamingAssistantMessage(source.getStreamingMessage?.());\n\t\tthis.unsubscribe = source.subscribe?.((event) => {\n\t\t\tthis.live.applyEvent(event);\n\t\t\tthis.requestRender();\n\t\t});\n\t}\n\tinvalidate(): void {\n\t\tthis.components = new WeakMap();\n\t}\n\trender(width: number): string[] {\n\t\treturn this.entries.flatMap((entry) => {\n\t\t\tlet component = this.components.get(entry);\n\t\t\tif (!component) {\n\t\t\t\tcomponent = renderChatMessageEntry(entry, {\n\t\t\t\t\tui: { requestRender: this.requestRender },\n\t\t\t\t\tcwd: process.cwd(),\n\t\t\t\t\thideThinkingBlock: true,\n\t\t\t\t\ttoolOutputExpanded: true,\n\t\t\t\t\tshowImages: false,\n\t\t\t\t});\n\t\t\t\tthis.components.set(entry, component);\n\t\t\t}\n\t\t\treturn component.render(width);\n\t\t});\n\t}\n\tdispose(): void {\n\t\tthis.unsubscribe?.();\n\t}\n}\n"]}
|
package/docs/background-tasks.md
CHANGED
package/docs/build.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Build with Atomic
|
|
3
|
+
description: Extend Atomic with skills, subagents, intercom, workflows, extensions, prompt templates, themes, packages, custom models and providers, and programmatic integrations.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Build with Atomic
|
|
7
|
+
|
|
8
|
+
Build covers everything you add to Atomic: customization mechanisms that change how a session behaves, and programmatic interfaces that embed Atomic in your own software.
|
|
9
|
+
|
|
10
|
+
If you have not run Atomic yet, start at the [Quickstart](/quickstart). If you are looking for an exact contract rather than a way to build something, go to the [Reference index](/reference).
|
|
11
|
+
|
|
12
|
+
## Choose the lightest mechanism
|
|
13
|
+
|
|
14
|
+
Work down this list and stop at the first mechanism that solves your problem.
|
|
15
|
+
|
|
16
|
+
1. [Prompt templates](/prompt-templates) — reusable prompts that expand from a slash command. No code.
|
|
17
|
+
2. [Skills](/skills) — on-demand instructions the agent loads when a task matches. Markdown plus optional scripts.
|
|
18
|
+
3. [Subagents](/subagents) — delegate a focused, bounded task to a child agent.
|
|
19
|
+
4. [Intercom](/intercom) — coordinate several sessions on one machine.
|
|
20
|
+
5. [Workflows](/workflows) — multi-stage, resumable engineering loops with gates and artifacts.
|
|
21
|
+
6. [Extensions](/extensions) — TypeScript that adds tools, commands, events, and custom UI when nothing lighter fits.
|
|
22
|
+
7. [Atomic packages](/packages) — bundle and distribute the result.
|
|
23
|
+
|
|
24
|
+
## Change the model layer
|
|
25
|
+
|
|
26
|
+
- [Custom models](/models) — add model entries for a supported provider API.
|
|
27
|
+
- [Custom providers](/custom-provider) — implement a provider API or OAuth flow Atomic does not ship.
|
|
28
|
+
- [Themes](/themes) — restyle the terminal interface.
|
|
29
|
+
|
|
30
|
+
## Embed Atomic in your own software
|
|
31
|
+
|
|
32
|
+
[Programmatic use](/programmatic) compares JSON mode, RPC, and the SDK, and links each one to its protocol or API reference.
|
|
33
|
+
|
|
34
|
+
## Customization
|
|
35
|
+
|
|
36
|
+
- [Extensions](/extensions) - TypeScript modules for tools, commands, events, and custom UI.
|
|
37
|
+
- [Skills](/skills) - Agent Skills for reusable on-demand capabilities.
|
|
38
|
+
- [Subagents](/subagents) - focused child agents for research, analysis, debugging, cleanup, and review compositions.
|
|
39
|
+
- [Workflows](/workflows) - executable engineering loops with tracked stages, artifacts, gates, and resumable runs.
|
|
40
|
+
- [Prompt templates](/prompt-templates) - reusable prompts that expand from slash commands.
|
|
41
|
+
- [Themes](/themes) - built-in and custom terminal themes.
|
|
42
|
+
- [Atomic packages](/packages) - bundle and share extensions, skills, prompts, and themes.
|
|
43
|
+
- [Custom models](/models) - add model entries for supported provider APIs.
|
|
44
|
+
- [Custom providers](/custom-provider) - implement custom APIs and OAuth flows.
|
|
45
|
+
|
|
46
|
+
## Programmatic usage
|
|
47
|
+
|
|
48
|
+
- [SDK](/sdk) - embed Atomic in Node.js applications.
|
|
49
|
+
- [RPC mode](/rpc) - integrate over stdin/stdout JSONL.
|
|
50
|
+
- [JSON event stream mode](/json) - print mode with structured events.
|
|
51
|
+
- [TUI components](/tui) - build custom terminal UI for extensions.
|
package/docs/changelog.mdx
CHANGED
|
@@ -7,9 +7,9 @@ description: "What's new in Atomic"
|
|
|
7
7
|
|
|
8
8
|
## Pi 0.83.0 parity
|
|
9
9
|
|
|
10
|
-
- **One door prints a stored credential.** `atomic auth print-api-key --model <model>` and `atomic auth print-bearer-token --model <model>` hand a configured credential to an external client. The credential is all that reaches stdout — warnings, provider selection, refresh notices, and help go to stderr — `--model` is required, and each failure has its own exit code, with stdout empty on every non-zero exit. `--min-expiry` accepts `ms`, `s`, `m`, or `h`, defaults to `30m`, and is a usage error on `print-api-key`, which has no expiry to wait for. See [Usage](/
|
|
11
|
-
- **Extensions can read the session's model scope.** `ctx.scopedModels` reports the models scoped to the current session — the set `/scoped-models` shows — resolved at access time and correct under the isolated engine. It reports the scope and cannot widen it: the accessor is read-only, and every read hands back a frozen copy of the array, its entries, and their models, so neither pushing an entry nor swapping one in place reaches the session. `ScopedModel` and `ExtensionScopedModels` are exported so an extension can name what it read. See [Extensions](/extensions#
|
|
12
|
-
- **An unrecognised stop reason is now an error.** A provider that ends a turn with a terminal reason the mapping does not know surfaces a provider error naming the raw value instead of an ordinary successful stop, so a new truncation or safety signal is visible rather than silent. Streaming partials carry the new `pending` reason until the terminal event replaces it; a stream that ends still `pending` is an error. See [Providers](/providers#stop-reasons) and [Custom Providers](/custom-provider#stop-reasons).
|
|
10
|
+
- **One door prints a stored credential.** `atomic auth print-api-key --model <model>` and `atomic auth print-bearer-token --model <model>` hand a configured credential to an external client. The credential is all that reaches stdout — warnings, provider selection, refresh notices, and help go to stderr — `--model` is required, and each failure has its own exit code, with stdout empty on every non-zero exit. `--min-expiry` accepts `ms`, `s`, `m`, or `h`, defaults to `30m`, and is a usage error on `print-api-key`, which has no expiry to wait for. See [Usage](/reference/cli#credential-commands) and [Security](/security#credential-export).
|
|
11
|
+
- **Extensions can read the session's model scope.** `ctx.scopedModels` reports the models scoped to the current session — the set `/scoped-models` shows — resolved at access time and correct under the isolated engine. It reports the scope and cannot widen it: the accessor is read-only, and every read hands back a frozen copy of the array, its entries, and their models, so neither pushing an entry nor swapping one in place reaches the session. `ScopedModel` and `ExtensionScopedModels` are exported so an extension can name what it read. See [Extensions](/extensions/api-reference#ctx-modelregistry-/-ctx-model-/-ctx-scopedmodels).
|
|
12
|
+
- **An unrecognised stop reason is now an error.** A provider that ends a turn with a terminal reason the mapping does not know surfaces a provider error naming the raw value instead of an ordinary successful stop, so a new truncation or safety signal is visible rather than silent. Streaming partials carry the new `pending` reason until the terminal event replaces it; a stream that ends still `pending` is an error. See [Providers](/providers/reference#stop-reasons) and [Custom Providers](/custom-provider/streaming#stop-reasons).
|
|
13
13
|
- **OAuth tokens refresh before they expire.** A stored token with under five minutes of validity left is refreshed rather than used, so a long turn does not start on a credential that dies mid-request. The refresh runs under the `auth.json` lock and re-checks expiry inside it, so concurrent sessions sharing one credential file refresh it once between them. See [Providers](/providers#token-refresh).
|
|
14
14
|
- **Remote OpenRouter logins have a way through.** When the browser cannot reach the loopback callback — over SSH, or on a headless host — the OpenRouter login accepts the final redirect URL, or the authorization code alone, pasted into the prompt. See [Providers](/providers#subscriptions).
|
|
15
15
|
|
|
@@ -18,10 +18,10 @@ description: "What's new in Atomic"
|
|
|
18
18
|
|
|
19
19
|
## Pi 0.82.1 parity
|
|
20
20
|
|
|
21
|
-
- **Constrained tool sampling is public end to end.** Extensions and SDK tools can request strict JSON Schema (`prefer` or `require`) or OpenAI Lark/regex grammars. Atomic preserves exact optional-property identity during public inspection, and typed RPC model listings expose all four enforcement flags through optional `ModelInfo.compat`. Unsupported models never claim required enforcement. See [Extensions](/extensions#constrained-sampling), [Models](/models#constrained-tool-sampling), and [RPC](/rpc#get_available_models).
|
|
22
|
-
- **Authentication and flagship catalogs expanded.** `/login openrouter` uses provider-owned browser PKCE with optional key minting, `/login kimi-coding` uses device authorization with automatic refresh, and isolated engine-only extension OAuth now supports safe descriptor discovery, correlated UI callbacks/cancellation, transactional engine persistence/refresh/rollback/logout, and no credential or executable-function transport. Intentional Escape/Ctrl+C/native-abort cancellation is quiet; genuine provider, network, token, storage, and refresh failures remain visible. Claude Opus 5 is available through Anthropic and Bedrock, while bearer-only `ANTHROPIC_AUTH_TOKEN` remains selectable across normal and isolated/RPC catalogs without emitting an API key or `x-api-key`. See [Providers](/providers) and [Custom Providers](/custom-provider#oauth-support).
|
|
23
|
-
- **Catalogs and model configuration stay resilient.** Remote catalogs use ETag revalidation and accept empty `304` responses, newer bundled data beats stale overlays, and a validated persisted llama.cpp catalog remains selectable when the first online refresh after restart fails while Atomic still reports the original error; a later success replaces it. Opening `/model` reloads layered legacy `.pi` then primary `.atomic` configuration. See [Models](/models#catalog-freshness-and-precedence) and [llama.cpp](/llama-cpp).
|
|
24
|
-
- **Bash integrations are session-aware and stream precisely.** Bash commands receive Atomic and exact Pi session/model environment aliases; unsaved sessions omit the file path. Direct RPC bash streams request-correlated stdout/stderr deltas, targeted/legacy cancellation, one terminal response, and exactly-once starting-session persistence across new/switch/import/fork/clone replacement; RPC shutdown drains active owners. See [Environment Variables](/environment-variables#bash-and-powershell-session-environment) and [RPC](/rpc#bash).
|
|
21
|
+
- **Constrained tool sampling is public end to end.** Extensions and SDK tools can request strict JSON Schema (`prefer` or `require`) or OpenAI Lark/regex grammars. Atomic preserves exact optional-property identity during public inspection, and typed RPC model listings expose all four enforcement flags through optional `ModelInfo.compat`. Unsupported models never claim required enforcement. See [Extensions](/extensions/authoring#constrained-sampling), [Models](/models/reference#constrained-tool-sampling), and [RPC](/rpc/protocol#get_available_models).
|
|
22
|
+
- **Authentication and flagship catalogs expanded.** `/login openrouter` uses provider-owned browser PKCE with optional key minting, `/login kimi-coding` uses device authorization with automatic refresh, and isolated engine-only extension OAuth now supports safe descriptor discovery, correlated UI callbacks/cancellation, transactional engine persistence/refresh/rollback/logout, and no credential or executable-function transport. Intentional Escape/Ctrl+C/native-abort cancellation is quiet; genuine provider, network, token, storage, and refresh failures remain visible. Claude Opus 5 is available through Anthropic and Bedrock, while bearer-only `ANTHROPIC_AUTH_TOKEN` remains selectable across normal and isolated/RPC catalogs without emitting an API key or `x-api-key`. See [Providers](/providers) and [Custom Providers](/custom-provider/oauth#oauth-support).
|
|
23
|
+
- **Catalogs and model configuration stay resilient.** Remote catalogs use ETag revalidation and accept empty `304` responses, newer bundled data beats stale overlays, and a validated persisted llama.cpp catalog remains selectable when the first online refresh after restart fails while Atomic still reports the original error; a later success replaces it. Opening `/model` reloads layered legacy `.pi` then primary `.atomic` configuration. See [Models](/models/reference#catalog-freshness-and-precedence) and [llama.cpp](/llama-cpp).
|
|
24
|
+
- **Bash integrations are session-aware and stream precisely.** Bash commands receive Atomic and exact Pi session/model environment aliases; unsaved sessions omit the file path. Direct RPC bash streams request-correlated stdout/stderr deltas, targeted/legacy cancellation, one terminal response, and exactly-once starting-session persistence across new/switch/import/fork/clone replacement; RPC shutdown drains active owners. See [Environment Variables](/environment-variables#bash-and-powershell-session-environment) and [RPC](/rpc/protocol#bash).
|
|
25
25
|
- **Coding-agent parity fixes landed.** Custom message renderers receive `outputPad` in normal and isolated rendering; summaries use fresh routing IDs without prompt-cache writes; complete literal IDs win before thinking-suffix/glob parsing; unavailable scoped entries remain removable; shared async Ctrl+G editing uses a private exact `prompt.md`, configured/environment/platform precedence, and recursive cleanup across main and extension editors; `/copy` falls back after `wl-copy` failure; explicit update checks bypass startup-skip settings; configured agent directories own TUI logs; and context discovery ignores directories named like context files.
|
|
26
26
|
|
|
27
27
|
</Update>
|
|
@@ -42,7 +42,7 @@ See [SDK](/sdk) and [Custom providers](/custom-provider).
|
|
|
42
42
|
|
|
43
43
|
- **Synced through upstream Pi 0.80.6.** Atomic and its bundled extensions now use the 0.80.6 Pi runtime packages, including signed empty Anthropic thinking preservation, request-wide GPT-5.4/5.5 long-context pricing, and corrected GPT-5.6 catalog/backend metadata.
|
|
44
44
|
- **Model controls are current.** Atomic accepts `max` wherever the active model advertises it, applies the active agent directory's single `models.json` overrides to matching extension-registered models and built-ins, and falls back instead of synthesizing removed catalog IDs on resume. See [Models](/models).
|
|
45
|
-
- **Custom pricing tiers are preserved.** Custom `models.json` entries and extension providers can declare complete request-wide `cost.tiers`; matching `modelOverrides` can update scalar rates without losing inherited tiers or replace/clear the tier array explicitly. See [Models](/models#request-wide-cost-tiers) and [Custom Providers](/custom-provider#usage-and-cost).
|
|
45
|
+
- **Custom pricing tiers are preserved.** Custom `models.json` entries and extension providers can declare complete request-wide `cost.tiers`; matching `modelOverrides` can update scalar rates without losing inherited tiers or replace/clear the tier array explicitly. See [Models](/models/reference#request-wide-cost-tiers) and [Custom Providers](/custom-provider/streaming#usage-and-cost).
|
|
46
46
|
- **Safer session and tool behavior.** Invalid explicit bash timeouts fail instead of being clamped; missing exact session IDs warn before creation; lax null or omitted message content is normalized; auth writes surface persistence failures; Windows context traversal terminates at drive roots; and duplicate fork selections are ignored. See [Tools](/tools) and [Usage](/usage).
|
|
47
47
|
- **More reliable streaming and binaries.** Visible custom messages stay before the live assistant row, standalone Linux clipboard reads can fall back to xclip with correctly packaged native bindings, caller-relative `TMPDIR` paths work from external directories, and `--skip-deps` tolerates a missing optional clipboard wrapper.
|
|
48
48
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Compaction reference
|
|
3
|
+
sidebarTitle: "Compaction internals"
|
|
4
|
+
description: Compaction parameters, persistence, extension hooks, formats, settings, and historical formats.
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Compaction reference
|
|
8
|
+
|
|
9
|
+
## Parameters
|
|
10
|
+
|
|
11
|
+
The effective parameters appear in extension events and successful results:
|
|
12
|
+
|
|
13
|
+
| Parameter | Default | Meaning |
|
|
14
|
+
|---|---:|---|
|
|
15
|
+
| `compression_ratio` | `0.5` | Fraction of compactable **lines to keep**, not a token ratio |
|
|
16
|
+
| `preserve_recent` | `2` | Exact number of newest context-visible messages protected client-side |
|
|
17
|
+
| `query` | Last visible user message | Relevance focus for deciding which older lines to retain |
|
|
18
|
+
|
|
19
|
+
`preserve_recent` counts context-visible messages without aligning the boundary to a user turn. An assistant message or tool result may therefore begin the kept tail. Because such a tail can start or end mid-turn, the kept messages are not replayed as structured message blocks: they are serialized with the same transcript grammar as the compacted region and appended to the end of the boundary string, so the whole boundary reaches the provider as one message. Serialization of the kept tail is lossless — tool results keep their full text instead of being truncated at 16k characters, and images stay attached as image blocks rather than becoming `[image]` markers — so protected content is preserved, not merely summarized. A value of `0` protects no messages and makes the entire active transcript compactable. If `query` is absent, Atomic derives it from the last visible user message.
|
|
20
|
+
|
|
21
|
+
One consequence is worth stating for Claude models that sign their reasoning. Because the kept tail is serialized into the boundary string rather than replayed as structured assistant messages, no `thinking` or `redacted_thinking` block survives a compaction boundary. Compaction therefore **intentionally resets the signed reasoning chain**: reasoning produced before a boundary is not carried across it. This is deliberate, and it is the first of the two remedies Anthropic documents for keep-tail compaction — carry the text and tool calls across, leave the thinking blocks behind — reached structurally rather than by a stripping pass. The tail's own content is unaffected: text, tool calls, and tool results cross the boundary losslessly. See [Preserved thinking and model switches](/models/reference#preserved-thinking-and-model-switches) for how Atomic handles prefix changes *between* boundaries, which is a separate mechanism.
|
|
22
|
+
|
|
23
|
+
The query is used whole and is never truncated. This matters for structured prompts: a truncated query would make section order the retention policy, because only the leading section could influence what the planner kept, and a constraint stated later in the prompt could not. Long queries are safe — an oversized planner request surfaces as an explicit provider-overflow failure rather than silent truncation — but `keepContext` tags, not query length, are the way to guarantee a span survives.
|
|
24
|
+
|
|
25
|
+
Configure defaults in `~/.atomic/agent/settings.json` or `.atomic/settings.json`:
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"compaction": {
|
|
30
|
+
"enabled": true,
|
|
31
|
+
"reserveTokens": 16384,
|
|
32
|
+
"compression_ratio": 0.5,
|
|
33
|
+
"preserve_recent": 2,
|
|
34
|
+
"query": "optional focus"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`reserveTokens` controls the automatic threshold that decides when compaction runs; it is not converted into a classifier line ratio. Manual calls can pass parameter overrides through the SDK.
|
|
40
|
+
|
|
41
|
+
### Per-model budgets
|
|
42
|
+
|
|
43
|
+
Use `compaction.modelOverrides` to set `reserveTokens` and/or `preserve_recent` for an exact `"provider/modelId"` key. For example:
|
|
44
|
+
|
|
45
|
+
```json
|
|
46
|
+
{
|
|
47
|
+
"compaction": {
|
|
48
|
+
"reserveTokens": 16384,
|
|
49
|
+
"preserve_recent": 2,
|
|
50
|
+
"modelOverrides": {
|
|
51
|
+
"anthropic/claude-sonnet-4-5": { "reserveTokens": 32768, "preserve_recent": 4 }
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Each field falls back independently to the ordinary setting, then its built-in default. Keys are case-sensitive and do not support wildcards or reasoning suffixes. Both fields require non-negative safe integers. The active session model selects the budgets for manual, automatic, overflow, and post-tool compaction; switching models changes the next resolution, while borrowing a fallback planner does not. Explicit manual parameters take precedence over resolved defaults.
|
|
58
|
+
|
|
59
|
+
Atomic intentionally differs from upstream pi: the recent-history override is an exact message count (`preserve_recent`), not a token budget (`keepRecentTokens`). Verbatim line reconstruction, `compression_ratio`, and `query` are unchanged; the latter two and `enabled` remain ordinary settings. See [Settings](/settings#compaction) for merge and validation details.
|
|
60
|
+
|
|
61
|
+
## Persistence and resume
|
|
62
|
+
|
|
63
|
+
A successful run appends the existing pi-style `type:"compaction"` entry shape:
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"type": "compaction",
|
|
68
|
+
"id": "c1",
|
|
69
|
+
"parentId": "m9",
|
|
70
|
+
"timestamp": "2026-07-13T10:00:00.000Z",
|
|
71
|
+
"summary": "[User]: fix the failing test\n(filtered 42 lines)\n[Assistant]: Fixed.",
|
|
72
|
+
"firstKeptEntryId": "m7",
|
|
73
|
+
"tokensBefore": 51234,
|
|
74
|
+
"details": {
|
|
75
|
+
"strategy": "verbatim-lines",
|
|
76
|
+
"promptVersion": 3,
|
|
77
|
+
"rung": "planned",
|
|
78
|
+
"parameters": {"compression_ratio": 0.5, "preserve_recent": 2, "query": "fix the failing test"},
|
|
79
|
+
"stats": {"linesBefore": 812, "linesDeleted": 417, "linesKept": 395, "rangeCount": 63, "tokensBefore": 51234, "tokensAfter": 24980, "percentReduction": 51.2}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
`details.rung` is one of `"planned"` (a model ranked the lines — the session model **or** a borrowed fallback, including silent partial recovery), `"extension"` (a `session_before_compact` override), or `"fresh"` (the compactable conversation was discarded and a new context window started). `details.plannerModel` is present **only** when a borrowed fallback model ranked the lines:
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
"details": {
|
|
88
|
+
"strategy": "verbatim-lines",
|
|
89
|
+
"promptVersion": 3,
|
|
90
|
+
"rung": "planned",
|
|
91
|
+
"plannerModel": {"provider": "openai", "id": "gpt-5.1", "thinkingLevel": "high"}
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
There is no format-version bump and no new entry type. Both `"fresh"` and `plannerModel` are additive: they are absent on every existing entry and on any compaction that used the session model, so old readers are unaffected. A `"fresh"` boundary that had to drop the `preserve_recent` tail persists `firstKeptEntryId: null`.
|
|
96
|
+
|
|
97
|
+
A `compaction` entry is active only when `details.strategy === "verbatim-lines"`. On rebuild, Atomic emits one visible custom-role boundary message: the durable `summary` with the kept tail—the entries from `firstKeptEntryId` up to the boundary—serialized and concatenated onto its end. The tail is never restored as separate assistant/tool-result blocks, so a tail that starts or ends mid-turn cannot produce out-of-order provider blocks; images inside the tail ride along as image blocks on that same boundary message. When no pre-boundary context-visible message is retained—such as with `preserve_recent: 0`—`firstKeptEntryId` is `null` and the boundary carries the `summary` alone. Messages appended after the boundary are always replayed as real messages. The boundary is converted to a user-role provider message and shown in the TUI as a collapsible compaction card.
|
|
98
|
+
|
|
99
|
+
Resume does not rerun planning or re-derive deletions: the exact compacted string and nullable tail boundary are already in JSONL. Existing records with a string `firstKeptEntryId` keep their original resume behavior. Legacy `context_compaction` logical-deletion records and old `compaction` summary records without the discriminator are inert archival data. Their historical omissions are not reapplied when an old session resumes.
|
|
100
|
+
|
|
101
|
+
## Extension hooks
|
|
102
|
+
|
|
103
|
+
### `session_before_compact`
|
|
104
|
+
|
|
105
|
+
Extensions may cancel or provide a complete replacement for the prepared region:
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
pi.on("session_before_compact", async (event) => {
|
|
109
|
+
const { reason, parameters, preparation, branchEntries, signal } = event;
|
|
110
|
+
if (signal.aborted) return { cancel: true };
|
|
111
|
+
|
|
112
|
+
// Optional offline override. It must contain non-whitespace text.
|
|
113
|
+
if (reason === "manual" && branchEntries.length > 100) {
|
|
114
|
+
return { compactedText: preparation.region.lines.slice(0, 40).join("\n") };
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`preparation` is a deep-frozen clone. An override changes only the compacted region text; Atomic retains the prepared boundary and persists the supplied text verbatim. Empty/whitespace text is rejected. The override path does not require provider credentials.
|
|
120
|
+
|
|
121
|
+
### `session_compact`
|
|
122
|
+
|
|
123
|
+
After persistence, Atomic emits an observe-only event:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
pi.on("session_compact", async (event) => {
|
|
127
|
+
console.log(event.result.rung, event.result.stats); // rung: "planned" | "extension" | "fresh"
|
|
128
|
+
console.log(event.result.plannerModel); // set only when a fallback model was borrowed
|
|
129
|
+
console.log(event.compactionEntry.details.strategy); // "verbatim-lines"
|
|
130
|
+
console.log(event.fromExtension);
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Observer errors are isolated and cannot roll back the already-persisted boundary.
|
|
135
|
+
|
|
136
|
+
### `session_compact_failed`
|
|
137
|
+
|
|
138
|
+
A failed or cancelled manual, threshold, or overflow compaction emits an observe-only failure event:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
pi.on("session_compact_failed", async (event) => {
|
|
142
|
+
console.log(event.reason, event.errorMessage);
|
|
143
|
+
console.log(event.aborted, event.willRetry, event.fromExtension);
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
`errorMessage` is absent for cancellation. `fromExtension` identifies failures after a `session_before_compact` handler supplied replacement text; no compaction boundary is persisted.
|
|
148
|
+
|
|
149
|
+
## Branch Summary Format
|
|
150
|
+
|
|
151
|
+
Branch summarization uses a structured format:
|
|
152
|
+
|
|
153
|
+
```markdown
|
|
154
|
+
## Goal
|
|
155
|
+
[What the user is trying to accomplish]
|
|
156
|
+
|
|
157
|
+
## Constraints & Preferences
|
|
158
|
+
- [Requirements mentioned by user]
|
|
159
|
+
|
|
160
|
+
## Progress
|
|
161
|
+
### Done
|
|
162
|
+
- [x] [Completed tasks]
|
|
163
|
+
|
|
164
|
+
### In Progress
|
|
165
|
+
- [ ] [Current work]
|
|
166
|
+
|
|
167
|
+
### Blocked
|
|
168
|
+
- [Issues, if any]
|
|
169
|
+
|
|
170
|
+
## Key Decisions
|
|
171
|
+
- **[Decision]**: [Rationale]
|
|
172
|
+
|
|
173
|
+
## Next Steps
|
|
174
|
+
1. [What should happen next]
|
|
175
|
+
|
|
176
|
+
## Critical Context
|
|
177
|
+
- [Data needed to continue]
|
|
178
|
+
|
|
179
|
+
<read-files>
|
|
180
|
+
path/to/file1.ts
|
|
181
|
+
path/to/file2.ts
|
|
182
|
+
</read-files>
|
|
183
|
+
|
|
184
|
+
<modified-files>
|
|
185
|
+
path/to/changed.ts
|
|
186
|
+
</modified-files>
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Message Serialization for Branch Summaries
|
|
190
|
+
|
|
191
|
+
Before branch summarization, messages are serialized to text via [`serializeConversation()`](https://github.com/bastani-inc/atomic/blob/main/packages/coding-agent/src/core/compaction/utils.ts):
|
|
192
|
+
|
|
193
|
+
```text
|
|
194
|
+
[User]: What they said
|
|
195
|
+
[Assistant thinking]: Internal reasoning
|
|
196
|
+
[Assistant]: Response text
|
|
197
|
+
[Assistant tool calls]: read(path="foo.ts"); edit(path="bar.ts", ...)
|
|
198
|
+
[Tool result]: Output from tool
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
This prevents the model from treating it as a conversation to continue.
|
|
202
|
+
|
|
203
|
+
Tool results are truncated to 2000 characters during serialization. Content beyond that limit is replaced with a marker indicating how many characters were truncated.
|
|
204
|
+
|
|
205
|
+
## Extension Hooks for Branch Summarization
|
|
206
|
+
|
|
207
|
+
### session_before_tree
|
|
208
|
+
|
|
209
|
+
Fired before `/tree` navigation. Always fires regardless of whether user chose to summarize. Can cancel navigation or provide custom summary.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
pi.on("session_before_tree", async (event, ctx) => {
|
|
213
|
+
const { preparation, signal } = event;
|
|
214
|
+
|
|
215
|
+
// preparation.targetId - where we're navigating to
|
|
216
|
+
// preparation.oldLeafId - current position (being abandoned)
|
|
217
|
+
// preparation.commonAncestorId - shared ancestor
|
|
218
|
+
// preparation.entriesToSummarize - entries that would be summarized
|
|
219
|
+
// preparation.userWantsSummary - whether user chose to summarize
|
|
220
|
+
|
|
221
|
+
// Cancel navigation entirely:
|
|
222
|
+
return { cancel: true };
|
|
223
|
+
|
|
224
|
+
// Provide custom summary (only used if userWantsSummary is true):
|
|
225
|
+
if (preparation.userWantsSummary) {
|
|
226
|
+
return {
|
|
227
|
+
summary: {
|
|
228
|
+
summary: "Your summary...",
|
|
229
|
+
details: { /* custom data */ },
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
See `SessionBeforeTreeEvent` and `TreePreparation` in the types file.
|
|
237
|
+
|
|
238
|
+
## Settings
|
|
239
|
+
|
|
240
|
+
Configure compaction in `~/.atomic/agent/settings.json` or `<project-dir>/.atomic/settings.json` (legacy `.pi` paths are also supported):
|
|
241
|
+
|
|
242
|
+
```json
|
|
243
|
+
{
|
|
244
|
+
"compaction": {
|
|
245
|
+
"enabled": true,
|
|
246
|
+
"reserveTokens": 16384
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
| Setting | Default | Description |
|
|
252
|
+
|---------|---------|-------------|
|
|
253
|
+
| `enabled` | `true` | Enable automatic Verbatim Compaction. |
|
|
254
|
+
| `reserveTokens` | `16384` | Tokens to reserve for the next LLM response; threshold auto-compaction starts when completed-response usage or a prospective post-tool context exceeds the model's effective input budget minus this reserve. It is an **input-side** reserve only and never caps planner output. |
|
|
255
|
+
|
|
256
|
+
Compaction has no configuration key of its own for fallback borrowing: it reuses `settings.fallbackModels`, the same ordered `provider/model[:thinkingLevel]` list that main-chat model fallback walks. With no `fallbackModels` configured, compaction behaves as before: one planner model, then either an honest failure (recoverable) or a fresh context window (load-bearing).
|
|
257
|
+
|
|
258
|
+
Disable auto-compaction with `"enabled": false`. You can still compact manually with `/compact`.
|
|
259
|
+
|
|
260
|
+
## Historical formats
|
|
261
|
+
|
|
262
|
+
Two old formats remain parseable but inactive:
|
|
263
|
+
|
|
264
|
+
- `type:"context_compaction"` records store logical entry/content-block deletion targets from older versions. Those records are inert, so content they once hid can re-enter context when an old session resumes.
|
|
265
|
+
- `type:"compaction"` without `details.strategy: "verbatim-lines"` stored generated summary prose. Those records also remain inert.
|
|
266
|
+
|
|
267
|
+
Both are distinguished from active boundaries by the discriminated `details` on the shared `CompactionEntry` shape; the session format version is the same for all of them.
|