atris 3.37.1 → 3.40.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 (164) hide show
  1. package/AGENTS.md +25 -6
  2. package/README.md +2 -0
  3. package/atris/PERSONA.md +11 -11
  4. package/atris.md +17 -2
  5. package/ax +160 -34
  6. package/bin/atris.js +78 -6
  7. package/commands/activate.js +16 -3
  8. package/commands/agent-spawn.js +13 -18
  9. package/commands/autoland.js +76 -47
  10. package/commands/autopilot-front.js +0 -4
  11. package/commands/autopilot.js +0 -12
  12. package/commands/bench.js +10 -12
  13. package/commands/brain.js +0 -7
  14. package/commands/brief.js +0 -2
  15. package/commands/business-sync.js +0 -1
  16. package/commands/business.js +452 -12
  17. package/commands/chat-scan.js +5 -7
  18. package/commands/clean.js +31 -2
  19. package/commands/close.js +46 -11
  20. package/commands/cloud.js +0 -1
  21. package/commands/codex-goal.js +8 -12
  22. package/commands/computer.js +6 -5
  23. package/commands/console.js +64 -11
  24. package/commands/decide.js +166 -0
  25. package/commands/deck.js +30 -12
  26. package/commands/dream.js +0 -7
  27. package/commands/drill.js +14 -25
  28. package/commands/engine.js +206 -26
  29. package/commands/experiments.js +0 -4
  30. package/commands/fleet-report.js +12 -4
  31. package/commands/game.js +261 -0
  32. package/commands/gm.js +17 -7
  33. package/commands/goal.js +0 -6
  34. package/commands/harvest.js +1 -5
  35. package/commands/improve.js +29 -8
  36. package/commands/init.js +41 -37
  37. package/commands/integrations.js +0 -7
  38. package/commands/land.js +77 -20
  39. package/commands/launchpad.js +1 -16
  40. package/commands/lesson.js +17 -7
  41. package/commands/lifecycle.js +5 -5
  42. package/commands/log.js +55 -5
  43. package/commands/loop-front.js +0 -2
  44. package/commands/loops.js +6 -5
  45. package/commands/meet.js +0 -2
  46. package/commands/member.js +622 -573
  47. package/commands/mission.js +790 -314
  48. package/commands/next.js +0 -1
  49. package/commands/now.js +0 -12
  50. package/commands/one-lap.js +8 -8
  51. package/commands/orb.js +0 -4
  52. package/commands/pack-craft.js +0 -1
  53. package/commands/pack.js +3599 -424
  54. package/commands/play.js +6 -6
  55. package/commands/probe.js +12 -6
  56. package/commands/pulse.js +88 -64
  57. package/commands/push.js +0 -4
  58. package/commands/radar.js +14 -11
  59. package/commands/recover.js +340 -0
  60. package/commands/release.js +10 -9
  61. package/commands/report.js +0 -1
  62. package/commands/router.js +320 -0
  63. package/commands/run-front.js +0 -2
  64. package/commands/search.js +0 -3
  65. package/commands/site-deploy.js +870 -0
  66. package/commands/site.js +11 -2
  67. package/commands/skill.js +7 -2
  68. package/commands/slop.js +237 -7
  69. package/commands/status.js +6 -0
  70. package/commands/stream.js +4 -20
  71. package/commands/study.js +0 -3
  72. package/commands/sync-checkout.js +51 -0
  73. package/commands/sync.js +11 -40
  74. package/commands/task.js +999 -595
  75. package/commands/taste.js +101 -0
  76. package/commands/teach.js +86 -0
  77. package/commands/team.js +83 -3
  78. package/commands/unknowns.js +0 -10
  79. package/commands/update.js +0 -1
  80. package/commands/vercel.js +4 -2
  81. package/commands/visualize.js +0 -5
  82. package/commands/voice.js +195 -0
  83. package/commands/watch.js +1 -28
  84. package/commands/wiki.js +42 -15
  85. package/commands/wish.js +0 -1
  86. package/commands/workflow.js +2 -2
  87. package/commands/worktree.js +88 -28
  88. package/commands/xp.js +28 -30
  89. package/commands/youtube.js +0 -2
  90. package/lib/accept-verify-gate.js +126 -0
  91. package/lib/activity-stream.js +0 -2
  92. package/lib/arg-parser.js +41 -0
  93. package/lib/auto-accept-certified.js +261 -3
  94. package/lib/autoland.js +66 -3
  95. package/lib/ax-auto-lane.js +68 -7
  96. package/lib/bench/context.js +0 -2
  97. package/lib/bench/report.js +0 -1
  98. package/lib/bench/runner.js +18 -11
  99. package/lib/brief-ledger.js +0 -6
  100. package/lib/business-simulate.js +382 -0
  101. package/lib/chat-log-scan.js +0 -5
  102. package/lib/checkout-sync.js +52 -0
  103. package/lib/claude-boot-block.js +48 -0
  104. package/lib/cloud-mission.js +0 -6
  105. package/lib/codex-flight.js +0 -1
  106. package/lib/conductor-artifacts.js +25 -0
  107. package/lib/context-gatherer.js +7 -2
  108. package/lib/deck-compose.js +0 -1
  109. package/lib/deck-history.js +0 -1
  110. package/lib/deck-layout.js +0 -5
  111. package/lib/deck-review.js +0 -7
  112. package/lib/deck-schema.js +0 -1
  113. package/lib/default-verifier.js +0 -2
  114. package/lib/engine-registry.js +157 -28
  115. package/lib/experiments/daily.js +0 -6
  116. package/lib/falsifier-probe.js +84 -0
  117. package/lib/fleet.js +275 -42
  118. package/lib/functional-owner.js +0 -3
  119. package/lib/git-spawn.js +15 -0
  120. package/lib/inspect-fields.js +0 -1
  121. package/lib/json-file.js +37 -0
  122. package/lib/known-commands.js +2 -2
  123. package/lib/learnings.js +0 -2
  124. package/lib/lesson-preflight.js +146 -0
  125. package/lib/loop-doctor.js +11 -19
  126. package/lib/manifest.js +0 -2
  127. package/lib/member-alive.js +0 -1
  128. package/lib/member-switches.js +0 -5
  129. package/lib/mission-artifact.js +0 -1
  130. package/lib/mission-human-asks.js +28 -0
  131. package/lib/mission-protected-lane.js +296 -0
  132. package/lib/mission-room.js +0 -2
  133. package/lib/mission-runtime-loop.js +0 -1
  134. package/lib/next-moves.js +0 -18
  135. package/lib/official-cli-integration.js +47 -6
  136. package/lib/orb-context.js +14 -4
  137. package/lib/orb-scorecard.js +0 -1
  138. package/lib/outbound-send-gate.js +0 -6
  139. package/lib/pack-capabilities.js +685 -0
  140. package/lib/permission-grants.js +0 -6
  141. package/lib/policy-lessons.js +0 -2
  142. package/lib/pulse.js +68 -15
  143. package/lib/review-integrity.js +0 -2
  144. package/lib/router-brain.js +51 -3
  145. package/lib/runner-command.js +0 -10
  146. package/lib/runtime-bootstrap.js +0 -1
  147. package/lib/scorecard.js +0 -1
  148. package/lib/self-drive.js +45 -14
  149. package/lib/state-detection.js +0 -6
  150. package/lib/task-db.js +137 -7
  151. package/lib/task-decision.js +50 -0
  152. package/lib/taste-lessons.js +153 -0
  153. package/lib/team-presence.js +0 -1
  154. package/lib/tool-result-encode.js +17 -1
  155. package/lib/voice-gate.js +129 -2
  156. package/lib/wiki.js +152 -9
  157. package/lib/wish-audit.js +21 -7
  158. package/lib/wish-delegate.js +27 -21
  159. package/lib/wish-stats.js +0 -5
  160. package/lib/wish-store.js +0 -1
  161. package/lib/workspace-safety.js +0 -2
  162. package/lib/zip.js +95 -8
  163. package/package.json +2 -1
  164. package/templates/business-starter/persona.md +9 -0
