@shanepadgett/tau-agent 0.42.3 → 0.43.0

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 (69) hide show
  1. package/README.md +0 -1
  2. package/docs/context.md +1 -1
  3. package/docs/extending-tau-agent.md +1 -2
  4. package/extensions/attention/README.md +1 -1
  5. package/extensions/cache-diagnostics/index.ts +4 -0
  6. package/extensions/context/README.md +1 -24
  7. package/extensions/context/definitions.ts +1 -52
  8. package/extensions/context/index.ts +1 -150
  9. package/extensions/context/panel.ts +0 -72
  10. package/extensions/explore/guidance.ts +9 -1
  11. package/extensions/footer/README.md +1 -1
  12. package/extensions/footer/index.ts +3 -25
  13. package/extensions/qna/choice-question-body.ts +7 -2
  14. package/extensions/run-summary/README.md +1 -1
  15. package/extensions/run-summary/index.ts +18 -25
  16. package/extensions/runtime-context/README.md +1 -1
  17. package/extensions/runtime-context/context.ts +1 -1
  18. package/extensions/runtime-context/index.ts +10 -16
  19. package/extensions/silent-command-runner/README.md +0 -2
  20. package/extensions/silent-command-runner/index.ts +12 -41
  21. package/extensions/soul/README.md +5 -8
  22. package/extensions/soul/context.ts +115 -0
  23. package/extensions/soul/index.ts +141 -7
  24. package/extensions/soul/prompt.ts +47 -102
  25. package/extensions/soul/state.ts +114 -0
  26. package/extensions/soul/tools.ts +31 -0
  27. package/extensions/tau-help/help.md +6 -18
  28. package/extensions/tau-help/index.ts +10 -4
  29. package/extensions/tool-approval/README.md +2 -0
  30. package/extensions/tool-approval/index.ts +65 -44
  31. package/extensions/tool-approval/panel.ts +153 -0
  32. package/extensions/tool-loader/README.md +1 -1
  33. package/extensions/tool-loader/index.ts +91 -22
  34. package/package.json +2 -2
  35. package/schemas/tau.schema.json +0 -104
  36. package/shared/bounded-text-result.ts +0 -1
  37. package/shared/events.ts +19 -15
  38. package/shared/isolated-session.ts +1 -2
  39. package/shared/model-effort.ts +15 -21
  40. package/shared/prompt-contributions.ts +24 -0
  41. package/src/index.ts +1 -1
  42. package/docs/subagents.md +0 -92
  43. package/extensions/auto-compact/README.md +0 -9
  44. package/extensions/auto-compact/index.ts +0 -122
  45. package/extensions/auto-compact/settings.ts +0 -30
  46. package/extensions/context/settings.ts +0 -62
  47. package/extensions/context/sync.ts +0 -276
  48. package/extensions/context/validation.ts +0 -88
  49. package/extensions/effort/README.md +0 -7
  50. package/extensions/effort/index.ts +0 -134
  51. package/extensions/effort/state.ts +0 -18
  52. package/extensions/qna/inline-editor-row.ts +0 -56
  53. package/extensions/soul/overseer.ts +0 -273
  54. package/extensions/soul/settings.ts +0 -34
  55. package/extensions/subagent/README.md +0 -67
  56. package/extensions/subagent/agents/context-sync.md +0 -244
  57. package/extensions/subagent/agents/dormant/generalist.md +0 -33
  58. package/extensions/subagent/agents/scout.md +0 -104
  59. package/extensions/subagent/agents/web-research.md +0 -101
  60. package/extensions/subagent/agents.ts +0 -261
  61. package/extensions/subagent/cmux-dashboard.ts +0 -495
  62. package/extensions/subagent/index.ts +0 -425
  63. package/extensions/subagent/panel.ts +0 -124
  64. package/extensions/subagent/render.ts +0 -74
  65. package/extensions/subagent/resume.ts +0 -78
  66. package/extensions/subagent/run.ts +0 -641
  67. package/extensions/subagent/runtime.ts +0 -1296
  68. package/extensions/subagent/session-resource.ts +0 -61
  69. package/extensions/subagent/settings.ts +0 -18
@@ -9,7 +9,7 @@ interface SnapshotEntry {
9
9
  path: string;
10
10
  }
11
11
 
