@armadra/agent 0.7.3 → 0.7.4
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.
- package/CHANGELOG.md +37 -0
- package/CHANGELOG.zh-CN.md +37 -0
- package/dist/agent/retry.d.ts +14 -5
- package/dist/agent/retry.js +30 -8
- package/dist/agent/session-cache-key.d.ts +8 -0
- package/dist/agent/session-cache-key.js +46 -0
- package/dist/agent/session-cache.d.ts +2 -3
- package/dist/agent/session-cache.js +19 -37
- package/dist/agent/session-classifier.d.ts +3 -2
- package/dist/agent/session-classifier.js +36 -10
- package/dist/agent/session-compaction.d.ts +24 -5
- package/dist/agent/session-compaction.js +54 -20
- package/dist/agent/session-core.d.ts +2 -0
- package/dist/agent/session-run.d.ts +12 -0
- package/dist/agent/session-run.js +29 -10
- package/dist/agent/session-subagent.d.ts +8 -1
- package/dist/agent/session-subagent.js +37 -14
- package/dist/agent/session-sync.d.ts +8 -2
- package/dist/agent/session-sync.js +7 -10
- package/dist/agent/session-tools.js +3 -2
- package/dist/agent/subagent-fork.d.ts +67 -0
- package/dist/agent/subagent-fork.js +87 -0
- package/dist/agent/subagent-registry.d.ts +2 -2
- package/dist/agent/subagent-registry.js +2 -2
- package/dist/agent/tool-availability.d.ts +8 -0
- package/dist/agent/tool-availability.js +8 -0
- package/dist/agent/tool-runner.js +13 -4
- package/dist/agent/worktree.d.ts +8 -0
- package/dist/agent/worktree.js +51 -4
- package/dist/agents/parse.js +6 -1
- package/dist/agents/task-record.js +3 -0
- package/dist/agents/types.d.ts +2 -0
- package/dist/ai/apis/anthropic-messages.js +10 -5
- package/dist/ai/apis/anthropic-request.d.ts +2 -1
- package/dist/ai/apis/anthropic-request.js +9 -3
- package/dist/ai/apis/google-generative-ai.js +4 -2
- package/dist/ai/apis/max-tokens.d.ts +42 -0
- package/dist/ai/apis/max-tokens.js +121 -0
- package/dist/ai/apis/openai-completions.js +10 -5
- package/dist/ai/apis/openai-request.js +1 -1
- package/dist/ai/apis/openai-responses-request.js +1 -1
- package/dist/ai/apis/openai-responses.js +9 -5
- package/dist/ai/apis/shared.d.ts +3 -1
- package/dist/ai/apis/shared.js +21 -3
- package/dist/ai/cache/fingerprint.d.ts +4 -1
- package/dist/ai/cache/fingerprint.js +18 -1
- package/dist/ai/cache/miss.d.ts +2 -1
- package/dist/ai/cache/miss.js +9 -3
- package/dist/ai/cache/reporting.js +5 -1
- package/dist/ai/cache/types.d.ts +10 -2
- package/dist/ai/cache/warmer.d.ts +6 -1
- package/dist/ai/cache/warmer.js +7 -0
- package/dist/ai/context.d.ts +12 -4
- package/dist/ai/context.js +43 -24
- package/dist/ai/fake/fake-provider.d.ts +8 -1
- package/dist/ai/fake/fake-provider.js +19 -7
- package/dist/ai/fake/fake-script.d.ts +2 -0
- package/dist/ai/http.d.ts +17 -9
- package/dist/ai/http.js +33 -13
- package/dist/ai/image-intern.d.ts +29 -0
- package/dist/ai/image-intern.js +58 -0
- package/dist/ai/json-body.d.ts +31 -0
- package/dist/ai/json-body.js +208 -0
- package/dist/ai/overflow.js +5 -0
- package/dist/ai/providers/catalog-data.js +13 -13
- package/dist/ai/providers/catalog.d.ts +33 -0
- package/dist/ai/providers/catalog.js +105 -6
- package/dist/ai/providers/enrich.d.ts +13 -3
- package/dist/ai/providers/enrich.js +87 -31
- package/dist/ai/sse.d.ts +3 -2
- package/dist/ai/sse.js +3 -2
- package/dist/ai/types.d.ts +9 -2
- package/dist/bundle/ama.cjs +3110 -1793
- package/dist/cli/compose-agents.js +3 -0
- package/dist/cli/compose-extensions.js +2 -0
- package/dist/cli/compose-request.d.ts +20 -0
- package/dist/cli/compose-request.js +37 -0
- package/dist/cli/compose-session.js +2 -2
- package/dist/cli/subcommands/model-meta.d.ts +2 -1
- package/dist/cli/subcommands/model-meta.js +3 -2
- package/dist/codemode/host-side.d.ts +11 -4
- package/dist/codemode/host-side.js +25 -6
- package/dist/codemode/tool.d.ts +2 -0
- package/dist/codemode/tool.js +4 -0
- package/dist/config/json-schema.js +9 -2
- package/dist/config/key-docs.js +13 -4
- package/dist/config/schema-w5.js +9 -1
- package/dist/config/schema.d.ts +1 -1
- package/dist/config/schema.js +19 -3
- package/dist/config/settings-registry.js +6 -0
- package/dist/config/types-w5.d.ts +2 -0
- package/dist/config/types.d.ts +19 -1
- package/dist/config/types.js +4 -0
- package/dist/i18n/catalog.d.ts +13 -2
- package/dist/i18n/messages/cli.d.ts +2 -0
- package/dist/i18n/messages/cli.js +2 -0
- package/dist/i18n/messages/config-keys.d.ts +10 -0
- package/dist/i18n/messages/config-keys.js +12 -2
- package/dist/i18n/messages/config.d.ts +14 -0
- package/dist/i18n/messages/config.js +4 -0
- package/dist/i18n/messages/errors.d.ts +3 -2
- package/dist/i18n/messages/errors.js +15 -2
- package/dist/i18n/messages/report.d.ts +2 -0
- package/dist/i18n/messages/report.js +2 -0
- package/dist/i18n/messages/settings.d.ts +4 -0
- package/dist/i18n/messages/settings.js +6 -2
- package/dist/i18n/messages/subcommands.d.ts +2 -2
- package/dist/i18n/messages/subcommands.js +4 -2
- package/dist/modes/acp/acp-server.js +15 -5
- package/dist/modes/acp/acp-sessions.d.ts +9 -0
- package/dist/modes/acp/acp-sessions.js +25 -0
- package/dist/modes/context-report.js +3 -2
- package/dist/modes/print/json-event.d.ts +12 -1
- package/dist/modes/print/json-event.js +48 -1
- package/dist/modes/rpc/rpc-mode.d.ts +1 -0
- package/dist/modes/rpc/rpc-mode.js +9 -2
- package/dist/modes/session-report.d.ts +1 -1
- package/dist/modes/session-report.js +12 -6
- package/dist/rpc.d.ts +5 -2
- package/dist/session/line-reader.d.ts +28 -0
- package/dist/session/line-reader.js +82 -0
- package/dist/session/list.d.ts +14 -0
- package/dist/session/list.js +150 -0
- package/dist/session/manager.d.ts +7 -3
- package/dist/session/manager.js +21 -48
- package/dist/session/migrate.d.ts +2 -1
- package/dist/session/migrate.js +2 -1
- package/dist/session/projection.d.ts +16 -3
- package/dist/session/projection.js +96 -29
- package/dist/session/scan.d.ts +3 -3
- package/dist/session/scan.js +9 -18
- package/dist/session/store.d.ts +3 -0
- package/dist/session/store.js +37 -31
- package/dist/session/types.d.ts +7 -2
- package/dist/tools/bash.d.ts +1 -1
- package/dist/tools/bash.js +5 -4
- package/dist/tools/glob.d.ts +2 -2
- package/dist/tools/glob.js +2 -2
- package/dist/tools/grep.js +4 -6
- package/dist/tools/image-file.js +2 -1
- package/dist/tools/read-lines.d.ts +32 -0
- package/dist/tools/read-lines.js +147 -0
- package/dist/tools/read.d.ts +6 -2
- package/dist/tools/read.js +47 -27
- package/dist/tools/task.d.ts +9 -0
- package/dist/tools/task.js +21 -8
- package/dist/tools/truncate.d.ts +7 -0
- package/dist/tools/truncate.js +11 -0
- package/dist/tools/types.d.ts +13 -0
- package/dist/trace/build-index.js +13 -1
- package/dist/tui/ansi.d.ts +2 -0
- package/dist/tui/ansi.js +7 -3
- package/dist/tui/components/editor-buffer.js +2 -2
- package/dist/tui/components/editor.js +5 -6
- package/dist/tui/keys.js +2 -2
- package/dist/tui/stdin-buffer.js +3 -3
- package/docs/acp.md +5 -1
- package/docs/agents.md +42 -6
- package/docs/codemode.md +4 -2
- package/docs/en/acp.md +3 -2
- package/docs/en/host-api.md +1 -1
- package/docs/en/permissions.md +2 -2
- package/docs/en/providers.md +54 -20
- package/docs/en/rpc.md +23 -7
- package/docs/en/sessions.md +4 -2
- package/docs/en/tui.md +2 -2
- package/docs/permissions.md +4 -1
- package/docs/providers.md +85 -22
- package/docs/rpc.md +22 -6
- package/docs/session-format.md +3 -2
- package/docs/sessions.md +3 -1
- package/docs/tui.md +1 -1
- package/package.json +2 -2
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
import { msg } from "../../i18n/index.js";
|
|
17
17
|
import { CURSOR_MARKER } from "../component.js";
|
|
18
|
-
import { truncateToWidth, visibleWidth } from "../ansi.js";
|
|
18
|
+
import { graphemeSegmenter, truncateToWidth, visibleWidth } from "../ansi.js";
|
|
19
19
|
import { defaultKeybindings } from "../keybindings.js";
|
|
20
20
|
import { isPasteData, isPrintableText, unwrapPaste } from "../keys.js";
|
|
21
21
|
import { plainTheme } from "../theme.js";
|
|
@@ -24,7 +24,6 @@ import { appendHistoryFile, loadHistoryFile } from "./editor-history.js";
|
|
|
24
24
|
import { PasteStore } from "./editor-paste.js";
|
|
25
25
|
import { SelectList } from "./select-list.js";
|
|
26
26
|
export { loadHistoryFile } from "./editor-history.js";
|
|
27
|
-
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
28
27
|
/** 提示符 `› ` / 续行缩进的宽度。 */
|
|
29
28
|
const PROMPT_WIDTH = 2;
|
|
30
29
|
export class Editor {
|
|
@@ -290,7 +289,7 @@ export class Editor {
|
|
|
290
289
|
const line = this.buffer.getLines()[row.line];
|
|
291
290
|
let col = row.start;
|
|
292
291
|
let width = 0;
|
|
293
|
-
for (const { segment, index } of
|
|
292
|
+
for (const { segment, index } of graphemeSegmenter().segment(line.slice(row.start, row.end))) {
|
|
294
293
|
const w = visibleWidth(segment);
|
|
295
294
|
if (width + w > targetWidth)
|
|
296
295
|
break;
|
|
@@ -304,7 +303,7 @@ export class Editor {
|
|
|
304
303
|
}
|
|
305
304
|
prevGrapheme(line, col) {
|
|
306
305
|
let prev = 0;
|
|
307
|
-
for (const { index } of
|
|
306
|
+
for (const { index } of graphemeSegmenter().segment(line)) {
|
|
308
307
|
if (index >= col)
|
|
309
308
|
break;
|
|
310
309
|
prev = index;
|
|
@@ -403,7 +402,7 @@ export class Editor {
|
|
|
403
402
|
let start = 0;
|
|
404
403
|
let used = 0;
|
|
405
404
|
let pos = 0;
|
|
406
|
-
const graphemes = [...
|
|
405
|
+
const graphemes = [...graphemeSegmenter().segment(text)];
|
|
407
406
|
let gi = 0;
|
|
408
407
|
while (gi < graphemes.length) {
|
|
409
408
|
const g = graphemes[gi];
|
|
@@ -521,7 +520,7 @@ export class Editor {
|
|
|
521
520
|
}
|
|
522
521
|
}
|
|
523
522
|
function nextGrapheme(text, pos) {
|
|
524
|
-
const first =
|
|
523
|
+
const first = graphemeSegmenter()
|
|
525
524
|
.segment(text.slice(pos, pos + 32))[Symbol.iterator]()
|
|
526
525
|
.next();
|
|
527
526
|
return first.done ? " " : first.value.segment;
|
package/dist/tui/keys.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* KeyId 形如 `ctrl+shift+up`:修饰键按 ctrl → alt → shift 排序,键名小写;
|
|
9
9
|
* 可打印字符的键名就是字符本身(`a`、`/`、`中`),空格为 `space`。
|
|
10
10
|
*/
|
|
11
|
+
import { graphemeSegmenter } from "./ansi.js";
|
|
11
12
|
export const PASTE_START = "\x1b[200~";
|
|
12
13
|
export const PASTE_END = "\x1b[201~";
|
|
13
14
|
const NO_MODS = { ctrl: false, alt: false, shift: false };
|
|
@@ -212,10 +213,9 @@ export function parseKey(data) {
|
|
|
212
213
|
return undefined;
|
|
213
214
|
return withAlt(inner);
|
|
214
215
|
}
|
|
215
|
-
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
216
216
|
function isSingleGrapheme(data) {
|
|
217
217
|
let count = 0;
|
|
218
|
-
for (const _ of graphemeSegmenter.segment(data)) {
|
|
218
|
+
for (const _ of graphemeSegmenter().segment(data)) {
|
|
219
219
|
count++;
|
|
220
220
|
if (count > 1)
|
|
221
221
|
return false;
|
package/dist/tui/stdin-buffer.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* - 块末尾的不完整转义序列先挂起;在 `AMA_TUI_ESC_TIMEOUT` 毫秒内没有后续数据就原样发出
|
|
9
9
|
* (孤立 ESC 即 Escape 键)。缺省:SSH / tmux 下 100 ms,本地 10 ms。
|
|
10
10
|
*/
|
|
11
|
+
import { graphemeSegmenter } from "./ansi.js";
|
|
11
12
|
import { PASTE_END, PASTE_START } from "./keys.js";
|
|
12
13
|
/** 读环境:`AMA_TUI_ESC_TIMEOUT` 优先;否则 SSH / tmux 100 ms,本地 10 ms。 */
|
|
13
14
|
export function defaultEscTimeout(env = process.env) {
|
|
@@ -21,7 +22,6 @@ export function defaultEscTimeout(env = process.env) {
|
|
|
21
22
|
return 100;
|
|
22
23
|
return 10;
|
|
23
24
|
}
|
|
24
|
-
const segmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
25
25
|
/** 从 0 开始扫描一个转义序列(buf[0] === ESC)。 */
|
|
26
26
|
function scanEscape(buf) {
|
|
27
27
|
if (buf.length === 1)
|
|
@@ -74,7 +74,7 @@ function scanEscape(buf) {
|
|
|
74
74
|
}
|
|
75
75
|
// Alt 前缀 + 单个字素
|
|
76
76
|
const rest = buf.slice(1);
|
|
77
|
-
const first =
|
|
77
|
+
const first = graphemeSegmenter().segment(rest.slice(0, 64))[Symbol.iterator]().next();
|
|
78
78
|
const segment = first.done ? rest[0] : first.value.segment;
|
|
79
79
|
return { kind: "complete", length: 1 + segment.length };
|
|
80
80
|
}
|
|
@@ -180,7 +180,7 @@ export class StdinBuffer {
|
|
|
180
180
|
const c = buf.charCodeAt(0);
|
|
181
181
|
if (c < 0x20 || c === 0x7f)
|
|
182
182
|
return 1;
|
|
183
|
-
const first =
|
|
183
|
+
const first = graphemeSegmenter().segment(buf.slice(0, 64))[Symbol.iterator]().next();
|
|
184
184
|
if (first.done)
|
|
185
185
|
return 1;
|
|
186
186
|
// 字素簇不能吞掉后面的 ESC 或控制字符
|
package/docs/acp.md
CHANGED
|
@@ -47,8 +47,12 @@ ama --mode acp # 与 -p 互斥;其余参数(--model、-
|
|
|
47
47
|
「本会话允许」的记忆(切回来要重新允许,与 TUI `/resume` 一致)。
|
|
48
48
|
- 权限模式按会话记:对前台会话 `set_mode` 立即生效;对其它会话只记下(照样发 `current_mode_update`),轮到它跑时
|
|
49
49
|
再应用到权限管线,并再发一条 `current_mode_update`。所以对排队中的会话改模式不会影响正在跑的那个。
|
|
50
|
+
- 后台子 Agent 完成后的通知回合不经这个队列,在会话空闲时自己开始;它在跑时,对同一会话的 `session/prompt` 也等它
|
|
51
|
+
结束再开始(不报 busy),等待期间客户端先收到通知回合的 `session/update`。等待中 `session/cancel` 会中断通知回合,
|
|
52
|
+
这条提示回 `cancelled`,之后也不会再被发出。
|
|
50
53
|
- 每个回合结束后发 `session_info_update`(`updatedAt`,标题变了才带 `title`)。
|
|
51
|
-
- 关掉一个会话只释放它(跑 SessionEnd Hook
|
|
54
|
+
- 关掉一个会话只释放它(跑 SessionEnd Hook),它的转录、图片与子 Agent 句柄随之可被回收;进程启动时建的那个会话
|
|
55
|
+
对象由进程持有到退出。stdin 关闭时排队的提示回 `cancelled`,等在跑的结束,再依次释放全部会话。
|
|
52
56
|
|
|
53
57
|
### 事件映射
|
|
54
58
|
|
package/docs/agents.md
CHANGED
|
@@ -39,6 +39,7 @@ thinking: low
|
|
|
39
39
|
max-turns: 20 # 缺省 30
|
|
40
40
|
isolation: none # none(缺省)| worktree
|
|
41
41
|
background: false # 不写 = 按 config subagents.background
|
|
42
|
+
context: fresh # fresh(缺省)| fork:继承父会话已完成的回合,见「fork 模式」
|
|
42
43
|
runner: ama # ama(缺省)| claude | codex | acp:<程序>
|
|
43
44
|
---
|
|
44
45
|
|
|
@@ -69,6 +70,7 @@ runner: ama # ama(缺省)| claude | codex | acp:<程序>
|
|
|
69
70
|
| `taskId` | 续聊:向已有任务的子会话追加一条消息(忽略 `agent` / `tools` / `model`) |
|
|
70
71
|
| `isolation` | `worktree`:在独立 git worktree 里运行 |
|
|
71
72
|
| `budgetUsd` | 外部 Agent 的美元预算 |
|
|
73
|
+
| `context` | `fresh`(缺省):子会话从空白开始;`fork`:继承本会话已完成的回合(同模型、同前缀),见「fork 模式」。缺省取类型的 `context` |
|
|
72
74
|
| `tools` / `model` / `thinkingLevel` / `maxTurns` | 保留的高级参数(描述里不展开) |
|
|
73
75
|
|
|
74
76
|
同一条回复里的多个 `task` **并行**执行,由会话的任务池限流(`subagents.maxConcurrent`,缺省 4);排队超过
|
|
@@ -77,7 +79,8 @@ runner: ama # ama(缺省)| claude | codex | acp:<程序>
|
|
|
77
79
|
|
|
78
80
|
结果是子 Agent 的最终报告,前面带 `[task tN]`。超过 50 KB 时保留开头 70% 与结尾 30%,中间注明省略了多少字节,
|
|
79
81
|
全文写到会话目录的 `outputs/<会话 id>-<taskId>.md`(内存会话写到系统临时目录)。轮数用尽且最后一步停在工具结果上时,
|
|
80
|
-
ama
|
|
82
|
+
ama 再跑一轮、在消息里要求「不要调用工具、直接给最终报告」,结果前加 `[Turn limit reached; …]`,状态 `max_turns`。这一轮的请求
|
|
83
|
+
不发 `toolChoice`(改动它会让缓存前缀失效);模型仍调用工具时这一轮就此结束,结果为「(the sub-agent returned no text)」。
|
|
81
84
|
|
|
82
85
|
### 前台与后台
|
|
83
86
|
|
|
@@ -127,11 +130,12 @@ ama 以「不允许调用工具」再跑一轮要最终报告,结果前加 `[T
|
|
|
127
130
|
|
|
128
131
|
### 续聊、保留与 resume
|
|
129
132
|
|
|
130
|
-
每个任务有会话内唯一的 `taskId`(`t1`、`t2` …)。子会话结束后不立即释放,最多保留
|
|
131
|
-
JSONL 一直在)。`task{taskId}` / `task_ctl send` 续聊时,保留中的直接追加消息,已释放的按会话文件重新打开再追加——
|
|
133
|
+
每个任务有会话内唯一的 `taskId`(`t1`、`t2` …)。子会话结束后不立即释放,最多保留 4 个(`subagents.retainSessions`;
|
|
134
|
+
最久未用的先释放内存,JSONL 一直在)。`task{taskId}` / `task_ctl send` 续聊时,保留中的直接追加消息,已释放的按会话文件重新打开再追加——
|
|
132
135
|
同一个任务始终写同一个 JSONL。父会话在任务开始、每次续聊与结束时写一条 `custom{ama.task}`
|
|
133
136
|
([session-format.md](session-format.md));`--resume` 时据此重建任务列表,当时还在运行的标 `interrupted`,仍可续聊。
|
|
134
|
-
内存会话(`--no-session
|
|
137
|
+
内存会话(`--no-session`)的任务被释放后不能续聊。子会话转录常达数十 MB,保留得越多常驻内存越大;频繁轮换续聊
|
|
138
|
+
5 个以上任务时可调大保留数,代价只是被释放的任务续聊时多一次从磁盘重开。
|
|
135
139
|
|
|
136
140
|
### worktree 隔离
|
|
137
141
|
|
|
@@ -140,7 +144,38 @@ JSONL 一直在)。`task{taskId}` / `task_ctl send` 续聊时,保留中的
|
|
|
140
144
|
worktree 里与父 cwd 对应的目录。结束时没有改动(工作区干净且没有新提交)就删掉 worktree 与分支;有改动则保留,结果末尾给出
|
|
141
145
|
分支名、路径与 `git diff --stat`(含未跟踪文件),由你决定是否合并。`.ama/worktrees/` 下自动写一个 `.gitignore`(`*`),
|
|
142
146
|
父仓库的 `git status` 与 grep / glob 都看不到这些目录。不在 git 仓库里时直接报错,不会退回共享目录。
|
|
143
|
-
worktree 里的编辑不记进父会话的检查点。注意:worktree 不共享依赖(`node_modules` 等),不保证能直接构建或运行测试。
|
|
147
|
+
多个隔离任务同时开始或结束时,同一仓库的 worktree 增删与删分支按顺序执行(它们都会改 `.git/worktrees`);另一个 ama 进程同时操作时遇到的瞬时错误会短暂重试。worktree 里的编辑不记进父会话的检查点。注意:worktree 不共享依赖(`node_modules` 等),不保证能直接构建或运行测试。
|
|
148
|
+
|
|
149
|
+
### fork 模式
|
|
150
|
+
|
|
151
|
+
`context: "fork"`(调用参数或类型 frontmatter)的子会话**继承父会话到发起这次 `task` 之前的全部对话**:
|
|
152
|
+
|
|
153
|
+
- 子会话文件复制父分支到发起调用的那条 assistant 之前(同一回复里并行的多个 fork 任务共用这个点),首条仍是
|
|
154
|
+
`custom{ama.task}`,data 多 `context: "fork"` 与 `forkedFrom`(复制的最后一条条目 id);`parentSession` 指回父文件。
|
|
155
|
+
- 系统提示与工具表与父**逐字节相同**(不加 `role` 节,`task` / `task_ctl` 仍在表里、按深度拒绝),所以子会话的首个请求
|
|
156
|
+
就是父上一次请求的前缀加一条 `<task>` 消息,前缀按读价计费;`prompt_cache_key` 沿用父链的根会话 id。
|
|
157
|
+
- 角色说明与任务写在 `<task>` 消息里,声明「上面的请求属于主 Agent、只做这项任务、本块优先于上文的计划与提醒」。子会话
|
|
158
|
+
看得到父的计划、提醒与读过的文件内容,但 `read` 记录从空开始(编辑前照旧要先读)。
|
|
159
|
+
- 类型声明了 `tools` / `disallowed-tools`(或参数给了 `tools`)时,工具表不变,不可用的工具在执行层拒绝
|
|
160
|
+
(`Tool "X" is not available in this session.`),并在 `<task>` 里列出。只读类型照旧走只读管线。
|
|
161
|
+
- `isolation: "worktree"` 时子会话的 cwd 是 worktree,`<task>` 里说明上文的相对路径指父目录;cwd 节变化以尾部补丁发送,
|
|
162
|
+
不影响已缓存的前缀。
|
|
163
|
+
|
|
164
|
+
以下情况回落为 fresh(日志记一条 info,结果 `details.context` 标 `fresh`):调用参数或类型指定了与父不同的模型或思考级别;
|
|
165
|
+
父会话还没有发出过请求;父上一次请求的输入超过(窗口 − `compaction.reserveTokens`)的一半。续聊(`taskId`)与 resume
|
|
166
|
+
照原文件重开,不受影响。只在请求了 fork 时 `details.context` 才出现。
|
|
167
|
+
|
|
168
|
+
何时用 fork:子任务需要你已经读过、讨论过的内容时。fork 的首个请求把父上下文整段带上,按命中价计费;fresh 只发系统提示、
|
|
169
|
+
工具表与任务说明,但你得在 `prompt` 里写清全部背景,子 Agent 往往还要重新读文件。
|
|
170
|
+
|
|
171
|
+
| 供应商(命中价 / 未命中价) | 8k 父上下文 fork 一次(实测,见 [benchmarks](benchmarks/efficiency-2026-10.md)「F」) | 适合 |
|
|
172
|
+
| ------------------------------- | ------------------------------------------------------------------------------------- | ---------------------------- |
|
|
173
|
+
| DeepSeek(约 1/50) | 命中 77%,与父自己的下一回合相同;折合约 1.9k 全价 token | 需要父上下文时几乎总划算 |
|
|
174
|
+
| Kimi(约 1/4–1/10) | 命中 97.5% | 需要父上下文、且要重读较多时 |
|
|
175
|
+
| OpenAI 系(经中转,不发路由键) | 两次分别 0% 与 85%,取决于中转把请求路由到哪个上游 | 视中转而定 |
|
|
176
|
+
| Anthropic(1/10,写入 1.25×) | 未实测;前缀相同,命中应与父的下一回合相同 | 需要父上下文时 |
|
|
177
|
+
|
|
178
|
+
`explore` 这类只需要定位文件的任务建议保持 fresh;内置类型都缺省 fresh。
|
|
144
179
|
|
|
145
180
|
### 事件与统计
|
|
146
181
|
|
|
@@ -154,6 +189,7 @@ RPC `get_tasks` / `get_agents` 返回任务快照与可用类型(来源、定
|
|
|
154
189
|
| --------------------------------- | -------------------------------------------------------------------------------- |
|
|
155
190
|
| `subagents.maxConcurrent` | 同时运行的子 Agent,缺省 4 |
|
|
156
191
|
| `subagents.maxPending` | 排队上限,缺省 16 |
|
|
192
|
+
| `subagents.retainSessions` | 保留在内存里的已结束子会话数(LRU),缺省 4,0 = 结束即释放;只认用户级 |
|
|
157
193
|
| `subagents.defaultModel` | 子 Agent 缺省模型,不设继承父会话 |
|
|
158
194
|
| `subagents.background` | `auto`(缺省)\| `always` \| `never`,见「前台与后台」;用户 / 项目 / 宿主级都认 |
|
|
159
195
|
| `subagents.autoBackgroundAfterMs` | 前台任务运行超过该毫秒数自动转后台,缺省 0(关闭);用户 / 项目级都认 |
|
|
@@ -165,7 +201,7 @@ RPC `get_tasks` / `get_agents` 返回任务快照与可用类型(来源、定
|
|
|
165
201
|
|
|
166
202
|
- 子 Agent 不能再委派(深度 1);协调多个 Agent 的场景交给宿主(如 Armadra 画布)。
|
|
167
203
|
- 只读类型的 bash 只放行 plan 模式认可的只读命令,识别不了的一律拒绝,宁可少用。
|
|
168
|
-
- 不读取 `.claude/agents
|
|
204
|
+
- 不读取 `.claude/agents`。fork 模式只用父的模型与思考级别(不同则回落 fresh),外部 Agent 不支持 fork。
|
|
169
205
|
|
|
170
206
|
## 外部 Agent
|
|
171
207
|
|
package/docs/codemode.md
CHANGED
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
{ "version": 1, "tools": { "preset": "codemode-only" }, "permission": { "allow": ["codemode"] } }
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
-
其它配置:`codemode.inlineBudget`(`only` 模式在描述里内联声明的预算,估算 token,缺省 3000,超出只列名字;`on` 模式不内联)、`codemode.requireStrict
|
|
37
|
+
其它配置:`codemode.inlineBudget`(`only` 模式在描述里内联声明的预算,估算 token,缺省 3000,超出只列名字;`on` 模式不内联)、`codemode.requireStrict`(见下文沙箱)、`codemode.maxHeapMb`(脚本子进程的堆上限,见下文沙箱)。项目级配置只能把 `codemode.mode` 设为 `off`。
|
|
38
38
|
|
|
39
39
|
`on` 模式的前缀开销:去重前 `codemode` 描述把已直接暴露的六个工具的声明又内联一遍,其它工具各追加一行提示,系统提示 + 工具表比 `off` 多约 1356 token;现在只多约 390 token(字符 / 4 估算,测试锁定 ≤ 500)。升级后续接的旧会话因为描述字节变化会有一次缓存未命中。
|
|
40
40
|
|
|
@@ -94,9 +94,11 @@
|
|
|
94
94
|
每次执行起一个子进程:
|
|
95
95
|
|
|
96
96
|
```text
|
|
97
|
-
<node> --permission --allow-fs-read=<ama-sandbox.cjs> --disallow-code-generation-from-strings <ama-sandbox.cjs> --ama-codemode-sandbox
|
|
97
|
+
<node> --max-old-space-size=256 --permission --allow-fs-read=<ama-sandbox.cjs> --disallow-code-generation-from-strings <ama-sandbox.cjs> --ama-codemode-sandbox
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
- 堆上限:`--max-old-space-size` 取 `codemode.maxHeapMb`(缺省 256 MB,0 不加;只认用户级)。脚本超出时子进程 OOM 退出,工具结果是脚本错误 `Script exceeded the codemode memory limit (256 MB); process the data in smaller pieces`,宿主进程内存不受影响;需要一次处理大数据时调大或分片。
|
|
101
|
+
|
|
100
102
|
- 空环境启动,拿不到密钥、会话文件与环境变量(Windows 上 libuv 会从父进程补入 PATH、SYSTEMROOT、USERPROFILE 等系统变量,不含密钥);不授予文件写、子进程、worker、addon、inspector 权限;Node 22.0–22.12 用 `--experimental-permission`;嵌入 Electron 时设 `ELECTRON_RUN_AS_NODE=1`。
|
|
101
103
|
- 子进程里用 `node:vm` 建只含 ECMAScript 内建对象的上下文(`codeGeneration: { strings: false, wasm: false }`,沙箱对象空原型);全局函数都在上下文内定义,只经一个宿主函数交换 JSON 字符串;子进程主 realm 也禁止字符串生成代码,经构造器链逃逸拿不到 `Function("return process")`。
|
|
102
104
|
- `tools.*` 经 stdin / stdout 的 JSON 行协议回调父进程执行。
|
package/docs/en/acp.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../acp.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/acp.md](../acp.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/acp.md](../acp.md) as of commit `f6419be`. When the two differ, the Chinese version is
|
|
6
6
|
> authoritative.
|
|
7
7
|
|
|
8
8
|
ama works on both sides of ACP:
|
|
@@ -47,8 +47,9 @@ One `ama --mode acp` process can keep several sessions open (Zed's threads share
|
|
|
47
47
|
- **One turn runs at a time**: while another session runs, `session/prompt` goes into a FIFO queue and starts when the previous turn ends; it no longer fails busy. `session/new`, `load`, `resume`, `list`, `set_mode`, `set_config_option` and `close` can be called at any time.
|
|
48
48
|
- When a turn starts, its session becomes the "foreground" session: the host (`HostApi.session.*`), the common fields of hooks and the session seen by tools all switch to it, and "allowed for this session" grants are cleared (switching back means allowing again, as with `/resume` in the TUI).
|
|
49
49
|
- Permission modes are kept per session: `set_mode` on the foreground session applies at once; on another session it is only recorded (a `current_mode_update` is still sent) and applied to the permission pipeline when that session's turn starts, with another `current_mode_update`. So changing the mode of a queued session does not affect the one that is running.
|
|
50
|
+
- The notification turn that follows a finished background subagent does not go through this queue; it starts on its own once the session is idle. While it runs, `session/prompt` on the same session also waits for it to end (no busy error), and the client sees the notification turn's `session/update` first. `session/cancel` while waiting interrupts the notification turn; the prompt answers `cancelled` and is not sent later either.
|
|
50
51
|
- After every turn a `session_info_update` is sent (`updatedAt`; `title` only when it changed).
|
|
51
|
-
- Closing a session releases only that session (running the SessionEnd hook);
|
|
52
|
+
- Closing a session releases only that session (running the SessionEnd hook), and its transcript, images and subagent handles can then be garbage-collected; the session object created at process start is held by the process until exit. When stdin closes, queued prompts answer `cancelled`, the running turn is allowed to finish, then all sessions are released in turn.
|
|
52
53
|
|
|
53
54
|
### Event mapping
|
|
54
55
|
|
package/docs/en/host-api.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../host-api.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/host-api.md](../host-api.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/host-api.md](../host-api.md) as of commit `979608b`. When the two differ, the Chinese
|
|
6
6
|
> version is authoritative.
|
|
7
7
|
|
|
8
8
|
A host adapter is a local JS module that ama loads at startup and hands a `HostApi`. With it the adapter can register tools, append to the system prompt, observe events, answer approvals, inject user messages and show notifications and status in the interface. The Armadra canvas plugs in as a host adapter (the canvas tools `canvas_*` / `context_*` are all registered by the adapter). The types are defined in `src/host/types.ts` and exported from `@armadra/agent/host`; `HOST_API_VERSION = 1`. The design rationale is in [design.md](../design.md) §6.2, §6.3 and §11.1 step 13 (Chinese).
|
package/docs/en/permissions.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../permissions.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/permissions.md](../permissions.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/permissions.md](../permissions.md) as of commit `6b5455a`. When the two differ, the
|
|
6
6
|
> Chinese version is authoritative.
|
|
7
7
|
|
|
8
8
|
This document covers ama's six permission modes, the decision order for every tool call, and how the three tiers of `auto` mode, "rule tier → static judgement → model classifier", decide between allowing and asking. The overall design is in [design.md](../design.md) §6.3 and §7 (Chinese); hook input and output are in [hooks.md](../hooks.md) (Chinese).
|
|
@@ -172,7 +172,7 @@ It only handles calls neither ① nor ② decided (e.g. `rm old.txt`, `node scri
|
|
|
172
172
|
- **A separate request**: it never enters the session transcript and does not change the main session's messages or prefix (the main session's prompt cache is unaffected), and does not trigger warming; the request purpose is `purpose: "classify"`.
|
|
173
173
|
- **Input**: tool name, arguments (JSON, truncated to 4000 characters), cwd, project root, and a summary of the latest user message (truncated to 600 characters). Arguments and the user message sit inside a `<tool_call_data>` … `</tool_call_data>` data block, with end markers inside the block escaped; the system prompt requires treating everything inside as data and ignoring instructions in it (including things like "ignore previous instructions" or "respond allow"), leaning to ask when such text appears.
|
|
174
174
|
- **Output**: strict JSON `{"decision":"allow"|"ask","reason":"…"}`. A parse failure, a timeout (10 s) or a request error → ask.
|
|
175
|
-
- **Model**: `permission.autoModel` (`provider/model`);
|
|
175
|
+
- **Model**: `permission.autoModel` (`provider/model`); when unset (or not found), the small model from the session provider's catalog (the file-level `small`: deepseek → `deepseek-flash`, anthropic → `claude-haiku-4-5`, openai → `gpt-6-luna`, google → `gemini-3.5-flash-lite`, moonshot → `kimi-k2.6` and so on; it must be found and have a key), otherwise the current session model; the chosen model is logged once at debug level. Relays and custom providers have no catalog small model and keep the session model. A cheap, fast model is recommended, e.g. `packy/qwen3.8-flash`. `maxTokens` 256, thinking off.
|
|
176
176
|
- **Caching**: successful decisions are cached within the session by "tool name + normalized arguments" (bash collapses whitespace, others use JSON with sorted keys), so identical calls are classified once; errors and timeouts are not cached.
|
|
177
177
|
- **Cost**: each classification records a `usage` entry with `kind: "permission_classify"`, counted in `/session` cost and RPC stats, never in the context.
|
|
178
178
|
- The classifier can only judge undecided calls as allow or ask; it cannot overturn ①'s deny or ask.
|
package/docs/en/providers.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../providers.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/providers.md](../providers.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/providers.md](../providers.md) as of commit `514202f`. When the two differ, the
|
|
6
6
|
> Chinese version is authoritative.
|
|
7
7
|
|
|
8
8
|
Built-in providers, model references, API keys, custom providers and relays, the compat switches of each protocol, and caching. The design rationale is in [design.md](../design.md) §3 and §9.1 (Chinese).
|
|
@@ -259,7 +259,7 @@ export PACKY_API_KEY=sk-...
|
|
|
259
259
|
|
|
260
260
|
- A model's `api` defaults to the provider's (here `openai-completions`); `modelOverrides[]` can change `api` too.
|
|
261
261
|
- The three protocols share one `baseUrl`: Completions / Responses append `/chat/completions`, `/responses`; Messages appends `/messages` when the baseUrl ends with `/v1`, otherwise `/v1/messages`.
|
|
262
|
-
- Don't want to write `models` by hand: `ama models discover packy` lists the relay's models (`GET {baseUrl}/models`); `--probe` tries minimal requests per model with the provider's protocol, completions, responses and messages in turn and records the first that succeeds (at most 3 per model; `--limit` caps the number of probed models, default 30; an estimate is printed first; models are probed concurrently with the same rules as `providers add --probe` below); `--write` merges the result into the user-level `config.json` (existing ids are not overwritten; only `id` and an `api` differing from the provider's are written; the original is backed up as `config.json.bak`). Context window and other metadata are filled from the models.dev snapshot at runtime (see "Model metadata" below); entries without a match have no `contextWindow`, so automatic compaction is off for them; add it by hand when needed.
|
|
262
|
+
- Don't want to write `models` by hand: `ama models discover packy` lists the relay's models (`GET {baseUrl}/models`); `--probe` tries minimal requests per model with the provider's protocol, completions, responses and messages in turn and records the first that succeeds (at most 3 per model; `--limit` caps the number of probed models, default 30; an estimate is printed first; models are probed concurrently with the same rules as `providers add --probe` below); `--write` merges the result into the user-level `config.json` (existing ids are not overwritten; only `id` and an `api` differing from the provider's are written; the original is backed up as `config.json.bak`). Context window and other metadata are filled from the models.dev snapshot at runtime (see "Model metadata" below), and ids that line up with an official catalog entry also inherit intrinsic properties such as the thinking map (see "Inheriting from the official catalog" below); entries without a match have no `contextWindow`, so automatic compaction is off for them; add it by hand when needed. `--probe` only verifies the protocol; request formats such as `thinkingFormat` still need testing and an entry in `modelOverrides`.
|
|
263
263
|
|
|
264
264
|
```sh
|
|
265
265
|
ama models discover packy --probe --write --limit 8
|
|
@@ -361,7 +361,7 @@ Measured (2026-10-02, a relay offering all three endpoint types, 22 models, 29 r
|
|
|
361
361
|
- **Weekly refresh**: `.github/workflows/models-dev.yml` runs the script every Monday at 03:17 UTC (and on manual `workflow_dispatch`), automatically removes catalog overrides identical to the new snapshot, and only opens / updates a PR on the fixed branch `chore/models-dev-refresh` when something changed (the summary lists additions, removals and context / output / price changes; `needs-review` is added when more than 30% is removed). The PR is created with the repository secret `MODELS_DEV_PR_TOKEN` (a GitHub App token or a fine-grained PAT with `contents` + `pull-requests` write access) so that CI triggers; without that secret the workflow runs `pnpm run ci` itself first, then opens the PR with the default token and states the result in the description. A person merges it.
|
|
362
362
|
- **`ama models refresh [--provider <id>[,<id>…]]`**: explicitly fetches the latest `api.json` over the network, trims it with the same list, writes `models-dev.json` into the data directory (default `~/.local/share/ama/`) and prints additions / upstream removals / price changes. From then on the index = the built-in snapshot ⊕ this override: the override applies only when its `fetchedAt` is later than the snapshot's (so an old override expires automatically once an ama upgrade brings a newer snapshot); for the same `provider/model` the override wins, and entries removed upstream keep the snapshot's values. `refresh-catalog` is the old name. `AMA_MODELS_DEV_URL` changes the refresh source. The full cache written by older ama versions (version 1) is no longer read. `ama providers add|refresh` and `ama models discover` only use the local index and no longer fetch models.dev (their `/models` requests are unchanged).
|
|
363
363
|
- **Built-in catalogs (`catalog/*.json`) only hold overrides**: a file-level `"modelsDev": "<snapshot provider id>"` (local services write `false`); entries inherit name, reasoning, contextWindow, maxTokens, input, cost, family, knowledge, releaseDate, inputLimit and status from the snapshot via `<modelsDev>/<id>`, and the catalog only writes ama-specific fields (`api`, `thinkingLevelMap`, `promptCache`, `compat`) plus values deliberately different from the snapshot (such as openai's 272k window or deepseek's prices); `cost` may list only the keys to change. When the id differs from the snapshot the entry writes `"modelsDev": "provider/model"`, and `false` to inherit nothing. Models missing from the snapshot (deprecated etc.) get complete entries. `catalog.test.ts` reports fields equal to the snapshot's value as redundant; `UPDATE_CATALOG=1 pnpm vitest run src/ai/providers/catalog.test.ts` removes them automatically and regenerates `catalog-data.ts` (run prettier afterwards); to pin a value equal to the snapshot on purpose, write `"_reason": "…"` on the entry (a comment ignored at runtime that exempts the entry from the redundancy check). The inherited mapping for catalogs differs slightly from custom models: `maxTokens` is not capped (it is `min(limit.output, contextWindow)`), and missing cache prices count as 0.
|
|
364
|
-
- **Priority**: user config (fields written in `models[]` / `modelOverrides[]`) > built-in catalog (snapshot ⊕ catalog overrides) > the models.dev index > custom defaults (`maxTokens: 8192`, `input: ["text"]`, `reasoning: false`, no guessed `contextWindow`). `ama models list` and `ama config show` mark where each field comes from (`config` / catalog / `models.dev` / default; fields a built-in catalog inherits from the snapshot are marked `models.dev`).
|
|
364
|
+
- **Priority**: user config (fields written in `models[]` / `modelOverrides[]`) > built-in catalog (snapshot ⊕ catalog overrides) > intrinsic properties inherited from the official catalog by id (next section) > the models.dev index > custom defaults (`maxTokens: 8192`, `input: ["text"]`, `reasoning: false`, no guessed `contextWindow`). `ama models list` and `ama config show` mark where each field comes from (`config` / catalog / `catalog (via id)` / `models.dev` / default; fields a built-in catalog inherits from the snapshot are marked `models.dev`), and name the catalog entry when inheriting by id (`catalog deepseek/deepseek-flash`).
|
|
365
365
|
- **Field mapping** (custom models): `contextWindow = limit.context`; `maxTokens = min(limit.output, 65536, contextWindow)`. `maxTokens` is sent as `max_tokens` on every request, and models.dev gives the vendor's limit (quite a few models list 1M, equal to their context); relays that switched upstreams often reject very large values, the thinking budget of the Anthropic protocol is also derived from it, and 64k is plenty for a coding agent's single-turn output; write it in the config when you need more. `input` becomes `["text","image"]` or `["text"]` depending on whether `modalities.input` contains `image`; `reasoning`; `cost` takes `input` / `output` / `cache_read` / `cache_write` ($/M), using the input price when cache prices are missing (no discount assumed, so warming economics err on the conservative side); `cost.tiers` takes models.dev's per-context tiers, folding to a single 200k tier when only `context_over_200k` exists; plus `family`, `knowledge`, `releaseDate`, `inputLimit` (when it differs from the context) and `status: "beta"`.
|
|
366
366
|
- **Matching rules** (the same id often appears under several resellers with differing values):
|
|
367
367
|
1. `"modelsDev": "provider/model"` on the model → use that entry directly (`false` disables enrichment);
|
|
@@ -372,6 +372,17 @@ Measured (2026-10-02, a relay offering all three endpoint types, 22 models, 29 r
|
|
|
372
372
|
6. when no entry has the same name, try normalized ids in turn: strip the `vendor/` prefix, strip suffixes like `:free`, strip `-latest`, strip date suffixes (`-0902`, `-20250514`, `-2025-05-14`);
|
|
373
373
|
7. nothing found → "unmatched", keeping the custom defaults (no guessed `contextWindow`, automatic compaction off). The snapshot only covers major vendors, so reseller-specific ids may not match; write `modelsDev` on the model to point at an included entry.
|
|
374
374
|
|
|
375
|
+
### Inheriting from the official catalog
|
|
376
|
+
|
|
377
|
+
Models of relays and custom providers (`models[]` entries, and models a relay synthesizes by id) whose id lines up with a built-in catalog entry inherit that model's **intrinsic properties**, which travel with the model whoever resells it:
|
|
378
|
+
|
|
379
|
+
- Inherited: `reasoning`, `input`, `thinkingLevelMap`, `promptCache.minTokens`, `compat.requiresReasoningContentOnAssistantMessages`; fields you wrote are not overwritten, and `modelOverrides` still wins.
|
|
380
|
+
- Not inherited: `cost` (relay prices differ; write the relay's price yourself), `promptCache.short` / `long` (TTLs are promises of the official host), request formats such as `compat.thinkingFormat`, and cache host capabilities.
|
|
381
|
+
- models.dev: on a hit, models.dev explicitly matches that catalog entry's snapshot (e.g. `deepseek/deepseek-flash` rather than a reseller's entry of the same name); window, output limit and price still come from models.dev (the price source stays `models.dev`). Without models.dev the window comes from the catalog and the output limit is capped at 64k.
|
|
382
|
+
- Matching: after lowercasing and stripping one vendor prefix (like `deepseek-ai/`, `moonshotai/`) and `:latest`, the id is compared exactly with first-party catalog ids and their `aliases`; only a **unique hit** inherits (when two catalog entries claim the same key, neither is used); `vendor/model` ids in aggregator catalogs (openrouter etc.) do not take part. When the exact comparison misses, a thinking-tier suffix (`-minimal` / `-low` / `-medium` / `-high` / `-xhigh` / `-tiered`) is stripped and tried once: the relay picks the thinking tier from such a name, so only images, window and cache threshold are inherited, `reasoning` stays off and ama sends no thinking parameters. Example: `gemini-3.8-flash-low` → `google/gemini-3.8-flash`, with image input and a 1M window.
|
|
383
|
+
- Switches: `"catalog": false` on a `models[]` entry turns it off; `"catalog": "deepseek/deepseek-v4-pro"` names the catalog entry explicitly (when the id does not line up). Catalog aliases live in the `aliases` of `catalog/*.json` entries (such as `deepseek-v4-flash` on `deepseek-flash`).
|
|
384
|
+
- A relay that points the same id at another upstream causes a mismatch: the sources column of `ama models list` shows which entry was inherited; turn it off with `catalog: false`. Whether `requiresReasoningContentOnAssistantMessages` holds through a relay depends on the relay passing `reasoning_content` through; if not, set it to `false` in the `compat` of `modelOverrides`.
|
|
385
|
+
|
|
375
386
|
### Image input
|
|
376
387
|
|
|
377
388
|
- All four protocols put images into user messages: Chat Completions `image_url` (data URL), Responses `input_image`, Anthropic `image` (base64 source), Gemini `inlineData`; images in tool results are mapped the same way.
|
|
@@ -439,22 +450,26 @@ Most usage in long tasks is cache reads: once the prefix changes, every later re
|
|
|
439
450
|
|
|
440
451
|
Prefix stability is guaranteed by assembly: the system prompt sections have a fixed order without timestamps, tools are sorted by name, and mid-session changes are only appended at the end as system patches ([session-format.md](../session-format.md), Chinese).
|
|
441
452
|
|
|
442
|
-
Section patches after the conversation has started (AGENTS.md, Skills or SessionStart hook output changed on resume, host instructions refreshed, the memory section re-rendered after compaction)
|
|
453
|
+
**The leading system prompt and the tool declarations are written once per session.** Section patches after the conversation has started (AGENTS.md, Skills or SessionStart hook output changed on resume, host instructions refreshed, the memory section re-rendered after compaction) do not rewrite the head: protocols without `supportsMidConvoSystemMessages` render them as a user message wrapped in `<system-reminder>` at the patch's position, so the previous request stays a byte-for-byte prefix. Measured on DeepSeek, a mid-conversation system message is accepted and the prefix cache holds, but the model still answers from the leading one, so DeepSeek keeps the switch off ([cache-midconvo-2026-10-09](../benchmarks/cache-midconvo-2026-10-09.md), Chinese).
|
|
454
|
+
|
|
455
|
+
The tool table works the same way: tools added after the conversation started are appended at the end of the table; **removing a tool keeps its declaration**, and the reminder says `Tool "X" is no longer available in this session; calls to it are rejected.` — if the model still calls it, the execution layer rejects the call (`Tool "X" is not available in this session.`); adding it back only says `Tool "X" is available again.` and keeps the declaration that was sent first. The `tools` / `rules` sections of the system prompt are also frozen to the text already sent once the conversation has started.
|
|
456
|
+
|
|
457
|
+
Compaction does not rewrite the head either: the system checkpoint after compaction replays only the system messages from before the conversation started, and all later patches are merged into one placed after the summary (rendered as a reminder as well); the tool table is rebuilt in the order and version each tool first appeared. So the first request after compaction has the same system + tools bytes as before, also across repeated compactions, and only the messages after the summary are written at full price.
|
|
443
458
|
|
|
444
459
|
### Request fields
|
|
445
460
|
|
|
446
|
-
| Protocol | Field
|
|
447
|
-
| ----------------------------------------- |
|
|
448
|
-
| `anthropic-messages` |
|
|
449
|
-
| `anthropic-messages` | `ttl: "1h"`
|
|
450
|
-
| `openai-completions` | `prompt_cache_key = sessionId` (cut to 64 characters)
|
|
451
|
-
| `openai-completions` | `prompt_cache_retention: "24h"`
|
|
452
|
-
| `openai-completions` (`anthropic/*` etc.) | `cache_control` (`cacheControlFormat: "anthropic"`), with `ttl: "1h"` for `long`
|
|
453
|
-
| `openai-responses` | `prompt_cache_key`
|
|
454
|
-
| `openai-responses` | `prompt_cache_options: { ttl: "30m" }`, otherwise `prompt_cache_retention: "24h"`
|
|
455
|
-
| Both OpenAI lines | Affinity header `x-session-affinity` + a per-request `x-client-request-id` (OpenRouter: `x-session-id`)
|
|
456
|
-
| `google-generative-ai` | None (implicit caching)
|
|
457
|
-
| All | `toolChoice: "none"` → each provider's way of forbidding tool calls
|
|
461
|
+
| Protocol | Field | Condition |
|
|
462
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------ |
|
|
463
|
+
| `anthropic-messages` | Four `cache_control` breakpoints, the first `maxCacheBreakpoints` by priority: last user message, end of system, second-to-last user message (where the previous request wrote), last tool | `cacheRetention` is not `none` |
|
|
464
|
+
| `anthropic-messages` | `ttl: "1h"` | `long` and `supportsLongCacheRetention`; otherwise 5m |
|
|
465
|
+
| `openai-completions` | `prompt_cache_key = sessionId` (cut to 64 characters) | `sendPromptCacheKey` and not `none` |
|
|
466
|
+
| `openai-completions` | `prompt_cache_retention: "24h"` | `long` and `supportsLongCacheRetention` |
|
|
467
|
+
| `openai-completions` (`anthropic/*` etc.) | `cache_control` (`cacheControlFormat: "anthropic"`), with `ttl: "1h"` for `long` | Same as Anthropic |
|
|
468
|
+
| `openai-responses` | `prompt_cache_key` | Same as Completions |
|
|
469
|
+
| `openai-responses` | `prompt_cache_options: { ttl: "30m" }`, otherwise `prompt_cache_retention: "24h"` | `long` and `supportsExplicitPromptCacheMode`; otherwise `long` and `supportsLongCacheRetention` |
|
|
470
|
+
| Both OpenAI lines | Affinity header `x-session-affinity` + a per-request `x-client-request-id` (OpenRouter: `x-session-id`) | `sendSessionAffinityHeaders` and a sessionId |
|
|
471
|
+
| `google-generative-ai` | None (implicit caching) | — |
|
|
472
|
+
| All | `toolChoice: "none"` → each provider's way of forbidding tool calls | When the request has tools (summary continuation does **not** use it, see "Compaction summary continuation") |
|
|
458
473
|
|
|
459
474
|
Retention tier: `StreamOptions.cacheRetention` wins; otherwise `AMA_CACHE_RETENTION=none|short|long` is read; with neither it is `short`. The Anthropic request body gets a final TTL order check (if a 1h appears after a 5m across tools → system → messages, everything falls back to 5m). When the Anthropic `baseUrl` ends with `/v1`, requests go to `{baseUrl}/messages`, never `/v1/v1/messages`.
|
|
460
475
|
|
|
@@ -472,6 +487,23 @@ Inference only looks at the final request's host name (`HOST_CACHE_CAPABILITIES`
|
|
|
472
487
|
|
|
473
488
|
**Automatic stripping on 400**: when an endpoint rejects with 400 and the error body names `prompt_cache_key` / `prompt_cache_retention` / `prompt_cache_options` / `cache_control`, ama records `provider/model` in an in-process strip table, resends once without those fields (still with a single terminal event), and suggests once which switch to write; later requests in the same process omit them directly.
|
|
474
489
|
|
|
490
|
+
The second-to-last user breakpoint: when one turn carries many parallel tool results, the last user message can sit beyond the lookback window the official docs describe (about 20 blocks) from where the previous request wrote, and without it the whole stretch is rewritten; breakpoints themselves are free. The lookback window cannot be measured through relays, so this follows the official docs.
|
|
491
|
+
|
|
492
|
+
### max_tokens
|
|
493
|
+
|
|
494
|
+
Before each request is sent, its `max_tokens` (Completions' `max_completion_tokens`, Responses' `max_output_tokens`, Gemini's `maxOutputTokens`) goes through two steps:
|
|
495
|
+
|
|
496
|
+
- **Proactive clamp**: when the model's `contextWindow` is known, it becomes `min(requested, max(1024, window − estimated input − 2048))`, with input estimated as request-body characters / 4 (CJK-heavy contexts are underestimated; the next step covers that). Anthropic budgeted thinking (`thinking.type: "enabled"`) and Gemini with a positive thinking budget are not clamped: the budget is derived from the limit, and changing it would invalidate the message cache.
|
|
497
|
+
- **Reactive fix**: when an endpoint rejects with 400 and states the limit (wordings like `Range of max_tokens should be [1, N]` or `max_tokens … must be / at most / less than or equal to N`), the limit for `provider/model` is recorded in an in-process table and the request is resent once with it (like the automatic stripping above, only before the stream starts and still with a single terminal event); later requests in the same process use the limit directly. Anthropic's `input length and max_tokens exceed context limit: X + Y > Z` is resent once with `Z − X` and not recorded as a model limit; when `Z − X` is below 1024 it is handled as a context overflow (compact, then retry).
|
|
498
|
+
|
|
499
|
+
Relays often list a catalog `maxTokens` equal to the window; the first request is then rejected once and never again in the same process. For a model you use long term, set `maxTokens` to the endpoint's real limit in `models[]` / `modelOverrides[]` to skip even that first rejection.
|
|
500
|
+
|
|
501
|
+
### Request timeouts and retries
|
|
502
|
+
|
|
503
|
+
- **Two timeouts**: `request.idleTimeoutMs` (default 300 000, overridden by `AMA_IDLE_TIMEOUT_MS`) only covers waiting for the response headers; once the stream has started, the gap between two chunks is limited by `request.streamIdleTimeoutMs` (default 180 000, overridden by `AMA_STREAM_IDLE_TIMEOUT_MS`). 0 turns either off, and a timeout is handled as a retryable error. Endpoints that hide thinking may send no bytes for a long time while reasoning, and a false stall would bill the whole output again, so the in-stream limit is not shorter; if you raised `idleTimeoutMs` for a slow endpoint before, `streamIdleTimeoutMs` is now the one to raise.
|
|
504
|
+
- **Retries** (`retry.*`): the wait is `min(maxDelayMs, max(baseDelayMs × 2^(n−1), Retry-After)) × random 0.8–1.2`, so when the endpoint sends `Retry-After` ama waits at least that long (recorded as `retryAfterMs` on the failed message). 429 / 529 / rate-limit wording counts as rate limiting and is retried up to `maxRetries + 2` times; a 5xx only counts when the status code starts the message or follows `status` / `HTTP` ("500 tokens" in the middle of the text does not).
|
|
505
|
+
- **Fallback model**: with `fallbackModel` set, an overloaded error is first retried once quickly (about 1 s) and only then switches to the fallback model.
|
|
506
|
+
|
|
475
507
|
### usage and `cacheReported`
|
|
476
508
|
|
|
477
509
|
If any cache field appears in the raw usage (even 0) → `Usage.cacheReported = true`; none → `false`. Completions recognizes `prompt_tokens_details.cached_tokens` / `cache_write_tokens`, `prompt_cache_hit_tokens` / `prompt_cache_miss_tokens` and top-level `cached_tokens`; Responses recognizes `input_tokens_details.cached_tokens`; Anthropic recognizes `cache_read_input_tokens` / `cache_creation_input_tokens`; Google recognizes `cachedContentTokenCount` (often missing on implicit cache misses). Endpoints whose fields exist but stay 0 are judged `silent` by the session layer after 3 in a row.
|
|
@@ -482,10 +514,10 @@ Only publicly documented values are written (seconds / tokens): all Anthropic mo
|
|
|
482
514
|
|
|
483
515
|
### Session layer: fingerprints, misses and three states
|
|
484
516
|
|
|
485
|
-
Every real request records an in-memory entry: the prefix fingerprint (the first 16 hex characters of sha256 for the system prompt and the tool table each, plus `provider/model`), `promptTokens` (input + cacheRead + cacheWrite), usage and the send time. The next request is compared with the previous entry:
|
|
517
|
+
Every real request records an in-memory entry: the prefix fingerprint (the first 16 hex characters of sha256 for the system prompt and the tool table each, plus `provider/model`, and a hash per system section that `/cache fingerprint` lists), `promptTokens` (input + cacheRead + cacheWrite), usage and the send time. The next request is compared with the previous entry:
|
|
486
518
|
|
|
487
519
|
- **Miss**: `missed = min(previous prefix, current prefix) − current cacheRead`, ignored below the noise floor (`max(1024, promptCache.minTokens, endpoint cache granularity)`). Some endpoints report cache reads in blocks (DeepSeek through a relay uses blocks of 2048); the granularity is the GCD of the non-zero cacheRead values observed for the same endpoint (provider + host + model), trusted only with at least 2 samples and within 128–8192, kept in memory only and reused across sessions in the process. It counts as one miss when the relative share exceeds a scale-adaptive threshold (about `0.10 × √(100k / prefix)`, clamped to 2%–30%) or the absolute value is ≥ 20 000. The re-billing amount is estimated from the difference between the price actually paid and the read price; without model prices there are only tokens.
|
|
488
|
-
- **Cause** (decided in order): the system / tool table fingerprint changed → `prefix_changed` (`detail` says which part
|
|
520
|
+
- **Cause** (decided in order): the system / tool table fingerprint changed → `prefix_changed` (`detail` says which part: `tools`, or `system:<section,…>` such as `system:hooks,memory`; with the head written once, this usually means a host or extension changed the first system message directly instead of patching); the model changed → `model_changed`; the gap exceeds the TTL → `idle` (implicit caches without a catalog TTL assume 10 minutes); a `task` sub-task took more than 80% of the gap between the two requests → `subtask`; otherwise → `evicted` (server eviction).
|
|
489
521
|
- **Not a miss**: the first request after compaction, a branch summary or tier-one pruning (the context changed legitimately); prefixes below the minimum cacheable length. A model switch is **not** exempt.
|
|
490
522
|
- **Three states**: maintained in-process per `(provider, baseUrl host name, model)`. `unknown`: no long enough comparable request yet; `reported`: a cacheRead or cacheWrite > 0 has been seen; `silent`: reads and writes were 0 for 3 comparable requests in a row (prefix ≥ minTokens, fingerprint unchanged, gap < TTL), or `compat.cacheReporting: "silent"`. Only `reported` shows a hit rate, detects misses and warms; `unknown` / `silent` requests stay out of the hit-rate denominator, and the interface shows `—` / "not reported" instead of 0%.
|
|
491
523
|
|
|
@@ -493,7 +525,7 @@ Every real request records an in-memory entry: the prefix fingerprint (the first
|
|
|
493
525
|
|
|
494
526
|
### Warming
|
|
495
527
|
|
|
496
|
-
While a tool runs for a long time (long tests, `task` sub-tasks, codemode scripts), the prefix may expire before the next request. Warming replays the previous real request before the TTL expires (same model, same context, `maxTokens: 1
|
|
528
|
+
While a tool runs for a long time (long tests, `task` sub-tasks, codemode scripts), the prefix may expire before the next request. Warming replays the previous real request before the TTL expires (same model, same context, `maxTokens` at the smallest value the protocol accepts: 16 for `openai-responses`, 1 elsewhere), paying only one read to keep the cache alive.
|
|
497
529
|
|
|
498
530
|
| Item | Rule |
|
|
499
531
|
| ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
@@ -509,7 +541,9 @@ Hosts can veto or force every warming through `api.cache.onWarmingDecision` ([ho
|
|
|
509
541
|
|
|
510
542
|
### Compaction summary continuation
|
|
511
543
|
|
|
512
|
-
The summary request of tier-two compaction no longer starts a separate conversation; instead it appends a summary instruction after a prefix byte-identical to the last real request (`cacheRetention: "short"`), so the whole history is billed at the read price. The continuation request **does not send `tool_choice`**: relays and Kimi were measured to render the prompt without tool definitions under `tool_choice: "none"`, breaking the prefix at the tools section so the cache is not read; Anthropic also documents that changing tool_choice invalidates the message cache. The tool table is sent as usual, and "do not call tools, only output the summary" is in the final instruction. When the response is empty, truncated, contains tool calls or the request fails, it falls back to a standalone summary request (`cacheRetention: "none"`) with a warning.
|
|
544
|
+
The summary request of tier-two compaction no longer starts a separate conversation; instead it appends a summary instruction after a prefix byte-identical to the last real request (`cacheRetention: "short"`), so the whole history is billed at the read price. The continuation request **does not send `tool_choice`**: relays and Kimi were measured to render the prompt without tool definitions under `tool_choice: "none"`, breaking the prefix at the tools section so the cache is not read; Anthropic also documents that changing tool_choice invalidates the message cache. The tool table is sent as usual, and "do not call tools, only output the summary" is in the final instruction. When the response is empty, truncated, contains tool calls or the request fails, it falls back to a standalone summary request (`cacheRetention: "none"`) with a warning. For models with thinking enabled (except Anthropic models whose thinking budget derives from `max_tokens`), the output limit of the continuation is the summary limit 4096 plus the thinking budget of the level (capped at the model limit), so thinking cannot use up the allowance and truncate the summary.
|
|
545
|
+
|
|
546
|
+
Order of tier one and tier two: the threshold check first computes how much tier one (pruning tool results) would save. If pruning brings the context back within budget → prune only; if it is still over budget and continuation is possible (same model, the last request is still a byte-for-byte prefix, the cache is not cold) → **do not prune** and continue straight into the summary — the pruned content goes into the summary anyway, while pruning rewrites history and breaks the continuation prefix, turning it into a full-price standalone request; if the cache is already cold (past the TTL the catalog promises) → prune first as before and summarize with a standalone request, since the prefix is rewritten at full price anyway and the pruned request is smaller.
|
|
513
547
|
|
|
514
548
|
### Configuration
|
|
515
549
|
|
package/docs/en/rpc.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../rpc.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/rpc.md](../rpc.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/rpc.md](../rpc.md) as of commit `a567833`. When the two differ, the Chinese version is
|
|
6
6
|
> authoritative.
|
|
7
7
|
|
|
8
8
|
`ama --mode rpc` reads commands from stdin and writes responses and events to stdout, one JSON value per line. The types are defined in `@armadra/agent/rpc` (`src/rpc.ts`) and implemented in `src/modes/rpc/`. The events printed by `ama -p --output-format stream-json` have the same shapes. The design rationale is in [design.md](../design.md) §13.2 (Chinese).
|
|
@@ -18,7 +18,7 @@ English · [简体中文](../rpc.md)
|
|
|
18
18
|
On startup the server first sends `hello`, then the current session's `session_start`:
|
|
19
19
|
|
|
20
20
|
```json
|
|
21
|
-
{"type":"hello","protocolVersion":1,"agent":"ama","version":"0.1.0","capabilities":["approvals","images","hooks","plans"]}
|
|
21
|
+
{"type":"hello","protocolVersion":1,"agent":"ama","version":"0.1.0","capabilities":["approvals","images","hooks","plans","compact_events"]}
|
|
22
22
|
{"type":"session_start","sessionId":"…","cwd":"/work","reason":"startup"}
|
|
23
23
|
```
|
|
24
24
|
|
|
@@ -115,10 +115,12 @@ After a session switch the server re-subscribes to events and sends `session_sta
|
|
|
115
115
|
|
|
116
116
|
### Approvals
|
|
117
117
|
|
|
118
|
-
| Command | Parameters
|
|
119
|
-
| ------------------------- |
|
|
120
|
-
| `set_client_capabilities` | `capabilities: ("approvals" \| "images" \| "hooks" \| "plans")[]`
|
|
121
|
-
| `permission_response` | `requestId: string`, `decision: "allow" \| "deny" \| "allow_session"`
|
|
118
|
+
| Command | Parameters | `data` |
|
|
119
|
+
| ------------------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
120
|
+
| `set_client_capabilities` | `capabilities: ("approvals" \| "images" \| "hooks" \| "plans" \| "compact_events")[]` | `{ capabilities }` |
|
|
121
|
+
| `permission_response` | `requestId: string`, `decision: "allow" \| "deny" \| "allow_session"` | `{ accepted: boolean }` (false = not currently waiting for this id; kept and applied when that request is asked) |
|
|
122
|
+
|
|
123
|
+
`compact_events` (docs/memory-plan.md D9, Chinese): once declared, `turn_end.toolResults`, `message_start` and `entry_appended` no longer repeat the body of tool results and of user messages with images (marked `contentOmitted: true`); the body is sent only in `message_end` and `tool_execution_end`. Without it the event shapes are byte-for-byte unchanged. Shapes are in [Compact events](#compact-events-compact_events). `stream-json` has no such switch and always emits the full shapes.
|
|
122
124
|
|
|
123
125
|
### Tools, permissions, discovery
|
|
124
126
|
|
|
@@ -225,6 +227,20 @@ There is also the non-session event `{"type":"notification","level":"info"|"warn
|
|
|
225
227
|
|
|
226
228
|
`parentToolCallId` appears only on inner calls made through `tools.*` inside codemode scripts; its value is the id of the outer `codemode` call, which clients use to fold the display. Inner calls do not enter the transcript.
|
|
227
229
|
|
|
230
|
+
Assistant messages in `message_end`, `turn_end`, `done` / `error` and in replays (`get_messages`, `get_entries`) may carry two optional fields (model efficiency batch, docs/model-efficiency-plan.md §1.10, Chinese): `retryAfterMs` on failed messages (`Retry-After`, in ms) and `rawArguments` on tool-call blocks (the raw argument string the model produced). `subagent_*` events are unchanged; `TaskInfo` from `get_tasks` may carry `context?: "fork" | "fresh"` (the sub-session's actual context mode). All are new optional fields, `RPC_PROTOCOL_VERSION` is unchanged, and clients can ignore fields they do not know.
|
|
231
|
+
|
|
232
|
+
### Compact events (`compact_events`)
|
|
233
|
+
|
|
234
|
+
In the full shapes, the result of one tool call appears once each in `message_start`, `message_end`, `tool_execution_end`, `turn_end` and `entry_appended`. After the client declares `compact_events` with `set_client_capabilities`, three of these change:
|
|
235
|
+
|
|
236
|
+
| Event | Full (not declared) | With `compact_events` declared |
|
|
237
|
+
| ---------------- | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
238
|
+
| `turn_end` | `toolResults`: complete toolResult messages | each `toolResults[]` item is only `{ toolCallId, toolName, isError, timestamp, contentOmitted: true }` (no `content`, no `details`) |
|
|
239
|
+
| `message_start` | `message` as is | when `message` is a toolResult or a user message with images: `content: ""`, `contentOmitted: true`, other fields unchanged; other messages as is |
|
|
240
|
+
| `entry_appended` | `entry` as is | `message` entries as above (toolResult, user with images); other entries as is |
|
|
241
|
+
|
|
242
|
+
`message_end` and `tool_execution_end` are always full: the former is what clients use to replace the whole message, the latter carries `details`. Replay commands (`get_messages`, `get_entries`) are unaffected. The declaration keeps applying after a session switch; declaring a capability list without `compact_events` restores the full shapes. `RPC_PROTOCOL_VERSION` is unchanged.
|
|
243
|
+
|
|
228
244
|
### Sub-agent events (wave 5)
|
|
229
245
|
|
|
230
246
|
Sub-agents started by `task` / `task_ctl` (ama sub-sessions and external agents share the same events, see [agents.md](../agents.md), Chinese):
|
|
@@ -333,7 +349,7 @@ The `data` of `get_session_stats` is `SessionStats`:
|
|
|
333
349
|
{"type":"context_pressure","percent":71,"threshold":70,"remainingTokens":57990,"estimatedTurnsLeft":6}
|
|
334
350
|
```
|
|
335
351
|
|
|
336
|
-
`cache_miss` gives `detail` only for `prefix_changed` (`system`
|
|
352
|
+
`cache_miss` gives `detail` only for `prefix_changed` (`tools`, `system`, or `system:<section,…>` naming the changed sections, such as `system:hooks,memory`). The values and meanings of `reason` for `cache_warm{stopped}` are in [tui.md](tui.md) "Cache and context". The result object of `ama -p --output-format json` also has a `cache` field of the same shape.
|
|
337
353
|
|
|
338
354
|
It also carries `context: { tokens, window, percent }` (context used, window size and usage 0–100, defined like `contextTokens` / `contextWindow` / `contextPercent` in the table above); an unknown item is omitted (for a model without a window only `tokens` is present), and when all are unknown there is no `context`.
|
|
339
355
|
|
package/docs/en/sessions.md
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
English · [简体中文](../sessions.md)
|
|
4
4
|
|
|
5
|
-
> Translated from the Chinese [docs/sessions.md](../sessions.md) as of commit `
|
|
5
|
+
> Translated from the Chinese [docs/sessions.md](../sessions.md) as of commit `e57b54f`. When the two differ, the Chinese
|
|
6
6
|
> version is authoritative. Sample command output below is illustrative; exact wording follows the interface language.
|
|
7
7
|
|
|
8
|
-
These commands only read the session directory (`<data dir>/sessions`, changeable with `--session-dir`; the file format is in [session-format.md](../session-format.md), Chinese): no locks, no repair of half-written lines, no file changes, so sessions that are still running can be read too. By default the scope is the sessions of the **current directory**; `--all` covers everything.
|
|
8
|
+
These commands only read the session directory (`<data dir>/sessions`, changeable with `--session-dir`; the file format is in [session-format.md](../session-format.md), Chinese): no locks, no repair of half-written lines, no file changes, so sessions that are still running can be read too. Session files are read line by line in chunks rather than loaded whole: `ama sessions list` parses only each file's header, first entry, renames and first prompt and looks at just the type of every other line, so sessions of tens of MB do not make memory grow with file size; `--resume` likewise parses a session line by line. By default the scope is the sessions of the **current directory**; `--all` covers everything.
|
|
9
9
|
|
|
10
10
|
## Stats: `ama stats`
|
|
11
11
|
|
|
@@ -78,6 +78,8 @@ the same fields):
|
|
|
78
78
|
| `context.autoCompactAt` | Threshold of automatic summarization (tier two) = window − `compaction.reserveTokens`; **absent** when auto-compaction is off, tripped after repeated failures, or there is no window |
|
|
79
79
|
| `context.pruneAt` | Threshold of tool-result pruning (tier one) = 0.7 × (window − `reserveTokens`); absent under the same conditions |
|
|
80
80
|
|
|
81
|
+
The "window" above is min(model window, `compaction.contextBudget`) when that key is set (≥ 32768): tier one, tier two, the circuit breaker and `context_pressure` all use this soft window, while `contextWindow` and the percentage still show the model's real window. It suits models with a very large window priced by context length — for example `128000` on a 1M-window model makes long sessions compact at a 128k rhythm so the prefix re-read every turn does not grow to hundreds of thousands of tokens; there is no need to set it when the window is small or cache reads are already cheap.
|
|
82
|
+
|
|
81
83
|
- **Estimation**: CJK ideographs / kana / hangul count 1 token each, other characters / 4, images 1,600 each; usage uses `totalTokens` (or input + output + cache reads / writes, output included).
|
|
82
84
|
- **Before the first request** (`prefix`): the system prompt and tool table are written to the session only at the first request, so until then the estimate is taken from what is about to be sent (section texts + tool declaration JSON); a new session is not at 0 the moment it opens. This is for stats only: nothing is written to the session and the request body does not change.
|
|
83
85
|
- **After compaction / context edits** (`estimate`): the last usage no longer describes the current context, so the whole context is estimated until the next request brings new usage; the UI puts `≈` in front of the number.
|