package/AGENTS.md CHANGED
@@ -1,9 +1,28 @@
1
- # AGENTS.md — Universal Agent Instructions
1
+ # AGENTS.md: Universal Agent Instructions
2
2
 
3
- > **New here?** Read [`FOR_AGENTS.md`](FOR_AGENTS.md) — open letter from an agent who tried it.
3
+ > **New here?** Read [`FOR_AGENTS.md`](FOR_AGENTS.md): open letter from an agent who tried it.
4
4
 
5
5
  > Works with: Claude Code, Cursor, Codex, OpenClaw, Windsurf, and any AI coding agent.
6
6
 
7
+ ## The way of talking (every reply, no exceptions)
8
+
9
+ Talk like a person. This governs every message you send the operator: boot, status, summaries, everything.
10
+
11
+ - Plain words. Say what happened and what it means for the reader, cause and effect. Not the machinery.
12
+ - No task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick) in the message body. If the reader needs a command, put ONE copyable line at the end.
13
+ - No insider terms without defining them in the same breath. A sharp non-engineer must follow it on first read.
14
+ - Lead with the outcome. Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies; those belong in files.
15
+ - Detail lives in files and receipts. Offer "want the detail?" instead of dumping it.
16
+ - No em dashes anywhere; use a comma, colon, or period.
17
+ - The test before sending: read it fried at 2am. If decoding takes work, rewrite it.
18
+
19
+ copy these shapes:
20
+ - **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
21
+ - **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
22
+ - **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
23
+
24
+ Canonical source: `atris/atris.md` `## voice`. This block is a copy for tools that only read `AGENTS.md`; if they differ, atris.md wins.
25
+
7
26
  ## Quick Start
