@causyne/agent 1.10.13 → 1.10.18

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 (76) hide show
  1. package/docs/ARCHITECTURE.md +118 -17
  2. package/docs/SECURITY_REVIEW.md +13 -1
  3. package/docs/THIRD_PARTY_NOTICES.md +46 -10
  4. package/package.json +10 -4
  5. package/src/agentLoop.js +123 -21
  6. package/src/agentProfiles.js +135 -0
  7. package/src/agentProfiles.ts +143 -0
  8. package/src/approvalGrants.js +17 -35
  9. package/src/approvalGrants.ts +54 -0
  10. package/src/auditLog.d.ts +3 -0
  11. package/src/browser/browserRuntime.js +88 -0
  12. package/src/browser/browserRuntime.ts +107 -0
  13. package/src/browser/causyneBrowser.js +495 -0
  14. package/src/browser/causyneBrowser.ts +594 -0
  15. package/src/browser/searchProviders.js +156 -0
  16. package/src/browser/searchProviders.ts +156 -0
  17. package/src/browser/snapshotWindow.js +28 -0
  18. package/src/browser/snapshotWindow.ts +40 -0
  19. package/src/browser/urlPolicy.js +89 -0
  20. package/src/browser/urlPolicy.ts +89 -0
  21. package/src/checkpoints.js +19 -6
  22. package/src/cliMode.js +13 -1
  23. package/src/commands.js +37 -8
  24. package/src/config.d.ts +2 -0
  25. package/src/decisionPolicy.js +68 -0
  26. package/src/decisionPolicy.ts +106 -0
  27. package/src/hooks.js +121 -107
  28. package/src/hooks.ts +193 -0
  29. package/src/implementationPlan.js +50 -0
  30. package/src/implementationPlan.ts +59 -0
  31. package/src/mcp/mcpClient.js +520 -132
  32. package/src/mcp/mcpClient.ts +584 -0
  33. package/src/mcp/mcpRegistry.js +399 -185
  34. package/src/mcp/mcpRegistry.ts +451 -0
  35. package/src/planCache.js +473 -450
  36. package/src/planCache.ts +840 -0
  37. package/src/plugins/pluginManager.js +40 -5
  38. package/src/plugins/pluginSkills.js +84 -0
  39. package/src/plugins/pluginSkills.ts +106 -0
  40. package/src/provisioning/capabilityProvisioner.d.ts +10 -0
  41. package/src/provisioning/capabilityProvisioner.js +5 -0
  42. package/src/provisioning/capabilityTools.js +2 -1
  43. package/src/sandbox.js +32 -2
  44. package/src/sessionJournal.js +471 -0
  45. package/src/sessionJournal.ts +496 -0
  46. package/src/sessionStore.js +3 -279
  47. package/src/skillsManager.js +18 -0
  48. package/src/subagentJournal.js +215 -0
  49. package/src/subagentJournal.ts +254 -0
  50. package/src/taskShape.js +179 -198
  51. package/src/taskShape.ts +259 -0
  52. package/src/toolPacks.js +7 -5
  53. package/src/tools/browserAutomator.js +45 -241
  54. package/src/tools/browserAutomator.ts +57 -0
  55. package/src/tools/manageLocalAiDependencies.js +1 -1
  56. package/src/tools/registry.js +4 -3
  57. package/src/tools/requestDecision.js +87 -0
  58. package/src/tools/requestDecision.ts +107 -0
  59. package/src/tools/subagentOrchestrator.js +73 -12
  60. package/src/tools/updatePlan.js +37 -84
  61. package/src/tools/updatePlan.ts +48 -0
  62. package/src/tools/webFetch.js +183 -68
  63. package/src/tools/webFetch.ts +185 -0
  64. package/src/tools/webSearch.js +50 -75
  65. package/src/tools/webSearch.ts +56 -0
  66. package/src/tui/ask.js +41 -1
  67. package/src/tui/chatRenderer.js +28 -0
  68. package/src/tui/dialogs.js +91 -0
  69. package/src/tui/prompt.js +32 -3
  70. package/src/tui/toolDisplay.js +0 -1
  71. package/src/types/contracts.js +8 -0
  72. package/src/types/contracts.ts +263 -0
  73. package/src/utils/paths.d.ts +3 -0
  74. package/src/version.d.ts +1 -0
  75. package/src/mcp/jsonRpc.js +0 -165
  76. package/src/tools/todoWrite.js +0 -75
@@ -31,18 +31,37 @@ are application policies and require adversarial review.
31
31
  | Concern | Source |
32
32
  | --- | --- |
33
33
  | Invocation and local session | `entrypoint.js`, `cliMode.js`, `tui/` |
