@noetic-tools/core 0.0.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 (207) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +38 -0
  3. package/dist/adapters/in-memory-fs-adapter.d.ts +3 -0
  4. package/dist/adapters/in-memory-fs-adapter.js +245 -0
  5. package/dist/adapters/in-memory-shell-adapter.d.ts +9 -0
  6. package/dist/adapters/in-memory-shell-adapter.js +20 -0
  7. package/dist/adapters/in-memory-subprocess-adapter.d.ts +5 -0
  8. package/dist/adapters/in-memory-subprocess-adapter.js +126 -0
  9. package/dist/adapters/local-fs-adapter.d.ts +3 -0
  10. package/dist/adapters/local-fs-adapter.js +43 -0
  11. package/dist/adapters/local-shell-adapter.d.ts +51 -0
  12. package/dist/adapters/local-shell-adapter.js +167 -0
  13. package/dist/adapters/local-subprocess-adapter.d.ts +16 -0
  14. package/dist/adapters/local-subprocess-adapter.js +187 -0
  15. package/dist/adapters/openrouter.d.ts +57 -0
  16. package/dist/adapters/openrouter.js +248 -0
  17. package/dist/builders/channel-builder.d.ts +36 -0
  18. package/dist/builders/channel-builder.js +15 -0
  19. package/dist/builders/control-flow-builders.d.ts +79 -0
  20. package/dist/builders/control-flow-builders.js +53 -0
  21. package/dist/builders/every.d.ts +45 -0
  22. package/dist/builders/every.js +60 -0
  23. package/dist/builders/layer-provides-builders.d.ts +32 -0
  24. package/dist/builders/layer-provides-builders.js +33 -0
  25. package/dist/builders/loop-builder.d.ts +23 -0
  26. package/dist/builders/loop-builder.js +46 -0
  27. package/dist/builders/memory-builder.d.ts +11 -0
  28. package/dist/builders/memory-builder.js +15 -0
  29. package/dist/builders/provide-builder.d.ts +20 -0
  30. package/dist/builders/provide-builder.js +34 -0
  31. package/dist/builders/spawn-builder.d.ts +20 -0
  32. package/dist/builders/spawn-builder.js +33 -0
  33. package/dist/builders/step-builders.d.ts +62 -0
  34. package/dist/builders/step-builders.js +99 -0
  35. package/dist/builders/tool-builder.d.ts +52 -0
  36. package/dist/builders/tool-builder.js +59 -0
  37. package/dist/conditions/conditions.d.ts +114 -0
  38. package/dist/conditions/conditions.js +271 -0
  39. package/dist/conditions/cosine-similarity.d.ts +11 -0
  40. package/dist/conditions/cosine-similarity.js +28 -0
  41. package/dist/conditions/index.d.ts +3 -0
  42. package/dist/conditions/index.js +2 -0
  43. package/dist/errors/noetic-config-error.d.ts +41 -0
  44. package/dist/errors/noetic-config-error.js +44 -0
  45. package/dist/errors/noetic-error.d.ts +18 -0
  46. package/dist/errors/noetic-error.js +52 -0
  47. package/dist/index.d.ts +152 -0
  48. package/dist/index.js +112 -0
  49. package/dist/internal-test.d.ts +11 -0
  50. package/dist/internal-test.js +11 -0
  51. package/dist/interpreter/clone-guard.d.ts +4 -0
  52. package/dist/interpreter/clone-guard.js +18 -0
  53. package/dist/interpreter/collect-tools.d.ts +12 -0
  54. package/dist/interpreter/collect-tools.js +70 -0
  55. package/dist/interpreter/execute-branch.d.ts +3 -0
  56. package/dist/interpreter/execute-branch.js +10 -0
  57. package/dist/interpreter/execute-every.d.ts +15 -0
  58. package/dist/interpreter/execute-every.js +143 -0
  59. package/dist/interpreter/execute-fork.d.ts +3 -0
  60. package/dist/interpreter/execute-fork.js +215 -0
  61. package/dist/interpreter/execute-llm.d.ts +4 -0
  62. package/dist/interpreter/execute-llm.js +217 -0
  63. package/dist/interpreter/execute-loop.d.ts +3 -0
  64. package/dist/interpreter/execute-loop.js +171 -0
  65. package/dist/interpreter/execute-provide.d.ts +10 -0
  66. package/dist/interpreter/execute-provide.js +64 -0
  67. package/dist/interpreter/execute-run.d.ts +3 -0
  68. package/dist/interpreter/execute-run.js +39 -0
  69. package/dist/interpreter/execute-spawn.d.ts +11 -0
  70. package/dist/interpreter/execute-spawn.js +113 -0
  71. package/dist/interpreter/execute-tool.d.ts +5 -0
  72. package/dist/interpreter/execute-tool.js +81 -0
  73. package/dist/interpreter/execute.d.ts +14 -0
  74. package/dist/interpreter/execute.js +119 -0
  75. package/dist/interpreter/framework-cast.d.ts +10 -0
  76. package/dist/interpreter/framework-cast.js +16 -0
  77. package/dist/interpreter/message-helpers.d.ts +9 -0
  78. package/dist/interpreter/message-helpers.js +42 -0
  79. package/dist/interpreter/typeguards.d.ts +25 -0
  80. package/dist/interpreter/typeguards.js +50 -0
  81. package/dist/memory/budget.d.ts +23 -0
  82. package/dist/memory/budget.js +117 -0
  83. package/dist/memory/function-call-utils.d.ts +6 -0
  84. package/dist/memory/function-call-utils.js +25 -0
  85. package/dist/memory/layer-api.d.ts +14 -0
  86. package/dist/memory/layer-api.js +91 -0
  87. package/dist/memory/layer-lifecycle.d.ts +160 -0
  88. package/dist/memory/layer-lifecycle.js +552 -0
  89. package/dist/memory/layer-usage.d.ts +15 -0
  90. package/dist/memory/layer-usage.js +48 -0
  91. package/dist/memory/layers/durable-task-state.d.ts +79 -0
  92. package/dist/memory/layers/durable-task-state.js +118 -0
  93. package/dist/memory/layers/file-reference.d.ts +55 -0
  94. package/dist/memory/layers/file-reference.js +538 -0
  95. package/dist/memory/layers/history-window.d.ts +29 -0
  96. package/dist/memory/layers/history-window.js +114 -0
  97. package/dist/memory/layers/observational-memory.d.ts +53 -0
  98. package/dist/memory/layers/observational-memory.js +110 -0
  99. package/dist/memory/layers/plan.d.ts +55 -0
  100. package/dist/memory/layers/plan.js +369 -0
  101. package/dist/memory/layers/static-content.d.ts +27 -0
  102. package/dist/memory/layers/static-content.js +35 -0
  103. package/dist/memory/layers/steering.d.ts +51 -0
  104. package/dist/memory/layers/steering.js +258 -0
  105. package/dist/memory/layers/tool-memory-layer.d.ts +16 -0
  106. package/dist/memory/layers/tool-memory-layer.js +43 -0
  107. package/dist/memory/layers/working-memory.d.ts +44 -0
  108. package/dist/memory/layers/working-memory.js +92 -0
  109. package/dist/memory/projector.d.ts +10 -0
  110. package/dist/memory/projector.js +19 -0
  111. package/dist/memory/scope.d.ts +3 -0
  112. package/dist/memory/scope.js +31 -0
  113. package/dist/memory/strip-unresolved.d.ts +13 -0
  114. package/dist/memory/strip-unresolved.js +35 -0
  115. package/dist/observability/genai-attributes.d.ts +11 -0
  116. package/dist/observability/genai-attributes.js +12 -0
  117. package/dist/observability/span-impl.d.ts +20 -0
  118. package/dist/observability/span-impl.js +33 -0
  119. package/dist/observability/trace-exporter.d.ts +15 -0
  120. package/dist/observability/trace-exporter.js +26 -0
  121. package/dist/patterns/flow.d.ts +52 -0
  122. package/dist/patterns/flow.js +103 -0
  123. package/dist/patterns/interview.d.ts +53 -0
  124. package/dist/patterns/interview.js +113 -0
  125. package/dist/patterns/plans.d.ts +45 -0
  126. package/dist/patterns/plans.js +125 -0
  127. package/dist/patterns/ralph-wiggum.d.ts +20 -0
  128. package/dist/patterns/ralph-wiggum.js +37 -0
  129. package/dist/patterns/react.d.ts +18 -0
  130. package/dist/patterns/react.js +39 -0
  131. package/dist/portable.d.ts +26 -0
  132. package/dist/portable.js +34 -0
  133. package/dist/runtime/agent-harness.d.ts +206 -0
  134. package/dist/runtime/agent-harness.js +1170 -0
  135. package/dist/runtime/broadcaster-utils.d.ts +30 -0
  136. package/dist/runtime/broadcaster-utils.js +53 -0
  137. package/dist/runtime/channel-store.d.ts +18 -0
  138. package/dist/runtime/channel-store.js +187 -0
  139. package/dist/runtime/context-impl.d.ts +80 -0
  140. package/dist/runtime/context-impl.js +140 -0
  141. package/dist/runtime/cwd-helpers.d.ts +42 -0
  142. package/dist/runtime/cwd-helpers.js +68 -0
  143. package/dist/runtime/detached-handle.d.ts +14 -0
  144. package/dist/runtime/detached-handle.js +56 -0
  145. package/dist/runtime/event-broadcaster.d.ts +61 -0
  146. package/dist/runtime/event-broadcaster.js +187 -0
  147. package/dist/runtime/exec-context-factory.d.ts +25 -0
  148. package/dist/runtime/exec-context-factory.js +30 -0
  149. package/dist/runtime/in-memory-storage.d.ts +9 -0
  150. package/dist/runtime/in-memory-storage.js +33 -0
  151. package/dist/runtime/item-log-impl.d.ts +11 -0
  152. package/dist/runtime/item-log-impl.js +25 -0
  153. package/dist/runtime/message-queue.d.ts +30 -0
  154. package/dist/runtime/message-queue.js +46 -0
  155. package/dist/runtime/session-runner.d.ts +77 -0
  156. package/dist/runtime/session-runner.js +253 -0
  157. package/dist/runtime/session-streams.d.ts +11 -0
  158. package/dist/runtime/session-streams.js +204 -0
  159. package/dist/runtime/tool-memory.d.ts +6 -0
  160. package/dist/runtime/tool-memory.js +22 -0
  161. package/dist/schemas/item.d.ts +35 -0
  162. package/dist/schemas/item.js +155 -0
  163. package/dist/types/channel.d.ts +29 -0
  164. package/dist/types/channel.js +1 -0
  165. package/dist/types/common.d.ts +111 -0
  166. package/dist/types/common.js +1 -0
  167. package/dist/types/context.d.ts +93 -0
  168. package/dist/types/context.js +1 -0
  169. package/dist/types/detached.d.ts +17 -0
  170. package/dist/types/detached.js +7 -0
  171. package/dist/types/embed.d.ts +5 -0
  172. package/dist/types/embed.js +1 -0
  173. package/dist/types/error.d.ts +51 -0
  174. package/dist/types/error.js +1 -0
  175. package/dist/types/fs-adapter.d.ts +51 -0
  176. package/dist/types/fs-adapter.js +3 -0
  177. package/dist/types/harness-result.d.ts +43 -0
  178. package/dist/types/harness-result.js +2 -0
  179. package/dist/types/items.d.ts +115 -0
  180. package/dist/types/items.js +2 -0
  181. package/dist/types/memory.d.ts +336 -0
  182. package/dist/types/memory.js +13 -0
  183. package/dist/types/mutable-context.d.ts +13 -0
  184. package/dist/types/mutable-context.js +1 -0
  185. package/dist/types/observability.d.ts +30 -0
  186. package/dist/types/observability.js +1 -0
  187. package/dist/types/runtime.d.ts +255 -0
  188. package/dist/types/runtime.js +1 -0
  189. package/dist/types/shell-adapter.d.ts +34 -0
  190. package/dist/types/shell-adapter.js +10 -0
  191. package/dist/types/steering.d.ts +89 -0
  192. package/dist/types/steering.js +14 -0
  193. package/dist/types/step.d.ts +206 -0
  194. package/dist/types/step.js +1 -0
  195. package/dist/types/subprocess-adapter.d.ts +41 -0
  196. package/dist/types/subprocess-adapter.js +1 -0
  197. package/dist/types/tool-context.d.ts +34 -0
  198. package/dist/types/tool-context.js +1 -0
  199. package/dist/unstable.d.ts +28 -0
  200. package/dist/unstable.js +24 -0
  201. package/dist/until/combinators.d.ts +17 -0
  202. package/dist/until/combinators.js +49 -0
  203. package/dist/until/never.d.ts +9 -0
  204. package/dist/until/never.js +12 -0
  205. package/dist/until/predicates.d.ts +43 -0
  206. package/dist/until/predicates.js +126 -0
  207. package/package.json +81 -0