8
27
 
9
28
  ```bash
@@ -31,7 +50,7 @@ task truth, proof, review, and backend/cloud sync all flow through Atris.
31
50
  |------|---------|
32
51
  | `atris/atris.md` | Protocol/backbone for this workspace |
33
52
  | `atris/PERSONA.md` | Communication style (read first) |
34
- | `atris task` | Current tasks, claims, dialogue, proof — deep dive: [`tasks.md`](tasks.md) |
53
+ | `atris task` | Current tasks, claims, dialogue, proof; deep dive: [`tasks.md`](tasks.md) |
35
54
  | `.atris/state/tasks.projection.json` | Readable task projection for UIs/agents |
36
55
  | `atris/TODO.md` | Rendered/legacy task view only |
37
56
  | `atris/MAP.md` | Navigation (where is X?) |
@@ -125,13 +144,13 @@ member -> mission start --verify -> status --status active -> one bounded step -
125
144
  - Close: if the verifier passes, run `atris mission complete <id> --proof "<receipt_path>"`; if current-agent work should keep going, repeat status -> step -> tick.
126
145
  - Rollout: `atrisos-backend` and `atrisos-web` agents must check active missions before picking work; if no active mission exists and autonomy was requested, create one with owner, verifier, lane, and stop condition first.
127
146
 
128
- ## Build Craft — what decides acceptance
147
+ ## Build Craft: what decides acceptance
129
148
 
130
149
  Mined from this repo's receipts (803 receipts, 1711 episodes): proofs naming a
131
150
  runnable verify command were accepted 670/674 at the gate; 24 of 28 bounces
132
151
  named none. These rules are the difference, in priority order:
133
152
 
134
- 1. **Name a runnable verify command in every proof**, and run it bare — never
153
+ 1. **Name a runnable verify command in every proof**, and run it bare; never
135
154
  `test | tail` or `| grep`; a pipe replaces your exit code with the filter's.
136
155
  2. **A task naming a spec file (`atris/features/<name>/idea.md`) is a contract.**
137
156
  Read it before writing code; build the named slice only; use its verify
@@ -157,7 +176,7 @@ named none. These rules are the difference, in priority order:
157
176
  reproduces it against the real runtime, not a mock that can stay green
158
177
  through breakage.
159
178
  10. **When your engine dies mid-build (credits, limits), that is a staffing
160
- event, not a failure** — leave the worktree intact with a note; the
179
+ event, not a failure**; leave the worktree intact with a note; the
161
180
  conductor restaffs it.
162
181
 
163
182
  ## Rules
package/README.md CHANGED
@@ -226,6 +226,8 @@ atris business record atris/reports/2026-04-12-operator-recap.md --outcome mixed
226
226
  | `atris experiments` | Run small experiments and compare results |
227
227
  | `atris computer card` | Show or write the local owner/computer card |
228
228
  | `atris release` | Draft or publish a version bump, tag, GitHub release, and launch post |
229
+ | `atris slop` | Deterministic slop detector: frontend/prose tells, plus `slop dead --exports` for dead code |
230
+ | `atris clean` | Housekeeping: heal MAP refs, archive journals, report stale pages and dead code |
229
231
 
230
232
  ## Built-In Systems
231
233
 
package/atris/PERSONA.md CHANGED
@@ -1,4 +1,4 @@
1
- # PERSONA.md — Atris Agent Personality
1
+ # PERSONA.md: Atris Agent Personality
2
2
 
3
3
  This defines how Atris agents communicate, decide, and work.
4
4
 
@@ -28,21 +28,17 @@ This defines how Atris agents communicate, decide, and work.
28
28
 
29
29
  ## Core Workflow
30
30
 
31
- **Read before you act.** Before planning or building, read the relevant files. Understand the current state. Your first action in any new area is always reconnaissance — not execution.
31
+ **Read before you act.** Before planning or building, read the relevant files. Understand the current state. Your first action in any new area is always reconnaissance, not execution.
32
32
 
33
- **Always ask for intent.** Clarify before acting.
33
+ **Ask for intent when it's fuzzy or the blast radius is real.** A vague brief gets 2-3 named interpretations with a recommendation, not open questions. Small, reversible, clearly-scoped work: act, then report. Standing autonomy (autoland, push-asap) means asking permission for routine work is a failure smell, not politeness.
34
34
 
35
- **Use ASCII visualization to confirm understanding:**
35
+ **Use ASCII visualization to confirm understanding when a plan is worth confirming:**
36
36
  - **UI elements:** Show design using ASCII
37
37
  - **Backend:** Use arrows, diagrams, logic gates
38
38
  - **Databases:** Tables and graphs showing relationships
39
39
  - **Other cases:** Use best judgment
40
40
 
41
- **Always confirm understanding in ASCII visualization layer for planning.**
42
-
43
- Then go 3-4 sentences one by one through each task.
44
-
45
- Once every task is confirmed, create a plan.
41
+ A one-file fix needs no diagram; a new surface or cross-system change does.
46
42
 
47
43
  **If a task is too big, break it down.** One job per task. If you can't describe "done" in one sentence, decompose it. Small precise tasks compound into big results.
48
44
 
@@ -56,9 +52,13 @@ We can always add layer by layer.
56
52
 
57
53
  ## Communication Style
58
54
 
59
- **3-4 sentences max.** No verbose explanations. Get to the point.
55
+ **Talk like a person.** Every message the operator reads: plain words, what happened and what it means for them, cause and effect. Not the machinery.
56
+
57
+ **No codes in the message body.** No task ids, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick). If the reader needs a command, ONE copyable line at the end. Insider terms get defined in the same breath or cut.
58
+
59
+ **Lead with the outcome.** Complete sentences, one or two per paragraph, blank line between. No headers, bullet stacks, or tables in chat replies. Detail lives in files; offer "want the detail?" instead of dumping it.
60
60
 
61
- Direct and casual tone. No corporate speak.
61
+ **The test before sending:** read it fried at 2am. If decoding takes work, rewrite it.
62
62
 
63
63
  If something is slop, call it out. Optimize ruthlessly.
64
64
 
package/atris.md CHANGED
@@ -7,7 +7,9 @@ leave a trail another agent or human can trust.
7
7
 
8
8
  ## activate
9
9
 
10
- On session start, before responding:
10
+ On session start, before responding (if the harness already booted for you, via a
11
+ SessionStart hook or an `atris` status block in context, do not repeat it; go
12
+ straight to the work):
11
13
 
12
14
  1. Read:
13
15
  - `atris/logs/YYYY/YYYY-MM-DD.md`: today's journal
@@ -39,13 +41,17 @@ If a task was already given, show the box and proceed with that task.
39
41
 
40
42
  You can move fast. You do not get to move blindly.
41
43
 
42
- Before changing anything, state:
44
+ Scale ceremony to blast radius. Before a nontrivial change (new surface,
45
+ multi-file, migrations, anything hard to roll back), state:
43
46
  - the goal
44
47
  - the files or systems in scope
45
48
  - what "done" means
46
49
  - how it will be checked
47
50
  - what happens if it fails
48
51
 
52
+ For a small bounded fix, the task's Exit and Verify fields carry this; do not
53
+ restate them as ritual.
54
+
49
55
  Then:
50
56
  - when you begin work in a repo that has no `atris/` folder, run `npx -y atris init` before anything else, then read `atris/atris.md`
51
57
  - do not execute if another agent owns the same task or files
@@ -77,6 +83,13 @@ What you ship should not read as generated. The test: if someone said "an AI mad
77
83
 
78
84
  The same discipline for words. Output stays sharp no matter how bloated the context. A full context is not license to ramble.
79
85
 
86
+ - **The way of talking.** Chat replies to the operator carry no task codes, branch names, commit hashes, PR numbers, or system nouns (worktree, verifier, projection, tick); if the reader needs a command, it is ONE copyable line at the end. Insider terms get defined in the same breath or cut. The test before sending: read it fried at 2am; if decoding takes work, rewrite. This is the canonical statement; `AGENTS.md` and other adapters carry a copy of it, never a variant.
87
+
88
+ copy these shapes:
89
+ - **Status:** "The reply check is built. I am running the final checks now, so the result is not ready yet."
90
+ - **Landing:** "Replies now get a plain-language check before they reach you. The checks passed, and the change is ready."
91
+ - **Failure:** "The plain-language check could not run because its model was unavailable. Your reply still went through, so no work was blocked."
92
+
80
93
  - **Lead with the move.** Answer first, support after. No preamble, no agreement reflex ("great question", "you're absolutely right").
81
94
  - **Specific over buzzy.** Name the exact thing. If you can't, you don't understand it yet; go look, don't hedge.
82
95
  - **Cut filler.** Drop "it's worth noting", "in order to", "leverage", "seamless", "robust", "delve", stacked hedges, and em dashes. `atris slop` flags the prose tells (em-dash, hype-copy) too.
@@ -90,6 +103,8 @@ The same discipline for words. Output stays sharp no matter how bloated the cont
90
103
  - "Missions survive everything: one engine starts the work, another picks it up cold, a third lands it. Work is no longer tied to a chat window, a session, or a vendor."
91
104
  - "The system reports in plain English: one daily message with what landed, what waits on you, and who should own what's next. One person can supervise many projects."
92
105
  - **Fit the screen.** An operator-facing report shows three results, air between them, and holds the rest on ask. No scrolling: reading it is one glance, and the reader asks for more if they want more.
106
+ - **Pace with whitespace.** Words for the operator arrive as complete sentences, one or two per paragraph, blank line between. The whitespace does the pacing, never fragments: fast to scan, still flowing.
107
+ - **Assume attention is scarce and ambition is not.** The reader is at the end of a long day and still moving something big forward. Nothing may require held state (remembering step 2 while reading step 5), every surface ends in one obvious next action that starts in under a minute, and each glance should leave the reader one step closer. Engineers and non-engineers get the same plain sentence; precision stays one ask away, never in the way.
93
108
  - **Human surfaces are gated.** Anything rendered for the operator (brief, digest, boot) goes through the operator-voice translator and a deterministic jargon gate; raw ids, commands, paths, and test tallies never reach a page. A gate violation is a build error, not a style note.
94
109
  - **Interview the fuzz.** When the operator hands off a fuzzy build ("make me the best X", "yeah ok this is my thought, go make it"), do not ask open questions and do not dispatch as-is. Offer 2-3 named interpretations with a recommendation ("best could mean capture, resurfacing, or closure: I'd bet resurfacing, right?") so the operator confirms or corrects in five words. Repeat until the brief fits one screen (goal, why now, done-looks-like, open risks), show the brief, then dispatch. The interview lends the operator a structure to think in; the sharpened brief is what makes every downstream lap cheap.
95
110
 
package/ax CHANGED
@@ -12,7 +12,8 @@ const permissionGrants = require(path.join(__dirname, 'lib', 'permission-grants.
12
12
  const { loadCredentials } = require('./utils/auth');
13
13
  const { apiRequestJson, getApiBaseUrl } = require('./utils/api');
14
14
  const missionRuntime = require('./lib/mission-runtime-loop');
15
- const { pickLane } = require('./lib/ax-auto-lane');
15
+ const { loadOverrides, pickLane } = require('./lib/ax-auto-lane');
16
+ const { buildToolResultBody } = require('./lib/tool-result-encode');
16
17
 
17
18
  const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
18
19
  const BACKEND = {
@@ -957,16 +958,92 @@ function normalizeMode(mode) {
957
958
  return mode === 'fast' ? 'fast' : 'pro';
958
959
  }
959
960
 
961
+ function autoPickLogPath(options = {}) {
962
+ return options.pickLogPath
963
+ || process.env.ATRIS_ROUTER_PICKS_PATH
964
+ || path.join(os.homedir(), '.atris', 'ax-auto-picks.jsonl');
965
+ }
966
+
967
+ function autoMessageWords(message) {
968
+ return new Set(String(message || '').toLowerCase().match(/[a-z0-9]+/g) || []);
969
+ }
970
+
971
+ function autoMessageOverlap(left, right) {
972
+ const leftWords = autoMessageWords(left);
973
+ const rightWords = autoMessageWords(right);
974
+ const union = new Set([...leftWords, ...rightWords]);
975
+ if (!union.size) return 0;
976
+ let shared = 0;
977
+ for (const word of leftWords) {
978
+ if (rightWords.has(word)) shared += 1;
979
+ }
980
+ return shared / union.size;
981
+ }
982
+
983
+ function appendRephrasedOutcome(message, logPath) {
984
+ try {
985
+ const rows = fs.readFileSync(logPath, 'utf8').split(/\r?\n/).filter((line) => line.trim());
986
+ let previous = null;
987
+ for (let index = rows.length - 1; index >= 0; index -= 1) {
988
+ try {
989
+ const row = JSON.parse(rows[index]);
990
+ if (row && row.pick_id && row.lane && row.message) {
991
+ previous = row;
992
+ break;
993
+ }
994
+ } catch (_) {
995
+ // Ignore malformed historical lines.
996
+ }
997
+ }
998
+ if (!previous) return;
999
+ const ageMs = Date.now() - Date.parse(previous.at);
1000
+ if (ageMs < 0 || ageMs >= 10 * 60 * 1000) return;
1001
+ if (autoMessageOverlap(previous.message, message) < 0.5) return;
1002
+ fs.appendFileSync(logPath, `${JSON.stringify({
1003
+ event: 'outcome',
1004
+ pick_id: previous.pick_id,
1005
+ outcome: 'rephrased',
1006
+ })}\n`);
1007
+ } catch (_) {
1008
+ // Rephrase detection is best-effort and must never block a turn.
1009
+ }
1010
+ }
1011
+
960
1012
  function appendAutoPick(message, picked, options = {}) {
1013
+ const pick = { ...picked, pick_id: crypto.randomUUID() };
1014
+ const logPath = autoPickLogPath(options);
1015
+ Object.defineProperty(pick, '_logPath', { value: logPath, enumerable: false });
961
1016
  try {
962
- const dir = path.join(os.homedir(), '.atris');
963
- fs.mkdirSync(dir, { recursive: true });
964
- fs.appendFileSync(path.join(dir, 'ax-auto-picks.jsonl'), `${JSON.stringify({
1017
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
1018
+ appendRephrasedOutcome(message, logPath);
1019
+ fs.appendFileSync(logPath, `${JSON.stringify({
965
1020
  at: new Date().toISOString(),
966
- lane: picked.lane,
967
- reason: picked.reason,
1021
+ pick_id: pick.pick_id,
1022
+ lane: pick.lane,
1023
+ reason: pick.reason,
1024
+ message: String(message || ''),
968
1025
  message_chars: String(message || '').length,
969
1026
  print: Boolean(options.print),
1027
+ ...(pick.override_id ? { override_id: pick.override_id } : {}),
1028
+ })}\n`);
1029
+ } catch (_) {
1030
+ // Lane telemetry is best-effort and must never block a turn.
1031
+ }
1032
+ return pick;
1033
+ }
1034
+
1035
+ function appendAutoOutcome(picked, result, options = {}) {
1036
+ if (!picked || !picked.pick_id) return;
1037
+ try {
1038
+ const logPath = autoPickLogPath({ pickLogPath: options.pickLogPath || picked._logPath });
1039
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
1040
+ fs.appendFileSync(logPath, `${JSON.stringify({
1041
+ event: 'outcome',
1042
+ pick_id: picked.pick_id,
1043
+ ok: Boolean(result && result.ok),
1044
+ model: String((result && result.model) || modelForMode(picked.lane)),
1045
+ duration_ms: Number((result && (result.duration_ms || result.durationMs)) || 0),
1046
+ error: result && result.error ? String(result.error) : null,
970
1047
  })}\n`);
971
1048
  } catch (_) {
972
1049
  // Lane telemetry is best-effort and must never block a turn.
@@ -974,8 +1051,10 @@ function appendAutoPick(message, picked, options = {}) {
974
1051
  }
975
1052
 
976
1053
  function autoLaneForMessage(message, options = {}) {
977
- const picked = pickLane(message);
978
- appendAutoPick(message, picked, options);
1054
+ const overrides = Array.isArray(options.overrides)
1055
+ ? options.overrides
1056
+ : loadOverrides(options.overridesPath);
1057
+ const picked = appendAutoPick(message, pickLane(message, { overrides }), options);
979
1058
  const errorOutput = options.errorOutput || process.stderr;
980
1059
  errorOutput.write(`${picked.reason}\n`);
981
1060
  return picked;
@@ -2793,7 +2872,7 @@ async function postTurn(message, options = {}) {
2793
2872
  if (options.business) {
2794
2873
  return options.business.postToolResult(callId, result, backendBaseUrl({ route: 'cloud' }));
2795
2874
  }
2796
- return postJson(BACKEND_API_TOOL_RESULT_PATH, { call_id: callId, result }, {
2875
+ return postJson(BACKEND_API_TOOL_RESULT_PATH, buildToolResultBody(callId, result), {
2797
2876
  token,
2798
2877
  route: endpointRoute,
2799
2878
  timeoutMs: 30000,
@@ -3016,8 +3095,11 @@ async function runHeadlessTurn(message, options = {}) {
3016
3095
  const startedAt = Date.now();
3017
3096
  const sink = options.output || bufferedOutput();
3018
3097
  const turnFunction = options.turnFunction || turnFunctionForMode(mode);
3019
- const autoFields = options.autoLane && options.autoReason
3020
- ? { auto_lane: normalizeMode(options.autoLane), auto_reason: String(options.autoReason) }
3098
+ const autoPick = options.autoPick || null;
3099
+ const autoFields = autoPick
3100
+ ? { auto_lane: normalizeMode(autoPick.lane), auto_reason: String(autoPick.reason) }
3101
+ : options.autoLane && options.autoReason
3102
+ ? { auto_lane: normalizeMode(options.autoLane), auto_reason: String(options.autoReason) }
3021
3103
  : {};
3022
3104
 
3023
3105
  try {
@@ -3030,15 +3112,17 @@ async function runHeadlessTurn(message, options = {}) {
3030
3112
  output: sink,
3031
3113
  showProgress: false,
3032
3114
  });
3033
- return {
3115
+ const payload = {
3034
3116
  ok: true,
3035
3117
  model: modelForMode(mode),
3036
3118
  output: String((result && result.output) || '').trim(),
3037
3119
  durationMs: Number((result && result.durationMs) || 0) || (Date.now() - startedAt),
3038
3120
  ...autoFields,
3039
3121
  };
3122
+ appendAutoOutcome(autoPick, payload);
3123
+ return payload;
3040
3124
  } catch (error) {
3041
- return {
3125
+ const payload = {
3042
3126
  ok: false,
3043
3127
  model: modelForMode(mode),
3044
3128
  output: '',
@@ -3046,6 +3130,8 @@ async function runHeadlessTurn(message, options = {}) {
3046
3130
  error: String((error && error.message) || error || 'headless turn failed'),
3047
3131
  ...autoFields,
3048
3132
  };
3133
+ appendAutoOutcome(autoPick, payload);
3134
+ return payload;
3049
3135
  }
3050
3136
  }
3051
3137
 
@@ -3125,11 +3211,16 @@ async function chat(options = {}) {
3125
3211
  return false;
3126
3212
  }
3127
3213
 
3214
+ let autoPick = null;
3128
3215
  if (options.auto) {
3129
- mode = autoLaneForMessage(trimmed, {
3216
+ autoPick = autoLaneForMessage(trimmed, {
3130
3217
  errorOutput: options.errorOutput,
3131
3218
  print: false,
3132
- }).lane;
3219
+ overrides: options.overrides,
3220
+ overridesPath: options.overridesPath,
3221
+ pickLogPath: options.pickLogPath,
3222
+ });
3223
+ mode = autoPick.lane;
3133
3224
  }
3134
3225
 
3135
3226
  if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
@@ -3230,22 +3321,38 @@ async function chat(options = {}) {
3230
3321
  lastCompactionNotice = compactedTurns;
3231
3322
  output.write(`${paint(`· context: ${compactedTurns} earlier turn${compactedTurns === 1 ? '' : 's'} compacted into a digest`, [ANSI.muted], output)}\n`);
3232
3323
  }
3233
- const result = await turnFunction(trimmed, {
3234
- mode,
3235
- cwd,
3236
- history,
3237
- input,
3238
- output,
3239
- route,
3240
- business: options.business,
3241
- verify: options.verify,
3242
- conversationId,
3243
- turnId,
3244
- approveBackendApi: options.approveBackendApi,
3245
- apiRequestJson: options.apiRequestJson,
3246
- loadCredentials: options.loadCredentials,
3247
- getApiBaseUrl: options.getApiBaseUrl,
3248
- });
3324
+ let result;
3325
+ try {
3326
+ result = await turnFunction(trimmed, {
3327
+ mode,
3328
+ cwd,
3329
+ history,
3330
+ input,
3331
+ output,
3332
+ route,
3333
+ business: options.business,
3334
+ verify: options.verify,
3335
+ conversationId,
3336
+ turnId,
3337
+ approveBackendApi: options.approveBackendApi,
3338
+ apiRequestJson: options.apiRequestJson,
3339
+ loadCredentials: options.loadCredentials,
3340
+ getApiBaseUrl: options.getApiBaseUrl,
3341
+ });
3342
+ appendAutoOutcome(autoPick, {
3343
+ ok: true,
3344
+ model: modelForMode(mode),
3345
+ durationMs: Number(result && result.durationMs) || (Date.now() - turnStartedAt),
3346
+ });
3347
+ } catch (error) {
3348
+ appendAutoOutcome(autoPick, {
3349
+ ok: false,
3350
+ model: modelForMode(mode),
3351
+ durationMs: Date.now() - turnStartedAt,
3352
+ error: String((error && error.message) || error),
3353
+ });
3354
+ throw error;
3355
+ }
3249
3356
  if (result.output && !result.output.endsWith('\n')) output.write('\n');
3250
3357
  const approvalExecution = normalizeMode(mode) === 'code-fast'
3251
3358
  ? null
@@ -4288,8 +4395,7 @@ async function main() {
4288
4395
  route: route === 'auto' ? undefined : route,
4289
4396
  business,
4290
4397
  verify,
4291
- autoLane: autoPick && autoPick.lane,
4292
- autoReason: autoPick && autoPick.reason,
4398
+ autoPick,
4293
4399
  })
4294
4400
  : { ok: false, model: modelForMode(mode), output: '', durationMs: 0, error: 'missing prompt' };
4295
4401
  console.log(JSON.stringify(payload));
@@ -4305,7 +4411,23 @@ async function main() {
4305
4411
  console.log('');
4306
4412
  maybeWriteAtrisGoalBanner(process.stdout, process.cwd());
4307
4413
  const turnStartedAt = Date.now();
4308
- const result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
4414
+ let result;
4415
+ try {
4416
+ result = await turnFunctionForMode(mode)(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
4417
+ appendAutoOutcome(autoPick, {
4418
+ ok: true,
4419
+ model: modelForMode(mode),
4420
+ durationMs: Number(result && result.durationMs) || (Date.now() - turnStartedAt),
4421
+ });
4422
+ } catch (error) {
4423
+ appendAutoOutcome(autoPick, {
4424
+ ok: false,
4425
+ model: modelForMode(mode),
4426
+ durationMs: Date.now() - turnStartedAt,
4427
+ error: String((error && error.message) || error),
4428
+ });
4429
+ throw error;
4430
+ }
4309
4431
  const receipt = receiptFromState(result);
4310
4432
  const approvalRequest = normalizeMode(mode) === 'code-fast' ? null : approvalRequestFromReceipt(receipt);
4311
4433
  const approvalRecord = approvalRequest
@@ -4338,6 +4460,10 @@ if (require.main === module) {
4338
4460
  }
4339
4461
 
4340
4462
  module.exports = {
4463
+ appendAutoOutcome,
4464
+ appendAutoPick,
4465
+ autoLaneForMessage,
4466
+ autoMessageOverlap,
4341
4467
  authToken,
4342
4468
  authUserId,
4343
4469
  approvalExecutionRequestFromReceipt,