34
- | Orchestration and budgets | `agentLoop.js`, `modelRouter.js`, `tokenTracker.js` |
34
+ | Orchestration and budgets | `agentLoop.js`, `decisionPolicy.ts`, `implementationPlan.ts`, `agentProfiles.ts`, `subagentJournal.ts`, `modelRouter.js`, `tokenTracker.js` |
35
35
  | Host execution | `tools/registry.js`, `sandbox.js`, `execSafe.js` |
36
36
  | Models | `providers/`, `modelCatalogue.js` |
37
37
  | Context and memory | `memory.js`, `memoryManager.js`, `contextManager.js`, `cortex/` |
38
- | Recovery | `checkpoints.js`, `gitSnapshot.js`, `sessionStore.js` |
39
- | Extensions | `mcp/`, `plugins/`, `hooks.js`, `skillsManager.js`, `toolPacks.js` |
38
+ | Recovery | `checkpoints.js`, `gitSnapshot.js`, `sessionJournal.ts`, `sessionStore.js` |
39
+ | Extensions | `mcp/`, `plugins/`, `hooks.ts`, `skillsManager.js`, `toolPacks.js` |
40
40
  | Readable interpreter source | `src/tool-scripts/` |
41
41
 
42
- The current implementation remains JavaScript/ESM on Node.js 24+. The accepted
43
- language direction is incremental strict TypeScript. Runtime validation is
44
- still needed at model, filesystem, subprocess and extension boundaries.
45
- Rust is a possible narrow native worker later; no Rust rewrite is underway.
42
+ The shipping runtime is ESM on Node.js 24+. Incremental strict TypeScript
43
+ migration is active: `tsconfig.runtime.json` emits readable JavaScript beside
44
+ strict TypeScript runtime modules, shared contracts live under `src/types/`,
45
+ and retained JavaScript has a separately named migration ratchet. Session,
46
+ plugin-skill, task-shape, delegated-agent, hook, approval-grant, plan-cache and
47
+ MCP boundaries now compile from strict TypeScript.
48
+ Runtime validation is still required at model, filesystem, subprocess and
49
+ extension boundaries. Rust is reserved for a measured, narrow native worker;
50
+ no Rust rewrite is underway.
51
+
52
+ ## Planning and durable decisions
53
+
54
+ Complex implementation uses `update_plan` to maintain a validated checklist in
55
+ session state. The TUI keeps the current steps visible beside the input surface;
56
+ plans are not written into the user's project. A mutating tool call on a complex
57
+ task is held until the model has created this plan.
58
+
59
+ `request_decision` handles choices with durable product, architecture, data,
60
+ security, or workflow consequences. It presents two to four concrete options
61
+ and a custom-answer path. This checkpoint is distinct from a tool permission:
62
+ selecting a design never grants filesystem or command access. The checkpoint
63
+ runs in the provider-independent agent loop, so Mode C can remove routine
64
+ approval friction without silently choosing an unresolved architecture.
46
65
 
47
66
  ## Tool and extension boundaries
48
67
 
@@ -54,11 +73,82 @@ the task shape or `tool_search` makes them relevant.
54
73
  Local plugins use a static `plugin.json` manifest. Creating a plugin leaves it
55
74
  disabled. Enabling it pins a digest of every file the plugin process may read;
56
75
  any later manifest, source, helper or asset change revokes trust. Discovery
