@bastani/atomic 0.9.18-alpha.2 → 0.9.18-alpha.3

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.
Files changed (28) hide show
  1. package/dist/builtin/intercom/CHANGELOG.md +8 -0
  2. package/dist/builtin/intercom/README.md +3 -3
  3. package/dist/builtin/intercom/broker/broker.ts +156 -12
  4. package/dist/builtin/intercom/broker/client.ts +89 -49
  5. package/dist/builtin/intercom/broker/pending-question-index.ts +10 -0
  6. package/dist/builtin/intercom/broker/send-handler.ts +32 -17
  7. package/dist/builtin/intercom/index.bundle.mjs +153 -50
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/skills/intercom/SKILL.md +1 -1
  10. package/dist/builtin/intercom/types.ts +30 -2
  11. package/dist/builtin/mcp/package.json +1 -1
  12. package/dist/builtin/subagents/package.json +1 -1
  13. package/dist/builtin/web-access/package.json +1 -1
  14. package/dist/builtin/workflows/CHANGELOG.md +18 -0
  15. package/dist/builtin/workflows/README.md +11 -2
  16. package/dist/builtin/workflows/builtin/{chunk-ngffz3y8.js → chunk-fghhy2a5.js} +1 -1
  17. package/dist/builtin/workflows/builtin/{chunk-6v0yv8tj.js → chunk-h3r2vkzc.js} +1 -1
  18. package/dist/builtin/workflows/builtin/{chunk-brerg33r.js → chunk-n58a7v26.js} +0 -1
  19. package/dist/builtin/workflows/builtin/goal.js +2 -2
  20. package/dist/builtin/workflows/builtin/index.js +3 -3
  21. package/dist/builtin/workflows/builtin/ralph.js +2 -2
  22. package/dist/builtin/workflows/package.json +1 -1
  23. package/dist/builtin/workflows/src/extension/index.bundle.mjs +258 -49
  24. package/dist/builtin/workflows/src/index.js +35 -9
  25. package/docs/intercom.md +7 -5
  26. package/docs/workflows.md +18 -9
  27. package/npm-shrinkwrap.json +32 -32
  28. package/package.json +3 -3
