atris 3.35.0 → 3.36.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 (133) hide show
  1. package/AGENTS.md +37 -0
  2. package/README.md +5 -3
  3. package/atris/GETTING_STARTED.md +1 -1
  4. package/atris/atris.md +3 -0
  5. package/atris/policies/day-loop-voice.md +102 -0
  6. package/atris/policies/outbound-artifact-gate.md +2 -0
  7. package/atris/skills/design/SKILL.md +56 -32
  8. package/atris/skills/endgame/SKILL.md +12 -6
  9. package/atris/skills/engines/SKILL.md +22 -4
  10. package/atris/skills/fable-method/SKILL.md +66 -0
  11. package/atris/skills/improve/SKILL.md +65 -45
  12. package/atris/skills/youtube/SKILL.md +10 -1
  13. package/atris.md +2 -0
  14. package/ax +147 -19
  15. package/bin/atris.js +565 -265
  16. package/commands/activate.js +194 -88
  17. package/commands/agents.js +166 -0
  18. package/commands/autoland.js +459 -107
  19. package/commands/autopilot-front.js +20 -2
  20. package/commands/autopilot.js +118 -2
  21. package/commands/avail.js +407 -0
  22. package/commands/bench.js +188 -0
  23. package/commands/brain.js +3 -0
  24. package/commands/brief.js +651 -0
  25. package/commands/business-sync.js +192 -6
  26. package/commands/clean.js +50 -24
  27. package/commands/close.js +1083 -0
  28. package/commands/cloud.js +245 -0
  29. package/commands/compile.js +292 -1
  30. package/commands/computer.js +150 -3
  31. package/commands/dream.js +365 -0
  32. package/commands/drill.js +371 -0
  33. package/commands/engine.js +993 -32
  34. package/commands/experiments.js +28 -0
  35. package/commands/feedback.js +34 -12
  36. package/commands/fleet-report.js +206 -0
  37. package/commands/gm.js +23 -0
  38. package/commands/goal.js +247 -0
  39. package/commands/improve.js +642 -26
  40. package/commands/init.js +72 -44
  41. package/commands/interview.js +67 -1
  42. package/commands/land.js +152 -52
  43. package/commands/lifecycle.js +39 -3
  44. package/commands/log.js +84 -1
  45. package/commands/loops.js +220 -16
  46. package/commands/meet.js +220 -0
  47. package/commands/member.js +511 -34
  48. package/commands/mission.js +3029 -339
  49. package/commands/next.js +137 -0
  50. package/commands/now.js +220 -25
  51. package/commands/one-lap.js +776 -0
  52. package/commands/orb.js +314 -0
  53. package/commands/pack-craft.js +179 -0
  54. package/commands/pack.js +823 -0
  55. package/commands/play.js +3 -2
  56. package/commands/probe.js +30 -3
  57. package/commands/pulse.js +241 -46
  58. package/commands/push.js +260 -82
  59. package/commands/rainmaker.js +49 -0
  60. package/commands/report.js +415 -0
  61. package/commands/scout.js +147 -0
  62. package/commands/search.js +363 -0
  63. package/commands/skill.js +47 -3
  64. package/commands/slop.js +50 -2
  65. package/commands/soul.js +1 -1
  66. package/commands/stream.js +861 -0
  67. package/commands/study.js +693 -0
  68. package/commands/sync.js +67 -54
  69. package/commands/task.js +1346 -117
  70. package/commands/team.js +73 -0
  71. package/commands/verify.js +96 -0
  72. package/commands/watch.js +303 -0
  73. package/commands/wish.js +500 -0
  74. package/commands/workflow.js +11 -5
  75. package/commands/worktree.js +234 -13
  76. package/commands/xp.js +29 -11
  77. package/lib/auto-accept-certified.js +331 -34
  78. package/lib/autoland.js +319 -54
  79. package/lib/ax-auto-lane.js +79 -0
  80. package/lib/bench/context.js +147 -0
  81. package/lib/bench/engines.js +141 -0
  82. package/lib/bench/report.js +140 -0
  83. package/lib/bench/runner.js +512 -0
  84. package/lib/brief-ledger.js +350 -0
  85. package/lib/cloud-mission.js +259 -0
  86. package/lib/codex-flight.js +154 -0
  87. package/lib/default-runner.js +45 -0
  88. package/lib/default-verifier.js +70 -0
  89. package/lib/engine-registry.js +232 -0
  90. package/lib/experiments/daily.js +640 -0
  91. package/lib/fleet.js +2219 -67
  92. package/lib/improve-vitals-html.js +171 -0
  93. package/lib/known-commands.js +58 -0
  94. package/lib/loop-doctor.js +416 -0
  95. package/lib/member-switches.js +144 -0
  96. package/lib/mission-room.js +1 -0
  97. package/lib/mission-root.js +52 -0
  98. package/lib/next-moves.js +327 -10
  99. package/lib/one-lap-validator.js +60 -0
  100. package/lib/orb-context.js +477 -0
  101. package/lib/orb-scorecard.js +224 -0
  102. package/lib/policy-lessons.js +52 -1
  103. package/lib/pulse.js +277 -3
  104. package/lib/receipt-block.js +168 -0
  105. package/lib/receipt-evidence.js +65 -4
  106. package/lib/router-brain.js +352 -0
  107. package/lib/runner-command.js +10 -0
  108. package/lib/self-drive.js +258 -0
  109. package/lib/short-name.js +103 -0
  110. package/lib/spawn-env.js +18 -0
  111. package/lib/state-detection.js +56 -1
  112. package/lib/sync-status.js +59 -0
  113. package/lib/task-db.js +108 -29
  114. package/lib/task-proof.js +23 -1
  115. package/lib/team-presence.js +260 -0
  116. package/lib/tool-result-encode.js +7 -0
  117. package/lib/trust-tiers.js +90 -0
  118. package/lib/usage.js +107 -0
  119. package/lib/voice-gate.js +163 -0
  120. package/lib/wish-audit.js +1368 -0
  121. package/lib/wish-delegate.js +1840 -0
  122. package/lib/wish-design.js +110 -0
  123. package/lib/wish-stats.js +183 -0
  124. package/lib/wish-store.js +354 -0
  125. package/lib/zip.js +221 -0
  126. package/package.json +3 -1
  127. package/templates/loops/atris/loops/LOOPS.md +55 -0
  128. package/templates/loops/atris/loops/TICK.md +24 -0
  129. package/templates/loops/atris/loops/feedback.md +22 -0
  130. package/templates/loops/atris/loops/quality.md +22 -0
  131. package/templates/loops/atris/wiki/systems/loops.md +41 -0
  132. package/utils/api.js +5 -1
  133. package/utils/auth.js +57 -21
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: improve
3
- description: "Run one RL improvement tick on the workspace via POST /api/improve. Ships one verifiable change, scores it, writes the scorecard. The thing you pay for. Triggers on: improve, make this better, ship one thing, run a tick, get smarter."
4
- version: 1.0.0
3
+ description: "Run one verified, scored improvement tick and write its receipt. Use when the user asks to improve, make this better, ship one thing, run a tick, or get smarter. `atris improve` alone shows metabolism vitals; use `atris improve tick` to ship work."
4
+ version: 1.1.0
5
5
  tags:
6
6
  - rl
7
7
  - improve
@@ -12,74 +12,94 @@ tags:
12
12
 
13
13
  # /improve
14
14
 
15
- Runs one improvement tick on the workspace. Calls `POST /api/improve` on the backend, which plans one task, builds it, verifies it, and scores it. Returns what shipped + the reward. Writes the scorecard locally.
15
+ Run one improvement tick on the workspace. A successful tick ships one result, passes a real verifier, receives a score, and writes a machine-readable scorecard.
16
16
 
17
- This is the product. The thing the user pays for. One call, one verifiable result.
17
+ One call means one verifiable result. Never turn one invocation into a hidden batch.
18
18
 
19
- ## How it works
19
+ ## Command contract
20
20
 
21
+ `atris improve` alone shows the self-improvement metabolism vitals. It does not ship a change.
22
+
23
+ Use the explicit `tick` subcommand when the user asks for improvement work:
24
+
25
+ ```bash
26
+ atris improve tick # one full tick: plan, build, verify, score
27
+ atris improve tick --json # the same tick as machine-readable output
28
+ atris improve tick plan # plan only; no change or receipt
29
+ atris improve tick --dry-run # execute without committing; no shipping receipt
30
+ atris improve tick --no-fallback # report API failure instead of running locally
31
+ atris improve history # reward trend, credits, and pass rate
32
+ atris improve # vitals only
33
+ atris improve --json # vitals only as JSON
21
34
  ```