57
- validates static manifests and hashes bytes without importing JavaScript, so code cannot run
58
- during pre-trust initialization. A plugin tool runs in a separate Node process
59
- with the permission model enabled and only its declared workspace, network, or
60
- subprocess capabilities. Plugin calls still pass through Causyne's sandbox and
61
- approval gates.
76
+ validates static manifests and hashes bytes without importing JavaScript, so
77
+ code cannot run during pre-trust initialization. Reviewed plugins may contribute
78
+ namespaced, progressively disclosed Markdown skills; these remain inert data
79
+ and disappear from the catalogue if the digest changes. A plugin tool runs in a
80
+ separate Node process with the permission model enabled and only its declared
81
+ workspace, network, or subprocess capabilities. Plugin calls still pass through
82
+ Causyne's sandbox and approval gates.
83
+
84
+ MCP uses the official Model Context Protocol client packages rather than a
85
+ project-specific JSON-RPC implementation. It supports local stdio servers and
86
+ remote Streamable HTTP servers, with legacy SSE fallback when configured for
87
+ automatic negotiation. The SDK owns initialization, capability negotiation,
88
+ protocol framing, request cancellation, and transport shutdown. Causyne adds
89
+ connection timeouts, bounded output, current-workspace roots, concurrent
90
+ discovery, `tools/list_changed` refresh, structured content handling, and
91
+ namespaced access to server resources and prompts.
92
+
93
+ Each MCP grant is bound to a SHA-256 digest of the complete server definition.
94
+ Changing a command, URL, environment, header, working directory, timeout,
95
+ protocol mode, or tool filter invalidates the old grant. Disconnect and revoke
96
+ also invalidate an in-flight handshake so a stale connection cannot become
97
+ active afterward. Per-server include and exclude patterns limit which
98
+ capabilities enter the model's catalogue. Connection state distinguishes
99
+ disabled, connecting, connected, failed, and authentication-required servers;
100
+ one failing server does not prevent native tools or other MCP servers from
101
+ loading. Static bearer or custom headers may be configured locally. Interactive
102
+ browser OAuth is not implemented yet; an HTTP 401 is surfaced as
103
+ `needs_auth` instead of being reported as a generic transport failure.
104
+
105
+ ## Agent browser boundary
106
+
107
+ Causyne's web foundation has two layers. Lightweight TypeScript search and
108
+ fetch clients work without a browser runtime. Rendered navigation uses a
109
+ dedicated in-process Causyne Browser adapter built from a locally reviewed
110
+ upstream browser engine. Required upstream names and licenses remain in the
111
+ third-party notices. The adapter is deliberately smaller than the reference
112
+ service:
113
+ it does not import an HTTP server, inbound port, cloud deployment layer, VNC,
114
+ telemetry, remote WebSocket control, or OpenClaw integration.
115
+
116
+ The browser engine and Playwright are exact-pinned optional capability packages
117
+ installed into the user's private Causyne capability directory only after an
118
+ explicit browser capability grant. Causyne does not download a browser binary. Advanced
119
+ rendered browsing requires a separately installed supported browser runtime
120
+ selected by `CAUSYNE_BROWSER_EXECUTABLE` or discovered from a standard system
121
+ location.
122
+ The main npm package therefore stays small and web search remains useful when
123
+ the optional rendered browser is absent.
124
+
125
+ The browser runs headless by default with WebRTC blocked and a Causyne-owned
126
+ profile. It provides tabs, navigation, accessibility snapshots with bounded
127
+ pagination, stable element references, form interaction, bounded extraction,
128
+ and atomic downloads restricted to the selected workspace. Search first uses
129
+ bounded DuckDuckGo HTML and Bing RSS providers, then uses rendered provider
130
+ pages when the optional browser is available. Failures remain visible after
131
+ every provider is exhausted instead of being replaced with fabricated results.
132
+
133
+ Every top-level navigation and browser subresource passes through the public
134
+ URL policy. The policy accepts only HTTP(S), rejects embedded credentials,
135
+ resolves hostnames before access, and blocks loopback, private, link-local,
136
+ multicast, reserved, local, internal, and metadata addresses. Redirects in the
137
+ non-browser fetch path are revalidated one at a time. Responses and extracted
138
+ text are bounded, and network downloads require the existing approval gate.
139
+ These controls reduce SSRF and unbounded-output risk while preserving normal
140
+ public-web use from the user's selected workspace.
141
+
142
+ User-defined subagent profiles are static JSON manifests under the local Causyne
143
+ state directory. A profile supplies bounded instructions, an allowlist of tools,
144
+ and hard step/token ceilings. It cannot widen its own scope, cannot recursively
145
+ delegate, and does not load executable project code. Calls may narrow a
146
+ profile's tool scope or budgets, but cannot exceed them.
147
+
148
+ Completed delegated runs write a separate append-only JSONL sidechain under the
149
+ local Causyne state directory. The parent receives only the worker's final
150
+ result, while the bounded sidechain retains its messages and outcome for local
151
+ inspection. A torn final append preserves earlier complete events.
62
152
 
63
153
  Hooks are user-owned argv arrays stored in the local Causyne configuration.
64
154
  They never pass through a shell and project repositories are not searched for
@@ -66,10 +156,19 @@ startup hooks. `PreToolUse` runs after the built-in sandbox and before an
66
156
  approval prompt; exit code 2 denies, and any other failure fails closed.
67
157
  `PostToolUse` can annotate a result or reject it without rewriting history.
68
158
 
69
- Host execution remains the default because Causyne must work in the directory
70
- the user selected. Optional container isolation can be added as an execution
71
- profile later, but it must remain explicit: mounting a workspace into Docker is
72
- a different trust boundary and cannot silently replace native desktop access.
159
+ Host execution remains the current architecture because Causyne must operate in
160
+ the directory and desktop environment the user selected. Container work is
161
+ deferred and is not part of the present implementation plan.
162
+
163
+ ## Durable sessions
164
+
165
+ Each workspace-scoped conversation is stored as an append-only JSONL event
166
+ journal. Reads project `session.created`, `messages.appended`,
167
+ `messages.windowed`, and explicit replacement events into the current session.
168
+ A torn final append preserves prior complete events, while complete corruption
169
+ fails safely. Legacy per-session JSON and global history files migrate on read.
170
+ The compatibility `sessionStore.js` entry keeps existing callers stable while
171
+ the strict TypeScript journal emits the JavaScript used at runtime.
73
172
 
