@bastani/atomic 0.9.11 → 0.9.12-alpha.1

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 (119) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  3. package/dist/builtin/intercom/closed-workflow-stage-message.ts +1 -1
  4. package/dist/builtin/intercom/contact-supervisor-tool.ts +1 -1
  5. package/dist/builtin/intercom/index-heavy.ts +2 -2
  6. package/dist/builtin/intercom/intercom-tool.ts +2 -2
  7. package/dist/builtin/intercom/intercom-utils.ts +1 -1
  8. package/dist/builtin/intercom/lifecycle.ts +2 -2
  9. package/dist/builtin/intercom/overlay.ts +2 -2
  10. package/dist/builtin/intercom/package.json +1 -1
  11. package/dist/builtin/intercom/reply-tracker.ts +1 -1
  12. package/dist/builtin/intercom/subagent-relay.ts +2 -2
  13. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  14. package/dist/builtin/mcp/apps-cancellation.ts +1 -1
  15. package/dist/builtin/mcp/commands.ts +7 -7
  16. package/dist/builtin/mcp/direct-tool-executor.ts +1 -1
  17. package/dist/builtin/mcp/direct-tools.ts +3 -3
  18. package/dist/builtin/mcp/host-html-template.ts +1 -1
  19. package/dist/builtin/mcp/index.ts +3 -3
  20. package/dist/builtin/mcp/init.ts +6 -6
  21. package/dist/builtin/mcp/mcp-auth-flow.ts +1 -1
  22. package/dist/builtin/mcp/mcp-callback-server.ts +1 -1
  23. package/dist/builtin/mcp/mcp-panel-state.ts +1 -1
  24. package/dist/builtin/mcp/mcp-panel.ts +1 -1
  25. package/dist/builtin/mcp/metadata-cache.ts +1 -1
  26. package/dist/builtin/mcp/package.json +1 -1
  27. package/dist/builtin/mcp/proxy-call.ts +2 -2
  28. package/dist/builtin/mcp/proxy-info-modes.ts +2 -2
  29. package/dist/builtin/mcp/sampling-handler.ts +1 -1
  30. package/dist/builtin/mcp/server-manager.ts +3 -3
  31. package/dist/builtin/mcp/startup-warmup.ts +5 -5
  32. package/dist/builtin/mcp/state.ts +1 -1
  33. package/dist/builtin/mcp/tool-call-timeout.ts +1 -1
  34. package/dist/builtin/mcp/tool-metadata.ts +2 -2
  35. package/dist/builtin/mcp/types.ts +1 -1
  36. package/dist/builtin/mcp/ui-server.ts +4 -4
  37. package/dist/builtin/mcp/ui-session.ts +3 -3
  38. package/dist/builtin/subagents/CHANGELOG.md +6 -0
  39. package/dist/builtin/subagents/package.json +1 -1
  40. package/dist/builtin/subagents/src/runs/background/result-delivery-processor.ts +5 -5
  41. package/dist/builtin/subagents/src/runs/background/result-file-claims.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/background/result-status.ts +2 -2
  43. package/dist/builtin/subagents/src/runs/background/result-watcher-data.ts +2 -2
  44. package/dist/builtin/subagents/src/runs/background/result-watcher.ts +1 -1
  45. package/dist/builtin/subagents/src/runs/foreground/execution-detach-reservations.ts +1 -1
  46. package/dist/builtin/subagents/src/shared/artifacts.ts +1 -1
  47. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  48. package/dist/builtin/web-access/index-heavy.ts +1 -1
  49. package/dist/builtin/web-access/package.json +1 -1
  50. package/dist/builtin/workflows/CHANGELOG.md +11 -0
  51. package/dist/builtin/workflows/README.md +1 -1
  52. package/dist/builtin/workflows/builtin/goal-prompts.ts +4 -1
  53. package/dist/builtin/workflows/builtin/ralph-core.ts +7 -4
  54. package/dist/builtin/workflows/builtin/ralph-forked-prompts.ts +5 -2
  55. package/dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts +2 -1
  56. package/dist/builtin/workflows/builtin/ralph-runner.ts +2 -1
  57. package/dist/builtin/workflows/builtin/shared-prompts.ts +34 -7
  58. package/dist/builtin/workflows/package.json +1 -1
  59. package/dist/builtin/workflows/src/authoring/keep-context.ts +46 -0
  60. package/dist/builtin/workflows/src/authoring.d.ts +11 -0
  61. package/dist/builtin/workflows/src/extension/index.bundle.mjs +35 -17
  62. package/dist/builtin/workflows/src/extension/workflow-prompts.ts +4 -0
  63. package/dist/builtin/workflows/src/sdk-surface.ts +1 -0
  64. package/dist/core/agent-session-methods.d.ts +1 -1
  65. package/dist/core/agent-session-methods.js.map +1 -1
  66. package/dist/core/agent-session.d.ts +1 -1
  67. package/dist/core/agent-session.js.map +1 -1
  68. package/dist/core/async/session-manager.d.ts +1 -1
  69. package/dist/core/async/session-manager.js.map +1 -1
  70. package/dist/core/compaction/branch-summarization.js.map +1 -1
  71. package/dist/core/compaction/compaction-boundary.d.ts +2 -2
  72. package/dist/core/compaction/compaction-boundary.js.map +1 -1
  73. package/dist/core/compaction/compaction-parameters.d.ts +5 -0
  74. package/dist/core/compaction/compaction-parameters.d.ts.map +1 -1
  75. package/dist/core/compaction/compaction-parameters.js +6 -5
  76. package/dist/core/compaction/compaction-parameters.js.map +1 -1
  77. package/dist/core/compaction/compaction-runner.d.ts +4 -0
  78. package/dist/core/compaction/compaction-runner.d.ts.map +1 -1
  79. package/dist/core/compaction/compaction-runner.js +5 -1
  80. package/dist/core/compaction/compaction-runner.js.map +1 -1
  81. package/dist/core/compaction/compaction-types.d.ts +7 -1
  82. package/dist/core/compaction/compaction-types.d.ts.map +1 -1
  83. package/dist/core/compaction/compaction-types.js.map +1 -1
  84. package/dist/core/compaction/deleted-ranges.d.ts.map +1 -1
  85. package/dist/core/compaction/deleted-ranges.js +2 -0
  86. package/dist/core/compaction/deleted-ranges.js.map +1 -1
  87. package/dist/core/compaction/fallback-planner.d.ts +1 -1
  88. package/dist/core/compaction/fallback-planner.js.map +1 -1
  89. package/dist/core/compaction/range-planner.d.ts +3 -3
  90. package/dist/core/compaction/range-planner.d.ts.map +1 -1
  91. package/dist/core/compaction/range-planner.js +12 -14
  92. package/dist/core/compaction/range-planner.js.map +1 -1
  93. package/dist/core/compaction/transcript-serialization.d.ts +12 -0
  94. package/dist/core/compaction/transcript-serialization.d.ts.map +1 -1
  95. package/dist/core/compaction/transcript-serialization.js +99 -1
  96. package/dist/core/compaction/transcript-serialization.js.map +1 -1
  97. package/dist/core/compaction/utils.d.ts +3 -0
  98. package/dist/core/compaction/utils.d.ts.map +1 -1
  99. package/dist/core/compaction/utils.js +16 -0
  100. package/dist/core/compaction/utils.js.map +1 -1
  101. package/dist/core/session-manager-history.js.map +1 -1
  102. package/dist/core/tools/bash-async-execution.d.ts +1 -1
  103. package/dist/core/tools/bash-async-execution.js.map +1 -1
  104. package/dist/core/tools/bash.js.map +1 -1
  105. package/dist/core/tools/oversized-tool-result.js.map +1 -1
  106. package/dist/extensions/index.d.ts +1 -1
  107. package/dist/extensions/index.js.map +1 -1
  108. package/dist/extensions/llama/huggingface-ui.d.ts +2 -2
  109. package/dist/extensions/llama/huggingface-ui.js.map +1 -1
  110. package/dist/extensions/llama/index.d.ts +1 -1
  111. package/dist/extensions/llama/index.js.map +1 -1
  112. package/dist/extensions/llama/ui.d.ts +1 -1
  113. package/dist/extensions/llama/ui.js.map +1 -1
  114. package/dist/modes/interactive-engine/remote-queue-pause.d.ts +1 -1
  115. package/dist/modes/interactive-engine/remote-queue-pause.js.map +1 -1
  116. package/docs/compaction.md +24 -0
  117. package/docs/workflows.md +56 -0
  118. package/npm-shrinkwrap.json +39 -39
  119. package/package.json +3 -3