22
- /improve
23
- → POST /api/improve { workspace: ".", mode: "full" }
24
- → backend picks a task, plans, builds, reviews, verifies
25
- → returns { task, reward, files_changed, verify_pass, summary }
26
- → CLI writes scorecard to .atris/presidio/scorecards.md
27
- → CLI reports result to user
28
- ```
29
35
 
30
- The inference is Claude Code (or whatever model the backend uses). The environment is the folder. The endpoint is the bridge.
36
+ ## Run one tick
37
+
38
+ 1. Run `atris improve tick`, adding `--json` only when structured output is useful.
39
+ 2. Inspect the returned source, task summary, verifier result, reward, files, and receipt path.
40
+ 3. Count the tick only when `ok` is true, verification passed, and the scorecard was written.
41
+ 4. Report what shipped and the exact verifier result. If the tick fails, report the failure and stop.
42
+
43
+ For a paid API tick, the CLI:
31
44
 
32
- ## On invoke
45
+ 1. Loads credentials with `utils/auth.loadCredentials`.
46
+ 2. Calls `POST /api/improve` with the workspace, mode, model, and dry-run setting.
47
+ 3. Lets the backend plan, build, verify, score, and bill the successful tick.
48
+ 4. Writes the normalized receipt to `.atris/state/scorecards.jsonl` and the human trail to the daily Atris journal.
33
49
 
34
- Run the CLI command — it does the whole tick (auth, the credit-metered call, scorecard, fallback):
50
+ The full response may omit the billed credit count. In that case the CLI reports that billing happened server-side instead of inventing a number.
51
+
52
+ ## Local fallback
53
+
54
+ Local fallback is allowed when there is no login, the backend is unreachable, or the hosted backend cannot access the local workspace path. The CLI runs this exact bounded command internally:
35
55
 
36
56
  ```bash
37
- atris improve # one full tick: plan → build → verify → score (deducts credits)
38
- atris improve plan # show the plan only, change nothing
39
- atris improve --json # machine-readable result (this is what the member loop consumes)
40
- atris improve --no-fallback # fail loudly instead of running a local tick when the backend is down
57
+ atris mission run --due --headless --max-ticks 1 --complete-on-pass --json
41
58
  ```
42
59
 
43
- Under the hood `atris improve` (`commands/improve.js`):
60
+ The local result succeeds only when all of these are true:
44
61
 
45
- 1. Loads the auth token via `utils/auth.loadCredentials`
46
- 2. `POST /api/improve { workspace, mode, model }` via `utils/api.apiRequestJson`
47
- 3. The backend plans, builds, runs the verify command, scores it, and **deducts Atris credits per successful tick** (`bill_tick`)
48
- 4. Writes a per-tick scorecard row to `.atris/state/scorecards.jsonl` (the receipt the brain ledger counts)
49
- 5. Falls back to a local autopilot tick **only** when you are not logged in or the backend is unreachable — a real error (insufficient credits, server error) is reported, never silently retried
62
+ 1. Exactly one mission tick ran.
63
+ 2. A headless worker actually ran; caller-session and no-worker placeholders are rejected.
64
+ 3. The mission verifier passed.
65
+ 4. The scorecard and journal receipt were written.
50
66
 
51
- The full-mode response does not echo `credits_deducted` (credits are still billed server-side), so the CLI shows "billed server-side" when the count is absent. To call the endpoint directly instead of the command, `POST /api/improve` with `{ workspace, mode, model }`.
67
+ A verified local tick earns the conservative local reward of `+1` and deducts no credits. No due headless mission, a skipped worker, a failed or missing verifier, or a receipt write failure makes the improve command fail without a reward.
52
68
 
53
- ## Modes
69
+ Do not silently fall back for answerable API failures such as insufficient credits, unrelated authorization failures, or server errors.
54
70
 
55
- - `full` — plan, build, review, verify (default)
56
- - `plan` — just pick the task and show what it would do
57
- - `dry_run` — run everything but don't commit
71
+ ## Expected output
58
72
 
59
- ## Fallback
73
+ User says: "Improve this once."
60
74
 
61
- If the backend is unreachable (no auth, no network, localhost not running), fall back to local mode: run `atris autopilot --auto --iterations=1` instead. Same loop, just local inference via `claude -p` subprocess. Report that it ran locally.
75
+ Action: run `atris improve tick`.
62
76
 
63
- ## Output
77
+ Expected shape:
64
78
 
65
- ```
79
+ ```text
66
80
  improved.
81
+ task: fixed the stale wiki reference
82
+ verify: pass
83
+ reward: 4
84
+ files: atris/wiki/auth-flow.md
85
+ scorecard: .atris/state/scorecards.jsonl
86
+ ```
67
87
 