74
173
  ## Plan cache and context pressure
75
174
 
@@ -77,7 +176,9 @@ Completed multi-step runs teach argument-free plan templates. Lookup tries an
77
176
  indexed exact signature first, then bounded lexical similarity only for a task
78
177
  shape with enough confidence. Latency, hit type and recent hit rate are recorded;
79
178
  lookup temporarily cools down when a populated cache repeatedly misses while
80
- new successful plans may continue warming it. `/plans import` accepts sanitized
179
+ new successful plans may continue warming it. Persisted plans and metrics are
180
+ validated before entering the typed runtime; malformed entries are ignored.
181
+ `/plans import` accepts sanitized
81
182
  offline samples for known workloads, and `/plans` exposes the measurements.
82
183
  `npm run benchmark:plan-cache` measures exact lookup, fuzzy lookup, misses and
83
184
  prewarming at bounded cache sizes in an isolated temporary state directory. It
@@ -1,6 +1,6 @@
1
1
  # Security review and migration status
2
2
 
3
- Review date: 2026-10-19. This is a scoped source and package review, not a
3
+ Review date: 2026-10-21. This is a scoped source and package review, not a
4
4
  claim that Causyne Agent is free from vulnerabilities.
5
5
 
6
6
  ## OpenSSF records
@@ -65,6 +65,18 @@ The active source takes a different architecture and distribution path:
65
65
  - `web_fetch` downloads and curated game-asset downloads require the highest
66
66
  approval class. Curated assets are size-limited, constrained to catalog URLs,
67
67
  protected from output path traversal, and are not automatically extracted.
68
+ - Web search uses bounded HTTP providers without requiring a browser binary.
69
+ Interactive rendered browsing uses an outbound, in-process Causyne Browser adapter.
70
+ Causyne does not run an inbound browser-control server, inbound port, remote control,
71
+ VNC, telemetry, cloud deployment, or OpenClaw integration. Optional launcher
72
+ packages require an explicit capability grant. Causyne never downloads the
73
+ browser runtime; an existing external installation must be selected
74
+ through `CAUSYNE_BROWSER_EXECUTABLE` or a standard system location.
75
+ - Browser navigation and subresources accept only public HTTP(S) destinations.
76
+ Hostnames are resolved and checked against private, loopback, link-local,
77
+ multicast, reserved, local, internal, and metadata ranges. Non-browser
78
+ redirects are checked at every hop. Downloads are bounded, atomic, restricted
79
+ to the selected workspace, and remain in the highest approval class.
68
80
  - The package allowlist excludes the legacy archive, Android source, private
69
81
  operating documents, credentials, installers, bytecode, and generated
70
82
  tarballs.
@@ -1,6 +1,6 @@
1
1
  # Third-party software review
2
2
 
3
- Review date: 2026-10-19.
3
+ Review date: 2026-10-21.
4
4
 
5
5
  Causyne Agent's npm package declares third-party packages as dependencies; it
6
6
  does not bundle their source into the project tarball. Each dependency remains
@@ -9,22 +9,58 @@ license identifiers:
9
9
 
10
10
  | License expression | Lockfile package entries |
11
11
  | --- | ---: |
12
- | Apache-2.0 | 21 |
13
- | MIT | 9 |
12
+ | Apache-2.0 | 25 |
13
+ | MIT | 20 |
14
+ | ISC | 2 |
14
15
 
15
- The lockfile contained 30 dependency entries in total. Three are production
16
- entries; development and platform-specific optional entries account for the
17
- rest. npm's audit metadata reported 12 installed packages.
16
+ The lockfile contained 47 dependency entries in total. Development and
17
+ platform-specific optional entries account for most of the graph.
18
18
 
19
- The direct runtime dependencies are `acorn-walk` and `commander`. Direct
20
- development dependencies are `@babel/parser`, `@types/node`, `acorn`, and
21
- `typescript`. Exact versions and integrity hashes are recorded in
22
- `package-lock.json`.
19
+ The direct runtime dependencies are `@modelcontextprotocol/client`,
20
+ `acorn-walk`, and `commander`. Direct development dependencies are
21
+ `@babel/parser`, `@modelcontextprotocol/node`, `@modelcontextprotocol/server`,
22
+ `@types/node`, `acorn`, `typescript`, and `zod`. The three direct MCP packages
23
+ are licensed under Apache-2.0. Exact versions, transitive dependencies, license
24
+ identifiers, and integrity hashes are recorded in `package-lock.json`.
23
25
 
24
26
  No dependency in the reviewed main lockfile was marked with an install script.
25
27
  Optional capability dependencies are installed only after approval into a
26
28
  separate local environment and are not covered by the main lockfile review.
27
29
 