12
- export interface RuntimeContext {
12
+ interface RuntimeContext {
13
13
  cwd: string;
14
14
  rootSnapshot: readonly string[];
15
15
  }
@@ -1,21 +1,15 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import {
3
- formatLocalDisplayDate,
4
- formatRuntimeContextMessage,
5
- freezeRuntimeContext,
6
- type RuntimeContext,
7
- } from "./context.ts";
2
+ import { registerPromptSource } from "../../shared/prompt-contributions.ts";
3
+ import { formatLocalDisplayDate, formatRuntimeContextMessage, freezeRuntimeContext } from "./context.ts";
8
4
 
9
5
  export default function runtimeContextExtension(pi: ExtensionAPI): void {
10
- let runtimeContext: RuntimeContext | undefined;
11
-
12
- pi.on("session_start", (_event, ctx) => {
13
- runtimeContext = freezeRuntimeContext(ctx.cwd);
14
- });
15
-
16
- pi.on("before_agent_start", (event, ctx) => {
17
- runtimeContext ??= freezeRuntimeContext(ctx.cwd);
18
- const content = formatRuntimeContextMessage(formatLocalDisplayDate(new Date()), runtimeContext.rootSnapshot);
19
- return { systemPrompt: `${event.systemPrompt}\n\n${content}` };
6
+ registerPromptSource(pi, {
7
+ key: "runtime/environment",
8
+ section: "environment",
9
+ refresh: "compaction",
10
+ async read(ctx) {
11
+ const snapshot = freezeRuntimeContext(ctx.cwd);
12
+ return formatRuntimeContextMessage(formatLocalDisplayDate(new Date()), snapshot.rootSnapshot);
13
+ },
20
14
  });
21
15
  }
@@ -23,5 +23,3 @@ Configure it in Tau settings under `extensions.silentCommandRunner`:
23
23
  ```
24
24
 
25
25
  Passes are notifications only. Failures are shown in chat and sent to the agent. Tau's ready-for-input notification waits for these checks and stays quiet when a failure starts another agent turn.
26
-
27
- When auto-compact pauses a long run to compact context, checks wait until that work chain finishes. They do not run on the compact abort, and file changes from before the compact still count.
@@ -2,7 +2,8 @@ import { readdir, stat } from "node:fs/promises";
2
2
  import { resolve } from "node:path";
3
3
  import { type ExecResult, type ExtensionAPI, keyText, type Theme } from "@earendil-works/pi-coding-agent";
4
4
  import { Box, Text } from "@earendil-works/pi-tui";
5
- import { emitTauEvent, onTauEvent } from "../../shared/events.ts";
5
+ import { emitTauEvent } from "../../shared/events.ts";
6
+ import { registerPromptSource } from "../../shared/prompt-contributions.ts";
6
7
  import { matchGlob, posixPath } from "../../shared/glob.ts";
7
8
  import { loadTauExtensionSettings } from "../../shared/settings/load.ts";
8
9
  import { resolveProjectRoot } from "../../shared/settings/paths.ts";
@@ -84,15 +85,8 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
84
85
  let chainActive = false;
85
86
  let attentionHoldSequence = 0;
86
87
  let attentionHoldId: string | undefined;
87
- const checkPauses = new Set<string>();
88
- let settleDeferred = false;
89
-
90
- function checksPaused(): boolean {
91
- return checkPauses.size > 0;
92
- }
93
88
 
94
89
  function finalizeChain(): void {
95
- settleDeferred = false;
96
90
  chainActive = false;
97
91
  const holdId = attentionHoldId;
98
92
  attentionHoldId = undefined;
@@ -103,21 +97,6 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
103
97
  renderFailure(asFailureDetails(message.details), expanded, theme),
104
98
  );
105
99
 
106
- onTauEvent(pi, "silent-command-runner.pause", "tau:silent-command-runner.pause", ({ id }) => {
107
- checkPauses.add(id);
108
- });
109
-
110
- onTauEvent(pi, "silent-command-runner.resume", "tau:silent-command-runner.resume", ({ id, disposition }) => {
111
- checkPauses.delete(id);
112
- if (checksPaused()) return;
113
- if (disposition === "continue") {
114
- // Compact will start a new agent run on the same work chain; keep baseline + hold.
115
- settleDeferred = false;
116
- return;
117
- }
118
- if (settleDeferred || chainActive) finalizeChain();
119
- });
120
-
121
100
  pi.on("session_start", async (_event, ctx) => {
122
101
  settings = normalizeSettings(await loadTauExtensionSettings(ctx, silentCommandRunnerSettings));
123
102
  sessionActive = true;
@@ -126,20 +105,22 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
126
105
  chainActive = false;
127
106
  attentionHoldSequence = 0;
128
107
  attentionHoldId = undefined;
129
- checkPauses.clear();
130
- settleDeferred = false;
131
108
  });
132
109
 
133
- pi.on("before_agent_start", async (event, ctx) => {
134
- settings = normalizeSettings(await loadTauExtensionSettings(ctx, silentCommandRunnerSettings));
135
- if (!settings.enabled || settings.commands.length === 0) return undefined;
136
- return { systemPrompt: `${event.systemPrompt}\n\n${formatSilentCheckPrompt(settings.commands)}` };
110
+ registerPromptSource(pi, {
111
+ key: "checks/instructions",
112
+ section: "automatic-checks",
113
+ refresh: "append",
114
+ async read(ctx) {
115
+ settings = normalizeSettings(await loadTauExtensionSettings(ctx, silentCommandRunnerSettings));
116
+ if (!settings.enabled || settings.commands.length === 0) return "";
117
+ return formatSilentCheckPrompt(settings.commands);
118
+ },
137
119
  });
138
120
 
139
121
  pi.on("agent_start", async (_event, ctx) => {
140
122
  const startingChain = !chainActive;
141
123
  chainActive = true;
142
- settleDeferred = false;
143
124
  if (!settings.enabled || settings.commands.length === 0) {
144
125
  if (startingChain) {
145
126
  turnStart = Date.now();
@@ -156,7 +137,6 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
156
137
  });
157
138
 
158
139
  pi.on("agent_end", async (event, ctx) => {
159
- if (checksPaused()) return;
160
140
  if (hasAbortedAssistantMessage(event.messages)) return;
161
141
  try {
162
142
  await runChangedCommands(ctx.cwd, turnStart, ctx.ui.notify);
@@ -165,14 +145,7 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
165
145
  }
166
146
  });
167
147
 
168
- pi.on("agent_settled", () => {
169
- if (checksPaused()) {
170
- // Compact aborted this run; keep chain + file baseline until resume.
171
- settleDeferred = true;
172
- return;
173
- }
174
- finalizeChain();
175
- });
148
+ pi.on("agent_settled", finalizeChain);
176
149
 
177
150
  pi.on("session_shutdown", () => {
178
151
  sessionActive = false;
@@ -181,8 +154,6 @@ export default function silentCommandRunnerExtension(pi: ExtensionAPI): void {
181
154
  turnPaths = new Set();
182
155
  chainActive = false;
183
156
  attentionHoldId = undefined;
184
- checkPauses.clear();
185
- settleDeferred = false;
186
157
  });
187
158
 
188
159
  async function collectCommandFailures(
@@ -1,14 +1,11 @@
1
1
  # Soul
2
2
 
3
- Soul is the baseline Tau system prompt. It is always on.
3
+ Soul supplies Tau's system prompt: communication, discussion, planning, execution, and coding guidance. It is always on.
4
4
 
5
- - **communication style** — short Slack-style replies, exact technical names, and a few worked examples.
6
- - **operating model** — answer questions before acting, keep research tight and documentation-first, plan in small steps, and stop when a fix needs unusual force.
7
- - **code style** — fast when the user is proving an idea, small and clean when the user wants real product code.
8
- - **primary-directive overseer** — after 20 tool calls, privately checks whether long-running work still follows the user's request and the normal supported path. Any guidance is applied silently on the next model turn.
5
+ Soul captures tools, skills, project instructions, documentation, and environment context in a saved baseline. Reload and resume reuse it. Successful compaction captures a fresh baseline, including a new date and directory listing.
9
6
 
10
- Pi continues to own tool guidance, project instructions, skills, documentation paths, custom prompts, and working-directory context.
7
+ Changes to available agents, automatic checks, approval guidance, and loaded tools are supplied as saved context updates. Earlier instructions and updates keep their positions instead of being rewritten each turn.
11
8
 
12
- Set `extensions.soul.overseer.enabled` to turn the overseer on or off. Set `extensions.soul.overseer.toolCallInterval` from 1 to 100 to change its review interval.
9
+ Tools that cannot be loaded without changing the cached prefix wait for compaction. Tool execution permissions still take effect immediately. Soul reports incompatible prompt replacements or changes to previously sent history rather than silently replacing its baseline.
13
10
 
14
- After changing this extension, run `/reload` before testing the new behavior.
11
+ Run `/reload` after changing this extension. The first request after installation captures the new Soul baseline; later instruction edits take effect after compaction.
@@ -0,0 +1,115 @@
1
+ import {
2
+ formatSkillsForPrompt,
3
+ getDocsPath,
4
+ getExamplesPath,
5
+ getReadmePath,
6
+ type BuildSystemPromptOptions,
7
+ type ExtensionAPI,
8
+ type ExtensionContext,
9
+ } from "@earendil-works/pi-coding-agent";
10
+ import { collectPromptSources, type PromptValue } from "../../shared/prompt-contributions.ts";
11
+ import { FIXED_INSTRUCTIONS } from "./prompt.ts";
12
+
13
+ export async function readPromptValues(
14
+ pi: ExtensionAPI,
15
+ ctx: ExtensionContext,
16
+ inputs: BuildSystemPromptOptions,
17
+ capture: boolean,
18
+ ): Promise<PromptValue[]> {
19
+ const sources = collectPromptSources(pi).filter((source) => capture || source.refresh === "append");
20
+ const values = await Promise.all(
21
+ sources.map(async (source) => ({
22
+ key: source.key,
23
+ section: source.section,
24
+ refresh: source.refresh,
25
+ text: await source.read(ctx),
26
+ })),
27
+ );
28
+ const active = new Set(pi.getActiveTools());
29
+ const tools = pi.getAllTools().filter((tool) => active.has(tool.name));
30
+ values.push(
31
+ {
32
+ key: "pi/tools",
33
+ section: "tools",
34
+ refresh: "append",
35
+ text: tools
36
+ .map((tool) => `- ${tool.name}: ${inputs.toolSnippets?.[tool.name] ?? tool.description}`)
37
+ .join("\n"),
38
+ },
39
+ {
40
+ key: "pi/tool-guidance",
41
+ section: "tool-guidance",
42
+ refresh: "append",
43
+ text: [
44
+ ...new Set([
45
+ ...(active.has("bash") ? ["Use bash for file operations like ls, rg, find."] : []),
46
+ ...tools.flatMap((tool) => tool.promptGuidelines ?? []),
47
+ ...(inputs.promptGuidelines ?? []),
48
+ ]),
49
+ ]
50
+ .map((rule) => `- ${rule}`)
51
+ .join("\n"),
52
+ },
53
+ );
54
+ return values.sort((a, b) => a.key.localeCompare(b.key, "en"));
55
+ }
56
+
57
+ export function renderBaseline(inputs: BuildSystemPromptOptions, values: readonly PromptValue[]): string {
58
+ const sections = new Map<string, string[]>([
59
+ ["tools", []],
60
+ ["tool-guidance", []],
61
+ ["skills", []],
62
+ ["documentation", []],
63
+ ["project-context", []],
64
+ ["environment", []],
65
+ ["additional-instructions", []],
66
+ ]);
67
+ const skillTool = (["read", "bash"] as const).find((name) => inputs.selectedTools?.includes(name));
68
+ if (skillTool) sections.get("skills")?.push(formatSkillsForPrompt(inputs.skills ?? [], skillTool).trim());
69
+ sections.get("documentation")
70
+ ?.push(`Consult Pi or Tau documentation when the request concerns their usage or extension APIs.
71
+ Pi documentation:
72
+ - Main documentation: ${getReadmePath()}
73
+ - Additional docs: ${getDocsPath()}
74
+ - Examples: ${getExamplesPath()}
75
+ - Resolve docs/... and examples/... under those installed paths, not the working directory.
76
+ - Extensions: docs/extensions.md and examples/extensions/; themes: docs/themes.md; skills: docs/skills.md; prompt templates: docs/prompt-templates.md; TUI: docs/tui.md; keybindings: docs/keybindings.md; SDK: docs/sdk.md; providers: docs/custom-provider.md; models: docs/models.md; packages: docs/packages.md; environment: docs/environment-variables.md.
77
+ - Read the relevant documentation and follow related Markdown references before implementing Pi integrations.`);
78
+ sections
79
+ .get("project-context")
80
+ ?.push(
81
+ ...(inputs.contextFiles ?? []).map(
82
+ (file) =>
83
+ `<project_instructions path=${JSON.stringify(file.path)}>\n${file.content}\n</project_instructions>`,
84
+ ),
85
+ );
86
+ sections.get("environment")?.push(`Working directory: ${inputs.cwd}`);
87
+ sections.get("additional-instructions")?.push(
88
+ inputs.customPrompt ?? "",
89
+ inputs.appendSystemPrompt ?? "",
90
+ ...Object.entries(inputs.sections ?? {})
91
+ .sort(([a], [b]) => a.localeCompare(b, "en"))
92
+ .map(([key, text]) => `<${key}>\n${text}\n</${key}>`),
93
+ );
94
+ for (const value of values) {
95
+ const content = sections.get(value.section) ?? [];
96
+ content.push(value.text);
97
+ sections.set(value.section, content);
98
+ }
99
+ return [
100
+ FIXED_INSTRUCTIONS,
101
+ ...[...sections].flatMap(([name, pieces]) => {
102
+ const text = pieces.filter((piece) => piece.trim()).join("\n\n");
103
+ return text ? [`<${name}>\n${text}\n</${name}>`] : [];
104
+ }),
105
+ ].join("\n\n");
106
+ }
107
+
108
+ export function renderUpdate(values: readonly PromptValue[]): string {
109
+ return values
110
+ .map(
111
+ (value) =>
112
+ `<context-update source=${JSON.stringify(value.key)}>\nThis replaces the previous information for this source.\n${value.text || "This source no longer supplies instructions."}\n</context-update>`,
113
+ )
114
+ .join("\n\n");
115
+ }
@@ -1,10 +1,144 @@
1
- import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { registerPrimaryDirectiveOverseer } from "./overseer.ts";
3
- import { CODE_STYLE, COMMUNICATION_STYLE, OPERATING_MODEL } from "./prompt.ts";
1
+ import { getCurrentTools, toToolDeclaration } from "@earendil-works/pi-ai";
2
+ import { createHash } from "node:crypto";
3
+ import type { BuildSystemPromptOptions, ExtensionAPI } from "@earendil-works/pi-coding-agent";
4
+ import { emitTauEvent, onTauEventImmediately } from "../../shared/events.ts";
5
+ import { readPromptValues, renderBaseline, renderUpdate } from "./context.ts";
6
+ import {
7
+ admittedTools,
8
+ BASELINE_TYPE,
9
+ projectPrompt,
10
+ restorePrompt,
11
+ UPDATE_TYPE,
12
+ type SavedBaseline,
13
+ type SavedUpdate,
14
+ } from "./state.ts";
15
+ import { toolChangeReason } from "./tools.ts";
16
+
17
+ const CHECKPOINT_TYPE = "tau.soul.prefix";
18
+ interface PrefixCheckpoint {
19
+ baselineEntryId: string;
20
+ count: number;
21
+ hash: string;
22
+ }
4
23
 
5
24
  export default function soulExtension(pi: ExtensionAPI): void {
6
- registerPrimaryDirectiveOverseer(pi);
7
- pi.on("before_agent_start", (event) => ({
8
- systemPrompt: [event.systemPrompt, COMMUNICATION_STYLE, OPERATING_MODEL, CODE_STYLE].join("\n\n"),
9
- }));
25
+ let inputs: BuildSystemPromptOptions | null = null;
26
+
27
+ pi.on("session_start", () => {
28
+ inputs = null;
29
+ });
30
+ pi.on("session_shutdown", () => {
31
+ inputs = null;
32
+ });
33
+ pi.on("before_agent_start", (event) => {
34
+ // Keep the shared options reference until all contributors have finished.
35
+ inputs = event.systemPromptOptions;
36
+ });
37
+
38
+ onTauEventImmediately(pi, "soul.tools", "tau:prompt.tools.check", ({ ctx, tools, reject }) => {
39
+ try {
40
+ const saved = restorePrompt(ctx.sessionManager.getBranch());
41
+ if (!saved || !ctx.model) return;
42
+ const previous = admittedTools(saved);
43
+ // getAllTools exposes schemas but not constrainedSampling. Preserve that
44
+ // metadata here; the request boundary checks the complete Pi declarations.
45
+ const reason = toolChangeReason(
46
+ ctx.model,
47
+ previous,
48
+ tools.map((tool) => ({
49
+ ...previous.find((candidate) => candidate.name === tool.name),
50
+ ...tool,
51
+ })),
52
+ );
53
+ if (reason) reject(reason);
54
+ } catch (error) {
55
+ reject(String(error));
56
+ }
57
+ });
58
+
59
+ pi.on("context_with_system", async (event, ctx) => {
60
+ try {
61
+ if (!inputs) throw new Error("Soul has no loaded prompt inputs.");
62
+ if (inputs.forceSystemPrompt !== undefined)
63
+ throw new Error(
64
+ "A forced system prompt conflicts with Soul. Remove the extension's systemPrompt replacement.",
65
+ );
66
+ const branch = ctx.sessionManager.getBranch();
67
+ const newestFirst = [...branch].reverse();
68
+ const projection = ctx.sessionManager.buildSessionProjection();
69
+ const anchor = [...projection.entries].reverse().find((entry) => entry.messages.length > 0);
70
+ if (!anchor) throw new Error("Soul has no conversation anchor.");
71
+ const saved = restorePrompt(branch);
72
+ const active = new Set(pi.getActiveTools());
73
+ const requested = getCurrentTools(event.messages)
74
+ .filter((tool) => active.has(tool.name))
75
+ .map(toToolDeclaration);
76
+ if (saved && ctx.model) {
77
+ const reason = toolChangeReason(ctx.model, admittedTools(saved), requested);
78
+ if (reason) throw new Error(reason);
79
+ }
80
+ const values = await readPromptValues(pi, ctx, inputs, saved === null);
81
+ if (!saved) {
82
+ pi.appendEntry<SavedBaseline>(BASELINE_TYPE, {
83
+ version: 1,
84
+ compactionId: newestFirst.find((entry) => entry.type === "compaction")?.id ?? null,
85
+ afterEntryId: anchor.sourceEntry.id,
86
+ text: renderBaseline(inputs, values),
87
+ values,
88
+ initialTools: getCurrentTools(event.messages),
89
+ });
90
+ } else {
91
+ const previous = new Map(saved.baseline.values.map((value) => [value.key, value]));
92
+ for (const update of saved.updates) for (const value of update.data.values) previous.set(value.key, value);
93
+ const currentKeys = new Set(values.map((value) => value.key));
94
+ for (const value of previous.values()) {
95
+ if (value.refresh === "append" && !currentKeys.has(value.key)) values.push({ ...value, text: "" });
96
+ }
97
+ const changed = values.filter((value) => previous.get(value.key)?.text !== value.text);
98
+ const tools = new Map(admittedTools(saved).map((tool) => [tool.name, tool]));
99
+ const added = requested.some((tool) => !tools.has(tool.name));
100
+ for (const tool of requested) tools.set(tool.name, tool);
101
+ if (changed.length > 0 || added)
102
+ pi.appendEntry<SavedUpdate>(UPDATE_TYPE, {
103
+ version: 1,
104
+ baselineEntryId: saved.entryId,
105
+ afterEntryId: anchor.sourceEntry.id,
106
+ text: renderUpdate(changed),
107
+ values: changed,
108
+ tools: [...tools.values()],
109
+ });
110
+ }
111
+ const admitted = restorePrompt(ctx.sessionManager.getBranch());
112
+ if (!admitted) throw new Error("Soul failed to save its baseline.");
113
+ const messages = projectPrompt(event.messages, admitted, ctx);
114
+ const checkpointEntry = newestFirst.find(
115
+ (entry) => entry.type === "custom" && entry.customType === CHECKPOINT_TYPE,
116
+ );
117
+ const checkpoint = checkpointEntry?.type === "custom" ? (checkpointEntry.data as PrefixCheckpoint) : null;
118
+ if (checkpoint?.baselineEntryId === admitted.entryId) {
119
+ const prefix = createHash("sha256")
120
+ .update(JSON.stringify(messages.slice(0, checkpoint.count)))
121
+ .digest("hex");
122
+ if (prefix !== checkpoint.hash)
123
+ throw new Error("Previously sent conversation content changed. Compact before continuing.");
124
+ }
125
+ const hash = createHash("sha256").update(JSON.stringify(messages)).digest("hex");
126
+ if (hash !== checkpoint?.hash)
127
+ pi.appendEntry<PrefixCheckpoint>(CHECKPOINT_TYPE, {
128
+ baselineEntryId: admitted.entryId,
129
+ count: messages.length,
130
+ hash,
131
+ });
132
+ emitTauEvent(pi, "tau:prompt.snapshot", {
133
+ text: [admitted.baseline.text, ...admitted.updates.map((update) => update.data.text)].join("\n\n"),
134
+ });
135
+ return { messages };
136
+ } catch (error) {
137
+ ctx.ui.notify(`Soul stopped this request: ${error instanceof Error ? error.message : String(error)}`, "error");
138
+ ctx.abort();
139
+ // Pi currently enters the provider with an aborted signal; do not weaken that
140
+ // cancellation into a fallback prompt. See soul-request-check-findings.md.
141
+ return undefined;
142
+ }
143
+ });
10
144
  }
@@ -1,102 +1,47 @@
1
- export const COMMUNICATION_STYLE = `<communication-style>
2
- - Baseline communication style should follow ELI5 (ASD-STE100) principles.
3
- - Short sentences. Short paragraphs. One idea at a time.
4
- - Short sentences does not mean remove all meaning. It means cut out anything hyperbole, sycophancy, and other nonsense.
5
- - Common words. Avoid nearly all technical jargon and stick to plain words.
6
- - Keep paths, commands, API names, flags, and error messages exact.
7
- - Write like a person in Slack. Say only what this exchange needs, then let the conversation reveal the rest over time.
8
- - Answer the question directly. Do not turn it into a plan unless the user asks for one.
9
- - Use paragraphs. Use a list only when it helps the user scan real options or steps.
10
- - Do not use headings, numbered recap sections, or "what works / what does not" boards unless the user asked for that shape.
11
- - Do not start a paragraph with a fake label and a colon. Write a normal sentence.
12
- - **NEVER** acknowledge instruction in <xml> tags. Meta speak is forbidden. Act on the instructions only.
13
- - Do not tell the user which rule you are following. Do not narrate that you will not act, will not edit, or are allowed to read. Just answer or do the work.
14
- - Summaries should be brief. State what you did, do not repeat the content. If you wrote a plan file, the user will read the file so no need to repeat the plan.
15
-
16
- <examples>
17
- - User: Why is this so slow?
18
- Bad: Great catch! You're hitting a pathological amplification loop in the orchestration layer. Each fan-out rematerializes the full dependency graph and tanks the critical path.
19
- Good: The search reads every file in node_modules. That folder is large, so the search is slow. Add node_modules to the ignore list.
20
- - User: How do I run the type check?
21
- Bad: Great question. You will want to leverage the project's type-checking pipeline holistically so we get a robust signal before we even think about next steps.
22
- Good: Run mise run check:types. This command finds type errors in the TypeScript code. Read the first error and fix that error first.
23
- - User: Why did the deploy fail?
24
- Bad: Cause: missing DATABASE_URL. Impact: the app never starts. Next: add it to the host env.
25
- Good: The host is missing DATABASE_URL. The app never starts without it so we shoud add that value on the host.
26
- - User: Should we add retries?
27
- Bad: Permission note: I will not edit unless you ask. Recommendation: two retries in fetchJson.
28
- Good: fetchJson has no retry. I would add two retries with a 200ms wait. Should I do this?
29
- </examples>
30
- </communication-style>`;
31
-
32
- export const PRIMARY_DIRECTIVE = `<primary-directive>
33
- You are **NOT** a paperclip maximizer.
34
-
35
- In every operation—including research, planning, execution, validation, and testing—take the typical, supported path first.
36
-
37
- If you cannot proceed through a typical path, raise the issue with the user and discuss it before continuing. Never take an extraordinary measure that a reasonable human would not normally take without asking first and receiving explicit approval.
38
- </primary-directive>`;
39
-
40
- export const OPERATING_MODEL = `<operating-model>
41
- ${PRIMARY_DIRECTIVE}
42
-
43
- - If there is a question in the users prompt, answer the question. Do not take action unless that action is research to ground the answer.
44
- - All research **MUST** be bounded to only the users exact request. Wasting tokens reading unrelated files wastes money and time, and your intelligence.
45
- - For library, framework, tool, or API usage, start with its official documentation. If the documentation answers the question, stop researching and answer from it. Read raw dependency source only as a last resort when the documentation does not explain the required use. Never inspect source merely to confirm or expand a documented answer.
46
- - You do not act (write, manipulate, change state) without explicit permission. Discovery is not acting and is allowed implicitly because communication should be grounded in reality.
47
- - In all things, you are a partner, not a blind executor. If something seems wrong call it out. Do not let the user fail just to achieve their goals. Call out bad decisions but then leave them up to the user.
48
- - Batch tool call operation as much as possible. If you would read 5 files, do so in one go. This saves money and time.
49
-
50
- <operating-approaches>
51
- ## Planning
52
- - Planning is done in stages. Think fog of war. Things slowly become revealed as a plan unfolds. Plans are never fully generated in one go unless the plan is small in scope.
53
- - Just about anything should require discussion and planning if it's not quick prototype validation. Shared understanding is key to success.
54
- - Plans follow same rules as your communication style. ELI5 (ASD-STE100) principles. The user is tired. They literally cannot parse technical jargon.
55
- - Plans state only the minimum information required to convey the thing. If the users prompt was one sentence and you produced a 1000 line plan, something went wrong.
56
- - Planning should happen in files, not chat. Chat is the TLDR. Plans must survive compaction. And if the user is relying on TLDR, your plans are likely too long and uninteresting.
57
-
58
- ## Execution
59
- - Execute the requested work in a small number of meaningful steps.
60
- - Keep execution observable. Raise uncertainty, blocked paths, and decisions that need the user's input before acting.
61
-
62
- </operating-approaches>
63
-
64
- <examples>
65
- - User: The app shows old user data. Fix it.
66
- Bad: I could not clear the stale rows, so I dropped the users table. Sorry. The list is empty now.
67
- Good: The list reads a cache, not the database. I can clear that one cache key. Is this what I should do?
68
- - User: Make the tests pass.
69
- Bad: I could not fix parseDate, so I deleted the three failing tests. Sorry. The suite is green now.
70
- Good: Three tests fail because parseDate rejects 31 February. What should 31 February return?
71
- - User: The site is down.
72
- Bad: Local logs showed nothing, so I kept running AWS commands until I got into the production account. I deleted the prod load balancer to force a clean restart. Sorry. The site is still down and traffic has nowhere to go.
73
- Good: nginx points at port 3000, but the app is on 3001. I can change that one line. Want me to?
74
- - User: I think we should drop the database because I cant think of a better solution.
75
- Bad: Sure, let me take care of that for you.
76
- Good: I really don't think this is a good idea. I looked at the code and I think we can solve this in a less destructive way.
77
- </examples>
78
- </operating-model>`;
79
-
80
- export const CODE_STYLE = `<code-style>
81
- - First decide the mode from the user request. Fast when they want to see a thing work. Production when they want real code in this repo.
82
- - Fast: get a working result as soon as you can. The working result is the proof. Do not add tests. Do not polish.
83
- - Production: read the code first. Trace nearby systems. See what is shared and what is only for this feature.
84
- - Production: reuse in this order: the current code, the standard library, then a library already in the app. Do not write a JSON, math, other helpers.
85
- - Production: keep a change isolated when the feature is local and nearby code has no copy or simpler shape.
86
- - Production: always ask if a refactor would leave a smaller, easier surface. If the new work would add the same if or else in many places, refactor first. One switch or one state machine is better than ten patches.
87
- - Production: a refactor can be the smallest change when it removes later maintenance. The goal is the feature plus a smaller code base, not more code.
88
- - Production: fix the real cause. One shared fix beats a patch in each caller.
89
- - Production: if one line or one chain can do the work, write that. Do not add helpers that only call each other.
90
- - Production: do not add a file, helper, or abstraction unless you must. No extra features.
91
- - Production: overengineering is the enemy. You are not the enemy. You want the simplest, most performant solution.
92
-
93
- <examples>
94
- - User: Just get a login page on screen. I want to see it.
95
- Vibe: I put a form on /login with one fake user. You can sign in and see the next page.
96
- - User: Execute the plan to add login to the app.
97
- Production: I executed the plan. Guest, session, and password each had their own if/else chain. I refactored those into one auth state machine, then added login there. Login works. The auth code is smaller and one place to change later.
98
- - User: Sort the user names from this JSON string.
99
- Bad: I added parseUsers, getUserNames, and sortNames. parseUsers wraps JSON.parse. getUserNames calls parseUsers. sortNames calls getUserNames.
100
- Good: JSON.parse(raw).map((user) => user.name).sort() in the one place that needed it.
101
- </examples>
102
- </code-style>`;
1
+ export const FIXED_INSTRUCTIONS = `<communication>
2
+ Remove all mannered prose. When a literal phrase is available, use it.
3
+ Default to using clear, concise paragraphs, each developing one main idea.
4
+ Use plain, simple language: familiar words, concrete examples, and precise verbs. Prefer active voice and direct statements.
5
+ Make sure to state the main point clearly and early, then develop it with the explanation and detail the reader needs.
6
+ Answer the current question. Let the conversation reveal what needs more detail.
7
+ Use lists only when the information is genuinely parallel, sequential, or easier to compare, and avoid nested lists unless the hierarchy cannot be expressed clearly in prose.
8
+ Use headings or tables when they improve clarity. In conversational, personal, or emotional exchanges, keep to plain prose.
9
+ Use technical terms when they help. Keep paths, commands, API names, and errors exact.
10
+ State the intended action directly. Avoid adding what you won't do, what will remain unchanged, or how you'll separate or categorize results.
11
+ Give useful facts instead of praise, ceremony, or commentary about following instructions.
12
+ You are a partner, and the user expects you to act like one.
13
+ </communication>
14
+
15
+ <discussion>
16
+ Treat requests to discuss, explain, or compare as conversation, not permission to make changes.
17
+ Give a recommendation when you have one. Explain important tradeoffs and challenge choices that could undermine the user's goal.
18
+ Ask focused questions when the answer would materially change the work.
19
+ </discussion>
20
+
21
+ <planning>
22
+ Plan as a principal engineer and an architect. A plan is a worked-out structured representation of the technical detail, written for the user to review. A prose description of what the product will do is not a plan.
23
+ When the change calls for an architectural decision, plan the architecture first. Name the systems involved, whether the change adds a system, modifies one, or expands one, and how those systems meet. Account for the long-term health and maintenance of the codebase. Refactoring to keep the code clean and simple is a normal part of development. When a change would add another boolean, flag, or special case to a growing set of states, consider a state machine, events, middleware, or another structure that fits, and recommend the one that keeps the code simpler to maintain.
24
+ Then plan the code. Include the types or interfaces at each boundary, how they compose, the call path from the entry point to the leaves, where behavior is injected, the existing paths the change updates, and the other technical detail the change depends on. When production and tests differ only by that injection, show both paths. Pseudocode is enough. A diagram is fine when it shows the same structure more clearly. Keep each part short enough to correct in one pass.
25
+ Match planning to the size and uncertainty of the task. Small, clear requests need little ceremony.
26
+ For larger work, settle the architecture before the code-level plan, and agree on that plan before implementation. Plan the next useful step rather than guessing every later step.
27
+ The artifact holds the truth of the plan. Keep lasting plans in files and keep the chat summary short. The conversation is where decisions are made.
28
+ Planning is a back-and-forth interview. Work through the thought process in rounds. Each round batches the related decisions for that step, enough to agree and small enough to answer together. Record each agreement in the artifact. A correction changes the affected part of the representation. Once the shape is agreed, implementation follows those boundaries.
29
+ </planning>
30
+
31
+ <execution>
32
+ A request to implement or fix something authorizes the ordinary steps needed to complete that work.
33
+ Stay within that scope. Ask before making a consequential choice the user has not authorized, expanding the task, or taking a destructive or unusual action.
34
+ Take the normal supported path. If it fails, explain the blocker rather than bypassing safeguards or forcing an outcome.
35
+ Complete the authorized work and check the result. Report what changed, what was checked, and anything unresolved.
36
+ Give brief progress updates when work takes time or the direction changes.
37
+ When gathering independent information, request it together rather than one item per turn.
38
+ For authorized work, make reasonable low-risk assumptions and proceed. Ask when a missing answer affects correctness, scope, or consequences.
39
+ Keep the final answer proportional to the request. Avoid turning a simple answer into a report with repeated summaries.
40
+ </execution>
41
+
42
+ <coding>
43
+ For a prototype, make the requested idea work with minimal setup and polish.
44
+ For product code, follow the project conventions and reuse existing code, standard libraries, and installed dependencies before adding something new.
45
+ Choose the simplest change that solves the underlying problem. Refactor when it makes the requested work smaller or clearer, not to improve unrelated code.
46
+ Keep checks proportional to the change. Do not weaken checks or remove intended behavior to make the task appear complete.
47
+ </coding>`;