68
- task: fixed the stale wiki ref in auth-flow.md
69
- verify: pass (npm test, 143/143)
70
- reward: +4
71
- files: atris/wiki/briefs/auth-flow.md
72
- time: 47s
88
+ For local fallback, the first line is `improved (local fallback).` and the report still names the task, passing verifier, reward, and scorecard.
73
89
 
74
- scorecard updated.
75
- ```
90
+ ## Failure handling
91
+
92
+ - If verification fails or is missing, halt honestly and write a durable lesson. Do not claim improvement.
93
+ - If the command reports no due headless mission, stop; do not manufacture busywork or a scorecard.
94
+ - If the API returns insufficient credits or another answerable error, report it without a local retry.
95
+ - If the workspace is dirty, preserve existing changes. Use an isolated worktree for manual follow-up fixes.
96
+ - If a worker lands work but the outer receipt fails, report the landed commit separately and do not count the improve tick.
76
97
 
77
98
  ## Rules
78
99
 
79
100
  - One tick only. Never batch.
80
- - Always verify. No reward without a check.
101
+ - No reward without a passing verifier.
81
102
  - Show what shipped, not what was attempted.
82
- - Write the scorecard. This is the receipt.
83
- - If verify fails, halt honestly and write a lesson.
84
- - Fallback to local if backend is unreachable. Never error silently.
103
+ - A scorecard is required for success.
104
+ - Preserve user work and unrelated changes.
85
105
  - The user pays because something real happened. Never fake it.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: youtube
3
3
  description: "Process YouTube videos — extract insights, answer questions, store as knowledge. 5 credits per video. Triggers on: youtube, video, process video, watch this, learn from video."
4
- version: 2.2.0
4
+ version: 2.3.0
5
5
  tags:
6
6
  - youtube
7
7
  - research
@@ -13,6 +13,15 @@ tags:
13
13
 
14
14
  Process any YouTube video through Atris transcript-first analysis. The CLI extracts local captions with timestamps when available, sends that transcript to Atris, and falls back to cloud video processing when captions are unavailable or unusable. 5 credits per video, refunded if processing fails.
15
15
 
16
+ ## Route first: learning vs product
17
+
18
+ Two rails process YouTube videos — pick before running anything:
19
+
20
+ - **Learning / work rail** → use the `alpha-learn` skill (ytnotes). Local yt-dlp + grok, zero credits, podcastnotes-style notes, tweet-feed output, `[claimable]` entries in today's journal for other agents. Use this when the goal is to LEARN from a video or mine it for Atris work.
21
+ - **Product rail** → this skill (`atris youtube process`). Credits-billed, stores knowledge in the Atris backend, customer-facing path. Use this when a customer/agent needs the video stored as Atris knowledge or answered via the API.
22
+
23
+ If the user says "learn from", "notes on", "alpha", or "rabbit hole" → alpha-learn. If they say "process", "store", "add to knowledge" → this skill.
24
+
16
25
  ## Bootstrap (ALWAYS Run First)
17
26
 
18
27
  ```bash
package/atris.md CHANGED
@@ -90,6 +90,8 @@ The same discipline for words. Output stays sharp no matter how bloated the cont
90
90
  - "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
91
  - "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
92
  - **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.
93
+ - **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
+ - **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.
93
95
 
94
96
  `expected`: this is how an Atris agent writes and builds. Shipping slop or rambling is a failure smell, same as drift or a stale task.
95
97
 
package/ax CHANGED
@@ -12,6 +12,7 @@ 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
16
 
16
17
  const EXIT_WORDS = new Set(['exit', 'quit', ':q']);