30
+ The product-facing browser capability is **Causyne Browser**. Upstream names
31
+ below are used only for required attribution and exact dependency identifiers.
32
+
33
+ The optional browser capability installs `camoufox-js` 0.11.5 under MPL-2.0
34
+ and `playwright-core` 1.58.0 under Apache-2.0. They remain separate packages in
35
+ the user's local capability directory and are not copied into Causyne Agent's
36
+ npm tarball. Causyne does not download or bundle the Camoufox browser runtime.
37
+
38
+ The snapshot windowing and external executable bridge in `src/browser/` are
39
+ adapted from the MIT-licensed Camofox Browser project at source commit
40
+ `2c0036a8b18bf9cd983376751ff4d52dfd9add0f`. Its required notice is:
41
+
42
+ > MIT License
43
+ >
44
+ > Copyright (c) 2025 Jo, Inc
45
+ >
46
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
47
+ > of this software and associated documentation files (the "Software"), to deal
48
+ > in the Software without restriction, including without limitation the rights
49
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
50
+ > copies of the Software, and to permit persons to whom the Software is
51
+ > furnished to do so, subject to the following conditions:
52
+ >
53
+ > The above copyright notice and this permission notice shall be included in all
54
+ > copies or substantial portions of the Software.
55
+ >
56
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
57
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
58
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
59
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
60
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
61
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
62
+ > SOFTWARE.
63
+
28
64
  Repository scanning found no third-party copyright header or license marker in
29
65
  the project-authored source and skill files. That negative scan alone cannot
30
66
  establish authorship or permission. On 2026-10-19, the licensor confirmed that
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@causyne/agent",
3
- "version": "1.10.13",
3
+ "version": "1.10.18",
4
4
  "description": "Agentic AI for software, tools, and computer workflows",
5
5
  "main": "bin/causyne.js",
6
6
  "type": "module",
@@ -23,13 +23,15 @@
23
23
  "scripts": {
24
24
  "start": "node ./bin/causyne.js",
25
25
  "test": "node --test --import ./tests/helpers/isolateState.js \"tests/**/*.test.js\"",
26
- "build": "node scripts/compile_skills.js",
26
+ "build": "node scripts/compile_skills.js && tsc -p tsconfig.runtime.json",
27
27
  "benchmark:plan-cache": "node src/benchmarks/planCache.js",
28
28
  "check:release": "node scripts/check_release.js",
29
29
  "check:public": "npm run check:release && node scripts/check_public_release.js",
30
30
  "prepublishOnly": "npm run check:public && npm test && npm run typecheck:strict",
31
31
  "typecheck": "tsc -p tsconfig.json",
32
- "typecheck:strict": "tsc -p tsconfig.strict.json"
32
+ "typecheck:ts": "tsc -p tsconfig.strict.json",
33
+ "typecheck:js-ratchet": "tsc -p tsconfig.js-ratchet.json",
34
+ "typecheck:strict": "npm run typecheck:ts && npm run typecheck:js-ratchet"
33
35
  },
34
36
  "engines": {
35
37
  "node": ">=24.0.0"
@@ -51,14 +53,18 @@
51
53
  "url": "https://github.com/ikbal-causyne/causyne-agent/issues"
52
54
  },
53
55
  "dependencies": {
56
+ "@modelcontextprotocol/client": "2.3.1",
54
57
  "acorn-walk": "^8.3.5",
55
58
  "commander": "^11.1.0"
56
59
  },
57
60
  "devDependencies": {
58
61
  "@babel/parser": "^8.0.7",
62
+ "@modelcontextprotocol/node": "2.1.1",
63
+ "@modelcontextprotocol/server": "2.3.1",
59
64
  "@types/node": "^22.20.2",
60
65
  "acorn": "^8.18.0",
61
- "typescript": "^7.0.2"
66
+ "typescript": "^7.0.2",
67
+ "zod": "4.6.7"
62
68
  },
63
69
  "private": false
64
70
  }
package/src/agentLoop.js CHANGED
@@ -20,6 +20,7 @@ import { analyzeTask, buildTaskBrief, packsFromShape } from './taskShape.js';
20
20
  import { lookupPlan, recordPlan, recordMiss, renderPlanHint, toStep } from './planCache.js';
21
21
  import { routedConfig } from './modelRouter.js';
22
22
  import { assessStep, isThrashing } from './uncertainty.js';
23
+ import { assessDecisionRequirement, isMutatingToolCall, requiresImplementationPlan } from './decisionPolicy.js';
23
24
  import { VFETracker } from './vfeTracker.js';