@@ -2,7 +2,7 @@ import type { ExtensionAPI } from "@bastani/atomic";
2
2
  import { CONFIG_DIR_NAME } from "@bastani/atomic";
3
3
  import { appendFileSync } from "node:fs";
4
4
  import { getActiveGoogleEmail, isGeminiWebAvailable } from "./gemini-web.js";
5
- import { isBrowserCookieAccessAllowed } from "./gemini-web-config.js";
5
+ import { isBrowserCookieAccessAllowed } from "./gemini-web-config.ts";
6
6
  import { deleteResult, getAllResults } from "./storage.js";
7
7
  import { loadConfigForExtensionInit, resolveWorkflow, saveConfig, type WebSearchWorkflow } from "./web-search-config.js";
8
8
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.11",
3
+ "version": "0.9.12-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
6
6
  "contributors": [
@@ -6,6 +6,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.12-alpha.1] - 2026-08-04
10
+
11
+ ### Added
12
+
13
+ - Added `keepContext` to the public authoring surface: `import { keepContext } from "@bastani/workflows"`. It wraps prompt text so compaction protects it verbatim regardless of the compression ratio, and is a pure, idempotent string helper rather than a `ctx.*` primitive — no graph node, no side effect, callable anywhere a prompt is assembled. `KEEP_CONTEXT_OPEN_TAG` and `KEEP_CONTEXT_CLOSE_TAG` are exported alongside it. Reserve it for text whose loss silently changes behavior — role constraints, acceptance criteria, explicit prohibitions, and identifiers a stage must not lose — and not for bulk context, since protected lines count against the keep target rather than raising it ([#2172](https://github.com/bastani-inc/atomic/issues/2172)).
14
+ - `<keepContext>` tags also work in the run inputs and steering messages sent through the `workflow` tool, not only in authored stage prompts. Workflows inject inputs into their stage prompts, so a tagged clause in `prompt` or `acceptance_criteria` is inherited and protected by every stage that receives it, and a tagged `send` amendment survives until the stage acts on it instead of competing with the whole transcript for retention. Because `keepContext` is idempotent, an already-tagged input is not double-wrapped by a workflow that protects the same field. The workflow tool description and agent guidance now say so, so an agent can decide per launch or per steering message which clauses to protect ([#2172](https://github.com/bastani-inc/atomic/issues/2172)).
15
+
16
+ ### Fixed
17
+
18
+ - Builtin workflows protect their own invariants with `keepContext`, so a long-running stage can no longer lose the rules that bound it: the steering propagation contract carried by every builtin stage prompt, the literal objective contract, scope discipline, worktree discipline, ralph's per-run acceptance criteria, ralph's research-only role constraint, and goal's reviewer "inspect and report; do not implement" constraint. Previously a research stage could be compacted past its own prohibition and start implementing, and a stage could lose the checkout it was bound to ([#2172](https://github.com/bastani-inc/atomic/issues/2172)).
19
+
9
20
  ## [0.9.11] - 2026-08-03
10
21
 
11
22
  ### Added
@@ -608,7 +608,7 @@ Raw stage-chat prompt answer replay is live-memory only. `StageSnapshot.promptAn
608
608
  ```json
609
609
  {
610
610
  "name": "workflow",
611
- "description": "Run named builtin, project, user, or package workflows; custom definitions may import reusable project/package workflows or builtin definitions from @bastani/workflows/builtin and nest them with ctx.workflow(...), including deeper composition within the configured maxDepth; when workflow execution fits but another shape would better achieve the task, author a custom TypeScript workflow({...}) inline with normal coding tools, reload it, and run it; after successfully creating and reloading a newly authored custom workflow, report the folder containing its generated code as 'Custom workflow created. You can inspect its code at: <workflow-folder-path>'; do this only for newly created custom workflows, never builtin or pre-existing workflows; discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview. Use action 'models' to inspect models in the configured catalog; the result is a configured-auth snapshot showing what's present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. When authoring a workflow that should dynamically select a model, first call workflow({ action: 'models' }) to inspect the configured catalog, then select from the returned provider/id entries considering the isCurrent marker and available thinking levels.",
611
+ "description": "Run named builtin, project, user, or package workflows; custom definitions may import reusable project/package workflows or builtin definitions from @bastani/workflows/builtin and nest them with ctx.workflow(...), including deeper composition within the configured maxDepth; when workflow execution fits but another shape would better achieve the task, author a custom TypeScript workflow({...}) inline with normal coding tools, reload it, and run it; after successfully creating and reloading a newly authored custom workflow, report the folder containing its generated code as 'Custom workflow created. You can inspect its code at: <workflow-folder-path>'; do this only for newly created custom workflows, never builtin or pre-existing workflows; discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. Wrap critical parts of run inputs and steering messages in <keepContext>...</keepContext> so compaction preserves them verbatim in the stages that inherit them; tag role constraints, prohibitions, must-hold criteria, and identifiers, not background or bulk reference material. For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, quote the exact path without rewriting separators (Windows backslashes are valid), then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview. Use action 'models' to inspect models in the configured catalog; the result is a configured-auth snapshot showing what's present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. When authoring a workflow that should dynamically select a model, first call workflow({ action: 'models' }) to inspect the configured catalog, then select from the returned provider/id entries considering the isCurrent marker and available thinking levels.",
612
612
  "parameters": {
613
613
  "workflow": "string (optional) — workflow ID or normalized name",
614
614
  "inputs": "object (optional) — key/value map of workflow inputs",
@@ -14,6 +14,7 @@ import {
14
14
  SCOPE_DISCIPLINE_CONTRACT,
15
15
  WORKER_PREFLIGHT_CONTRACT,
16
16
  WORKTREE_DISCIPLINE_CONTRACT,
17
+ keepContext,
17
18
  renderE2eQaVideoReviewGuidance,
18
19
  } from "./shared-prompts.js";
19
20
  import type { GoalLedger } from "./goal-types.js";
@@ -196,7 +197,9 @@ export function renderReviewerPrompt(args: {
196
197
  "Lead with the verdict. Keep evidence, decisions, caveats, and next action; omit background and repetition while remaining readable rather than using fragments, arrow chains, or invented shorthand.",
197
198
  ].join("\n")],
198
199
  ["objective", [
199
- "Act as an independent, skeptical, technically fair reviewer. Inspect and report; do not implement. Protect correctness, security, performance, maintainability, and full objective completion without bikeshedding.",
200
+ keepContext(
201
+ "Act as an independent, skeptical, technically fair reviewer. Inspect and report; do not implement. Protect correctness, security, performance, maintainability, and full objective completion without bikeshedding.",
202
+ ),
200
203
  args.reviewerRole,
201
204
  args.focus,
202
205
  "Review the delivered change against the run objective stored in the goal ledger.",
@@ -5,6 +5,7 @@ import type { WorkflowTaskResult } from "../src/shared/types.js";
5
5
  import { createWorkflowArtifactDirectory } from "../src/shared/workflow-artifacts.js";
6
6
  import {
7
7
  E2E_VERIFICATION_GUIDANCE,
8
+ keepContext,
8
9
  LITERAL_OBJECTIVE_CONTRACT,
9
10
  } from "./shared-prompts.js";
10
11
  import type { ReviewDecision, ReviewFinding } from "./ralph-review-gate.js";
@@ -336,7 +337,7 @@ export function renderResearchPromptRefinementPrompt(args: {
336
337
  readonly latestReviewReportPath: string | undefined;
337
338
  }): string {
338
339
  return taggedPrompt([
339
- ["acceptance_criteria", args.acceptanceCriteria],
340
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
340
341
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
341
342
  args.workflowCwdContext,
342
343
  [
@@ -351,7 +352,9 @@ export function renderResearchPromptRefinementPrompt(args: {
351
352
  ["objective", `Research the full requested task: ${args.request}`],
352
353
  [
353
354
  "output",
354
- "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.",
355
+ keepContext(
356
+ "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.",
357
+ ),
355
358
  ],
356
359
  [
357
360
  "instruction",
@@ -369,7 +372,7 @@ export function renderResearchPrompt(args: {
369
372
  readonly latestReviewReportPath: string | undefined;
370
373
  }): string {
371
374
  return taggedPrompt([
372
- ["acceptance_criteria", args.acceptanceCriteria],
375
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
373
376
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
374
377
  args.workflowCwdContext,
375
378
  [
@@ -388,7 +391,7 @@ export function renderResearchPrompt(args: {
388
391
  "Return the complete research report as your final message. Downstream implementation and review stages read it from there.",
389
392
  "Produce a complete Markdown report with codebase and useful online/contextual findings, implementation guidance, relevant files/tests/docs, unresolved-finding analysis, and validation recommendations. Lead with conclusions; keep facts, caveats, and implementation-relevant next steps; drop background and repetition.",
390
393
  "Before reporting progress, audit each claim against a tool result from this session. Report only work you can point to evidence for; say so explicitly when something is unverified.",
391
- "Do not author an RFC/spec or implement code changes.",
394
+ keepContext("This stage researches only. Do not author an RFC/spec or implement code changes."),
392
395
  ].join("\n"),
393
396
  ],
394
397
  [
@@ -1,5 +1,6 @@
1
1
  // Forked stages inherit their contracts and receive only the iteration delta.
2
2
  import { taggedPrompt } from "./ralph-core.js";
3
+ import { keepContext } from "./shared-prompts.js";
3
4
 
4
5
  export function renderForkedResearchPromptRefinementPrompt(args: {
5
6
  readonly latestReviewReportPath: string | undefined;
@@ -16,7 +17,9 @@ export function renderForkedResearchPromptRefinementPrompt(args: {
16
17
  ],
17
18
  [
18
19
  "output",
19
- "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.",
20
+ keepContext(
21
+ "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.",
22
+ ),
20
23
  ],
21
24
  [
22
25
  "instruction",
@@ -44,7 +47,7 @@ export function renderForkedResearchPrompt(args: {
44
47
  [
45
48
  "Return the rewritten research report for this iteration as your final message.",
46
49
  "Restate the still-applicable findings in full rather than referring back to the previous iteration's artifact; the current artifact and transcript are the authoritative records for this iteration.",
47
- "Do not author an RFC/spec or implement code changes.",
50
+ keepContext("This stage researches only. Do not author an RFC/spec or implement code changes."),
48
51
  ].join("\n"),
49
52
  ],
50
53
  [
@@ -10,6 +10,7 @@ import {
10
10
  REVIEWER_OVERIMPLEMENTATION_GUARD,
11
11
  REVIEWER_SPEC_VS_OBJECTIVE_GUARD,
12
12
  WORKTREE_DISCIPLINE_CONTRACT,
13
+ keepContext,
13
14
  renderE2eQaVideoReviewGuidance,
14
15
  } from "./shared-prompts.js";
15
16
  import { taggedPrompt, type PromptSection } from "./ralph-core.js";
@@ -26,7 +27,7 @@ export function renderRalphReviewerPrompt(args: {
26
27
  readonly createPr: boolean;
27
28
  }): string {
28
29
  return taggedPrompt([
29
- ["acceptance_criteria", args.acceptanceCriteria],
30
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
30
31
  [
31
32
  "review_context",
32
33
  [
@@ -11,6 +11,7 @@ import {
11
11
  SCOPE_DISCIPLINE_CONTRACT,
12
12
  WORKER_PREFLIGHT_CONTRACT,
13
13
  WORKTREE_DISCIPLINE_CONTRACT,
14
+ keepContext,
14
15
  } from "./shared-prompts.js";
15
16
  import { renderRalphReviewerPrompt } from "./ralph-reviewer-prompt.js";
16
17
  import {
@@ -117,7 +118,7 @@ export async function runRalphWorkflow(
117
118
  const orchestratorForkOptions = forkContinuationOptions(previousOrchestratorSessionFile);
118
119
  const orchestratorPrompt = orchestratorForkOptions.forkFromSessionFile === undefined
119
120
  ? taggedPrompt([
120
- ["acceptance_criteria", acceptanceCriteria],
121
+ ["acceptance_criteria", keepContext(acceptanceCriteria)],
121
122
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
122
123
  ["acceptance_matrix", ACCEPTANCE_MATRIX_CONTRACT],
123
124
  ["divergence_audit", CONTRACT_FIDELITY_AUDIT],
@@ -1,3 +1,9 @@
1
+ // Builtin prompts use the same public `keepContext` helper workflow authors do, so the
2
+ // protection semantics cannot drift between builtin and user-authored workflows.
3
+ import { keepContext } from "../src/authoring/keep-context.js";
4
+
5
+ export { keepContext };
6
+
1
7
  /**
2
8
  * Steering propagation is a whole-repository pattern, not a per-workflow
3
9
  * option: every builtin stage prompt carries STEERING_PROPAGATION_CONTRACT
@@ -29,10 +35,15 @@ export const STEERING_PROPAGATION_CONTRACT = [
29
35
  * section, which carries the strongest positional weight. The contract is
30
36
  * inserted immediately before that closing section rather than after it, so a
31
37
  * stage's final words remain its instruction.
38
+ *
39
+ * The contract is `keepContext`-protected because only the user may amend a run's contract,
40
+ * and an amendment reaches later stages solely through this restatement duty. Compacted away,
41
+ * the run silently reverts to the launch contract while the implementer builds to the amended
42
+ * one — the exact split this pattern exists to prevent.
32
43
  */
33
44
  export function withSteeringPropagation(prompt: string): string {
34
45
  if (prompt.includes("<steering_propagation>")) return prompt;
35
- const tagged = `<steering_propagation>\n${STEERING_PROPAGATION_CONTRACT}\n</steering_propagation>`;
46
+ const tagged = `<steering_propagation>\n${keepContext(STEERING_PROPAGATION_CONTRACT)}\n</steering_propagation>`;
36
47
  const instructionAt = prompt.lastIndexOf("\n\n<instruction>");
37
48
  if (instructionAt === -1) return `${prompt}\n\n${tagged}`;
38
49
  return `${prompt.slice(0, instructionAt)}\n\n${tagged}${prompt.slice(instructionAt)}`;
@@ -69,7 +80,12 @@ export function renderE2eQaVideoReviewGuidance(
69
80
  ].join("\n");
70
81
  }
71
82
 
72
- export const LITERAL_OBJECTIVE_CONTRACT = [
83
+ /**
84
+ * `keepContext`-protected: this is the run's immutable contract and its central prohibition
85
+ * ("You may never widen the contract yourself"). A prohibition deleted from context reads as
86
+ * permission, so losing it turns scope creep into apparently sanctioned work.
87
+ */
88
+ export const LITERAL_OBJECTIVE_CONTRACT = keepContext([
73
89
  "Literal objective contract:",
74
90
  "- The objective and acceptance criteria are the sole literal source of required behavior; the run objective must not contradict them.",
75
91
  "- Only the user may change the contract. A mid-run user message — steering, a follow-up, or resume text — is authoritative: adopt it as required behavior from that point on, and carry it forward under the steering propagation contract. You may never widen the contract yourself; an improvement you thought of is deferred work, not a new criterion.",
@@ -78,7 +94,7 @@ export const LITERAL_OBJECTIVE_CONTRACT = [
78
94
  "- That loud-error preference applies only to enumerated errors. Otherwise accept permissively: do not invent behavior, restrictions, validation errors, required fields, uniqueness/format constraints, or follow-up requirements.",
79
95
  "- Produce named types, shapes, and formats exactly; do not substitute proxies, frozen collections, tuples-for-lists, or wrappers unless required because consumers may check identity.",
80
96
  "- Where behavior is unspecified, preserve input verbatim rather than normalizing, deduplicating, reordering, or rewriting it.",
81
- ].join("\n");
97
+ ].join("\n"));
82
98
 
83
99
  export const REVIEWER_SPEC_VS_OBJECTIVE_GUARD =
84
100
  "External spec/standard conformance alone does not make a wide trigger for an enumerated error defective; classify that spec-vs-objective tension as beyond_objective, not blocking.";
@@ -129,7 +145,12 @@ export const FINDINGS_CONSOLIDATION_CONTRACT = [
129
145
  "Defer only a genuinely blocked or contract-contradicting finding, recording the reason in the receipt.",
130
146
  ].join("\n");
131
147
 
132
- export const SCOPE_DISCIPLINE_CONTRACT = [
148
+ /**
149
+ * `keepContext`-protected: every clause here is a prohibition, and prohibitions are the class
150
+ * compaction erodes first — they are terse, stated once, and low-density next to the objective
151
+ * they bound. Losing this reads as license to keep going.
152
+ */
153
+ export const SCOPE_DISCIPLINE_CONTRACT = keepContext([
133
154
  "Scope discipline:",
134
155
  "- Before writing code, state the goal in one sentence and list the acceptance criteria. That list is the contract. Freeze it.",
135
156
  "- Done means the contract, not \"good.\" When all criteria pass, stop. Polish, refactors, and \"while I'm here\" fixes are new work, not this work.",
@@ -139,7 +160,7 @@ export const SCOPE_DISCIPLINE_CONTRACT = [
139
160
  "- Watch for the tells. \"It would be cleaner if...\", \"we should also...\", \"this really ought to...\" mean you are about to move the goalpost. Stop and check the contract.",
140
161
  "- Prefer the smallest diff that satisfies the contract: fewer files touched, fewer abstractions introduced, no speculative generality for futures nobody asked for.",
141
162
  "- Report three things at the end: what the contract was, evidence each criterion passes, and the deferred list. Scope changes belong in the report, never in the diff.",
142
- ].join("\n");
163
+ ].join("\n"));
143
164
 
144
165
  export const EVIDENCE_CLOSURE_POLICY = [
145
166
  "Convergence flag (stop_review_loop):",
@@ -149,10 +170,16 @@ export const EVIDENCE_CLOSURE_POLICY = [
149
170
  "- If the bounded loop ends first, preserve unresolved findings and remaining work for a human rather than relabeling them.",
150
171
  ].join("\n");
151
172
 
152
- export const WORKTREE_DISCIPLINE_CONTRACT = [
173
+ /**
174
+ * `keepContext`-protected: this binds the stage to a specific checkout, and a checkout is
175
+ * exactly the kind of identifier a stage must not lose. Compacted away during a long run, an
176
+ * agent hitting a lock or dirty state invents a second worktree and the delivered delta lands
177
+ * somewhere the reviewers never look. It is two lines, so protection costs almost nothing.
178
+ */
179
+ export const WORKTREE_DISCIPLINE_CONTRACT = keepContext([
153
180
  "Work in the workflow-designated checkout. Do not create another worktree, clone, or repository copy unless the task requests it; conflicts, locks, dirty state, and failed commands do not authorize one.",
154
181
  "Bring required work found elsewhere into this checkout by applying, cherry-picking, or replaying it before continuing.",
155
- ].join("\n");
182
+ ].join("\n"));
156
183
 
157
184
  export const REVIEW_CODE_DELTA_CONTRACT = [
158
185
  "Code delta integrity:",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.11",
3
+ "version": "0.9.12-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -0,0 +1,46 @@
1
+ /**
2
+ * `keepContext` marks prompt text that compaction must never compress.
3
+ *
4
+ * This is a pure string transform rather than a `ctx.*` primitive: it creates no graph node,
5
+ * has no side effect, and needs no durability, so it is safe to call anywhere a prompt is
6
+ * assembled — including outside a run.
7
+ */
8
+
9
+ export const KEEP_CONTEXT_OPEN_TAG = "<keepContext>";
10
+ export const KEEP_CONTEXT_CLOSE_TAG = "</keepContext>";
11
+
12
+ /**
13
+ * Wrap text so compaction protects it verbatim.
14
+ *
15
+ * Every line of the span, tag lines included, becomes a protected line, and protected lines are
16
+ * removed from the planner's deletion ranges after it responds, so the span survives regardless
17
+ * of the compression ratio. Because the tag lines are protected too, the span is re-detected on
18
+ * each subsequent boundary and stays protected for the life of the session.
19
+ *
20
+ * Tags must sit on their own line, and a span is scoped to one message. User and assistant
21
+ * messages may both protect, so an agent can pin its own core information as well as its prompt;
22
+ * tags inside tool results are inert, so file, page, or command output an agent reads cannot
23
+ * mark itself unreclaimable.
24
+ *
25
+ * Reserve it for text whose loss silently changes behavior — role constraints, acceptance
26
+ * criteria and immutable contracts, explicit prohibitions, and identifiers a stage must not
27
+ * lose such as a target branch, worktree path, or run ID. Compaction ranks lines individually,
28
+ * so a terse constraint loses to the verbose objective it qualifies, and a prohibition deleted
29
+ * from context reads as permission.
30
+ *
31
+ * Do not wrap bulk context. Protected lines count against the keep target rather than raising
32
+ * it, so a large protected span makes the surrounding transcript compress harder; tag the
33
+ * constraint, not the material it applies to, and pass that through files and `reads`.
34
+ *
35
+ * Nesting is flattened rather than rejected: an already-wrapped string is returned unchanged.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * const prompt = `${keepContext("Research only. Do not implement code changes.")}\n\n${question}`;
40
+ * ```
41
+ */
42
+ export function keepContext(text: string): string {
43
+ const trimmed = text.trim();
44
+ if (trimmed.startsWith(KEEP_CONTEXT_OPEN_TAG) && trimmed.endsWith(KEEP_CONTEXT_CLOSE_TAG)) return trimmed;
45
+ return `${KEEP_CONTEXT_OPEN_TAG}\n${trimmed}\n${KEEP_CONTEXT_CLOSE_TAG}`;
46
+ }
@@ -44,6 +44,17 @@ export interface WorkflowRegistry {
44
44
  */
45
45
  export declare const runWorkflow: never;
46
46
  export declare function workflow<const TInputs extends WorkflowInputSchemaMap = Record<never, never>, const TOutputs extends WorkflowOutputSchemaMap = WorkflowOutputSchemaMap, TActualOutputs extends WorkflowOutputsFromSchemas<TOutputs> = WorkflowOutputsFromSchemas<TOutputs>>(spec: AuthoredWorkflowSpec<TInputs, TOutputs, TActualOutputs>): AuthoredWorkflowDefinition<TInputs, TOutputs>;
47
+ /**
48
+ * Wrap prompt text so compaction protects it verbatim, regardless of the compression ratio.
49
+ *
50
+ * Reserve it for text whose loss silently changes behavior: role constraints, acceptance
51
+ * criteria and immutable contracts, explicit prohibitions, and identifiers a stage must not
52
+ * lose. Do not wrap bulk context — protected lines count against the keep target rather than
53
+ * raising it, so a large span makes the surrounding transcript compress harder.
54
+ */
55
+ export declare function keepContext(text: string): string;
56
+ export declare const KEEP_CONTEXT_OPEN_TAG: string;
57
+ export declare const KEEP_CONTEXT_CLOSE_TAG: string;
47
58
  export declare function createRegistry<TDefinitions extends readonly AnyWorkflowDefinition[] = readonly AnyWorkflowDefinition[]>(initial?: TDefinitions): WorkflowRegistry;
48
59
  export declare function normalizeWorkflowName(name: string): string;
49
60
  export declare function workflowNamesEqual(a: string, b: string): boolean;
@@ -53098,6 +53098,18 @@ function workflow(spec) {
53098
53098
  return Object.freeze(branded);
53099
53099
  }
53100
53100
 
53101
+ // dist/builtin/workflows/src/authoring/keep-context.ts
53102
+ var KEEP_CONTEXT_OPEN_TAG = "<keepContext>";
53103
+ var KEEP_CONTEXT_CLOSE_TAG = "</keepContext>";
53104
+ function keepContext(text) {
53105
+ const trimmed = text.trim();
53106
+ if (trimmed.startsWith(KEEP_CONTEXT_OPEN_TAG) && trimmed.endsWith(KEEP_CONTEXT_CLOSE_TAG))
53107
+ return trimmed;
53108
+ return `${KEEP_CONTEXT_OPEN_TAG}
53109
+ ${trimmed}
53110
+ ${KEEP_CONTEXT_CLOSE_TAG}`;
53111
+ }
53112
+
53101
53113
  // dist/builtin/workflows/builtin/shared-prompts.ts
53102
53114
  var STEERING_PROPAGATION_CONTRACT = [
53103
53115
  "Steering propagation contract:",
@@ -53113,7 +53125,7 @@ function withSteeringPropagation(prompt) {
53113
53125
  if (prompt.includes("<steering_propagation>"))
53114
53126
  return prompt;
53115
53127
  const tagged = `<steering_propagation>
53116
- ${STEERING_PROPAGATION_CONTRACT}
53128
+ ${keepContext(STEERING_PROPAGATION_CONTRACT)}
53117
53129
  </steering_propagation>`;
53118
53130
  const instructionAt = prompt.lastIndexOf(`
53119
53131
 
@@ -53153,7 +53165,7 @@ function renderE2eQaVideoReviewGuidance(knownVideoPath) {
53153
53165
  ].join(`
53154
53166
  `);
53155
53167
  }
53156
- var LITERAL_OBJECTIVE_CONTRACT = [
53168
+ var LITERAL_OBJECTIVE_CONTRACT = keepContext([
53157
53169
  "Literal objective contract:",
53158
53170
  "- The objective and acceptance criteria are the sole literal source of required behavior; the run objective must not contradict them.",
53159
53171
  "- Only the user may change the contract. A mid-run user message — steering, a follow-up, or resume text — is authoritative: adopt it as required behavior from that point on, and carry it forward under the steering propagation contract. You may never widen the contract yourself; an improvement you thought of is deferred work, not a new criterion.",
@@ -53163,7 +53175,7 @@ var LITERAL_OBJECTIVE_CONTRACT = [
53163
53175
  "- Produce named types, shapes, and formats exactly; do not substitute proxies, frozen collections, tuples-for-lists, or wrappers unless required because consumers may check identity.",
53164
53176
  "- Where behavior is unspecified, preserve input verbatim rather than normalizing, deduplicating, reordering, or rewriting it."
53165
53177
  ].join(`
53166
- `);
53178
+ `));
53167
53179
  var REVIEWER_SPEC_VS_OBJECTIVE_GUARD = "External spec/standard conformance alone does not make a wide trigger for an enumerated error defective; classify that spec-vs-objective tension as beyond_objective, not blocking.";
53168
53180
  var REVIEWER_OVERIMPLEMENTATION_GUARD = "Treat unrequired validation errors, required fields, uniqueness/format constraints, immutability wrappers, and normalization as required_by_objective defects when they reject permitted inputs or change permitted shapes. Probe at least one contract-permitted input absent from implementation-authored tests.";
53169
53181
  var ACCEPTANCE_MATRIX_CONTRACT = [
@@ -53209,7 +53221,7 @@ var FINDINGS_CONSOLIDATION_CONTRACT = [
53209
53221
  "Defer only a genuinely blocked or contract-contradicting finding, recording the reason in the receipt."
53210
53222
  ].join(`
53211
53223
  `);
53212
- var SCOPE_DISCIPLINE_CONTRACT = [
53224
+ var SCOPE_DISCIPLINE_CONTRACT = keepContext([
53213
53225
  "Scope discipline:",
53214
53226
  "- Before writing code, state the goal in one sentence and list the acceptance criteria. That list is the contract. Freeze it.",
53215
53227
  `- Done means the contract, not "good." When all criteria pass, stop. Polish, refactors, and "while I'm here" fixes are new work, not this work.`,
@@ -53220,7 +53232,7 @@ var SCOPE_DISCIPLINE_CONTRACT = [
53220
53232
  "- Prefer the smallest diff that satisfies the contract: fewer files touched, fewer abstractions introduced, no speculative generality for futures nobody asked for.",
53221
53233
  "- Report three things at the end: what the contract was, evidence each criterion passes, and the deferred list. Scope changes belong in the report, never in the diff."
53222
53234
  ].join(`
53223
- `);
53235
+ `));
53224
53236
  var EVIDENCE_CLOSURE_POLICY = [
53225
53237
  "Convergence flag (stop_review_loop):",
53226
53238
  "- stop_review_loop is the single authoritative convergence signal; the harness trusts it without recomputing approval from findings, priorities, or requirements_traceability.",
@@ -53229,11 +53241,11 @@ var EVIDENCE_CLOSURE_POLICY = [
53229
53241
  "- If the bounded loop ends first, preserve unresolved findings and remaining work for a human rather than relabeling them."
53230
53242
  ].join(`
53231
53243
  `);
53232
- var WORKTREE_DISCIPLINE_CONTRACT = [
53244
+ var WORKTREE_DISCIPLINE_CONTRACT = keepContext([
53233
53245
  "Work in the workflow-designated checkout. Do not create another worktree, clone, or repository copy unless the task requests it; conflicts, locks, dirty state, and failed commands do not authorize one.",
53234
53246
  "Bring required work found elsewhere into this checkout by applying, cherry-picking, or replaying it before continuing."
53235
53247
  ].join(`
53236
- `);
53248
+ `));
53237
53249
  var REVIEW_CODE_DELTA_CONTRACT = [
53238
53250
  "Code delta integrity:",
53239
53251
  "- Inspect the delivered checkout with version-control tooling (for git: `git worktree list`, `git status --short`, baseline diff, staged diff, and untracked files) and prove an objective-related delta exists before trusting receipts.",
@@ -54948,7 +54960,7 @@ function renderReviewerPrompt(args) {
54948
54960
  ].join(`
54949
54961
  `)],
54950
54962
  ["objective", [
54951
- "Act as an independent, skeptical, technically fair reviewer. Inspect and report; do not implement. Protect correctness, security, performance, maintainability, and full objective completion without bikeshedding.",
54963
+ keepContext("Act as an independent, skeptical, technically fair reviewer. Inspect and report; do not implement. Protect correctness, security, performance, maintainability, and full objective completion without bikeshedding."),
54952
54964
  args.reviewerRole,
54953
54965
  args.focus,
54954
54966
  "Review the delivered change against the run objective stored in the goal ledger.",
@@ -57037,7 +57049,7 @@ function forkContinuationOptions3(sessionFile) {
57037
57049
  }
57038
57050
  function renderResearchPromptRefinementPrompt(args) {
57039
57051
  return taggedPrompt4([
57040
- ["acceptance_criteria", args.acceptanceCriteria],
57052
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
57041
57053
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
57042
57054
  args.workflowCwdContext,
57043
57055
  [
@@ -57051,7 +57063,7 @@ function renderResearchPromptRefinementPrompt(args) {
57051
57063
  ["objective", `Research the full requested task: ${args.request}`],
57052
57064
  [
57053
57065
  "output",
57054
- "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec."
57066
+ keepContext("Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.")
57055
57067
  ],
57056
57068
  [
57057
57069
  "instruction",
@@ -57061,7 +57073,7 @@ function renderResearchPromptRefinementPrompt(args) {
57061
57073
  }
57062
57074
  function renderResearchPrompt(args) {
57063
57075
  return taggedPrompt4([
57064
- ["acceptance_criteria", args.acceptanceCriteria],
57076
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
57065
57077
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
57066
57078
  args.workflowCwdContext,
57067
57079
  [
@@ -57079,7 +57091,7 @@ function renderResearchPrompt(args) {
57079
57091
  "Return the complete research report as your final message. Downstream implementation and review stages read it from there.",
57080
57092
  "Produce a complete Markdown report with codebase and useful online/contextual findings, implementation guidance, relevant files/tests/docs, unresolved-finding analysis, and validation recommendations. Lead with conclusions; keep facts, caveats, and implementation-relevant next steps; drop background and repetition.",
57081
57093
  "Before reporting progress, audit each claim against a tool result from this session. Report only work you can point to evidence for; say so explicitly when something is unverified.",
57082
- "Do not author an RFC/spec or implement code changes."
57094
+ keepContext("This stage researches only. Do not author an RFC/spec or implement code changes.")
57083
57095
  ].join(`
57084
57096
  `)
57085
57097
  ],
@@ -57097,7 +57109,7 @@ import { join as join18, resolve as resolve3 } from "node:path";
57097
57109
  // dist/builtin/workflows/builtin/ralph-reviewer-prompt.ts
57098
57110
  function renderRalphReviewerPrompt(args) {
57099
57111
  return taggedPrompt4([
57100
- ["acceptance_criteria", args.acceptanceCriteria],
57112
+ ["acceptance_criteria", keepContext(args.acceptanceCriteria)],
57101
57113
  [
57102
57114
  "review_context",
57103
57115
  [
@@ -57183,7 +57195,7 @@ function renderForkedResearchPromptRefinementPrompt(args) {
57183
57195
  ],
57184
57196
  [
57185
57197
  "output",
57186
- "Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec."
57198
+ keepContext("Return only one concise, complete codebase and online research question. Do not implement code changes or write an RFC/spec.")
57187
57199
  ],
57188
57200
  [
57189
57201
  "instruction",
@@ -57206,7 +57218,7 @@ function renderForkedResearchPrompt(args) {
57206
57218
  [
57207
57219
  "Return the rewritten research report for this iteration as your final message.",
57208
57220
  "Restate the still-applicable findings in full rather than referring back to the previous iteration's artifact; the current artifact and transcript are the authoritative records for this iteration.",
57209
- "Do not author an RFC/spec or implement code changes."
57221
+ keepContext("This stage researches only. Do not author an RFC/spec or implement code changes.")
57210
57222
  ].join(`
57211
57223
  `)
57212
57224
  ],
@@ -57482,7 +57494,7 @@ async function runRalphWorkflow(ctx, options) {
57482
57494
  const orchestratorReportPath = join18(artifactDir, "orchestrator-report.md");
57483
57495
  const orchestratorForkOptions = forkContinuationOptions3(previousOrchestratorSessionFile);
57484
57496
  const orchestratorPrompt = orchestratorForkOptions.forkFromSessionFile === undefined ? taggedPrompt4([
57485
- ["acceptance_criteria", acceptanceCriteria],
57497
+ ["acceptance_criteria", keepContext(acceptanceCriteria)],
57486
57498
  ["literal_contract", LITERAL_OBJECTIVE_CONTRACT],
57487
57499
  ["acceptance_matrix", ACCEPTANCE_MATRIX_CONTRACT],
57488
57500
  ["divergence_audit", CONTRACT_FIDELITY_AUDIT],
@@ -58147,11 +58159,14 @@ __export(exports_sdk_surface, {
58147
58159
  run: () => run,
58148
58160
  resolveInputs: () => resolveInputs,
58149
58161
  normalizeWorkflowName: () => normalizeWorkflowName,
58162
+ keepContext: () => keepContext,
58150
58163
  createStore: () => createStore,
58151
58164
  createRegistry: () => createRegistry,
58152
58165
  createCancellationRegistry: () => createCancellationRegistry,
58153
58166
  cancellationRegistry: () => cancellationRegistry,
58154
58167
  NON_INTERACTIVE_WORKFLOW_POLICY: () => NON_INTERACTIVE_WORKFLOW_POLICY,
58168
+ KEEP_CONTEXT_OPEN_TAG: () => KEEP_CONTEXT_OPEN_TAG,
58169
+ KEEP_CONTEXT_CLOSE_TAG: () => KEEP_CONTEXT_CLOSE_TAG,
58155
58170
  INTERACTIVE_WORKFLOW_POLICY: () => INTERACTIVE_WORKFLOW_POLICY,
58156
58171
  GraphFrontierTracker: () => GraphFrontierTracker
58157
58172
  });
@@ -75441,7 +75456,7 @@ function renderResult(result, opts) {
75441
75456
  }
75442
75457
 
75443
75458
  // dist/builtin/workflows/src/extension/workflow-prompts.ts
75444
- var WORKFLOW_TOOL_DESCRIPTION = "Run named builtin, project, user, or package workflows; custom definitions may import reusable project/package workflows or builtin definitions from @bastani/workflows/builtin and nest them with ctx.workflow(...), including deeper composition within the configured maxDepth; " + "when workflow execution fits but another shape would better achieve the task, author a custom TypeScript workflow({...}) inline with normal coding tools, reload it, and run it; after successfully creating and reloading a newly authored custom workflow, report the folder containing its generated code as 'Custom workflow created. You can inspect its code at: <workflow-folder-path>'; do this only for newly created custom workflows, never builtin or pre-existing workflows; " + "discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, " + "send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. " + "For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. " + "For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, " + "quote the exact path without rewriting separators (Windows backslashes are valid), " + "then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview. " + "Use action 'models' to inspect models in the configured catalog; the result is a configured-auth snapshot showing what's present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. " + "When authoring a workflow that should dynamically select a model, first call workflow({ action: 'models' }) to inspect the configured catalog, then select from the returned provider/id entries considering the isCurrent marker and available thinking levels.";
75459
+ var WORKFLOW_TOOL_DESCRIPTION = "Run named builtin, project, user, or package workflows; custom definitions may import reusable project/package workflows or builtin definitions from @bastani/workflows/builtin and nest them with ctx.workflow(...), including deeper composition within the configured maxDepth; " + "when workflow execution fits but another shape would better achieve the task, author a custom TypeScript workflow({...}) inline with normal coding tools, reload it, and run it; after successfully creating and reloading a newly authored custom workflow, report the folder containing its generated code as 'Custom workflow created. You can inspect its code at: <workflow-folder-path>'; do this only for newly created custom workflows, never builtin or pre-existing workflows; " + "discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, " + "send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. " + "For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. " + "Wrap critical parts of run inputs and steering messages in <keepContext>...</keepContext> so compaction preserves them verbatim in the stages that inherit them; tag role constraints, prohibitions, must-hold criteria, and identifiers, not background or bulk reference material. " + "For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, " + "quote the exact path without rewriting separators (Windows backslashes are valid), " + "then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview. " + "Use action 'models' to inspect models in the configured catalog; the result is a configured-auth snapshot showing what's present in the registry with configured authentication, not proof of credentials, entitlements, OAuth freshness, or live provider access. " + "When authoring a workflow that should dynamically select a model, first call workflow({ action: 'models' }) to inspect the configured catalog, then select from the returned provider/id entries considering the isCurrent marker and available thinking levels.";
75445
75460
  var DEFAULT_PROMPT_GUIDANCE = [
75446
75461
  `**Workflows**: Treat workflows as the default execution path for any non-trivial task and for any request that has inherent structure plus an objective you can make verifiable. Use the \`workflow\` tool for existing named workflows; when the task needs a graph that is not installed, author a custom TypeScript \`workflow({...})\` inline with normal coding tools, reload workflow resources, and run it.
75447
75462
  - Workflow fit check: prefer a workflow for implementation, build, debug/diagnosis, bug-fix, migration, new-feature, scoped multi-file, or docs/code changes with validation; and whenever there are multiple steps, dependencies, handoffs, uncertainty, review/validation needs, long-running work, measurable done criteria, or an outcome that needs evidence.
@@ -75469,6 +75484,8 @@ var DEFAULT_PROMPT_GUIDANCE = [
75469
75484
  - Only skip workflows for tiny, deterministic, low-risk answers or direct edits where stage tracking clearly costs more than it adds, typically a single-file/no-test/no-review change or a simple answer.`,
75470
75485
  `**Workflow discovery and lifecycle**:
75471
75486
  - For unfamiliar named workflows, discover with \`action: "list"\`, inspect with \`action: "get"\` or \`action: "inputs"\`, and run with \`action: "run"\`, \`workflow\`, and validated \`inputs\`; do not invent workflow names or input keys.
75487
+ - Wrap critical parts of the \`inputs\` you pass — objective, prompt, acceptance criteria — in \`<keepContext>\` / \`</keepContext>\`. Tagged text survives compaction verbatim regardless of the compression ratio, and stages inherit it because workflows inject your inputs into their prompts. A long stage is compacted repeatedly, and compaction ranks lines individually: a verbose restated objective outlives the one line that bounds it, so a stage can end up executing a coherent but unbounded version of what you asked for. Decide this per launch rather than tagging everything — tag role constraints, prohibitions, must-hold criteria, and identifiers such as a target branch, worktree path, or issue number; leave background, quoted issue text, and reference material untagged and pass bulk through files and \`reads\`. Protected lines count against the keep target rather than raising it, so an over-tagged input makes the evidence stages need compress harder.
75488
+ - Tag steering the same way. A \`send\` amendment is authoritative and stages must carry it forward, but it arrives late in an already-long session and is exactly one short message competing with the whole transcript; \`<keepContext>\` around the requirement keeps it from being compacted away before the stage acts on it.
75472
75489
  - In interactive chat, named workflow launches run in the background. Run \`/workflow connect <run>\` to see agents working and chat with and steer each stage. Inspection and control calls (\`status\`, \`stages\`, \`stage\`, \`transcript\`, \`send\`, \`pause\`, \`resume\`, \`interrupt\`, \`quit\`) remain available while work runs.
75473
75490
  - \`workflow send\` is nonterminal-only: once the authoritative root run has completed, failed, skipped, been cancelled or killed, or ended terminal-blocked, start a new workflow if tracked work remains. Proceed inline only when the remaining task is small, deterministic, and low risk. Use explicit \`/workflow attach <run> <stage>\` for user-driven post-mortem chat; it does not resume or modify workflow execution.
75474
75491
  - Natural-language instructions to create or use a worktree do not enable runner isolation. A named workflow must declare and implement any worktree and feature-branch inputs it supports; inspect its inputs and pass a distinct path and branch for each concurrent item. Reusable \`baseBranch\` binding creates a missing target as a detached checkout from that base or reuses an existing same-repository worktree as-is; neither case checks out a separate feature-branch input.
@@ -75487,6 +75504,7 @@ var DEFAULT_PROMPT_GUIDANCE = [
75487
75504
  - Every workflow invocation automatically receives one stable, non-default Intercom group. Intercom-capable stages inherit it, nested workflows keep the top-level invocation group, and subagents inherit their launching stage's group. Do not mint or thread group names through ordinary workflow definitions. Use an explicit \`group\` only for an intentional override: a named group or \`group: true\` creates a subgroup, while \`group: "default"\` opts into the shared default group. Stages without Intercom access receive no group, and \`contact_supervisor\` still crosses group boundaries for authorized escalation.
75488
75505
  - Prefer \`ctx.tool(name, args, fn)\` for workflow-owned TypeScript operations with side effects, such as filesystem writes, network mutations, and external API actions. It creates a tracked, non-attachable durable graph node before invoking \`fn\`; it may be used before, between, after, or without model stages. A completed \`ctx.tool\` call is durably checkpointed with its serializable result, so resume replays that result without rerunning \`fn\`. For checks that may fail during a bounded repair loop, opt into \`failureMode: "return"\`, branch on the typed outcome, and pass only the needed error fields to the repair stage or artifact yourself; Atomic does not inject failure evidence into later prompts. Cancellation and storage faults still throw. Keep pure computation and side-effect-free transformations as ordinary TypeScript. Do not wrap agent-stage internals or every function call indiscriminately; this rule applies to side effects orchestrated directly by the workflow definition.
75489
75506
  - Pass large stage context through files/artifacts and \`reads\` with an explicit \`Read the file at <path>...\` prompt rather than large \`previous\` payloads or injected session history.
75507
+ - Wrap critical stage-prompt content in \`<keepContext>\` / \`</keepContext>\`. Tagged content survives compression verbatim regardless of the compression ratio, and every line of the span including the tag lines is protected. A long-running stage will be compacted, and compaction ranks lines individually: an objective is verbose and restated while the constraint that narrows it is usually one line, so the constraint is the cheaper deletion and what survives reads as broader permission than intended. Tag role constraints that bound a stage to part of the work, acceptance criteria and immutable contracts, explicit prohibitions, and identifiers a stage must not lose such as a target branch, worktree path, or run ID. Do not tag bulk context: protected lines count against the keep target rather than raising it, so a large protected span makes the surrounding transcript compress harder — tag the constraint, not the material it applies to.
75490
75508
  - Separate implementation/review acceptance from explicitly authorized final actions such as PR/MR/review creation, release tagging, deployment, or publication. Stop implementation loops once acceptance is proven and carry a remaining final action separately.
75491
75509
  - A model stage sees its local prompt, artifacts, tools, and reads, not the graph name or surrounding implementation. State the concrete action, evidence, and success criteria directly.`
75492
75510
  ];
@@ -4,6 +4,7 @@ export const WORKFLOW_TOOL_DESCRIPTION =
4
4
  "discover with list/get/inputs/models, list session runs with status (no runId; statusFilter narrows the list), inspect status/stages/stage details, " +
5
5
  "send prompt answers or steering only while the root workflow is nonterminal, pause/resume/interrupt/quit runs, and reload workflow resources. " +
6
6
  "For large stage handoffs, write context to files/artifacts, pass paths via reads, and prompt downstream agents to 'Read the file at <path>...' instead of injecting large previous text. " +
7
+ "Wrap critical parts of run inputs and steering messages in <keepContext>...</keepContext> so compaction preserves them verbatim in the stages that inherit them; tag role constraints, prohibitions, must-hold criteria, and identifiers, not background or bulk reference material. " +
7
8
  "For transcripts, prefer status/stages/stage to get sessionFile/transcriptPath, " +
8
9
  "quote the exact path without rewriting separators (Windows backslashes are valid), " +
9
10
  "then search it with rg/grep and read small ranges; transcript is path-only by default when sessionFile/transcriptPath exists, explicit tail/limit returns bounded previews, and missing transcript paths fall back to a small preview. " +
@@ -37,6 +38,8 @@ export const DEFAULT_PROMPT_GUIDANCE: string[] = [
37
38
  - Only skip workflows for tiny, deterministic, low-risk answers or direct edits where stage tracking clearly costs more than it adds, typically a single-file/no-test/no-review change or a simple answer.`,
38
39
  `**Workflow discovery and lifecycle**:
39
40
  - For unfamiliar named workflows, discover with \`action: "list"\`, inspect with \`action: "get"\` or \`action: "inputs"\`, and run with \`action: "run"\`, \`workflow\`, and validated \`inputs\`; do not invent workflow names or input keys.
41
+ - Wrap critical parts of the \`inputs\` you pass — objective, prompt, acceptance criteria — in \`<keepContext>\` / \`</keepContext>\`. Tagged text survives compaction verbatim regardless of the compression ratio, and stages inherit it because workflows inject your inputs into their prompts. A long stage is compacted repeatedly, and compaction ranks lines individually: a verbose restated objective outlives the one line that bounds it, so a stage can end up executing a coherent but unbounded version of what you asked for. Decide this per launch rather than tagging everything — tag role constraints, prohibitions, must-hold criteria, and identifiers such as a target branch, worktree path, or issue number; leave background, quoted issue text, and reference material untagged and pass bulk through files and \`reads\`. Protected lines count against the keep target rather than raising it, so an over-tagged input makes the evidence stages need compress harder.
42
+ - Tag steering the same way. A \`send\` amendment is authoritative and stages must carry it forward, but it arrives late in an already-long session and is exactly one short message competing with the whole transcript; \`<keepContext>\` around the requirement keeps it from being compacted away before the stage acts on it.
40
43
  - In interactive chat, named workflow launches run in the background. Run \`/workflow connect <run>\` to see agents working and chat with and steer each stage. Inspection and control calls (\`status\`, \`stages\`, \`stage\`, \`transcript\`, \`send\`, \`pause\`, \`resume\`, \`interrupt\`, \`quit\`) remain available while work runs.
41
44
  - \`workflow send\` is nonterminal-only: once the authoritative root run has completed, failed, skipped, been cancelled or killed, or ended terminal-blocked, start a new workflow if tracked work remains. Proceed inline only when the remaining task is small, deterministic, and low risk. Use explicit \`/workflow attach <run> <stage>\` for user-driven post-mortem chat; it does not resume or modify workflow execution.
42
45
  - Natural-language instructions to create or use a worktree do not enable runner isolation. A named workflow must declare and implement any worktree and feature-branch inputs it supports; inspect its inputs and pass a distinct path and branch for each concurrent item. Reusable \`baseBranch\` binding creates a missing target as a detached checkout from that base or reuses an existing same-repository worktree as-is; neither case checks out a separate feature-branch input.
@@ -55,6 +58,7 @@ export const DEFAULT_PROMPT_GUIDANCE: string[] = [
55
58
  - Every workflow invocation automatically receives one stable, non-default Intercom group. Intercom-capable stages inherit it, nested workflows keep the top-level invocation group, and subagents inherit their launching stage's group. Do not mint or thread group names through ordinary workflow definitions. Use an explicit \`group\` only for an intentional override: a named group or \`group: true\` creates a subgroup, while \`group: "default"\` opts into the shared default group. Stages without Intercom access receive no group, and \`contact_supervisor\` still crosses group boundaries for authorized escalation.
56
59
  - Prefer \`ctx.tool(name, args, fn)\` for workflow-owned TypeScript operations with side effects, such as filesystem writes, network mutations, and external API actions. It creates a tracked, non-attachable durable graph node before invoking \`fn\`; it may be used before, between, after, or without model stages. A completed \`ctx.tool\` call is durably checkpointed with its serializable result, so resume replays that result without rerunning \`fn\`. For checks that may fail during a bounded repair loop, opt into \`failureMode: "return"\`, branch on the typed outcome, and pass only the needed error fields to the repair stage or artifact yourself; Atomic does not inject failure evidence into later prompts. Cancellation and storage faults still throw. Keep pure computation and side-effect-free transformations as ordinary TypeScript. Do not wrap agent-stage internals or every function call indiscriminately; this rule applies to side effects orchestrated directly by the workflow definition.
57
60
  - Pass large stage context through files/artifacts and \`reads\` with an explicit \`Read the file at <path>...\` prompt rather than large \`previous\` payloads or injected session history.
61
+ - Wrap critical stage-prompt content in \`<keepContext>\` / \`</keepContext>\`. Tagged content survives compression verbatim regardless of the compression ratio, and every line of the span including the tag lines is protected. A long-running stage will be compacted, and compaction ranks lines individually: an objective is verbose and restated while the constraint that narrows it is usually one line, so the constraint is the cheaper deletion and what survives reads as broader permission than intended. Tag role constraints that bound a stage to part of the work, acceptance criteria and immutable contracts, explicit prohibitions, and identifiers a stage must not lose such as a target branch, worktree path, or run ID. Do not tag bulk context: protected lines count against the keep target rather than raising it, so a large protected span makes the surrounding transcript compress harder — tag the constraint, not the material it applies to.
58
62
  - Separate implementation/review acceptance from explicitly authorized final actions such as PR/MR/review creation, release tagging, deployment, or publication. Stop implementation loops once acceptance is proven and carry a remaining final action separately.
59
63
  - A model stage sees its local prompt, artifacts, tools, and reads, not the graph name or surrounding implementation. State the concrete action, evidence, and success criteria directly.`,
60
64
  ];
@@ -6,6 +6,7 @@
6
6
  */
7
7
 
8
8
  export type { Static, TSchema } from "typebox";
9
+ export { KEEP_CONTEXT_CLOSE_TAG, KEEP_CONTEXT_OPEN_TAG, keepContext } from "./authoring/keep-context.js";
9
10
  export { workflow } from "./authoring/workflow.js";
10
11
 
11
12
  const REMOVED_RUN_WORKFLOW_MESSAGE =
@@ -12,7 +12,7 @@ import type { ModelRuntime } from "./model-runtime.ts";
12
12
  import type { PathMetadata } from "./package-manager.ts";
13
13
  import type { PromptTemplate } from "./prompt-templates.ts";
14
14
  import type { ResourceLoader } from "./resource-loader.ts";
15
- import type { BranchSummaryEntry, SessionManager } from "./session-manager.js";
15
+ import type { BranchSummaryEntry, SessionManager } from "./session-manager.ts";
16
16
  import type { SettingsManager } from "./settings-manager.ts";
17
17
  import type { BuildSystemPromptOptions } from "./system-prompt.ts";
18
18
  import type { BashOperations } from "./tools/bash.ts";