17
18
  const BACKEND = {
@@ -34,6 +35,9 @@ const APPROVAL_EXECUTE_PATH = '/api/atris2/approvals/execute';
34
35
  const BACKEND_API_TOOL_NAME = 'backend_api';
35
36
  const BACKEND_API_TOOL_RESULT_PATH = '/api/atris2/turn/tool-result';
36
37
  const DEFAULT_APPROVAL_MAX_AGE_MS = 24 * 60 * 60 * 1000;
38
+ const DEFAULT_TURN_TIMEOUT_MS = 60000;
39
+ const PRO_TURN_TIMEOUT_MS = 180000;
40
+ const MAX_TURN_TIMEOUT_MS = 300000;
37
41
  const CONNECTOR_NAMES = {
38
42
  gmail: 'Gmail',
39
43
  google_calendar: 'Google Calendar',
@@ -97,6 +101,33 @@ function modelForMode(mode) {
97
101
  return mode === 'fast' ? 'atris:fast' : 'atris:pro';
98
102
  }
99
103
 
104
+ function payloadCanRunTools(payload = {}, options = {}) {
105
+ return Boolean(
106
+ options.business
107
+ || options.local
108
+ || payload.local_executor
109
+ || (Array.isArray(payload.local_tools) && payload.local_tools.length > 0)
110
+ );
111
+ }
112
+
113
+ function postTurnTimeoutMs(payload = {}, options = {}) {
114
+ const model = String(payload.model || '');
115
+ let timeoutMs = DEFAULT_TURN_TIMEOUT_MS;
116
+ if (model === 'atris:max') timeoutMs = MAX_TURN_TIMEOUT_MS;
117
+ else if (model === 'atris:pro') timeoutMs = PRO_TURN_TIMEOUT_MS;
118
+
119
+ // AX postTurn is the tool-capable Atris2 path. Fast tool turns need the same
120
+ // headroom as pro because the backend can spend long stretches inside tools
121
+ // without SSE traffic. Plain tool-free one-shot fast chat stays at 60s.
122
+ if (model === 'atris:fast' && payloadCanRunTools(payload, options)) {
123
+ timeoutMs = Math.max(timeoutMs, PRO_TURN_TIMEOUT_MS);
124
+ }
125
+ if (options.business || options.local) {
126
+ timeoutMs = Math.max(timeoutMs, PRO_TURN_TIMEOUT_MS);
127
+ }
128
+ return timeoutMs;
129
+ }
130
+
100
131
  function formatDuration(ms) {
101
132
  const value = Number(ms) || 0;
102
133
  if (value < 1000) return `${Math.max(0, Math.round(value))}ms`;
@@ -131,9 +162,9 @@ function formatUsage() {
131
162
  'ax - Atris local/code agent',
132
163
  '',
133
164
  'Usage:',
134
- ' ax [--max|--pro|--fast|--code-fast] [--local|--cloud] <message>',
135
- ' ax [--max|--pro|--fast|--code-fast] [--local|--cloud] --print <message>',
136
- ' ax [--max|--pro|--fast|--code-fast] [--local|--cloud] --chat',
165
+ ' ax [--auto|--max|--pro|--fast|--code-fast] [--local|--cloud] <message>',
166
+ ' ax [--auto|--max|--pro|--fast|--code-fast] [--local|--cloud] --print <message>',
167
+ ' ax [--auto|--max|--pro|--fast|--code-fast] [--local|--cloud] --chat',
137
168
  ' ax [--max|--pro|--fast] --business <slug> [<message>|--chat]',
138
169
  ' ax [--max|--pro|--fast|--code-fast] --doctor',
139
170
  ' ax --approvals',
@@ -147,13 +178,14 @@ function formatUsage() {
147
178
  ' ax [--max|--fast] --benchmark',
148
179
  '',
149
180
  'Modes:',
181
+ ' --auto pick a lane for each message and say why',
150
182
  ' --max hosted Atris 2, highest reasoning, slowest turns',
151
183
  ' --pro hosted Atris 2, deeper tool loop',
152
184
  ' --fast hosted Atris 2, faster low-latency turns',
153
185
  ' --code-fast Atris Code Fast public lane',
154
186
  ' --local opt into local backend/workspace tools',
155
187
  ' --cloud force authenticated cloud connectors/chat',
156
- ' --print headless JSON result: { ok, model, output, durationMs }',
188
+ ' --print headless JSON result, including auto_lane and auto_reason with --auto',
157
189
  ' --business <slug> run tools on that business cloud workspace (EC2)',
158
190
  ' --verify <cmd> gate the turn on this command passing (default: no verifier)',
159
191
  '',
@@ -199,10 +231,29 @@ function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', out
199
231
  ].join('\n'));
200
232
 
201
233
  let redactedChars = 0;
234
+ let logBuffer = '';
235
+ let logFlushTimer = null;
236
+ const flushLogBuffer = () => {
237
+ if (logFlushTimer) {
238
+ clearTimeout(logFlushTimer);
239
+ logFlushTimer = null;
240
+ }
241
+ if (!logBuffer) return;
242
+ fs.appendFileSync(logPath, logBuffer);
243
+ logBuffer = '';
244
+ };
245
+ const scheduleLogFlush = () => {
246
+ if (logFlushTimer) return;
247
+ logFlushTimer = setTimeout(() => {
248
+ logFlushTimer = null;
249
+ flushLogBuffer();
250
+ }, 500);
251
+ };
202
252
  const writeLog = (chunk) => {
203
253
  const text = stripAnsi(chunk);
204
254
  if (fullTranscript) {
205
- fs.appendFileSync(logPath, text);
255
+ logBuffer += text;
256
+ scheduleLogFlush();
206
257
  return;
207
258
  }
208
259
  redactedChars += text.length;
@@ -222,6 +273,7 @@ function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', out
222
273
  output: teeOutput,
223
274
  write: writeLog,
224
275
  close(exitCode = 0) {
276
+ flushLogBuffer();
225
277
  if (!fullTranscript) {
226
278
  fs.appendFileSync(logPath, `redacted_chars: ${redactedChars}\n`);
227
279
  fs.appendFileSync(logPath, 'note: full transcript disabled by default; set AX_LOG_FULL=1 to opt in.\n');
@@ -229,6 +281,7 @@ function createRunLogger({ cwd = process.cwd(), mode = 'pro', kind = 'play', out
229
281
  return;
230
282
  }
231
283
  writeLog(`\nexit_code: ${exitCode}\nfinished_at: ${new Date().toISOString()}\n`);
284
+ flushLogBuffer();
232
285
  }
233
286
  };
234
287
  }
@@ -904,10 +957,38 @@ function normalizeMode(mode) {
904
957
  return mode === 'fast' ? 'fast' : 'pro';
905
958
  }
906
959
 
960
+ function appendAutoPick(message, picked, options = {}) {
961
+ 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({
965
+ at: new Date().toISOString(),
966
+ lane: picked.lane,
967
+ reason: picked.reason,
968
+ message_chars: String(message || '').length,
969
+ print: Boolean(options.print),
970
+ })}\n`);
971
+ } catch (_) {
972
+ // Lane telemetry is best-effort and must never block a turn.
973
+ }
974
+ }
975
+
976
+ function autoLaneForMessage(message, options = {}) {
977
+ const picked = pickLane(message);
978
+ appendAutoPick(message, picked, options);
979
+ const errorOutput = options.errorOutput || process.stderr;
980
+ errorOutput.write(`${picked.reason}\n`);
981
+ return picked;
982
+ }
983
+
907
984
  function formatPrompt(mode, options = {}) {
908
985
  if (!mode) return '› ';
909
986
  const tier = normalizeMode(mode);
910
- return `${paint(tier, [ANSI.bold, tierColor(tier)], options)} › `;
987
+ const label = paint(tier, [ANSI.bold, tierColor(tier)], options);
988
+ if (options.approveMode === 'auto') {
989
+ return `${label} ${paint('[auto-approve]', [ANSI.muted], options)} › `;
990
+ }
991
+ return `${label} › `;
911
992
  }
912
993
 
913
994
  const TIER_COMMANDS = new Map([
@@ -924,6 +1005,8 @@ const CHAT_COMMANDS = [
924
1005
  ['/fast', 'quick answers, lowest latency'],
925
1006
  ['/pro', 'deeper tool loop for real work'],
926
1007
  ['/max', 'highest reasoning for the hardest jobs'],
1008
+ ['/clear', 'wipe chat history and reset conversation id'],
1009
+ ['/context', 'show turn count and rough token estimate'],
927
1010
  ['/help', 'show this menu'],
928
1011
  ['exit', 'leave chat'],
929
1012
  ];
@@ -2670,14 +2753,7 @@ async function postTurn(message, options = {}) {
2670
2753
  const payload = buildPayload(message, { ...options, route, connectionContext, connectionUserId, turnId });
2671
2754
  const postData = JSON.stringify(payload);
2672
2755
  const output = options.output || process.stdout;
2673
- // Relayed business turns wait on EC2 terminal calls (up to 60s each) with no
2674
- // SSE traffic in between, so the socket-idle timeout needs more headroom.
2675
- const baseTimeoutMs = payload.model === 'atris:max' ? 300000 : payload.model === 'atris:pro' ? 180000 : 60000;
2676
- let timeoutMs = options.business ? Math.max(baseTimeoutMs, 180000) : baseTimeoutMs;
2677
- // Local workspace tool loops (max_turns 16/24) legitimately run past the fast
2678
- // lane's 60s chat wall — SwapBench 2026-07-02: three tool-loop tasks died at
2679
- // ~60s as "Atris cloud did not respond". Same headroom as business relays.
2680
- if (local) timeoutMs = Math.max(timeoutMs, 180000);
2756
+ const timeoutMs = postTurnTimeoutMs(payload, { business: options.business, local });
2681
2757
  const turnUrl = new URL(backendUrl({ route: endpointRoute }));
2682
2758
  const transport = turnUrl.protocol === 'https:' ? https : http;
2683
2759
  const state = {
@@ -2940,6 +3016,9 @@ async function runHeadlessTurn(message, options = {}) {
2940
3016
  const startedAt = Date.now();
2941
3017
  const sink = options.output || bufferedOutput();
2942
3018
  const turnFunction = options.turnFunction || turnFunctionForMode(mode);
3019
+ const autoFields = options.autoLane && options.autoReason
3020
+ ? { auto_lane: normalizeMode(options.autoLane), auto_reason: String(options.autoReason) }
3021
+ : {};
2943
3022
 
2944
3023
  try {
2945
3024
  const result = await turnFunction(message, {
@@ -2956,6 +3035,7 @@ async function runHeadlessTurn(message, options = {}) {
2956
3035
  model: modelForMode(mode),
2957
3036
  output: String((result && result.output) || '').trim(),
2958
3037
  durationMs: Number((result && result.durationMs) || 0) || (Date.now() - startedAt),
3038
+ ...autoFields,
2959
3039
  };
2960
3040
  } catch (error) {
2961
3041
  return {
@@ -2964,6 +3044,7 @@ async function runHeadlessTurn(message, options = {}) {
2964
3044
  output: '',
2965
3045
  durationMs: Date.now() - startedAt,
2966
3046
  error: String((error && error.message) || error || 'headless turn failed'),
3047
+ ...autoFields,
2967
3048
  };
2968
3049
  }
2969
3050
  }
@@ -2977,7 +3058,9 @@ async function chat(options = {}) {
2977
3058
  const output = logger ? logger.output : baseOutput;
2978
3059
  const history = [];
2979
3060
  let lastCompactionNotice = 0;
2980
- const conversationId = options.conversationId || `ax-${process.pid}-${Date.now().toString(36)}`;
3061
+ let conversationId = options.conversationId || `ax-${process.pid}-${Date.now().toString(36)}`;
3062
+ const session = { approveMode: 'stage' };
3063
+ let turnInFlight = false;
2981
3064
 
2982
3065
  output.write(`${formatHeader({ mode, cwd, chat: true }, output)}\n\n`);
2983
3066
  if (logger) output.write(`${formatAuxRow('log', formatPathSubject(logger.path, output), output)}\n\n`);
@@ -3024,11 +3107,31 @@ async function chat(options = {}) {
3024
3107
  return false;
3025
3108
  }
3026
3109
 
3110
+ if (trimmed === '/clear') {
3111
+ history.length = 0;
3112
+ conversationId = `ax-${process.pid}-${Date.now().toString(36)}`;
3113
+ output.write(`${paint('context cleared', [ANSI.muted], output)}\n\n`);
3114
+ return false;
3115
+ }
3116
+
3117
+ if (trimmed === '/context') {
3118
+ const ctx = estimateHistoryContext(history);
3119
+ output.write(`${paint(`· ${ctx.turns} turn${ctx.turns === 1 ? '' : 's'} · ~${ctx.chars} chars · ~${ctx.tokens} tokens`, [ANSI.muted], output)}\n\n`);
3120
+ return false;
3121
+ }
3122
+
3027
3123
  if (trimmed.startsWith('/')) {
3028
3124
  output.write(`${chatMenu(output)}\n\n`);
3029
3125
  return false;
3030
3126
  }
3031
3127
 
3128
+ if (options.auto) {
3129
+ mode = autoLaneForMessage(trimmed, {
3130
+ errorOutput: options.errorOutput,
3131
+ print: false,
3132
+ }).lane;
3133
+ }
3134
+
3032
3135
  if (logger) logger.write(`${formatPrompt(mode)}${trimmed}\n`);
3033
3136
  output.write('\n');
3034
3137
 
@@ -3956,7 +4059,17 @@ async function main() {
3956
4059
  args.splice(verifyIdx, 2);
3957
4060
  }
3958
4061
 
3959
- const mode = args.includes('--code-fast') || args.includes('--code') ? 'code-fast' : args.includes('--max') ? 'max' : args.includes('--fast') ? 'fast' : 'pro';
4062
+ const explicitLane = args.includes('--code-fast') || args.includes('--code')
4063
+ ? 'code-fast'
4064
+ : args.includes('--max')
4065
+ ? 'max'
4066
+ : args.includes('--fast')
4067
+ ? 'fast'
4068
+ : args.includes('--pro')
4069
+ ? 'pro'
4070
+ : null;
4071
+ const autoEnabled = args.includes('--auto') && !explicitLane;
4072
+ let mode = explicitLane || 'pro';
3960
4073
  const doctor = args.includes('--doctor');
3961
4074
  const selfTest = args.includes('--self-test');
3962
4075
  const benchmark = args.includes('--benchmark');
@@ -4017,7 +4130,7 @@ async function main() {
4017
4130
 
4018
4131
  const route = forceCloud ? 'cloud' : forceLocal ? 'local' : 'auto';
4019
4132
  const prompt = args
4020
- .filter(arg => !['--max', '--fast', '--pro', '--code-fast', '--code', '--chat', '--doctor', '--approvals', '--self-test', '--benchmark', '--print', '--headless', '--local', '--cloud', '--help', '-h'].includes(arg))
4133
+ .filter(arg => !['--auto', '--max', '--fast', '--pro', '--code-fast', '--code', '--chat', '--doctor', '--approvals', '--self-test', '--benchmark', '--print', '--headless', '--local', '--cloud', '--help', '-h'].includes(arg))
4021
4134
  .join(' ')
4022
4135
  .trim();
4023
4136
 
@@ -4039,6 +4152,12 @@ async function main() {
4039
4152
  return;
4040
4153
  }
4041
4154
 
4155
+ const startsChat = !printMode && (!prompt || args.includes('--chat'));
4156
+ const autoPick = autoEnabled && prompt && !startsChat
4157
+ ? autoLaneForMessage(prompt, { print: printMode })
4158
+ : null;
4159
+ if (autoPick) mode = autoPick.lane;
4160
+
4042
4161
  let business = null;
4043
4162
  if (businessSlug) {
4044
4163
  if (normalizeMode(mode) === 'code-fast') {
@@ -4163,14 +4282,22 @@ async function main() {
4163
4282
 
4164
4283
  if (printMode) {
4165
4284
  const payload = prompt
4166
- ? await runHeadlessTurn(prompt, { mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify })
4285
+ ? await runHeadlessTurn(prompt, {
4286
+ mode,
4287
+ cwd: process.cwd(),
4288
+ route: route === 'auto' ? undefined : route,
4289
+ business,
4290
+ verify,
4291
+ autoLane: autoPick && autoPick.lane,
4292
+ autoReason: autoPick && autoPick.reason,
4293
+ })
4167
4294
  : { ok: false, model: modelForMode(mode), output: '', durationMs: 0, error: 'missing prompt' };
4168
4295
  console.log(JSON.stringify(payload));
4169
4296
  process.exit(payload.ok ? 0 : 1);
4170
4297
  }
4171
4298
 
4172
4299
  if (!prompt || args.includes('--chat')) {
4173
- await chat({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify });
4300
+ await chat({ mode, cwd: process.cwd(), route: route === 'auto' ? undefined : route, business, verify, auto: autoEnabled });
4174
4301
  return;
4175
4302
  }
4176
4303
 
@@ -4276,6 +4403,7 @@ module.exports = {
4276
4403
  postApprovalExecution,
4277
4404
  postCodeFastTurn,
4278
4405
  postTurn,
4406
+ postTurnTimeoutMs,
4279
4407
  readApprovalStore,
4280
4408
  removeStoredApproval,
4281
4409
  renderStreamingMarkdown,