@@ -0,0 +1,53 @@
1
+ export interface ObservationalState {
2
+ observations: string[];
3
+ buffer: string[];
4
+ bufferTokens: number;
5
+ version: number;
6
+ }
7
+ export type ObserverFn = (buffer: string[]) => Promise<string[]>;
8
+ export interface ObservationalMemoryConfig {
9
+ bufferThreshold?: number;
10
+ maxObservations?: number;
11
+ scope?: 'thread' | 'resource';
12
+ observer?: ObserverFn;
13
+ }
14
+ /**
15
+ * Creates a memory layer that buffers raw items and distills them into observations when a token threshold is reached.
16
+ *
17
+ * @public
18
+ * @param config - Optional configuration for buffer threshold, max observations, scope, and observer function.
19
+ * @returns A `MemoryLayer` that accumulates and summarizes observations over time.
20
+ */
21
+ export declare function observationalMemory(config?: ObservationalMemoryConfig): {
22
+ id: "observational-memory";
23
+ name: string;
24
+ slot: 200;
25
+ scope: "thread" | "resource";
26
+ budget: {
27
+ min: number;
28
+ max: number;
29
+ };
30
+ timeouts: {
31
+ store: number;
32
+ };
33
+ hooks: {
34
+ init({ storage }: import("../..").InitParams): Promise<{
35
+ state: ObservationalState;
36
+ }>;
37
+ recall({ state }: import("../..").RecallParams<ObservationalState>): Promise<{
38
+ items: import("../..").InputMessageItem[];
39
+ tokenCount: number;
40
+ } | null>;
41
+ store({ newItems, state }: import("../..").StoreParams<ObservationalState>): Promise<{
42
+ state: {
43
+ buffer: string[];
44
+ bufferTokens: number;
45
+ observations: string[];
46
+ version: number;
47
+ };
48
+ }>;
49
+ onSpawn({ parentState }: import("../..").SpawnParams<ObservationalState>): Promise<{
50
+ childState: ObservationalState;
51
+ }>;
52
+ };
53
+ };
@@ -0,0 +1,110 @@
1
+ import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
2
+ import { isOutputText } from '../../interpreter/typeguards';
3
+ import { Slot } from '../../types/memory';
4
+ const DEFAULT_BUFFER_THRESHOLD_TOKENS = 2_000;
5
+ const DEFAULT_MAX_OBSERVATIONS = 50;
6
+ /**
7
+ * Creates a memory layer that buffers raw items and distills them into observations when a token threshold is reached.
8
+ *
9
+ * @public
10
+ * @param config - Optional configuration for buffer threshold, max observations, scope, and observer function.
11
+ * @returns A `MemoryLayer` that accumulates and summarizes observations over time.
12
+ */
13
+ export function observationalMemory(config) {
14
+ const maxObs = config?.maxObservations ?? DEFAULT_MAX_OBSERVATIONS;
15
+ const threshold = config?.bufferThreshold ?? DEFAULT_BUFFER_THRESHOLD_TOKENS;
16
+ const observer = config?.observer;
17
+ return {
18
+ id: 'observational-memory',
19
+ name: 'Observational Memory',
20
+ slot: Slot.OBSERVATIONS,
21
+ scope: config?.scope ?? 'resource',
22
+ budget: {
23
+ min: 500,
24
+ max: 2_500,
25
+ },
26
+ timeouts: {
27
+ store: 60_000,
28
+ },
29
+ hooks: {
30
+ async init({ storage }) {
31
+ const saved = await storage.get('state');
32
+ return {
33
+ state: saved ?? {
34
+ observations: [],
35
+ buffer: [],
36
+ bufferTokens: 0,
37
+ version: 0,
38
+ },
39
+ };
40
+ },
41
+ async recall({ state }) {
42
+ if (!state?.observations?.length) {
43
+ return null;
44
+ }
45
+ const bullets = state.observations.map((o) => `- ${o}`).join('\n');
46
+ const text = `<observations>\n${bullets}\n</observations>`;
47
+ return {
48
+ items: [
49
+ createMessage(text, 'developer'),
50
+ ],
51
+ tokenCount: estimateTokens(text),
52
+ };
53
+ },
54
+ async store({ newItems, state }) {
55
+ const s = state ?? {
56
+ observations: [],
57
+ buffer: [],
58
+ bufferTokens: 0,
59
+ version: 0,
60
+ };
61
+ // Accumulate new items into buffer
62
+ const textItems = newItems
63
+ .filter((i) => i.type === 'message')
64
+ .map((i) => i.content
65
+ .filter(isOutputText)
66
+ .map((c) => c.text)
67
+ .join(''))
68
+ .filter((t) => t.length > 0);
69
+ const newBuffer = [
70
+ ...s.buffer,
71
+ ...textItems,
72
+ ];
73
+ // Token-based threshold: accumulate incrementally
74
+ const newTokens = textItems.reduce((sum, t) => sum + estimateTokens(t), 0);
75
+ const totalBufferTokens = s.bufferTokens + newTokens;
76
+ if (totalBufferTokens >= threshold) {
77
+ const distilled = observer
78
+ ? await observer(newBuffer)
79
+ : [
80
+ `Processed ${newBuffer.length} items`,
81
+ ];
82
+ const newObservations = [
83
+ ...s.observations,
84
+ ...distilled,
85
+ ].slice(-maxObs);
86
+ return {
87
+ state: {
88
+ observations: newObservations,
89
+ buffer: [],
90
+ bufferTokens: 0,
91
+ version: s.version + 1,
92
+ },
93
+ };
94
+ }
95
+ return {
96
+ state: {
97
+ ...s,
98
+ buffer: newBuffer,
99
+ bufferTokens: totalBufferTokens,
100
+ },
101
+ };
102
+ },
103
+ async onSpawn({ parentState }) {
104
+ return {
105
+ childState: structuredClone(parentState),
106
+ };
107
+ },
108
+ },
109
+ };
110
+ }
@@ -0,0 +1,55 @@
1
+ import type { FlowNode } from '../../patterns/flow';
2
+ import type { MemoryLayer, MemoryScope } from '../../types/memory';
3
+ export declare const PlanPhase: {
4
+ readonly Idle: "idle";
5
+ readonly Planning: "planning";
6
+ readonly Executing: "executing";
7
+ readonly Completed: "completed";
8
+ readonly Failed: "failed";
9
+ };
10
+ export type PlanPhase = (typeof PlanPhase)[keyof typeof PlanPhase];
11
+ export interface PlanExecutionEntry {
12
+ timestamp: number;
13
+ version: number;
14
+ outcome: 'success' | 'failure' | 'aborted';
15
+ }
16
+ export interface PlanState {
17
+ phase: PlanPhase;
18
+ prd: string | null;
19
+ planTree: FlowNode | null;
20
+ executionLog: PlanExecutionEntry[];
21
+ version: number;
22
+ /** Identifier of the on-disk plan session (set by `onEnterSession` host callback). */
23
+ planSlug?: string | null;
24
+ }
25
+ /** Host-supplied callback invoked when entering plan mode. Returns a session identifier the host owns (e.g. on-disk dir slug). */
26
+ export type PlanEnterSessionCallback = () => Promise<{
27
+ slug: string;
28
+ }>;
29
+ /**
30
+ * Host-supplied callback invoked when the model requests `exitPlanMode` with `action: 'execute'`.
31
+ * Return `{ approved: false }` to keep the layer in `Planning` (e.g. user rejected the plan in the UI).
32
+ */
33
+ export type PlanExitCallback = (state: PlanState) => Promise<{
34
+ approved: boolean;
35
+ }>;
36
+ export interface PlanMemoryConfig {
37
+ scope?: MemoryScope;
38
+ additionalAllowedTools?: string[];
39
+ maxPrdLength?: number;
40
+ maxTreeDepth?: number;
41
+ /** Extra free-form instructions appended to the planning-phase recall payload. */
42
+ additionalPlanInstructions?: string;
43
+ /** Called once when the layer transitions Idle → Planning. */
44
+ onEnterSession?: PlanEnterSessionCallback;
45
+ /** Called when `exitPlanMode` is requested with `action: 'execute'`. */
46
+ onExit?: PlanExitCallback;
47
+ }
48
+ /**
49
+ * Creates a plan memory layer that manages the PRD authoring and plan execution lifecycle.
50
+ *
51
+ * @public
52
+ * @param config - Optional configuration for scope, allowed tools, and limits.
53
+ * @returns A `MemoryLayer` providing plan mode, PRD storage, and execution tracking.
54
+ */
55
+ export declare function planMemory(config?: PlanMemoryConfig): MemoryLayer<PlanState>;
@@ -0,0 +1,369 @@
1
+ import { z } from 'zod';
2
+ import { layerData, layerFn } from '../../builders/layer-provides-builders';
3
+ import { createMessage, estimateTokens } from '../../interpreter/message-helpers';
4
+ import { FlowSchema, flowDepth } from '../../patterns/flow';
5
+ import { Slot } from '../../types/memory';
6
+ import { SteeringAction } from '../../types/steering';
7
+ //#region Constants
8
+ const MAX_PRD_LENGTH = 5e4;
9
+ const MAX_TREE_DEPTH = 5;
10
+ const MAX_EXECUTION_LOG_ENTRIES = 10;
11
+ const PLAN_SLOT = Slot.PROCEDURAL - 10; // 240
12
+ const ALLOWED_TOOLS_IN_PLAN_MODE = new Set([
13
+ 'Read',
14
+ 'Grep',
15
+ 'Find',
16
+ 'Ls',
17
+ 'activateSkill',
18
+ 'plan/enterPlanMode',
19
+ 'plan/updatePrd',
20
+ 'plan/setPlanTree',
21
+ 'plan/exitPlanMode',
22
+ ]);
23
+ //#endregion
24
+ //#region Types
25
+ export const PlanPhase = {
26
+ Idle: 'idle',
27
+ Planning: 'planning',
28
+ Executing: 'executing',
29
+ Completed: 'completed',
30
+ Failed: 'failed',
31
+ };
32
+ //#endregion
33
+ //#region Helpers
34
+ function createDefaultState() {
35
+ return {
36
+ phase: PlanPhase.Idle,
37
+ prd: null,
38
+ planTree: null,
39
+ executionLog: [],
40
+ version: 0,
41
+ };
42
+ }
43
+ function validateTreeDepth(node, maxDepth) {
44
+ return flowDepth(node) <= maxDepth;
45
+ }
46
+ function buildAllowedTools(config) {
47
+ if (!config?.additionalAllowedTools?.length) {
48
+ return ALLOWED_TOOLS_IN_PLAN_MODE;
49
+ }
50
+ return new Set([
51
+ ...ALLOWED_TOOLS_IN_PLAN_MODE,
52
+ ...config.additionalAllowedTools,
53
+ ]);
54
+ }
55
+ function trimExecutionLog(log) {
56
+ if (log.length <= MAX_EXECUTION_LOG_ENTRIES) {
57
+ return log;
58
+ }
59
+ return log.slice(log.length - MAX_EXECUTION_LOG_ENTRIES);
60
+ }
61
+ //#endregion
62
+ //#region Recall Renderers
63
+ function recallPlanning(state, additionalInstructions) {
64
+ const sections = [
65
+ '<plan_mode>',
66
+ 'You are in PLAN MODE. You may only use read-only tools (Read, Grep, Find, Ls) to explore the codebase.',
67
+ 'Your goal is to produce a PRD document and (optionally) a structured execution plan.',
68
+ '',
69
+ '## Workflow (5 phases)',
70
+ '',
71
+ '1. **Initial Understanding** — Read code and gather context. To parallelise exploration, fork over `{ kind: "subagent", preset: "explore", prompt: "..." }` nodes; up to 3 in parallel. Each Explore subagent returns a focused report.',
72
+ '2. **Design** — Synthesise findings. Optionally invoke `{ kind: "subagent", preset: "plan", prompt: "..." }` (1–3 in parallel) to draft alternative implementation approaches and surface trade-offs.',
73
+ '3. **Review** — Read the critical files identified by your subagents directly so you understand them first-hand. If anything is ambiguous, ask the user a focused question.',
74
+ '4. **Final Plan** — Write the PRD via `plan/updatePrd`. Lead with a **Context** section (why this change), then your single recommended approach, the paths of files to modify, existing functions/utilities to reuse, and a **Verification** section. If the task warrants custom step orchestration, also call `plan/setPlanTree` with a JSON noetic flow built from the existing step primitives (`step.llm`, `step.branch`, `step.fork`, `step.spawn`, `step.loop`) — tool refs by name string. Otherwise omit the tree.',
75
+ '5. **Exit** — Call `plan/exitPlanMode` with `{ action: "execute" }` to request approval. The user must accept before execution begins; if they reject, you stay in Plan Mode and may revise.',
76
+ '',
77
+ '## Available actions',
78
+ '- `plan/updatePrd` — set markdown PRD content',
79
+ '- `plan/setPlanTree` — set the optional JSON execution plan',
80
+ '- `plan/exitPlanMode` `{ action: "execute" }` — request approval and exit to executing',
81
+ '- `plan/exitPlanMode` `{ action: "cancel" }` — discard plan and return to idle',
82
+ '',
83
+ '## Constraints',
84
+ '- DO NOT create, modify, or delete files (other than via `plan/updatePrd`).',
85
+ '- DO NOT run mutating shell commands. Read-only exploration only.',
86
+ '- End each turn either by asking the user a focused clarifying question or by calling `plan/exitPlanMode`.',
87
+ ];
88
+ if (additionalInstructions) {
89
+ sections.push('', '## Additional Instructions', '', additionalInstructions);
90
+ }
91
+ if (state.prd) {
92
+ sections.push('', '## Current PRD Draft', '', state.prd);
93
+ }
94
+ if (state.planTree) {
95
+ sections.push('', '## Current Plan Tree', '', JSON.stringify(state.planTree, null, 2));
96
+ }
97
+ sections.push('</plan_mode>');
98
+ return sections.join('\n');
99
+ }
100
+ function recallExecuting(state) {
101
+ const sections = [
102
+ '<active_plan>',
103
+ '## PRD',
104
+ '',
105
+ state.prd ?? '',
106
+ '',
107
+ '## Execution Plan',
108
+ '',
109
+ JSON.stringify(state.planTree, null, 2),
110
+ '</active_plan>',
111
+ ];
112
+ return sections.join('\n');
113
+ }
114
+ function recallTerminal(state) {
115
+ const lastEntry = state.executionLog[state.executionLog.length - 1];
116
+ const outcome = lastEntry?.outcome ?? 'unknown';
117
+ return `<plan_outcome>Plan v${state.version} ${outcome}.</plan_outcome>`;
118
+ }
119
+ const RECALL_RENDERERS = {
120
+ [PlanPhase.Planning]: recallPlanning,
121
+ [PlanPhase.Executing]: recallExecuting,
122
+ [PlanPhase.Completed]: recallTerminal,
123
+ [PlanPhase.Failed]: recallTerminal,
124
+ };
125
+ //#endregion
126
+ //#region Public API
127
+ /**
128
+ * Creates a plan memory layer that manages the PRD authoring and plan execution lifecycle.
129
+ *
130
+ * @public
131
+ * @param config - Optional configuration for scope, allowed tools, and limits.
132
+ * @returns A `MemoryLayer` providing plan mode, PRD storage, and execution tracking.
133
+ */
134
+ export function planMemory(config) {
135
+ const scope = config?.scope ?? 'thread';
136
+ const maxPrdLength = config?.maxPrdLength ?? MAX_PRD_LENGTH;
137
+ const maxTreeDepth = config?.maxTreeDepth ?? MAX_TREE_DEPTH;
138
+ const allowedTools = buildAllowedTools(config);
139
+ const additionalPlanInstructions = config?.additionalPlanInstructions;
140
+ const onEnterSession = config?.onEnterSession;
141
+ const onExit = config?.onExit;
142
+ return {
143
+ id: 'plan',
144
+ name: 'Plan Memory',
145
+ slot: PLAN_SLOT,
146
+ scope,
147
+ budget: {
148
+ min: 100,
149
+ max: 3e3,
150
+ },
151
+ provides: {
152
+ status: layerData({
153
+ read: (state) => ({
154
+ phase: state.phase,
155
+ hasPrd: state.prd !== null,
156
+ hasPlanTree: state.planTree !== null,
157
+ version: state.version,
158
+ }),
159
+ }),
160
+ enterPlanMode: layerFn({
161
+ description: 'Enter plan mode. The agent switches to read-only exploration and PRD authoring.',
162
+ input: z.object({
163
+ goal: z.string().optional(),
164
+ }),
165
+ output: z.string(),
166
+ execute: async (args, state) => {
167
+ if (state.phase !== PlanPhase.Idle) {
168
+ return {
169
+ result: `Cannot enter plan mode: current phase is "${state.phase}".`,
170
+ state,
171
+ };
172
+ }
173
+ const session = onEnterSession ? await onEnterSession() : null;
174
+ return {
175
+ result: 'Plan mode activated. Explore the codebase, then call plan/updatePrd.',
176
+ state: {
177
+ ...state,
178
+ phase: PlanPhase.Planning,
179
+ prd: args.goal ? `# Goal\n\n${args.goal}\n` : null,
180
+ planTree: null,
181
+ version: state.version + 1,
182
+ planSlug: session?.slug ?? null,
183
+ },
184
+ };
185
+ },
186
+ }),
187
+ updatePrd: layerFn({
188
+ description: 'Update the PRD document with new markdown content.',
189
+ input: z.object({
190
+ content: z.string(),
191
+ }),
192
+ output: z.string(),
193
+ execute: async (args, state) => {
194
+ if (state.phase !== PlanPhase.Planning) {
195
+ return {
196
+ result: `Cannot update PRD: current phase is "${state.phase}". Enter plan mode first.`,
197
+ state,
198
+ };
199
+ }
200
+ if (args.content.length > maxPrdLength) {
201
+ return {
202
+ result: `PRD content exceeds maximum length of ${maxPrdLength} characters.`,
203
+ state,
204
+ };
205
+ }
206
+ return {
207
+ result: 'PRD updated successfully.',
208
+ state: {
209
+ ...state,
210
+ prd: args.content,
211
+ },
212
+ };
213
+ },
214
+ }),
215
+ setPlanTree: layerFn({
216
+ description: 'Set the execution plan tree. Input must be a valid FlowNode — a discriminated union with kind: "llm" | "subagent" | "fork" | "spawn" | "sequence". Structural nodes (sequence, fork, spawn) nest child FlowNodes; leaf nodes (llm, subagent) carry execution instructions.',
217
+ input: FlowSchema,
218
+ output: z.string(),
219
+ execute: async (args, state) => {
220
+ if (state.phase !== PlanPhase.Planning) {
221
+ return {
222
+ result: `Cannot set plan tree: current phase is "${state.phase}". Enter plan mode first.`,
223
+ state,
224
+ };
225
+ }
226
+ if (!validateTreeDepth(args, maxTreeDepth)) {
227
+ return {
228
+ result: `Plan tree exceeds maximum depth of ${maxTreeDepth}.`,
229
+ state,
230
+ };
231
+ }
232
+ return {
233
+ result: 'Plan tree set successfully. Call plan/exitPlanMode to begin execution.',
234
+ state: {
235
+ ...state,
236
+ planTree: args,
237
+ },
238
+ };
239
+ },
240
+ }),
241
+ exitPlanMode: layerFn({
242
+ description: 'Exit plan mode. Use action "execute" to begin executing the plan, or "cancel" to discard it.',
243
+ input: z.object({
244
+ action: z.enum([
245
+ 'execute',
246
+ 'cancel',
247
+ ]),
248
+ }),
249
+ output: z.string(),
250
+ execute: async (args, state) => {
251
+ if (state.phase !== PlanPhase.Planning) {
252
+ return {
253
+ result: `Cannot exit plan mode: current phase is "${state.phase}".`,
254
+ state,
255
+ };
256
+ }
257
+ if (args.action === 'cancel') {
258
+ return {
259
+ result: 'Plan cancelled. Returned to idle.',
260
+ state: createDefaultState(),
261
+ };
262
+ }
263
+ if (!state.prd) {
264
+ return {
265
+ result: 'Cannot execute: no PRD has been written. Call plan/updatePrd first.',
266
+ state,
267
+ };
268
+ }
269
+ if (!state.planTree) {
270
+ return {
271
+ result: 'Cannot execute: no plan tree has been set. Call plan/setPlanTree first.',
272
+ state,
273
+ };
274
+ }
275
+ if (onExit) {
276
+ const { approved } = await onExit(state);
277
+ if (!approved) {
278
+ return {
279
+ result: 'User did not approve the plan. Stay in plan mode, address their feedback, and call plan/exitPlanMode again when ready.',
280
+ state,
281
+ };
282
+ }
283
+ }
284
+ return {
285
+ result: 'Plan mode exited. Execution phase begun.',
286
+ state: {
287
+ ...state,
288
+ phase: PlanPhase.Executing,
289
+ },
290
+ };
291
+ },
292
+ }),
293
+ },
294
+ hooks: {
295
+ async init({ storage }) {
296
+ const saved = await storage.get('state');
297
+ return {
298
+ state: saved ?? createDefaultState(),
299
+ };
300
+ },
301
+ async recall({ state }) {
302
+ if (state.phase === PlanPhase.Idle) {
303
+ return null;
304
+ }
305
+ const renderer = RECALL_RENDERERS[state.phase];
306
+ if (!renderer) {
307
+ return null;
308
+ }
309
+ const content = renderer(state, additionalPlanInstructions);
310
+ return {
311
+ items: [
312
+ createMessage(content, 'developer'),
313
+ ],
314
+ tokenCount: estimateTokens(content),
315
+ };
316
+ },
317
+ async beforeToolCall({ toolName, state }) {
318
+ if (state.phase !== PlanPhase.Planning) {
319
+ return {
320
+ decision: {
321
+ action: SteeringAction.Allow,
322
+ },
323
+ state,
324
+ };
325
+ }
326
+ if (allowedTools.has(toolName)) {
327
+ return {
328
+ decision: {
329
+ action: SteeringAction.Allow,
330
+ },
331
+ state,
332
+ };
333
+ }
334
+ return {
335
+ decision: {
336
+ action: SteeringAction.Deny,
337
+ guidance: `Plan mode is active. "${toolName}" is not allowed during planning. Use read-only tools (Read, Grep, Find, Ls) to explore the codebase, then call plan/updatePrd to write your PRD.`,
338
+ },
339
+ state,
340
+ };
341
+ },
342
+ async onSpawn({ parentState }) {
343
+ return {
344
+ childState: structuredClone(parentState),
345
+ };
346
+ },
347
+ async onComplete({ state, outcome }) {
348
+ if (state.phase !== PlanPhase.Executing) {
349
+ return;
350
+ }
351
+ return {
352
+ state: {
353
+ ...state,
354
+ phase: outcome === 'success' ? PlanPhase.Completed : PlanPhase.Failed,
355
+ executionLog: trimExecutionLog([
356
+ ...state.executionLog,
357
+ {
358
+ timestamp: Date.now(),
359
+ version: state.version,
360
+ outcome,
361
+ },
362
+ ]),
363
+ },
364
+ };
365
+ },
366
+ },
367
+ };
368
+ }
369
+ //#endregion
@@ -0,0 +1,27 @@
1
+ import type { MemoryScope } from '../../types/memory';
2
+ interface StaticContentOpts {
3
+ id?: string;
4
+ slot?: number;
5
+ scope?: MemoryScope;
6
+ load: () => Promise<string>;
7
+ tag?: string;
8
+ }
9
+ /**
10
+ * Creates a read-only memory layer that loads static content once at init and injects it into recall.
11
+ *
12
+ * @public
13
+ * @param opts - Configuration with a `load` function and optional id, slot, scope, and XML tag name.
14
+ * @returns A `MemoryLayer` that provides static instructional content to the model.
15
+ */
16
+ export declare function staticContent(opts: StaticContentOpts): {
17
+ id: string;
18
+ slot: number;
19
+ scope: MemoryScope;
20
+ hooks: {
21
+ init(): Promise<{
22
+ state: string;
23
+ }>;
24
+ recall({ state }: import("../..").RecallParams<string>): Promise<string | null>;
25
+ };
26
+ };
27
+ export {};
@@ -0,0 +1,35 @@
1
+ import { Slot } from '../../types/memory';
2
+ /**
3
+ * Creates a read-only memory layer that loads static content once at init and injects it into recall.
4
+ *
5
+ * @public
6
+ * @param opts - Configuration with a `load` function and optional id, slot, scope, and XML tag name.
7
+ * @returns A `MemoryLayer` that provides static instructional content to the model.
8
+ */
9
+ export function staticContent(opts) {
10
+ const tag = opts.tag ?? 'instructions';
11
+ return {
12
+ id: opts.id ?? 'static-content',
13
+ slot: opts.slot ?? Slot.WORKING_MEMORY + 5,
14
+ scope: opts.scope ?? 'resource',
15
+ hooks: {
16
+ async init() {
17
+ const raw = await opts.load();
18
+ if (!raw) {
19
+ return {
20
+ state: '',
21
+ };
22
+ }
23
+ return {
24
+ state: `<${tag}>\n${raw}\n</${tag}>`,
25
+ };
26
+ },
27
+ async recall({ state }) {
28
+ if (!state) {
29
+ return null;
30
+ }
31
+ return state;
32
+ },
33
+ },
34
+ };
35
+ }