@@ -1392,7 +1392,6 @@ function compactStage(stage) {
1392
1392
  inputRequest,
1393
1393
  notices,
1394
1394
  mcpScope: _mcpScope,
1395
- pendingStageDeliveryAvailable: _pendingStageDeliveryAvailable,
1396
1395
  attemptedModels: _attemptedModels,
1397
1396
  modelAttempts: _modelAttempts,
1398
1397
  result: _result,
@@ -4459,6 +4458,7 @@ function durableStageCheckpointMetadata(stage, run, sourceOrder) {
4459
4458
  version: DURABLE_STAGE_TOPOLOGY_VERSION,
4460
4459
  stageId: stage.id,
4461
4460
  parentIds: [...stage.parentIds],
4461
+ ...stage.intercomGroup !== undefined ? { intercomGroup: stage.intercomGroup } : {},
4462
4462
  ...stage.executionOrder !== undefined ? { order: stage.executionOrder } : {},
4463
4463
  ...sourceOrder !== undefined && sourceOrder >= 0 ? { sourceOrder } : {},
4464
4464
  ...stage.promptFootprint !== undefined ? { occurrenceKey: stage.id } : {},
@@ -5191,11 +5191,13 @@ function createDurableStagePrimitive(input) {
5191
5191
  const isMidSessionResume = session?.sessionFile !== undefined;
5192
5192
  const topology = activeStageTopology(input.backend, input.workflowId, replayKey);
5193
5193
  const durableStageId = topology?.stageId ?? pendingStageIdForReplay(input.backend, input.workflowId, replayKey, name);
5194
+ const durableIntercomGroup = topology?.intercomGroup ?? input.durableIntercomGroup?.(replayKey, durableStageId);
5194
5195
  const liveOptions = {
5195
5196
  ...options ?? {},
5196
5197
  durableReplayKey: replayKey,
5197
5198
  ...durableStageId !== undefined ? { durableStageId } : {},
5198
5199
  ...topology !== undefined ? { durableParentIds: [...topology.parentIds] } : {},
5200
+ ...durableIntercomGroup !== undefined ? { durableIntercomGroup } : {},
5199
5201
  ...isMidSessionResume ? {
5200
5202
  resumeFromSessionFile: session.sessionFile,
5201
5203
  durableAccumulatedDurationMs: session.durationMs ?? 0
@@ -5224,6 +5226,7 @@ function createDurableTaskPrimitive(input) {
5224
5226
  }
5225
5227
  const session = input.backend.getStageSession(input.workflowId, replayKey);
5226
5228
  const topology = activeStageTopology(input.backend, input.workflowId, replayKey);
5229
+ const durableIntercomGroup = topology?.intercomGroup ?? input.durableIntercomGroup?.(replayKey, topology?.stageId);
5227
5230
  const taskOptions = {
5228
5231
  ...options,
5229
5232
  durableReplayKey: replayKey,
@@ -5231,6 +5234,7 @@ function createDurableTaskPrimitive(input) {
5231
5234
  durableStageId: topology.stageId,
5232
5235
  durableParentIds: [...topology.parentIds]
5233
5236
  } : {},
5237
+ ...durableIntercomGroup !== undefined ? { durableIntercomGroup } : {},
5234
5238
  ...session?.sessionFile !== undefined ? {
5235
5239
  resumeFromSessionFile: session.sessionFile,
5236
5240
  durableAccumulatedDurationMs: session.durationMs ?? 0
@@ -8608,6 +8612,7 @@ function appendStageStart(api, payload) {
8608
8612
  name: payload.name,
8609
8613
  parentIds: [...payload.parentIds],
8610
8614
  ...payload.model !== undefined ? { model: payload.model } : {},
8615
+ ...payload.intercomGroup !== undefined ? { intercomGroup: payload.intercomGroup } : {},
8611
8616
  ...payload.pendingStageDeliveryAvailable !== undefined ? { pendingStageDeliveryAvailable: payload.pendingStageDeliveryAvailable } : {},
8612
8617
  ...payload.replayKey !== undefined ? { replayKey: payload.replayKey } : {},
8613
8618
  ...payload.replayedFromStageId !== undefined ? { replayedFromStageId: payload.replayedFromStageId } : {},
@@ -10357,18 +10362,28 @@ function resolveStageGroup(stageOptions, workflowGroup) {
10357
10362
  const group = stageOptions.group;
10358
10363
  if (group === undefined)
10359
10364
  return workflowGroup;
10360
- if (group === true)
10361
- return randomUUID2();
10362
- const trimmed = group.trim();
10363
- return trimmed.length > 0 ? trimmed : undefined;
10365
+ const authored = group === true ? randomUUID2() : group.trim();
10366
+ if (authored.length === 0)
10367
+ return;
10368
+ if (workflowGroup === undefined || authored === DEFAULT_INTERCOM_GROUP)
10369
+ return authored;
10370
+ const owner2 = normalizeGroup(workflowGroup);
10371
+ if (authored === owner2 || authored.startsWith(`${owner2}/`))
10372
+ return authored;
10373
+ return `${owner2}/${authored}`;
10374
+ }
10375
+ function workflowInvocationOwnsGroup(workflowGroup, candidate) {
10376
+ if (workflowGroup === undefined || candidate === undefined)
10377
+ return false;
10378
+ const owner2 = normalizeGroup(workflowGroup);
10379
+ const group = normalizeGroup(candidate);
10380
+ return group === owner2 || group.startsWith(`${owner2}/`);
10364
10381
  }
10365
10382
  function stageHasIntercomAccess(_stageOptions) {
10366
10383
  return true;
10367
10384
  }
10368
10385
  function stageCanUseWorkflowPendingStageRoute(stageOptions, workflowGroup) {
10369
- if (!stageHasIntercomAccess(stageOptions) || stageOptions?.group === true)
10370
- return false;
10371
- return normalizeGroup(resolveStageGroup(stageOptions, workflowGroup)) === normalizeGroup(workflowGroup);
10386
+ return stageHasIntercomAccess(stageOptions) && workflowInvocationOwnsGroup(workflowGroup, resolveStageGroup(stageOptions, workflowGroup));
10372
10387
  }
10373
10388
 
10374
10389
  // dist/builtin/workflows/builtin/verification-usage.ts
@@ -14496,6 +14511,7 @@ function stripWorkflowOnlyOptions(options, defaultSessionDir, meta2, pendingStag
14496
14511
  durableReplayKey: _durableReplayKey,
14497
14512
  durableAccumulatedDurationMs: _durableAccumulatedDurationMs,
14498
14513
  durableStageId: _durableStageId,
14514
+ durableIntercomGroup: _durableIntercomGroup,
14499
14515
  durableParentIds: _durableParentIds,
14500
14516
  sessionDir,
14501
14517
  gitWorktreeDir: _gitWorktreeDir,
@@ -16618,15 +16634,18 @@ function createWorkflowStageFactory(input) {
16618
16634
  const replaySource = replayDecision.kind === "replay" ? replayDecision.source : undefined;
16619
16635
  const executeReplaySource = replayDecision.kind === "execute" ? replayDecision.source : undefined;
16620
16636
  const shouldReplay = replaySource !== undefined;
16621
- const stageOptionsForContext = executeReplaySource?.sessionFile === undefined ? options : {
16637
+ const replayStageOptions = executeReplaySource?.sessionFile === undefined ? options : {
16622
16638
  ...options ?? {},
16623
16639
  context: options?.context ?? "fork",
16624
16640
  forkFromSessionFile: options?.forkFromSessionFile ?? executeReplaySource.sessionFile
16625
16641
  };
16642
+ const intercomGroup = options?.durableIntercomGroup ?? resolveStageGroup(replayStageOptions, input.workflowIntercomGroup);
16643
+ const stageOptionsForContext = replayStageOptions?.group === undefined || intercomGroup === undefined ? replayStageOptions : { ...replayStageOptions, group: intercomGroup };
16626
16644
  const pendingStageDeliveryAvailable = stageCanUseWorkflowPendingStageRoute(stageOptionsForContext, input.workflowIntercomGroup);
16627
16645
  const stageSnapshot = {
16628
16646
  id: stageId,
16629
16647
  name,
16648
+ ...intercomGroup === undefined ? {} : { intercomGroup },
16630
16649
  replayKey,
16631
16650
  status: shouldReplay ? "completed" : "pending",
16632
16651
  parentIds: Object.freeze(parentIds),
@@ -16825,6 +16844,7 @@ function createWorkflowStageFactory(input) {
16825
16844
  name,
16826
16845
  parentIds: stageSnapshot.parentIds,
16827
16846
  ...stageReplayFields(stageSnapshot),
16847
+ ...intercomGroup === undefined ? {} : { intercomGroup },
16828
16848
  pendingStageDeliveryAvailable,
16829
16849
  ts: stageSnapshot.startedAt ?? Date.now()
16830
16850
  });
@@ -17506,11 +17526,16 @@ async function run(def, inputs, opts = {}) {
17506
17526
  completedStageReplayKeys,
17507
17527
  sourceToReplayedNodeIds: sourceToContinuationNodeIds
17508
17528
  });
17529
+ const durableIntercomGroup = (replayKey, stageId) => {
17530
+ const stages = activeStore.runs().find((candidate) => candidate.id === runId)?.stages ?? [];
17531
+ return stages.find((stage) => stageId !== undefined && stage.id === stageId || stage.replayKey === replayKey)?.intercomGroup;
17532
+ };
17509
17533
  let observedTaskTailQuit;
17510
17534
  const durableTask = createDurableTaskPrimitive({
17511
17535
  workflowId: runId,
17512
17536
  backend: durableBackend,
17513
17537
  nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
17538
+ durableIntercomGroup,
17514
17539
  task: taskRunners.task,
17515
17540
  recordCachedTask: cachedStage.record,
17516
17541
  signal: ownController.signal,
@@ -17559,6 +17584,7 @@ async function run(def, inputs, opts = {}) {
17559
17584
  workflowId: runId,
17560
17585
  backend: durableBackend,
17561
17586
  nextReplayKey: (stageName) => stageReplayKeyGenerator(stageName),
17587
+ durableIntercomGroup,
17562
17588
  recordCachedStage: cachedStage.record,
17563
17589
  stage: (name, options, replayKey) => {
17564
17590
  const stage = runtime.stage(name, options);
package/docs/intercom.md CHANGED
@@ -153,7 +153,7 @@ Name sessions with `/name` so they can target each other (for example `/name pla
153
153
  | `join` | Adds a trimmed named group membership and creates the group if needed. The action waits for broker acknowledgement and reports the complete resulting membership set. `default` is shared; `true` and `auto` are reserved for subagent auto-groups. |
154
154
  | `leave` | With `group`, removes only that membership and keeps all others. Without `group`, resets the session to its resolved startup home group. Both forms report the resulting membership set. |
155
155
  | `groups` | Lists every group represented by a connected session, with its session count and a marker for each group this session belongs to. Use it to discover names rather than guessing. |
156
- | `list` | Keeps session-listing semantics: returns the current session plus every active session sharing at least one membership, with name, full session ID, working directory, model, and live status. Pass `group` for a read-only view of one group. |
156
+ | `list` | Returns the current session, every active session sharing at least one membership, and discoverable workflow stages. Workflow rows are explicitly labeled `PENDING` or `RUNNING` and include the canonical `<runId>:<stageId>` target. Pass `group` for a read-only view of one group. |
157
157
  | `send` | Fire-and-forget delivery through ordinary Intercom. A live workflow-stage session receives the message immediately and returns `delivered`. A known workflow stage whose session has not initialized is addressed as `<runId>:<stageKey>`; Atomic persists the message and returns the distinct `queued` result with its FIFO position. Unknown stage identities retain the ordinary unknown-target failure. Requires `to` and `message`; cannot message the current session. |
158
158
  | `ask` | Sends a message and blocks until a live recipient replies (10-minute timeout). An ask to a known workflow stage whose session has not initialized is refused with `pending_stage_ask_unsupported` and recommends ordinary `send`; holding a waiter until a stage eventually starts would be unbounded. A live recipient disconnect fails promptly. From a foreground child to its launching parent, the existing fresh-subagent handoff path remains unchanged. |
159
159
  | `reply` | Replies to the intercom-triggered message of the current turn; otherwise falls back to the single unresolved inbound ask. With multiple pending asks, pass `to` or inspect with `pending` first. |
@@ -172,9 +172,9 @@ Sent and received messages are recorded in session history as `intercom_sent` /
172
172
 
173
173
  ### Targeting Sessions and Pending Workflow Stages
174
174
 
175
- Live-session lookup accepts only an exact full session ID or an exact case-insensitive session name. Ordinary `send` also accepts the exact `<runId>:<stageKey>` identity of a known workflow stage whose session has not initialized. The run ID is the full UUID, and the authored stage key is case-sensitive. Unknown runs and stages retain the ordinary unknown-target failure. Targeting is **group-scoped** see [Groups](#groups) below.
175
+ Live-session lookup accepts only an exact full Intercom session ID or an exact case-insensitive session name. Workflow stages use the canonical exact `<runId>:<stageId>` target printed by `intercom list` and workflow status surfaces; this target works while the row is `PENDING` and after it becomes `RUNNING`. Status surfaces label pending stages whose pre-start delivery capability is unavailable without presenting a usable target, and never advertise a retained pending stage after its run terminates. The `sessionId` shown by `workflow status` belongs to the workflow SDK and is **not** an Intercom target.
176
176
 
177
- Before steering a stage, join its invocation group. Use the Intercom `groups` action to discover it. Workflow invocation groups are named `workflow:<rootRunId>`.
177
+ Before steering a stage from the main chat, enter the workflow invocation context by joining `workflow:<rootRunId>` with `intercom({ action: "join", group: "workflow:<rootRunId>" })`; workflow-owned invocation sessions already start there. A member of that invocation group can list, `send` to, and live-`ask` exact stages in any invocation-owned subgroup (`workflow:<rootRunId>/<name>`), including intentionally isolated reviewer batches. This control is directional: a session registered as a subgroup stage cannot gain parent control by joining the invocation group, subgroup members cannot discover or reach sibling subgroups, and another workflow invocation remains refused. `PENDING` accepts queued `send` only; `RUNNING` accepts immediate `send` and correlated `ask`/`reply`.
178
178
 
179
179
  ### Deferred delivery to pending stages
180
180
 
@@ -182,7 +182,9 @@ Send material updates through Intercom to every affected workflow stage, includi
182
182
 
183
183
  The workflows extension persists up to **50 queued messages per exact run/stage key** with workflow state. Messages survive resume/replay and broker restart, and logical message IDs prevent redelivery across stage-attempt restarts. When the stage session initializes, it receives the FIFO entries through the ordinary Intercom inbound path before its first model turn, under the heading **Messages received before you started**, with sender identity and `Sent:` timestamps visible separately from the task prompt.
184
184
 
185
- Only a session in the workflow run's Intercom group can queue a message; a cross-group attempt is refused with `Target workflow run is in a different intercom group`. The 51st queued message is refused with `Pending stage message queue is full (limit 50)` rather than evicting an earlier entry. If the destination is skipped, the run is cancelled, or the stage becomes terminal before its session initializes, Atomic marks the message undeliverable and sends the correlated failure notification when acknowledgment was requested. Blocking `ask` is deliberately unsupported before initialization: use ordinary `send`, because a stage may start much later or never start.
185
+ Only a workflow invocation member with eligible invocation-control authority can queue to its invocation-owned stages; this includes a main-chat session that explicitly joined `workflow:<rootRunId>`. Subgroup peers and another root run remain refused even if they add that membership. An explicit stage `group: "default"` is a shared-group escape, is not workflow-owned, and does not receive pending invocation delivery. An ineligible attempt is refused with `Target workflow run is in a different intercom group`. The 51st queued message is refused with `Pending stage message queue is full (limit 50)` rather than evicting an earlier entry.
186
+
187
+ If the destination stage is skipped, the run terminates, or the stage becomes terminal before its session initializes, Atomic marks the queued message undeliverable and sends the correlated failure notification when acknowledgment was requested. Blocking `ask` is deliberately unsupported before initialization: use ordinary `send`, because a stage may start much later or never start.
186
188
 
187
189
  ### Groups
188
190
 
@@ -193,7 +195,7 @@ Every session belongs to a non-empty set of intercom **groups**. Sessions with n
193
195
  - `join` adds one membership. `leave` removes the named membership, while bare `leave` resets the complete set to the startup home group.
194
196
  - `status` reports the complete membership set. `session_joined`/`session_left`/`presence_update` events are delivered whenever a membership change affects visibility.
195
197
 
196
- A session's home group is resolved with this precedence: explicit stage/task/subagent group > runtime-owned workflow invocation group or inherited launching-session group > env `ATOMIC_INTERCOM_GROUP` (legacy `PI_INTERCOM_GROUP`) > Intercom `config.json` `"group"` > `"default"`. Each top-level workflow invocation gets a stable non-default group from its persistent run identity; Intercom-capable stages, nested workflows, and their delegated subagents inherit it without authors threading IDs. Explicit `group: "default"` opts into the shared group. Boolean `true` and the trimmed, case-insensitive strings `"true"`/`"auto"` request an automatically generated subgroup; those string names are reserved. Stages and children without Intercom access receive no group. See [workflows.md](/workflows) for workflow-stage rules. The subagent-only `contact_supervisor` path remains capability-based and can cross group boundaries; ordinary `send`, `ask`, and replies cannot use it as a bypass.
198
+ A session's home group is resolved with this precedence: explicit stage/task/subagent group > runtime-owned workflow invocation group or inherited launching-session group > env `ATOMIC_INTERCOM_GROUP` (legacy `PI_INTERCOM_GROUP`) > Intercom `config.json` `"group"` > `"default"`. Workflow stage named groups and `group: true` are namespaced under `workflow:<rootRunId>/...`, preventing cross-run collisions while preserving sibling isolation. `group: "default"` remains the explicit non-owned escape. The invocation group has asymmetric exact-target control over its owned subgroups; ownership does not grant reverse or lateral access.
197
199
 
198
200
  The broker, not the client, marks validated supervisor traffic. Ordinary `send` frames remain membership-isolated even if a raw client forges a supervisor marker, and replies cross back only through an exact broker-recorded `replyTo` match. Parent-held authorization state is restored after reconnects. Before an Intercom-enabled foreground child first runs, the parent wrapper may lazy-load and connect the broker provider to mint that exact child's capability; queued children request no capability. The child still connects only when it uses an Intercom delivery path, and claimed decisions or interviews terminally hand off before child send or waiter admission. A claimed provider failure aborts launch, while runtimes with no provider omit supervisor metadata and do not expose a broken channel.
199
201
 
package/docs/workflows.md CHANGED
@@ -116,7 +116,7 @@ Named workflow runs execute in the background. By default, after launch expect a
116
116
 
117
117
  For a request with several implementation items, do not turn list order into one serial workflow by default. Triage dependencies first, then launch independent items as a bounded wave of separate top-level runs; see [Task queues and software factories](#task-queues-and-software-factories).
118
118
 
119
- While a workflow is running, the visible below-editor `BACKGROUND` panel advances its elapsed label every second from the moment the run starts; it does not require opening or switching to the orchestrator. Updates repaint the existing mounted panel in place, paused timers stay frozen, the panel renders every qualifying top-level run, and terminal or quit cards retain their brief recent-run expiry. A zero-stage workflow whose work consists only of `ctx.tool(...)` calls mounts the same panel without a synthetic stage: at normal widths its run metadata reports the live-tool total when more than one is active, followed by pending and running durable tool-node names and statuses as space permits; the collapsed narrow form reports only the number of live tools. Quit cards remain resumable and discoverable with `/workflow status` after they leave the panel. A run waiting for human input uses the blue `?` indicator in the BACKGROUND panel, the `/workflow connect` picker, and the `/workflow status` listing; answering or cancelling the prompt restores the run's current indicator.
119
+ While a workflow is running, the visible below-editor `BACKGROUND` panel advances its elapsed label every second from the moment the run starts; it does not require opening or switching to the orchestrator. Updates repaint the existing mounted panel in place, paused timers stay frozen, the panel renders every qualifying top-level run, and terminal or quit cards retain their brief recent-run expiry. At normal widths the panel names materialized pending stages with canonical stage IDs and exact Intercom targets when pre-start delivery is available; unavailable delivery is labeled instead of implying steerability. An exact target is never partially truncated: the panel uses only pending-stage forms that fit the metadata-row budget, and omits the pending label entirely when none fit so existing live-tool and elapsed/status metadata is not displaced. The narrow form remains aggregate-only. A zero-stage workflow whose work consists only of `ctx.tool(...)` calls mounts the same panel without a synthetic stage: at normal widths its run metadata reports the live-tool total when more than one is active, followed by pending and running durable tool-node names and statuses as space permits; the collapsed narrow form reports only the number of live tools. Quit cards remain resumable and discoverable with `/workflow status` after they leave the panel. A run waiting for human input uses the blue `?` indicator in the BACKGROUND panel, the `/workflow connect` picker, and the `/workflow status` listing; answering or cancelling the prompt restores the run's current indicator.
120
120
 
121
121
  ### Workflow run identifiers and the BACKGROUND panel
122
122
 
@@ -126,7 +126,7 @@ Stage targeting is exact but not UUID-bound, because stage identifiers are not a
126
126
 
127
127
  #### Intercom delivery to pending workflow stages
128
128
 
129
- A known workflow stage whose session has not initialized is still addressable by the workflow run's full UUID and its exact authored stage key. From a sibling session in the same workflow Intercom group, use ordinary Intercom delivery:
129
+ A known workflow stage whose session has not initialized is still addressable by the workflow run's full UUID and its exact authored stage key while that run can still reach the stage. Model-facing `workflow status` and interactive status list/detail surfaces proactively enumerate materialized pending stages by display name and canonical stage ID. They print the exact `<runId>:<stageId>` target only when `pendingStageDeliveryAvailable` is true and the stage's owning run is nonterminal, and label delivery unavailable otherwise; an ended root or nested child run never advertises a retained pending stage as steerable through a projected parent. Duplicate display names therefore remain independently identifiable without overstating capability. Run `intercom({ action: "list" })` from the invocation context to discover materialized stages: each live route appears as `PENDING` or `RUNNING` with its canonical target. Note that the `sessionId` reported by `workflow status` is an SDK session id and is **not** an Intercom target. From a session in the workflow invocation group — including a main chat that explicitly joined `workflow:<rootRunId>` — use ordinary Intercom delivery:
130
130
 
131
131
  ```ts
132
132
  intercom({
@@ -141,12 +141,12 @@ Send material updates through Intercom to every affected workflow stage, includi
141
141
 
142
142
  The workflows extension persists pending messages with run state across resume/replay and broker restart. Each exact run/stage key accepts 50 queued messages; the next send is refused without eviction. Only sessions in the run's Intercom group may queue them. When the stage session initializes, Atomic delivers its messages FIFO through the ordinary inbound Intercom path **before the first model turn**. The transcript labels them **Messages received before you started**, preserves sender identity and `Sent:` timestamps, and keeps them separate from the stage task prompt. Duplicate logical message IDs and stage-attempt restarts do not redeliver a message.
143
143
 
144
- If the destination is skipped, the run is cancelled, or the stage becomes terminal before its session initializes, Atomic marks queued messages undeliverable rather than dropping them. Senders whose messages requested acknowledgment receive a correlated failure notification. Running and completed stages continue through their existing live, late, and post-mortem routes.
144
+ If the destination is skipped, the run terminates, or the stage becomes terminal before its session initializes, Atomic marks queued messages undeliverable rather than dropping them. Senders whose messages requested acknowledgment receive a correlated failure notification. Running and completed stages continue through their existing live, late, and post-mortem routes.
145
145
 
146
146
 
147
- At 80 columns and wider, each `BACKGROUND` card uses two rows so the id is not squeezed beside the workflow name: the first row contains the status glyph and full UUID, and the second contains the workflow name followed by its mode, progress, live-tool total when more than one is active, pending/running `ctx.tool` node names and statuses as space permits, and elapsed/status metadata. Tool nodes are read-only durable graph nodes, not attachable stage chats. The panel renders every qualifying top-level run, so each card is two rows high (plus the existing spacing between cards). Below 80 columns, the panel keeps its collapsed form, omits ids and tool names, and includes a live-tool count when one or more tool nodes are pending or running.
147
+ At 80 columns and wider, each `BACKGROUND` card keeps the full run identity and preserves its mode, progress, live-tool details, and elapsed/status metadata. When the remaining single-row budget permits, it adds bounded pending-stage details: a target is either shown exactly or replaced by a `stage`-labeled canonical ID, and `… N more` reports omitted pending stages. If no bounded pending-stage form fits, the pending label is omitted entirely rather than displacing the existing metadata. Tool nodes are read-only durable graph nodes, not attachable stage chats. Below 80 columns, the panel keeps its aggregate collapsed form and omits run IDs, stage identities, targets, and tool names.
148
148
 
149
- For chat surfaces such as workflow status, run detail, dispatch confirmation, and the run picker, a full id wraps onto continuation rows when the card is narrower than the id. The renderer never ellipsizes the id and keeps the card border closed at its minimum layout width, while terminals below that floor — including sub-30-column terminals — can hard-clip the box. An awaiting-input attribution banner is titled `AWAITING INPUT` and contains the same two identity rows — `?` plus the full run id, then the workflow name and optional metadata — while the existing prompt question and options remain below it in the normal prompt UI.
149
+ For chat surfaces such as workflow status, run detail, dispatch confirmation, and the run picker, a full id wraps onto continuation rows when the card is narrower than the id. Pending-stage targets in run detail use the same rule: the exact address wraps instead of being ellipsized, and narrow status cards wrap the canonical stage ID or drop its display-name decoration rather than rendering a partial ID. The renderer keeps the card border closed at its minimum layout width, while terminals below that floor — including sub-30-column terminals — can hard-clip the box. An awaiting-input attribution banner is titled `AWAITING INPUT` and contains the same two identity rows — `?` plus the full run id, then the workflow name and optional metadata — while the existing prompt question and options remain below it in the normal prompt UI.
150
150
 
151
151
  The `/workflow connect` run picker shows five runs at a time; use the arrow keys or mouse wheel to scroll through additional retained runs.
152
152
 
@@ -984,7 +984,7 @@ Author workflows to create at least one tracked execution node by calling `ctx.t
984
984
 
985
985
  ### Source layout for authored workflows
986
986
 
987
- Keep a small, readable workflow in one entry file. Do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
987
+ Keep a small, readable workflow in one entry file and write it for human maintainers. Keep the graph and control flow visible in the top-level workflow entry file, use stage names that state each stage's responsibility, and make its inputs, outputs, evidence, and success contract explicit. A developer reading the entry file from top to bottom should be able to identify the graph, branches, gates, artifacts, and stop conditions. Avoid both monolithic prompt blobs and gratuitous fragmentation: do not split short one-use prompts, create one file per stage, add wrapper-only modules, hide the graph across files, or use line counts alone as a module boundary.
988
988
 
989
989
  When a meaningful source boundary improves clarity, reuse, ownership, or testability, keep the graph and control flow in the top-level workflow entry file and extract cohesive concerns:
990
990
 
@@ -1007,6 +1007,12 @@ The repository uses this shape in `.atomic/workflows/release-docs.ts`: the entry
1007
1007
 
1008
1008
  The subdirectory is for cohesive, reusable support code, not a requirement to give every prompt or stage its own file.
1009
1009
 
1010
+ ### Workflow and extension responsibilities
1011
+
1012
+ Evaluate Atomic extension hooks when a workflow needs fine-grained, cross-cutting tool or session event control. Workflow TypeScript owns the inspectable DAG, stages, handoffs, durable `ctx.tool` side effects, and gates. Extension hooks own cross-cutting session and model-tool policy such as `tool_call` interception, input mutation, or blocking; `tool_result` transformation; context and provider hooks; lifecycle observation; or reusable custom tools. Use hooks only when cross-stage or cross-workflow event control is materially clearer than embedding the policy in each stage. Do not require a companion extension for ordinary workflow logic. See the authoritative [extension event documentation](/extensions#events) for hook contracts and ordering.
1013
+
1014
+ When a workflow depends on a companion extension, make that dependency explicit and package and document the extension with the workflow. If stages use `tools` allowlists, include any custom tools provided by the extension. Document the hook-driven behavior and keep the graph, stage contracts, artifacts, gates, and stop conditions visible in the workflow entry file so readers can distinguish inspectable workflow orchestration from event policy.
1015
+
1010
1016
  ### Dynamic topology must remain acyclic
1011
1017
 
1012
1018
  Atomic `workflow({ run })` definitions are imperative, dynamic TypeScript. The final graph is materialized only while `run(ctx)` executes and may depend on runtime inputs, branches, loops, files or network data, model or human output, helpers, and nested workflows. Discovery can report module import and definition-shape diagnostics: it loads the module, checks its exports, schemas, and `run` function, and rejects failures observable at that point. It does not execute every control-flow path or compile `run` into a complete graph. TypeScript and discovery cannot prove arbitrary dynamic acyclicity.
@@ -1579,7 +1585,7 @@ Ordinary `intercom` is mandatory in every workflow model stage. `noTools: "all"`
1579
1585
  - Never make a guard watch itself, recursively start another guard, reopen a terminal task, or add a dependency from the current frontier to an ancestor. Complete all turns on a retained guard before starting downstream dependency work.
1580
1586
  - Messages admitted before a worker generation closes drain through that stage boundary. Late messages do not reopen or mutate its terminal workflow state. Give each live branch a bounded stop rule; `ctx.parallel(...)` releases downstream work only after all started branches settle, even when one finishes first.
1581
1587
  - Persist decisions under stable keys. Pause/resume, model fallback, durable replay, and nested workflows then reread the artifact instead of sending duplicate interventions.
1582
- - Omit `group` for ordinary use. The worker, guard, nested workflows, and delegated subagents inherit the top-level workflow invocation's stable Intercom group. Set an explicit group only for intentional isolation; an override separates that stage from ordinary same-group peers.
1588
+ - Omit `group` for ordinary use. The worker, guard, nested workflows, and delegated subagents inherit the top-level workflow invocation's stable Intercom group. Set an explicit group only for intentional isolation; an override isolates that stage from ordinary same-group peers while leaving it steerable from the invocation context, which retains directional list/send/live-ask control over the subgroups it owns.
1583
1589
  - Use `context: "fresh"` for guards, reviewers, and judges. They should see only the contract, candidate, decision artifacts, and current files.
1584
1590
  - Use `context: "fork"` plus `forkFromSessionFile` for implementation, debugging, and repair roles that need continuity with an owned earlier session. `context: "fork"` alone does not name a fork source; an initial worker with no prior lineage may start fresh. A later continuation should use the earlier worker's `sessionFile` when available. Do not fork an independent guard from the worker it judges.
1585
1591
  - Send a forked continuation only the delta after the fork point: new evidence, the decision artifact, any human answer, and the next action. Keep the full shared contract in its canonical file.
@@ -2423,9 +2429,9 @@ readonly group?: string | true;
2423
2429
 
2424
2430
  Sets the stage session's [Intercom](/intercom) home group. Every top-level workflow invocation receives a stable, non-`"default"` runtime group derived from its persistent run identity. Intercom-capable stages inherit that group when `group` is omitted, including stages in nested workflows. The group stays stable across model fallback, pause/resume, and durable replay, while separate top-level invocations receive different groups.
2425
2431
 
2426
- `group` is accepted on `stage`/`task` options, on `ctx.parallel(...)` options, and per parallel step. Explicit values override the workflow invocation group; a step-level value also overrides its parallel-set value. A named string joins that group, including `group: "default"` to opt into the shared default group. Boolean `true` auto-generates one shared UUID group **per `ctx.parallel(...)` set** (minted once for every item in that set), while `true` on a non-parallel stage creates a fresh stage-only group. The trimmed, case-insensitive string sentinels `"true"` and `"auto"` have the same automatic behavior and are reserved.
2432
+ `group` is accepted on `stage`/`task` options, on `ctx.parallel(...)` options, and per parallel step. Explicit values override the workflow invocation group; a step-level value also overrides its parallel-set value. A named string becomes an **invocation-owned subgroup** resolved to `workflow:<rootRunId>/<name>`, so the same authored name in two concurrent runs never collides. Boolean `true` auto-generates one shared UUID group **per `ctx.parallel(...)` set** (minted once for every item in that set) and is namespaced the same way, while `true` on a non-parallel stage creates a fresh stage-only subgroup. The trimmed, case-insensitive string sentinels `"true"` and `"auto"` have the same automatic behavior and are reserved. `group: "default"` is the one exception: it opts into the shared default group, is **not** invocation-owned, and does not receive pending invocation delivery.
2427
2433
 
2428
- The full precedence is: explicit stage/task/parallel group > workflow invocation group > `ATOMIC_INTERCOM_GROUP` (or legacy `PI_INTERCOM_GROUP`) > Intercom config > `"default"`. Every workflow model stage receives its workflow invocation group because ordinary Intercom is mandatory. Tool restrictions do not suppress that group; explicit `group` values retain their existing precedence. Subagents inherit their launching stage's resolved group by default (see [subagents.md](/subagents)). The subagent-only `contact_supervisor` channel keeps its broker-authorized cross-group route; ordinary client sends remain group-bound.
2434
+ The full precedence is: explicit stage/task/parallel group > workflow invocation group > `ATOMIC_INTERCOM_GROUP` (or legacy `PI_INTERCOM_GROUP`) > Intercom config > `"default"`. Every workflow model stage receives its workflow invocation group because ordinary Intercom is mandatory. Tool restrictions do not suppress that group; explicit `group` values retain their existing precedence. Subagents inherit their launching stage's resolved group by default (see [subagents.md](/subagents)). The subagent-only `contact_supervisor` channel keeps its broker-authorized cross-group route. Ordinary client sends remain group-bound, with one deliberate exception: the workflow invocation group has directional list/send/live-ask control over the subgroups it owns, so an isolated stage stays steerable from the invocation context. That authority does not run in reverse or sideways — a subgroup stage cannot use it to reach a sibling subgroup, and another run cannot use it at all.
2429
2435
 
2430
2436
  Authors do not need to generate or pass a group through ordinary stages, tasks, parallel steps, nested workflows, or delegated subagents. Use an explicit named group or `group: true` only to create an intentional subgroup, such as isolating one reviewer level from another.
2431
2437
 
@@ -3049,6 +3055,9 @@ Expiry aborts the request operation signal so work that supports cancellation ca
3049
3055
 
3050
3056
  From interactive chat, named workflow launches run in the background so the parent chat stays available. Run `/workflow connect <run>` to see agents working and chat with and steer each stage. Inspection, prompt-response, and control calls (`status`, `stages`, `stage`, `transcript`, `answer`, `pause`, `resume`, `interrupt`, `quit`) remain available while work runs.
3051
3057
 
3058
+ The no-`runId` status listing includes bounded pending-stage rows after each run summary. Each row gives the display name, canonical stage ID, literal `pending` lifecycle, `pendingStageDeliveryAvailable`, and either the exact usable Intercom target or `unavailable`. Interactive status cards and run detail show the same identity/availability distinction within their width budgets. Status cards wrap exact targets onto continuation rows instead of rendering a partially truncated address; bounded omissions retain an explicit remaining-stage count.
3059
+
3060
+
3052
3061
  `workflow({ action: "models" })` returns the registry's configured-auth catalog snapshot in registry order. Each entry includes `provider`, `id`, `fullId`, an `isCurrent` marker, and `availableThinkingLevels` derived from the real model's `reasoning` and `thinkingLevelMap` metadata. This is not proof of credentials, entitlements, OAuth freshness, or live provider access, and it exposes no authentication details.
3053
3062
 
3054
3063
  Named launches wait only for **startup admission**, not for workflow completion. Atomic returns `status: "running"` after durable registration, reusable-worktree setup, and other pre-body setup succeed, while the workflow body and stages continue in the background. If setup fails before the workflow body is admitted — for example, `git_worktree_dir` points inside the invoking checkout — the original `workflow` tool call instead returns a structured `status: "failed"` result with the allocated full run id and concrete setup error. No background-start claim or orphan run is retained, so the caller can correct the inputs and retry immediately. Failures after admission remain ordinary background lifecycle outcomes reported through status and lifecycle notices.
@@ -1,16 +1,16 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@bastani/atomic",
9
- "version": "0.9.18-alpha.2",
9
+ "version": "0.9.18-alpha.3",
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
- "@bastani/atomic-natives": "0.9.18-alpha.2",
13
- "@bastani/pi-ai": "0.9.18-alpha.2",
12
+ "@bastani/atomic-natives": "0.9.18-alpha.3",
13
+ "@bastani/pi-ai": "0.9.18-alpha.3",
14
14
  "@dbos-inc/dbos-sdk": "4.25.14",
15
15
  "@earendil-works/pi-agent-core": "^0.84.4",
16
16
  "@earendil-works/pi-client": "^0.84.4",
@@ -517,18 +517,18 @@
517
517
  }
518
518
  },
519
519
  "node_modules/@bastani/atomic-natives": {
520
- "version": "0.9.18-alpha.2",
521
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.18-alpha.2.tgz",
520
+ "version": "0.9.18-alpha.3",
521
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives/-/atomic-natives-0.9.18-alpha.3.tgz",
522
522
  "license": "MIT",
523
523
  "optionalDependencies": {
524
- "@bastani/atomic-natives-darwin-arm64": "0.9.18-alpha.2",
525
- "@bastani/atomic-natives-darwin-x64": "0.9.18-alpha.2",
526
- "@bastani/atomic-natives-linux-arm64-gnu": "0.9.18-alpha.2",
527
- "@bastani/atomic-natives-linux-arm64-musl": "0.9.18-alpha.2",
528
- "@bastani/atomic-natives-linux-x64-gnu": "0.9.18-alpha.2",
529
- "@bastani/atomic-natives-linux-x64-musl": "0.9.18-alpha.2",
530
- "@bastani/atomic-natives-win32-arm64-msvc": "0.9.18-alpha.2",
531
- "@bastani/atomic-natives-win32-x64-msvc": "0.9.18-alpha.2"
524
+ "@bastani/atomic-natives-darwin-arm64": "0.9.18-alpha.3",
525
+ "@bastani/atomic-natives-darwin-x64": "0.9.18-alpha.3",
526
+ "@bastani/atomic-natives-linux-arm64-gnu": "0.9.18-alpha.3",
527
+ "@bastani/atomic-natives-linux-arm64-musl": "0.9.18-alpha.3",
528
+ "@bastani/atomic-natives-linux-x64-gnu": "0.9.18-alpha.3",
529
+ "@bastani/atomic-natives-linux-x64-musl": "0.9.18-alpha.3",
530
+ "@bastani/atomic-natives-win32-arm64-msvc": "0.9.18-alpha.3",
531
+ "@bastani/atomic-natives-win32-x64-msvc": "0.9.18-alpha.3"
532
532
  },
533
533
  "engines": {
534
534
  "bun": ">=1.4.0",
@@ -536,8 +536,8 @@
536
536
  }
537
537
  },
538
538
  "node_modules/@bastani/atomic-natives-darwin-arm64": {
539
- "version": "0.9.18-alpha.2",
540
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.18-alpha.2.tgz",
539
+ "version": "0.9.18-alpha.3",
540
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-arm64/-/atomic-natives-darwin-arm64-0.9.18-alpha.3.tgz",
541
541
  "license": "MIT",
542
542
  "os": [
543
543
  "darwin"
@@ -548,8 +548,8 @@
548
548
  "optional": true
549
549
  },
550
550
  "node_modules/@bastani/atomic-natives-darwin-x64": {
551
- "version": "0.9.18-alpha.2",
552
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.18-alpha.2.tgz",
551
+ "version": "0.9.18-alpha.3",
552
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-darwin-x64/-/atomic-natives-darwin-x64-0.9.18-alpha.3.tgz",
553
553
  "license": "MIT",
554
554
  "os": [
555
555
  "darwin"
@@ -560,8 +560,8 @@
560
560
  "optional": true
561
561
  },
562
562
  "node_modules/@bastani/atomic-natives-linux-arm64-gnu": {
563
- "version": "0.9.18-alpha.2",
564
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.18-alpha.2.tgz",
563
+ "version": "0.9.18-alpha.3",
564
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-gnu/-/atomic-natives-linux-arm64-gnu-0.9.18-alpha.3.tgz",
565
565
  "license": "MIT",
566
566
  "os": [
567
567
  "linux"
@@ -575,8 +575,8 @@
575
575
  "optional": true
576
576
  },
577
577
  "node_modules/@bastani/atomic-natives-linux-arm64-musl": {
578
- "version": "0.9.18-alpha.2",
579
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.18-alpha.2.tgz",
578
+ "version": "0.9.18-alpha.3",
579
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-arm64-musl/-/atomic-natives-linux-arm64-musl-0.9.18-alpha.3.tgz",
580
580
  "license": "MIT",
581
581
  "os": [
582
582
  "linux"
@@ -590,8 +590,8 @@
590
590
  "optional": true
591
591
  },
592
592
  "node_modules/@bastani/atomic-natives-linux-x64-gnu": {
593
- "version": "0.9.18-alpha.2",
594
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.18-alpha.2.tgz",
593
+ "version": "0.9.18-alpha.3",
594
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-gnu/-/atomic-natives-linux-x64-gnu-0.9.18-alpha.3.tgz",
595
595
  "license": "MIT",
596
596
  "os": [
597
597
  "linux"
@@ -605,8 +605,8 @@
605
605
  "optional": true
606
606
  },
607
607
  "node_modules/@bastani/atomic-natives-linux-x64-musl": {
608
- "version": "0.9.18-alpha.2",
609
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.18-alpha.2.tgz",
608
+ "version": "0.9.18-alpha.3",
609
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-linux-x64-musl/-/atomic-natives-linux-x64-musl-0.9.18-alpha.3.tgz",
610
610
  "license": "MIT",
611
611
  "os": [
612
612
  "linux"
@@ -620,8 +620,8 @@
620
620
  "optional": true
621
621
  },
622
622
  "node_modules/@bastani/atomic-natives-win32-arm64-msvc": {
623
- "version": "0.9.18-alpha.2",
624
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.18-alpha.2.tgz",
623
+ "version": "0.9.18-alpha.3",
624
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-arm64-msvc/-/atomic-natives-win32-arm64-msvc-0.9.18-alpha.3.tgz",
625
625
  "license": "MIT",
626
626
  "os": [
627
627
  "win32"
@@ -632,8 +632,8 @@
632
632
  "optional": true
633
633
  },
634
634
  "node_modules/@bastani/atomic-natives-win32-x64-msvc": {
635
- "version": "0.9.18-alpha.2",
636
- "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.18-alpha.2.tgz",
635
+ "version": "0.9.18-alpha.3",
636
+ "resolved": "https://registry.npmjs.org/@bastani/atomic-natives-win32-x64-msvc/-/atomic-natives-win32-x64-msvc-0.9.18-alpha.3.tgz",
637
637
  "license": "MIT",
638
638
  "os": [
639
639
  "win32"
@@ -644,8 +644,8 @@
644
644
  "optional": true
645
645
  },
646
646
  "node_modules/@bastani/pi-ai": {
647
- "version": "0.9.18-alpha.2",
648
- "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.18-alpha.2.tgz",
647
+ "version": "0.9.18-alpha.3",
648
+ "resolved": "https://registry.npmjs.org/@bastani/pi-ai/-/pi-ai-0.9.18-alpha.3.tgz",
649
649
  "license": "MIT",
650
650
  "dependencies": {
651
651
  "@anthropic-ai/sdk": "0.91.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/atomic",
3
- "version": "0.9.18-alpha.2",
3
+ "version": "0.9.18-alpha.3",
4
4
  "description": "Atomic coding agent CLI with read, bash, edit, write tools and session management",
5
5
  "type": "module",
6
6
  "atomicConfig": {
@@ -79,8 +79,8 @@
79
79
  "prepublishOnly": "bun run clean && bun run build && bun run shrinkwrap"
80
80
  },
81
81
  "dependencies": {
82
- "@bastani/atomic-natives": "0.9.18-alpha.2",
83
- "@bastani/pi-ai": "0.9.18-alpha.2",
82
+ "@bastani/atomic-natives": "0.9.18-alpha.3",
83
+ "@bastani/pi-ai": "0.9.18-alpha.3",
84
84
  "@dbos-inc/dbos-sdk": "4.25.14",
85
85
  "@earendil-works/pi-agent-core": "^0.84.4",
86
86
  "@earendil-works/pi-client": "^0.84.4",