24
25
  import {
25
26
  routePacks,
@@ -75,11 +76,15 @@ Specifically, you excel at:
75
76
  - **No Chitchat**: Tools are for actions, text output is *only* for communication. Do not add explanatory comments within tool calls or code blocks.
76
77
  - **File Paths**: ALWAYS output the absolute path of any file you create, modify, or generate so the user knows exactly where it is.
77
78
 
78
- # Task Management & Planning (TodoWrite)
79
- - ALWAYS use the TodoWrite tool to track and manage tasks before starting implementation. Create atomic, verb-led, clear outcome items.
79
+ # Task Management & Planning
80
+ - Use update_plan to create a visible implementation checklist before complex implementation. Create atomic, verb-led, verifiable steps and keep exactly one step in progress.
80
81
  - Check off completed TODOs immediately before reporting progress. Do not batch up multiple task completions.
81
82
  - Reconcile and close the todo list when all goal tasks are done. If the user asks you to implement, do not output a separate text-based plan. Just build and display the todo list.
82
83
 
84
+ # Durable Decisions
85
+ - Use request_decision when multiple materially different architecture, product, data, security, or workflow directions are valid. Give 2-4 concrete options with consequences; the UI adds a custom-answer choice.
86
+ - A decision is separate from permission. Do not ask for routine implementation details that can be resolved from the repository. Even in Mode C, pause for an unresolved durable architecture decision before changing the system.
87
+
83
88
  # Maximize Parallel Tool Calls (Speed is Key)
84
89
  - CRITICAL INSTRUCTION: For maximum efficiency, whenever you perform multiple operations, invoke all relevant tools concurrently (in parallel) rather than sequentially.
85
90
  - Searching for different patterns (imports, usage, definitions) MUST happen in parallel. Reading multiple files MUST happen in parallel.
@@ -344,6 +349,17 @@ export class AgentLoop {
344
349
  this.circuitBreakerUnlocked = false;
345
350
  this.stopRequested = false;
346
351
  this.pendingApprovals = new Map(); // Maps toolCallId -> resolve function
352
+ this.waitForDecision = async () => null;
353
+ this._decisionAnswers = new Map();
354
+ this._decisionRequirement = { required: false, kind: 'architecture', reason: '' };
355
+ this._decisionResolved = true;
356
+ this._implementationPlan = [];
357
+ this._implementationPlanRequired = false;
358
+ // A delegated subagent can tighten these two ceilings. They are applied
359
+ // after every config refresh so run() cannot accidentally replace a
360
+ // worker's declared bounds with the parent tier defaults.
361
+ this.maxStepsCeiling = null;
362
+ this.taskTokenLimit = null;
347
363
 
348
364
  // Limits based on tier/budget & emulator fast bypass
349
365
  this.updateTierLimits();
@@ -695,6 +711,58 @@ export class AgentLoop {
695
711
  }
696
712
  }
697
713
 
714
+ updateImplementationPlan(update) {
715
+ this._implementationPlan = update.plan.map(item => ({ ...item }));
716
+ this.emitEvent({
717
+ type: 'agent_plan',
718
+ explanation: update.explanation || '',
719
+ plan: this._implementationPlan.map(item => ({ status: item.status, text: item.step, id: item.id }))
720
+ });
721
+ auditLog.log({
722
+ event: 'implementation_plan_updated', taskId: this.taskId,
723
+ steps: this._implementationPlan.length,
724
+ completed: this._implementationPlan.filter(item => item.status === 'completed').length
725
+ });
726
+ }
727
+
728
+ async requestDecision(request) {
729
+ const fingerprint = createHash('sha256')
730
+ .update(JSON.stringify({ kind: request.kind, question: request.question, options: request.options.map(option => option.id) }))
731
+ .digest('hex');
732
+ const remembered = this._decisionAnswers.get(fingerprint);
733
+ if (remembered) return remembered;
734
+
735
+ this.emitEvent({ type: 'decision_request', request });
736
+ auditLog.log({ event: 'decision_requested', taskId: this.taskId, kind: request.kind, optionCount: request.options.length });
737
+ const answer = await this.waitForDecision(request);
738
+ if (!answer || typeof answer.label !== 'string' || !answer.label.trim()) {
739
+ this.emitEvent({ type: 'decision_result', request, answer: null });
740
+ return null;
741
+ }
742
+ const normalized = {
743
+ requestId: request.id,
744
+ optionId: typeof answer.optionId === 'string' ? answer.optionId : null,
745
+ label: answer.label.trim(),
746
+ custom: answer.custom === true
747
+ };
748
+ this._decisionAnswers.set(fingerprint, normalized);
749
+ if (request.kind === 'architecture') this._decisionResolved = true;
750
+ this.emitEvent({ type: 'decision_result', request, answer: normalized });
751
+ auditLog.log({ event: 'decision_answered', taskId: this.taskId, kind: request.kind, custom: normalized.custom });
752
+ return normalized;
753
+ }
754
+
755
+ mutationCheckpoint(functionName, args = {}) {
756
+ if (!isMutatingToolCall(functionName, args)) return null;
757
+ if (this._decisionRequirement.required && !this._decisionResolved) {
758
+ return 'ARCHITECTURE_DECISION_REQUIRED: Before changing the system, call request_decision with kind "architecture", 2-4 concrete options, their consequences, and a clear reason. Do not ask about routine details; this checkpoint exists because the current request crosses a durable architecture boundary.';
759
+ }
760
+ if (this._implementationPlanRequired && this._implementationPlan.length === 0) {
761
+ return 'IMPLEMENTATION_PLAN_REQUIRED: Call update_plan with atomic, verifiable steps and exactly one in_progress step before mutating this complex task.';
762
+ }
763
+ return null;
764
+ }
765
+
698
766
  updateTierLimits() {
699
767
  this.config = loadConfig();
700
768
  if (typeof this.initializeProvider === 'function') {
@@ -719,6 +787,17 @@ export class AgentLoop {
719
787
  } else {
720
788
  this.maxSteps = 300;
721
789
  }
790
+ if (Number.isInteger(this.maxStepsCeiling) && this.maxStepsCeiling > 0) {
791
+ this.maxSteps = Math.min(this.maxSteps, this.maxStepsCeiling);
792
+ }
793
+ if (Number.isInteger(this.taskTokenLimit) && this.taskTokenLimit > 0 && this.tokenTracker) {
794
+ this.tokenTracker.budget = {
795
+ ...this.tokenTracker.budget,
796
+ enabled: true,
797
+ hardStop: true,
798
+ maxTokensPerTask: this.taskTokenLimit
799
+ };
800
+ }
722
801
  this.maxCircuitBreaker = this.maxSteps;
723
802
  }
724
803
 
@@ -800,10 +879,26 @@ export class AgentLoop {
800
879
  this._lessonSimilarity = 0;
801
880
  this._lastToolResult = null;
802
881
  this._paradigmShiftPending = false;
882
+ this._implementationPlan = [];
883
+ this._decisionAnswers = new Map();
884
+ this._decisionRequirement = assessDecisionRequirement(this.userPrompt || '');
885
+ this._decisionResolved = !this._decisionRequirement.required;
886
+ this._implementationPlanRequired = false;
803
887
  if (this.vfeTracker) this.vfeTracker.reset();
804
888
 
805
889
  if (this.userPrompt) {
806
890
  this.taskShape = analyzeTask(this.userPrompt, this.workspaceContext);
891
+ this._implementationPlanRequired = requiresImplementationPlan(
892
+ this.userPrompt,
893
+ this.taskShape?.archetype || '',
894
+ this.taskShape?.confidence || ''
895
+ );
896
+ // request_decision is comparatively large and most tasks never need it.
897
+ // On providers without server-side schema deferral, expose it from the
898
+ // first model call only when the deterministic task policy found an
899
+ // unresolved durable choice. This keeps the resident prompt within its
900
+ // measured budget without making the checkpoint undiscoverable.
901
+ if (this._decisionRequirement.required) this.unlockedTools.add('request_decision');
807
902
 
808
903
  // Scored routing, with the regex router as a safety net so a pack can
809
904
  // never be missed just because it scored below threshold.
@@ -1061,9 +1156,8 @@ export class AgentLoop {
1061
1156
  // `tool_search_tool_regex`) keep the whole core in context for free —
1062
1157
  // the API holds the schemas server-side and expands them on demand,
1063
1158
  // with no discovery round trip. Every OTHER provider was serialising
1064
- // all 24 core schemas into every request: 3,341 tokens to advertise
1065
- // tools a typical turn never calls. There, ship the resident 8 (1,059
1066
- // tokens) and let `tool_search` — the same mechanism that already
1159
+ // all 24 core schemas into every request. There, ship the small resident
1160
+ // set and let `tool_search` — the same mechanism that already
1067
1161
  // unlocks packs — fetch the rest.
1068
1162
  const residentOnly = this.llm.supportsToolSearch?.() !== true;
1069
1163
  let dynamicTools = await getDynamicTools(this.config.autonomyMode, this.llm.supportsVision, {
@@ -1392,18 +1486,6 @@ export class AgentLoop {
1392
1486
  this.messages.push(assistantMsg);
1393
1487
 
1394
1488
  if (response.content) {
1395
- // V5: Parse TodoWrite checklist for visual plan display
1396
- const planRegex = /-\s*\[([ xX\/])\]\s*(.*)/g;
1397
- const planMatch = [...response.content.matchAll(planRegex)];
1398
- if (planMatch.length > 0) {
1399
- const planTasks = planMatch.map(m => {
1400
- const marker = m[1].toLowerCase();
1401
- const status = marker === 'x' ? 'completed' : (marker === '/' ? 'in_progress' : 'pending');
1402
- return { status, text: m[2].trim() };
1403
- });
1404
- this.emitEvent({ type: 'agent_plan', plan: planTasks });
1405
- }
1406
-
1407
1489
  this.emitEvent({ type: 'agent_message', content: response.content });
1408
1490
  }
1409
1491
 
@@ -1521,18 +1603,24 @@ export class AgentLoop {
1521
1603
  // if it was declined, fall through to the path that needs nothing.
1522
1604
  let visionReady = false;
1523
1605
  let visionDeclined = false;
1606
+ let browserReady = false;
1607
+ let browserDeclined = false;
1524
1608
  try {
1525
1609
  const { groupStatus, isGroupDeclined } = await import('./provisioning/capabilityProvisioner.js');
1526
1610
  visionReady = groupStatus('vision').state === 'ready';
1527
1611
  visionDeclined = isGroupDeclined('vision');
1612
+ browserReady = groupStatus('browser').state === 'ready';
1613
+ browserDeclined = isGroupDeclined('browser');
1528
1614
  } catch (e) { /* treat as not ready */ }
1529
1615
  let webGuardMsg = '';
1530
1616
 
1531
- if (hasVision && !visionReady && !visionDeclined) {
1532
- webGuardMsg = `[ANTI-SLOP CHECK: You are building a Web/UI project and must look at it before finishing, but the browser and screen-capture capability is not enabled yet. Call \`manage_local_dependencies\` with action "grant_capability" and group "vision" to request it from the user. If the user declines, read the final compiled HTML/CSS instead and verify it that way. This check fires once.]`;
1617
+ if (!browserReady && !browserDeclined) {
1618
+ webGuardMsg = `[ANTI-SLOP CHECK: You are building a Web/UI project and must inspect it before finishing, but the dedicated browser capability is not enabled yet. Call \`manage_local_dependencies\` with action "grant_capability" and group "browser" to request the Causyne Browser runtime. If the user declines, read the final compiled HTML/CSS instead. This check fires once.]`;
1619
+ } else if (hasVision && !visionReady && !visionDeclined) {
1620
+ webGuardMsg = `[ANTI-SLOP CHECK: The browser is available, but desktop screen capture is not enabled. Call \`manage_local_dependencies\` with action "grant_capability" and group "vision" to request it, or use \`browser_act\` snapshots and its workspace-scoped screenshot action. This check fires once.]`;
1533
1621
  } else if (hasVision && visionReady && !this.webVerificationState.screenshotTaken) {
1534
1622
  webGuardMsg = `[ANTI-SLOP CHECK: You are building a Web/UI project but attempted to finish without VISUALLY verifying your work. Use \`screenshot\` or \`vision_inspect_ui\` to look at the UI in a browser. A human looks at their work to avoid slop. This check fires once — if there is genuinely nothing visual to inspect, say so plainly and finish.]`;
1535
- } else if ((!hasVision || visionDeclined) && !this.webVerificationState.browserActUsed && !this.webVerificationState.screenshotTaken) {
1623
+ } else if ((!hasVision || visionDeclined) && browserReady && !this.webVerificationState.browserActUsed && !this.webVerificationState.screenshotTaken) {
1536
1624
  webGuardMsg = `[TRANSCENDENCE CHECK: You are building a Web/UI project. As a vision-disabled model you cannot screenshot, but you MUST use \`browser_act\` to inspect the DOM, or read the final compiled HTML/CSS, to verify semantic correctness before finishing. This check fires once.]`;
1537
1625
  }
1538
1626
 
@@ -1781,6 +1869,20 @@ export class AgentLoop {
1781
1869
  continue;
1782
1870
  }
1783
1871
 
1872
+ const checkpointError = this.mutationCheckpoint(functionName, args);
1873
+ if (checkpointError) {
1874
+ this.messages.push({
1875
+ role: 'tool',
1876
+ tool_call_id: toolCall.id,
1877
+ name: functionName,
1878
+ content: JSON.stringify({
1879
+ success: false,
1880
+ error: checkpointError
1881
+ })
1882
+ });
1883
+ continue;
1884
+ }
1885
+
1784
1886
  // A policy hook runs after the built-in sandbox but before any user
1785
1887
  // approval prompt, so a deterministic denial never asks the user to
1786
1888
  // approve work that will be rejected anyway.
@@ -2244,7 +2346,7 @@ export class AgentLoop {
2244
2346
  //
2245
2347
  // Only payloads worth the trade are folded. A small `{"success":true}`
2246
2348
  // ack costs less than the pointer that would replace it, and every
2247
- // TodoWrite would otherwise collide with every other TodoWrite.
2349
+ // Plan updates would otherwise collide with unrelated plan updates.
2248
2350
  const OBSERVATION_FOLD_FLOOR = 600;
2249
2351
  if (resultString.length >= OBSERVATION_FOLD_FLOOR) {
2250
2352
  const digest = createHash('sha256').update(resultString).digest('hex');