amness 0.0.0-stage → 0.1.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 (233) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -2
  3. package/dist/cli/acpcli.d.ts +7 -0
  4. package/dist/cli/acpcli.js +14 -0
  5. package/dist/cli/amness.d.ts +2 -0
  6. package/dist/cli/amness.js +693 -0
  7. package/dist/cli/benchcli.d.ts +5 -0
  8. package/dist/cli/benchcli.js +160 -0
  9. package/dist/cli/config.d.ts +79 -0
  10. package/dist/cli/config.js +54 -0
  11. package/dist/cli/demo.d.ts +13 -0
  12. package/dist/cli/demo.js +44 -0
  13. package/dist/cli/detect.d.ts +2 -0
  14. package/dist/cli/detect.js +20 -0
  15. package/dist/cli/evalcli.d.ts +15 -0
  16. package/dist/cli/evalcli.js +198 -0
  17. package/dist/cli/format.d.ts +5 -0
  18. package/dist/cli/format.js +55 -0
  19. package/dist/cli/headless.d.ts +2 -0
  20. package/dist/cli/headless.js +169 -0
  21. package/dist/cli/model.d.ts +11 -0
  22. package/dist/cli/model.js +19 -0
  23. package/dist/cli/plugincli.d.ts +2 -0
  24. package/dist/cli/plugincli.js +176 -0
  25. package/dist/cli/schedulecli.d.ts +2 -0
  26. package/dist/cli/schedulecli.js +66 -0
  27. package/dist/cli/state.d.ts +11 -0
  28. package/dist/cli/state.js +19 -0
  29. package/dist/cli/tui/ansi.d.ts +38 -0
  30. package/dist/cli/tui/ansi.js +142 -0
  31. package/dist/cli/tui/app.d.ts +328 -0
  32. package/dist/cli/tui/app.js +1358 -0
  33. package/dist/cli/tui/blocks.d.ts +151 -0
  34. package/dist/cli/tui/blocks.js +128 -0
  35. package/dist/cli/tui/brand.d.ts +9 -0
  36. package/dist/cli/tui/brand.js +16 -0
  37. package/dist/cli/tui/canvas.d.ts +92 -0
  38. package/dist/cli/tui/canvas.js +199 -0
  39. package/dist/cli/tui/cards.d.ts +44 -0
  40. package/dist/cli/tui/cards.js +215 -0
  41. package/dist/cli/tui/clipboard.d.ts +5 -0
  42. package/dist/cli/tui/clipboard.js +29 -0
  43. package/dist/cli/tui/commands.d.ts +10 -0
  44. package/dist/cli/tui/commands.js +649 -0
  45. package/dist/cli/tui/complete.d.ts +39 -0
  46. package/dist/cli/tui/complete.js +113 -0
  47. package/dist/cli/tui/custom.d.ts +9 -0
  48. package/dist/cli/tui/custom.js +34 -0
  49. package/dist/cli/tui/diffview.d.ts +25 -0
  50. package/dist/cli/tui/diffview.js +130 -0
  51. package/dist/cli/tui/editor.d.ts +60 -0
  52. package/dist/cli/tui/editor.js +230 -0
  53. package/dist/cli/tui/full.d.ts +86 -0
  54. package/dist/cli/tui/full.js +630 -0
  55. package/dist/cli/tui/keys.d.ts +31 -0
  56. package/dist/cli/tui/keys.js +134 -0
  57. package/dist/cli/tui/markdown.d.ts +37 -0
  58. package/dist/cli/tui/markdown.js +155 -0
  59. package/dist/cli/tui/mentions.d.ts +15 -0
  60. package/dist/cli/tui/mentions.js +48 -0
  61. package/dist/cli/tui/modal.d.ts +26 -0
  62. package/dist/cli/tui/modal.js +107 -0
  63. package/dist/cli/tui/overlays.d.ts +98 -0
  64. package/dist/cli/tui/overlays.js +237 -0
  65. package/dist/cli/tui/pager.d.ts +15 -0
  66. package/dist/cli/tui/pager.js +66 -0
  67. package/dist/cli/tui/pricing.d.ts +9 -0
  68. package/dist/cli/tui/pricing.js +23 -0
  69. package/dist/cli/tui/rich.d.ts +16 -0
  70. package/dist/cli/tui/rich.js +72 -0
  71. package/dist/cli/tui/screen.d.ts +30 -0
  72. package/dist/cli/tui/screen.js +62 -0
  73. package/dist/cli/tui/session.d.ts +77 -0
  74. package/dist/cli/tui/session.js +103 -0
  75. package/dist/cli/tui/sidebar.d.ts +84 -0
  76. package/dist/cli/tui/sidebar.js +114 -0
  77. package/dist/cli/tui/text.d.ts +15 -0
  78. package/dist/cli/tui/text.js +94 -0
  79. package/dist/cli/tui/theme.d.ts +59 -0
  80. package/dist/cli/tui/theme.js +148 -0
  81. package/dist/cli/tui/themes.d.ts +56 -0
  82. package/dist/cli/tui/themes.js +80 -0
  83. package/dist/cli/tui/tokens.d.ts +39 -0
  84. package/dist/cli/tui/tokens.js +71 -0
  85. package/dist/cli/tui/view.d.ts +60 -0
  86. package/dist/cli/tui/view.js +178 -0
  87. package/dist/cli/ui.d.ts +34 -0
  88. package/dist/cli/ui.js +224 -0
  89. package/dist/src/acp.d.ts +28 -0
  90. package/dist/src/acp.js +111 -0
  91. package/dist/src/agents.d.ts +23 -0
  92. package/dist/src/agents.js +71 -0
  93. package/dist/src/anthropic.d.ts +30 -0
  94. package/dist/src/anthropic.js +91 -0
  95. package/dist/src/approver.d.ts +15 -0
  96. package/dist/src/approver.js +40 -0
  97. package/dist/src/bench.d.ts +67 -0
  98. package/dist/src/bench.js +201 -0
  99. package/dist/src/bundle.d.ts +36 -0
  100. package/dist/src/bundle.js +55 -0
  101. package/dist/src/chaos.d.ts +15 -0
  102. package/dist/src/chaos.js +26 -0
  103. package/dist/src/code.d.ts +187 -0
  104. package/dist/src/code.js +590 -0
  105. package/dist/src/codeindex.d.ts +65 -0
  106. package/dist/src/codeindex.js +300 -0
  107. package/dist/src/deep.d.ts +62 -0
  108. package/dist/src/deep.js +177 -0
  109. package/dist/src/diff.d.ts +8 -0
  110. package/dist/src/diff.js +74 -0
  111. package/dist/src/dynamic.d.ts +42 -0
  112. package/dist/src/dynamic.js +133 -0
  113. package/dist/src/embed.d.ts +8 -0
  114. package/dist/src/embed.js +25 -0
  115. package/dist/src/evalcases.d.ts +11 -0
  116. package/dist/src/evalcases.js +196 -0
  117. package/dist/src/evals.d.ts +197 -0
  118. package/dist/src/evals.js +340 -0
  119. package/dist/src/gitflow.d.ts +27 -0
  120. package/dist/src/gitflow.js +56 -0
  121. package/dist/src/grade.d.ts +42 -0
  122. package/dist/src/grade.js +96 -0
  123. package/dist/src/guidance.d.ts +15 -0
  124. package/dist/src/guidance.js +68 -0
  125. package/dist/src/hardcases.d.ts +2 -0
  126. package/dist/src/hardcases.js +94 -0
  127. package/dist/src/harness.d.ts +411 -0
  128. package/dist/src/harness.js +725 -0
  129. package/dist/src/hooks.d.ts +44 -0
  130. package/dist/src/hooks.js +116 -0
  131. package/dist/src/images.d.ts +13 -0
  132. package/dist/src/images.js +27 -0
  133. package/dist/src/index.d.ts +57 -0
  134. package/dist/src/index.js +57 -0
  135. package/dist/src/ledger.d.ts +58 -0
  136. package/dist/src/ledger.js +78 -0
  137. package/dist/src/live.d.ts +16 -0
  138. package/dist/src/live.js +80 -0
  139. package/dist/src/lsp.d.ts +31 -0
  140. package/dist/src/lsp.js +147 -0
  141. package/dist/src/memory.d.ts +128 -0
  142. package/dist/src/memory.js +390 -0
  143. package/dist/src/metrics.d.ts +3 -0
  144. package/dist/src/metrics.js +42 -0
  145. package/dist/src/mining.d.ts +49 -0
  146. package/dist/src/mining.js +126 -0
  147. package/dist/src/openai.d.ts +50 -0
  148. package/dist/src/openai.js +307 -0
  149. package/dist/src/parallel.d.ts +66 -0
  150. package/dist/src/parallel.js +135 -0
  151. package/dist/src/patch.d.ts +45 -0
  152. package/dist/src/patch.js +219 -0
  153. package/dist/src/permissions.d.ts +46 -0
  154. package/dist/src/permissions.js +245 -0
  155. package/dist/src/plugins.d.ts +148 -0
  156. package/dist/src/plugins.js +225 -0
  157. package/dist/src/pluginscan.d.ts +8 -0
  158. package/dist/src/pluginscan.js +20 -0
  159. package/dist/src/procs.d.ts +41 -0
  160. package/dist/src/procs.js +94 -0
  161. package/dist/src/profiles.d.ts +36 -0
  162. package/dist/src/profiles.js +61 -0
  163. package/dist/src/progress.d.ts +29 -0
  164. package/dist/src/progress.js +75 -0
  165. package/dist/src/prompts.d.ts +10 -0
  166. package/dist/src/prompts.js +28 -0
  167. package/dist/src/ratelimit.d.ts +34 -0
  168. package/dist/src/ratelimit.js +116 -0
  169. package/dist/src/replay.d.ts +25 -0
  170. package/dist/src/replay.js +99 -0
  171. package/dist/src/review.d.ts +20 -0
  172. package/dist/src/review.js +49 -0
  173. package/dist/src/route.d.ts +9 -0
  174. package/dist/src/route.js +30 -0
  175. package/dist/src/router.d.ts +61 -0
  176. package/dist/src/router.js +126 -0
  177. package/dist/src/sandbox.d.ts +13 -0
  178. package/dist/src/sandbox.js +68 -0
  179. package/dist/src/schedule.d.ts +20 -0
  180. package/dist/src/schedule.js +59 -0
  181. package/dist/src/schema.d.ts +13 -0
  182. package/dist/src/schema.js +36 -0
  183. package/dist/src/secrets.d.ts +10 -0
  184. package/dist/src/secrets.js +41 -0
  185. package/dist/src/shell.d.ts +34 -0
  186. package/dist/src/shell.js +95 -0
  187. package/dist/src/skills.d.ts +20 -0
  188. package/dist/src/skills.js +112 -0
  189. package/dist/src/snapshot.d.ts +20 -0
  190. package/dist/src/snapshot.js +93 -0
  191. package/dist/src/stats.d.ts +24 -0
  192. package/dist/src/stats.js +62 -0
  193. package/dist/src/strategies.d.ts +16 -0
  194. package/dist/src/strategies.js +35 -0
  195. package/dist/src/suites.d.ts +30 -0
  196. package/dist/src/suites.js +112 -0
  197. package/dist/src/trace.d.ts +63 -0
  198. package/dist/src/trace.js +153 -0
  199. package/dist/src/tune.d.ts +38 -0
  200. package/dist/src/tune.js +49 -0
  201. package/dist/src/web.d.ts +19 -0
  202. package/dist/src/web.js +56 -0
  203. package/dist/src/worktree.d.ts +24 -0
  204. package/dist/src/worktree.js +85 -0
  205. package/docs/agent-loop.md +222 -0
  206. package/docs/architecture.md +228 -0
  207. package/docs/bench.md +293 -0
  208. package/docs/budgets.md +185 -0
  209. package/docs/build-your-own-harness.md +424 -0
  210. package/docs/cookbook.md +375 -0
  211. package/docs/development.md +150 -0
  212. package/docs/evaluation.md +450 -0
  213. package/docs/features.md +1320 -0
  214. package/docs/getting-started.md +196 -0
  215. package/docs/headless.md +183 -0
  216. package/docs/images/approval.png +0 -0
  217. package/docs/images/chat.png +0 -0
  218. package/docs/images/daylight.png +0 -0
  219. package/docs/images/loop.png +0 -0
  220. package/docs/images/palette.png +0 -0
  221. package/docs/images/running.png +0 -0
  222. package/docs/images/subagent-done.png +0 -0
  223. package/docs/images/subagent-running.png +0 -0
  224. package/docs/images/welcome.png +0 -0
  225. package/docs/index.md +25 -0
  226. package/docs/library.md +276 -0
  227. package/docs/plugins.md +691 -0
  228. package/docs/providers.md +175 -0
  229. package/docs/safety.md +237 -0
  230. package/docs/usage.md +348 -0
  231. package/docs/why.md +155 -0
  232. package/docs/writing-guide.md +39 -0
  233. package/package.json +72 -4
@@ -0,0 +1,56 @@
1
+ import { defineTool } from "./harness.js";
2
+ const decode = (s) => s.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;|&#39;/g, "'").replace(/&nbsp;/g, " ");
3
+ const strip = (s) => decode(s.replace(/<[^>]+>/g, "")).replace(/\s+/g, " ").trim();
4
+ /** Tavily (needs TAVILY_API_KEY): returns clean snippets built for agents. */
5
+ export const tavily = (key = process.env.TAVILY_API_KEY ?? "") => async (query, n) => {
6
+ const r = await fetch("https://api.tavily.com/search", {
7
+ method: "POST", headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
8
+ body: JSON.stringify({ query, max_results: n }), signal: AbortSignal.timeout(20_000),
9
+ });
10
+ if (!r.ok)
11
+ throw new Error(`tavily HTTP ${r.status}`);
12
+ return ((await r.json()).results ?? []).map((x) => ({ title: x.title, url: x.url, snippet: x.content }));
13
+ };
14
+ /** Keyless fallback: scrapes DuckDuckGo's HTML endpoint. Fragile by nature; prefer a real provider in production. */
15
+ export const duckduckgo = async (query, n) => {
16
+ const r = await fetch("https://html.duckduckgo.com/html/?q=" + encodeURIComponent(query), { headers: { "user-agent": "Mozilla/5.0 (amness)" }, signal: AbortSignal.timeout(20_000) });
17
+ if (!r.ok)
18
+ throw new Error(`duckduckgo HTTP ${r.status}`);
19
+ const html = await r.text();
20
+ const hits = [];
21
+ const re = /<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
22
+ for (let m; (m = re.exec(html)) && hits.length < n;) {
23
+ const href = decode(m[1]);
24
+ const real = new URL(href, "https://duckduckgo.com").searchParams.get("uddg") ?? href;
25
+ hits.push({ title: strip(m[2]), url: real, snippet: strip(m[3]) });
26
+ }
27
+ return hits;
28
+ };
29
+ const PRIVATE = /^(localhost|127\.|10\.|192\.168\.|169\.254\.|172\.(1[6-9]|2\d|3[01])\.|0\.|\[?::1)/;
30
+ /** Fetch a URL and reduce it to readable text. Refuses non-http(s) and local/private hosts. */
31
+ export async function fetchText(url, max = 20_000, offset = 0) {
32
+ const u = new URL(url);
33
+ if (!/^https?:$/.test(u.protocol) || PRIVATE.test(u.hostname))
34
+ throw new Error("refusing to fetch " + url);
35
+ const r = await fetch(u, { headers: { "user-agent": "Mozilla/5.0 (amness)" }, signal: AbortSignal.timeout(20_000), redirect: "follow" });
36
+ if (!r.ok)
37
+ throw new Error(`HTTP ${r.status} for ${url}`);
38
+ const body = await r.text();
39
+ const text = /html/.test(r.headers.get("content-type") ?? "")
40
+ ? strip(body.replace(/<(script|style|noscript|svg|nav|footer|header)[\s\S]*?<\/\1>/gi, " ").replace(/<(\/?(p|div|br|li|h\d|tr|section|article))[^>]*>/gi, "\n")).replace(/ ?\n ?/g, "\n")
41
+ : body;
42
+ const slice = text.slice(offset, offset + max);
43
+ return slice + (text.length > offset + max ? `\n[truncated; ${text.length - offset - max} more chars. Call again with offset=${offset + max}]` : "");
44
+ }
45
+ export const webTools = (search = process.env.TAVILY_API_KEY ? tavily() : duckduckgo) => [
46
+ defineTool({
47
+ name: "web_search", untrusted: true, description: "Search the web. Returns title, url, snippet per hit. Use specific queries; run several varied ones.",
48
+ schema: { type: "object", required: ["query"], properties: { query: { type: "string" }, n: { type: "integer" } } },
49
+ run: async ({ query, n }) => (await search(query, Math.min(n ?? 6, 10))).map((h, i) => `${i + 1}. ${h.title}\n ${h.url}\n ${h.snippet}`).join("\n") || "no results; rephrase the query",
50
+ }),
51
+ defineTool({
52
+ name: "fetch_url", untrusted: true, description: "Fetch a web page as readable text (20k chars per call; use offset to page).",
53
+ schema: { type: "object", required: ["url"], properties: { url: { type: "string" }, offset: { type: "integer" } } },
54
+ run: ({ url, offset }) => fetchText(url, 20_000, offset ?? 0),
55
+ }),
56
+ ];
@@ -0,0 +1,24 @@
1
+ export type Worktree = {
2
+ dir: string; /** the repository it was made from */
3
+ repo: string;
4
+ };
5
+ /** True inside a git repository with at least one commit. */
6
+ export declare function isRepo(cwd: string): Promise<boolean>;
7
+ /** A detached worktree of HEAD in a fresh temp folder. Uncommitted work in the main tree is NOT included (the isolated agent starts from the last commit). */
8
+ export declare function createWorktree(repo: string): Promise<Worktree>;
9
+ /** Everything changed in the worktree relative to its starting commit, as one unified diff (new files included). */
10
+ export declare function worktreeDiff(w: Worktree): Promise<string>;
11
+ export declare function removeWorktree(w: Worktree): Promise<void>;
12
+ /** Apply a unified diff to a working tree (atomically: `git apply --check` first). Returns what failed, or undefined. */
13
+ export declare function applyDiff(cwd: string, diff: string): Promise<string | undefined>;
14
+ /** Undo a diff that was applied with applyDiff. */
15
+ export declare function revertDiff(cwd: string, diff: string): Promise<string | undefined>;
16
+ /** A named, persistent worktree on its own branch (`amness/<name>`) inside the repository's `.amness/worktrees/`, for a whole working session. */
17
+ export declare function createSessionWorktree(repo: string, name: string): Promise<{
18
+ dir: string;
19
+ branch: string;
20
+ repo: string; /** the commit it branched from */
21
+ base: string;
22
+ }>;
23
+ /** True when nothing has changed in `dir` (no modified, staged or untracked files) and the branch has no commits beyond `base`. */
24
+ export declare function worktreeIsUntouched(dir: string, base: string): Promise<boolean>;
@@ -0,0 +1,85 @@
1
+ // Git worktrees: an isolated copy of the repository at HEAD, so an agent can edit freely without touching the working tree. The result comes back as a diff.
2
+ import { execFile } from "node:child_process";
3
+ import { mkdtempSync, rmSync } from "node:fs";
4
+ import { tmpdir } from "node:os";
5
+ import { join } from "node:path";
6
+ const git = (cwd, args, input) => new Promise((res, rej) => { const p = execFile("git", args, { cwd, maxBuffer: 64 << 20 }, (e, out, err) => (e ? rej(new Error((err || e.message).trim().split("\n")[0])) : res(out))); if (input !== undefined) {
7
+ p.stdin?.end(input);
8
+ } });
9
+ /** True inside a git repository with at least one commit. */
10
+ export async function isRepo(cwd) { try {
11
+ await git(cwd, ["rev-parse", "--verify", "HEAD"]);
12
+ return true;
13
+ }
14
+ catch {
15
+ return false;
16
+ } }
17
+ /** A detached worktree of HEAD in a fresh temp folder. Uncommitted work in the main tree is NOT included (the isolated agent starts from the last commit). */
18
+ export async function createWorktree(repo) {
19
+ const root = (await git(repo, ["rev-parse", "--show-toplevel"])).trim(), dir = mkdtempSync(join(tmpdir(), "amness-wt-"));
20
+ try {
21
+ await git(root, ["worktree", "add", "--detach", dir, "HEAD"]);
22
+ }
23
+ catch (e) {
24
+ rmSync(dir, { recursive: true, force: true });
25
+ throw e;
26
+ }
27
+ return { dir, repo: root };
28
+ }
29
+ /** Everything changed in the worktree relative to its starting commit, as one unified diff (new files included). */
30
+ export async function worktreeDiff(w) {
31
+ await git(w.dir, ["add", "-A", "-N", "."]).catch(() => { }); // make new files show up in the diff without staging content
32
+ return (await git(w.dir, ["diff", "--no-color", "--no-ext-diff", "HEAD"])).trimEnd();
33
+ }
34
+ export async function removeWorktree(w) {
35
+ await git(w.repo, ["worktree", "remove", "--force", w.dir]).catch(() => { });
36
+ rmSync(w.dir, { recursive: true, force: true });
37
+ await git(w.repo, ["worktree", "prune"]).catch(() => { });
38
+ }
39
+ /** Apply a unified diff to a working tree (atomically: `git apply --check` first). Returns what failed, or undefined. */
40
+ export async function applyDiff(cwd, diff) {
41
+ if (!diff.trim())
42
+ return undefined;
43
+ try {
44
+ await git(cwd, ["apply", "--check", "--whitespace=nowarn", "-"], diff + "\n");
45
+ await git(cwd, ["apply", "--whitespace=nowarn", "-"], diff + "\n");
46
+ return undefined;
47
+ }
48
+ catch (e) {
49
+ return e.message;
50
+ }
51
+ }
52
+ /** Undo a diff that was applied with applyDiff. */
53
+ export async function revertDiff(cwd, diff) {
54
+ if (!diff.trim())
55
+ return undefined;
56
+ try {
57
+ await git(cwd, ["apply", "-R", "--whitespace=nowarn", "-"], diff + "\n");
58
+ return undefined;
59
+ }
60
+ catch (e) {
61
+ return e.message;
62
+ }
63
+ }
64
+ /** A named, persistent worktree on its own branch (`amness/<name>`) inside the repository's `.amness/worktrees/`, for a whole working session. */
65
+ export async function createSessionWorktree(repo, name) {
66
+ const root = (await git(repo, ["rev-parse", "--show-toplevel"])).trim(), clean = name.replace(/[^\w.-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 40);
67
+ if (!clean)
68
+ throw new Error("the worktree needs a name made of letters, numbers, . _ or -");
69
+ const dir = join(root, ".amness", "worktrees", clean), branch = `amness/${clean}`;
70
+ const base = (await git(root, ["rev-parse", "HEAD"])).trim();
71
+ await git(root, ["worktree", "add", "-b", branch, dir, "HEAD"]);
72
+ return { dir, branch, repo: root, base };
73
+ }
74
+ /** True when nothing has changed in `dir` (no modified, staged or untracked files) and the branch has no commits beyond `base`. */
75
+ export async function worktreeIsUntouched(dir, base) {
76
+ try {
77
+ if ((await git(dir, ["status", "--porcelain"])).trim())
78
+ return false;
79
+ const ahead = (await git(dir, ["rev-list", "--count", `${base}..HEAD`]).catch(() => "0")).trim();
80
+ return ahead === "0";
81
+ }
82
+ catch {
83
+ return false;
84
+ }
85
+ }
@@ -0,0 +1,222 @@
1
+ # The agent loop, caching and where the design comes from
2
+
3
+ This page explains how Amness runs one turn of work, which checks keep a long run safe, and where the ideas come from.
4
+
5
+ In this page you will learn:
6
+
7
+ - what happens, step by step, from your message to the final answer
8
+ - which guards stop an agent that is stuck, and how to tune them
9
+ - how the claims ledger checks the agent's final summary
10
+ - how Amness keeps the prompt cache valid, and which sources the design follows
11
+
12
+ ## Words used on this page
13
+
14
+ | Term | Meaning |
15
+ |---|---|
16
+ | Token | A small piece of text that a model reads or writes. One token is about four letters. |
17
+ | Context window | The most text a model can read in one request. The whole conversation must fit in it. |
18
+ | Cache | A copy of the start of your request that the provider keeps for a short time. Cached input costs much less than fresh input. |
19
+ | Guard | A rule in the loop that watches for a problem (a repeated call, no progress) and reacts. |
20
+ | Ledger | A record of what really happened: files edited, commands run, exit codes. |
21
+ | Hook | A command of yours that runs at a fixed point of the loop and can block a step or add text. |
22
+ | Subagent | A second agent that the main agent starts for one job. It has its own fresh context. |
23
+ | Budget | A limit on tokens (and sometimes time) for a run. |
24
+
25
+ ## See how one turn works
26
+
27
+ **What it is.** A *turn* is one pass of the loop in `run()` in `src/harness.ts`. The agent asks the model what to do, runs the tools the model asks for, and repeats until the model calls `finish` or gives a plain answer.
28
+
29
+ **Why you would read this.** You can find out why a run stopped, or which guard sent a message to the model.
30
+
31
+ ```mermaid
32
+ flowchart LR
33
+ A["steer<br/>messages typed meanwhile"] --> B["budget<br/>check"] --> C["compact<br/>supersede · elide · handoff"] --> D["model call<br/>streaming"]
34
+ D -->|tool calls| E["validate · approve ·<br/>schedule: reads in parallel,<br/>writes one at a time"]
35
+ E --> F{"all failed?"}
36
+ F -->|"3×: reflect, 6×: stop"| A
37
+ F -->|no| A
38
+ D -->|finish| G{"verify gate<br/>npm test"}
39
+ G -->|rejected + feedback| A
40
+ G -->|passes| H(["done"])
41
+ ```
42
+
43
+ Each turn does these things in this order: steer, budget check, compaction, model call, tool calls, then the verification gate when the model calls `finish`.
44
+
45
+ ### Example: one turn from your message to the answer
46
+
47
+ You type `Fix the failing test in src/math.ts` in the chat and press Enter. The agent edits one file, runs the tests, and finishes. The numbered steps below show where each part happens.
48
+
49
+ 1. **You press Enter.** `handleKey()` in `cli/tui/app.ts` calls `submit()`. If the agent is already working, `submit()` puts your text in a queue instead. The loop reads that queue in step 5.
50
+ 2. **The UI starts the turn.** `submit()` calls `dispatch()`, which calls `turn()` in `cli/tui/app.ts`. `turn()` expands `@file` mentions and resets the token budget (cap from your config, 60 minutes). It then calls `agent.chat()`.
51
+ 3. **The agent prepares the request.** `chat()` in `src/code.ts` runs the `UserPromptSubmit` hook (`promptHooks`). It adds acceptance criteria when the reviewer is on (`withContract`), adds relevant memory notes, and builds the message list. It then calls `run()` of the harness.
52
+ 4. **The loop starts a step.** `run()` in `src/harness.ts` first checks the abort signal.
53
+ 5. **Steer.** `run()` calls `o.inbox()`. In the chat this is `drainQueue()` in `cli/tui/app.ts`. Each queued message joins the conversation.
54
+ 6. **Budget.** `run()` reads `Budget.fraction` (class `Budget` in `src/harness.ts`). At 50%, 75% and 90% it appends a note. At 95% or more, or when another step would not fit, it makes a final report call and stops.
55
+ 7. **Compaction.** If the transcript is longer than `contextBudget` (default 60,000 characters), `run()` calls `supersede()` and then `compact()`. Claude models skip this step (see the cache section). Before this, `makeHandoff()` runs if a context rollover is due.
56
+ 8. **Model call.** `run()` checks for a cache break (`firstCacheBreak()`), runs the `PreModel` hook, and calls the model. Text streams to the UI through `onText`.
57
+ 9. **Tool calls.** The model asks for `edit_file` and `sh`. Inside `run()`, `exec()` checks the arguments (`validate()` in `src/schema.ts`), runs the `PreToolUse` hook, checks repeated calls (`canonicalCall()`, `findCycle()`), and asks the permission rules and you for approval. Then it runs the tool. The edit and the shell command do not run at the same time, because they can change state.
58
+ 10. **Results.** `condense()` shortens the output. `redact()` in `src/secrets.ts` masks secrets. The `PostToolUse` hook runs. For an edit, `Ledger.onEdit()` in `src/ledger.ts` records it. For a shell command, `Ledger.onCommand()` records the exit code.
59
+ 11. **Checks after the tools.** `run()` updates the read streak, `ProgressTracker.observe()` in `src/progress.ts`, and the count of turns where every call failed.
60
+ 12. **The next step.** The model sees the test result. It calls `finish` with a summary.
61
+ 13. **The finish gate.** `exec()` runs the `Stop` hook, then `Ledger.check()`, then the `verify` command (built in `createCodeAgent()` in `src/code.ts`, run with `runShell()`), then the reviewer if it is on. If all pass, the loop emits an `audit` event and a `finish` event and returns.
62
+ 14. **The answer reaches you.** `chat()` calls `memory.endTurn()` and returns. `turn()` saves the messages. Each loop event passed through `onEvent()` in `cli/tui/app.ts` into the block store (`cli/tui/blocks.ts`), and `draw()` in `cli/tui/full.ts` showed it on screen.
63
+
64
+ **Limits and common problems.**
65
+
66
+ - The exact step numbers differ from run to run. A plain-text answer without `finish` also ends a run, and then `finished` is `false` in the result.
67
+ - The loop stops after `maxSteps` (the code agent uses 60 in chat and 40 otherwise, unless you set it).
68
+ - Not every event appears in every run. Guards only speak when they fire.
69
+
70
+ **Related pages.** [Architecture](architecture.md), [Library](library.md), [Why: what Amness checks](why.md).
71
+
72
+ ## Know what keeps long runs reliable
73
+
74
+ ### Scheduling of tool calls
75
+
76
+ **What it is.** Read-only calls and subagent calls in one turn run at the same time, up to 5 (`MAX_PARALLEL` in `src/harness.ts`). Anything that can change state runs alone, in the order the model gave.
77
+
78
+ **How to use it.** Mark your own tool `readOnly: true` or `parallel: true` in `defineTool()`. A tool can also set `concurrent`, a function that decides per call.
79
+
80
+ **Limits.** The `task` tool (subagents) has its own limit of 3 running at the same time (`taskSlots` in `src/code.ts`). The rule that writing tools never overlap is tested in `test/loop.test.ts`. The limit of 3 subagents is not covered by a test that I found.
81
+
82
+ ### Compaction in stages, cheapest first
83
+
84
+ **What it is.** Compaction makes the conversation shorter so it fits in the context window. Amness works in stages and starts with the cheapest:
85
+
86
+ 1. **Condense each result as it arrives** (`condense()`). It removes terminal codes and progress redraws and collapses repeated lines. If the output is still longer than 6,000 characters, it keeps the first 40%, the last 30%, and up to 25 error-like lines from the middle.
87
+ 2. **Drop repeated or expired output** (`supersede()`). An *expired* read is a read of a file that was edited afterwards. Both kinds are replaced by a short note.
88
+ 3. **Elide old tool output** (`compact()`). Output over 300 characters in older messages is replaced by a note. The last 6 messages stay intact. The cut goes down to 70% of `contextBudget` in one go.
89
+ 4. **Hand off to a fresh context** (`makeHandoff()`). The model writes a note, and the run continues from the task plus that note. For the code agent, this happens when the last prompt passes 120,000 tokens or the transcript passes 500,000 characters. You can change this with the `rollover` option.
90
+
91
+ **Why.** Every change to old messages breaks the provider's cache from that point on. One large cut every few turns costs less than a small cut every turn.
92
+
93
+ **Example.** Type `/loop` in the chat. It lists each model turn, the guards that fired, and how much was trimmed.
94
+
95
+ **Limits.** Stages 2 and 3 do not run for models that set `appendOnly` (Claude). Stage 4 works for every provider. A hook on `PreCompact` can veto stages 2 and 3.
96
+
97
+ ### Post-edit check
98
+
99
+ **What it is.** A fast command, such as a typecheck, that runs after every edit.
100
+
101
+ **How to use it.**
102
+
103
+ ```
104
+ /check npm run typecheck
105
+ ```
106
+
107
+ In headless mode, use `amness run --check "npm run typecheck" "your task"`. In the library, use the `check` option.
108
+
109
+ **Example.** Only a failure is shown to the model. It is appended to the edit result as `[post-edit check ... failed; fix this before moving on]`. The model sees the mistake one step after it makes it.
110
+
111
+ ### Guards
112
+
113
+ A *guard* watches the loop. You can switch each one off with the `off` option (see the list of names below).
114
+
115
+ | Guard (`off` name) | What it does |
116
+ |---|---|
117
+ | `loop` | Stops the same call (after normalising it) from running a third time while nothing was edited in between. Running tests again after an edit is fine. It also finds cycles: 2 to 4 different calls that repeat three times, such as an edit flipped back and forth around a test run. |
118
+ | `nudge` | If the model says what it will do ("I'll run the tests") instead of doing it, the loop tells it to act. At most 2 times. |
119
+ | `wrapup` | Warns 3 steps before the step cap. Adds budget notes. Makes the final report. Checks for 12 read-only turns in a row. |
120
+ | `stuck` | After 3 turns in a row where every tool call failed, asks the model to reflect. After 6, stops the run. |
121
+ | `stagnation` | See the next section. |
122
+ | `compact`, `condense`, `schedule`, `ledger`, `review`, `redact` | Switch off the matching feature. |
123
+
124
+ `off` takes a list of these names. The type is `Guard` in `src/harness.ts`. With `amness replay <trace.jsonl> --off loop,stuck` you can replay a recorded session with some guards off. See [Evaluating the harness](evaluation.md).
125
+
126
+ ### Stagnation is judged from signals you can check
127
+
128
+ **What it is.** A loop that keeps acting while nothing measurable improves gets a structured reflection after 8 such turns and a clean stop after 12. The code is `ProgressTracker` in `src/progress.ts`.
129
+
130
+ "Improves" means one of these:
131
+
132
+ - The count of failing tests, errors or lint problems reaches a new low. Amness reads the count from output of Jest, pytest, Go, Cargo, the Node test runner, `tsc` and `eslint`, among others (`countFailures()`).
133
+ - A file is edited for the first time.
134
+ - An edit adds a change that is not the undo of an earlier edit.
135
+ - The agent reads something new (counted at most 15 times per run).
136
+
137
+ The model's own confidence counts for nothing.
138
+
139
+ **How to tune it.** Set `stagnationTurns` (default 8; the stop comes at 8 plus half of 8, which is 12). Set it to `0`, or use `off: ["stagnation"]`, to switch it off. Tested in `test/progress.test.ts`.
140
+
141
+ **Limits.** The count is read from text output. If a tool prints failures in a format Amness does not know, it falls back to the exit code and counts 1 failure.
142
+
143
+ ### The claims ledger checks the final summary
144
+
145
+ **What it is.** The ledger (`Ledger` in `src/ledger.ts`) records what really happened: which files were edited, which commands ran, and their exit codes. When the model calls `finish`, `Ledger.check()` compares the summary with this record.
146
+
147
+ **What it checks.**
148
+
149
+ - "All tests pass" needs a passing test run after the last edit.
150
+ - "The build is clean" needs a successful build, typecheck or lint after the last edit. A passing test run also counts.
151
+ - A file named as changed must have been edited. This check is skipped if a shell command wrote files (`sed -i`, `>`, `mv` and similar).
152
+ - If your `verify` command already gates `finish`, the test and build claims are not checked a second time.
153
+
154
+ A mismatch is rejected with the reason. This happens at most twice, so it cannot lock the loop. After that, the loop accepts the summary and records a `Ledger` hook event.
155
+
156
+ **Example.** Accepted runs end with an `audit` event: files changed, whether tests passed after the final edit, and notes. A note appears when test files changed although your task did not mention tests, when more than 8 files changed, or when no test command ran. Tested in `test/ledger.test.ts`.
157
+
158
+ **Limits.** The checks use word patterns in English. A summary in another language, or an unusual test command, may not match. You can tell the ledger your own test command through `verify` or `check`. Agents that judge their own work accept plausible progress as real progress ([When Do Agent Loops Mistake Stagnation for Progress?](https://arxiv.org/abs/2607.25152)). The ledger is a cheap outside check.
159
+
160
+ ### Independent reviewer (opt-in)
161
+
162
+ **What it is.** A second agent in a fresh context. It writes acceptance criteria before the work (at most 7) and checks the diff before `finish` is accepted.
163
+
164
+ **Why.** Builders overpraise their own work, and a separate judge is easier to make sceptical than a builder is to make self-critical ([Anthropic's long-running harness write-up](https://www.anthropic.com/engineering/harness-design-long-running-apps)).
165
+
166
+ **How to use it.** Type `/reviewer on` in the chat, or add `--review` to `amness run`.
167
+
168
+ **Details.**
169
+
170
+ - The reviewer can read files and run only the project's own test, build and lint commands. It cannot edit.
171
+ - It runs after the ledger and after your `verify` command.
172
+ - Findings go back to the builder. After two rounds the work is accepted and flagged. A verdict that cannot be used never blocks.
173
+ - It can use another model: `review: { model }`.
174
+
175
+ **Limits.** It costs extra model calls. The number per task is not verified here. It is off by default. To measure it, run `amness eval --variants full,full+review`. See [Evaluating the harness](evaluation.md).
176
+
177
+ ### Done means verified
178
+
179
+ `finish` passes through your `verify` gate (for example your tests or build). It is rejected with feedback until the command exits with 0.
180
+
181
+ ## Understand the prompt cache
182
+
183
+ **What it is.** Cached input costs a fraction of fresh input. Amness keeps the start of each request identical from turn to turn so the cache stays valid.
184
+
185
+ **What Amness does.**
186
+
187
+ - **Append-only by default.** Old messages change only when the window needs it, in one cut well below the budget. New information (steering messages, memory notes) goes at the end.
188
+ - **Claude is append-only always.** The Claude adapter sets `appendOnly`, so the harness never edits past turns for Claude (`src/anthropic.ts`, `src/harness.ts`). Context rollover still works.
189
+ - **Stable request shape.** Tool definitions are sorted by name. The memory index is added once per conversation. OpenAI requests carry a `prompt_cache_key` and ask for `24h` cache retention. If a model refuses these fields, the adapter drops them (`src/openai.ts`).
190
+ - **Keep-alive (opt-in).** Claude drops a cached prefix after about 5 idle minutes. `/keepwarm on` re-sends the conversation with a 16-token output cap after 270 seconds of idle time, at most 3 times per break. This costs a cache read instead of a cold read. It is off by default because it spends tokens while you are away. It applies to Claude only.
191
+ - **You can see it.** `/loop` marks every turn where the start of the request changed (for example "earlier history was rewritten at message 14" or "the tool list changed"). The usage panel shows the cache hit rate. `amness eval` reports it per variant.
192
+
193
+ **Limits and common problems.**
194
+
195
+ - A change in the tool list, such as a mode switch or a loaded tool, breaks the whole cache once.
196
+ - The cost effect of these choices is from the papers below. Amness has not measured it on a real model yet.
197
+
198
+ **Related pages.** [Budgets and subagents](budgets.md), [Providers and models](providers.md).
199
+
200
+ ## Learn where these choices come from
201
+
202
+ Recent work on coding-agent loops points the same way. The loop follows it where this can be done without training a model.
203
+
204
+ - [An Empirical Study of Harness Design for Coding Agents](https://arxiv.org/abs/2609.20804) (2026): cheap rule-based elision before any LLM summary gave the lowest cost. A recall tool for elided content was almost never used, so Amness has none. Stuck detection and light post-edit checks are cheap ways to stop wasted turns.
205
+ - [AgentDiet](https://arxiv.org/abs/2509.23586) (trajectory reduction): removing useless, redundant and *expired* information cut input tokens by 40-60% with unchanged results. This is the source of the expired-read and repeated-call rules.
206
+ - [CoACT](https://arxiv.org/abs/2607.02911) and [SWE-Pruner](https://arxiv.org/abs/2601.16746): reads use most of the tokens. Compression must keep what the next action needs, and earlier messages stay untouched so the cache stays valid. Amness uses rules for this. The papers use trained compressors, which would add a dependency.
207
+ - [Don't Break the Cache](https://arxiv.org/abs/2601.06007) (prompt caching for long-horizon agents): caching cut API cost by 41-80% across OpenAI, Anthropic and Google, but naive full-context caching can hurt. Put dynamic content at the end, avoid dynamic tool lists, and keep dynamic tool results out of the cached prefix. [Keeping the Cache Warm Pays](https://arxiv.org/abs/2607.19214) motivates the longer OpenAI retention and the opt-in Claude keep-alive.
208
+ - Memory: [Infini Memory](https://arxiv.org/abs/2606.10677) (plain-text topic documents, lexical retrieval, merge and split maintenance, recency resolves contradictions; its structural maintenance mattered most in its ablations), [REALM](https://arxiv.org/abs/2609.16053) (retrieval feeds back into how memory is organised, which is why Amness records use and ranks by it) and the strategic-forgetting work ([SF-AMS](https://arxiv.org/abs/2607.22562), [FSFM](https://arxiv.org/abs/2604.20300)). Amness uses plain files: word-based retrieval plus optional embeddings, merge maintenance (`/memory consolidate`), a light note graph, ranking by outcome, and forgetting you can undo. "Learning" here means simple counts (wins and losses per note), not a trained model. There is no graph database.
209
+ - A [source-code study of eleven coding agents](https://arxiv.org/html/2609.00006v1): read-only tools run at the same time, tools that change things run one at a time.
210
+
211
+ The percentages above are the papers' results on their own benchmarks. They are not measurements of Amness. Use the eval runner (`evaluate`) with the guards switched on and off to measure them on your own tasks. Whether a source was read in full or only seen as a search snippet is marked on [Budgets and subagents](budgets.md).
212
+
213
+ ## Know what the budget does to the loop
214
+
215
+ The loop also watches the token budget: notes at 50%, 75% and 90%, a final report instead of a bare stop, a check for 12 read-only turns, and slices for subagents. These are explained, with sources, in [Budgets and subagents](budgets.md). They are tested with scripted models. Whether they reduce exhausted runs on real tasks is not measured.
216
+
217
+ ## Next steps
218
+
219
+ - [Budgets and subagents](budgets.md): choose a budget and read the budget notes.
220
+ - [Architecture](architecture.md): see which file does what.
221
+ - [Evaluating the harness](evaluation.md): measure a guard on your own tasks.
222
+ - [Plugins](plugins.md): add your own loop rules.
@@ -0,0 +1,228 @@
1
+ # Architecture
2
+
3
+ Amness has three layers: a terminal interface, an agent, and a loop called the harness. This page shows how they connect and which file does what.
4
+
5
+ In this page you will learn:
6
+
7
+ - how the layers depend on each other
8
+ - which file in `src/` and `cli/` you should open for a given job
9
+ - how the full-screen interface draws a frame
10
+
11
+ ## Find your way around the layers
12
+
13
+ **What it is.** The layers depend on each other in one direction only. The **UI** (`cli/`) drives the **agent** (`src/code.ts`, `src/deep.ts`). The agent is built on the **harness** (`src/harness.ts`). Models are plain functions that you pass in from outside.
14
+
15
+ **Why it matters.** The harness and the agents do not import the UI. So everything in `src/` works as a library. See [Use it as a library](library.md).
16
+
17
+ ```
18
+ you (keyboard, mouse)
19
+ |
20
+ +-----v--------------------------------------------+
21
+ | UI layer cli/ |
22
+ | amness.ts -> tui/app.ts -> full.ts / screen.ts
23
+ +-----+--------------------------------------------+
24
+ | prompts go down, events come up
25
+ +-----v--------------------------------------------+
26
+ | Agent layer src/code.ts src/deep.ts |
27
+ | coding agent, tools, subagents, memory |
28
+ +-----+--------------------------------------------+
29
+ |
30
+ +-----v--------------------------------------------+
31
+ | Harness layer src/harness.ts |
32
+ | run(): steer, budget, compact, model, tools, |
33
+ | verify. Guards: ledger.ts, progress.ts |
34
+ +-----+--------------------------------------------+
35
+ | model(messages, tools) -> message
36
+ +-----v--------------------------------------------+
37
+ | Model adapters src/openai.ts src/anthropic.ts|
38
+ | or any function with the same shape |
39
+ +--------------------------------------------------+
40
+ ```
41
+
42
+ The same picture with more detail:
43
+
44
+ ```mermaid
45
+ flowchart TB
46
+ subgraph UI["cli/ : terminal interface"]
47
+ direction TB
48
+ entry["amness.ts<br/>chat · run · menu · demo"]
49
+ app["tui/app.ts<br/>App: input, modes, approvals,<br/>slash commands, sessions"]
50
+ full["Full-screen view<br/>full.ts · cards · sidebar · modal<br/>canvas · themes · blocks"]
51
+ inline["Inline view<br/>screen.ts · view.ts"]
52
+ entry --> app
53
+ app --> full
54
+ app --> inline
55
+ end
56
+ subgraph AGENT["src/ : agents"]
57
+ code["code.ts<br/>coding agent + file/shell tools"]
58
+ deep["deep.ts<br/>research agent + subagents"]
59
+ end
60
+ subgraph CORE["harness.ts : the loop"]
61
+ loop["run(): steer → budget → compact →<br/>model → schedule tools → verify"]
62
+ reg["Registry<br/>deferred tools · MCP"]
63
+ end
64
+ subgraph MODELS["model adapters"]
65
+ oa["openai.ts<br/>Responses + Chat Completions"]
66
+ an["anthropic.ts<br/>Claude"]
67
+ local["any (messages, tools) → message"]
68
+ end
69
+ subgraph STATE["state"]
70
+ mem["memory.ts<br/>ranked, learning notes<br/>embed.ts: optional vectors"]
71
+ bundle["bundle.ts<br/>portable sessions"]
72
+ sess["cli/tui/session.ts<br/>chats + checkpoints"]
73
+ end
74
+ app -->|"events up, prompts down"| code
75
+ code --> loop
76
+ deep --> loop
77
+ loop --> reg
78
+ loop -->|"model(messages, tools)"| oa
79
+ loop --> an
80
+ loop --> local
81
+ code --> mem
82
+ app --> sess
83
+ app --> bundle
84
+ ev["evals.ts + stats.ts<br/>variants, bootstrap, sign test"] --> code
85
+ evcli["cli/evalcli.ts<br/>amness eval"] --> ev
86
+ ```
87
+
88
+ **Limits.** The diagrams show the main paths only. Many small files in `src/` (for example `permissions.ts`, `hooks.ts`, `sandbox.ts`) are called by the agent layer and are not drawn.
89
+
90
+ The runtime has no required packages. The Claude adapter needs the optional package `@anthropic-ai/sdk`.
91
+
92
+ ## Find which file does what
93
+
94
+ The table lists every file in `src/` and `cli/`. The text comes from the first comment lines of each file. Open the file for details.
95
+
96
+ ### `src/` : agent, harness and helpers
97
+
98
+ | File | What it does |
99
+ |---|---|
100
+ | `src/harness.ts` | The agent loop and its guards. Also `Registry` (the tools), `Budget`, `extract()` and the compaction functions. |
101
+ | `src/code.ts` | The coding agent: file and shell tools, the `task` tool for subagents, todos, plan mode, permission modes. |
102
+ | `src/deep.ts` | The research agent with subagents and an in-memory file system. |
103
+ | `src/index.ts` | The library entry point. It re-exports the other modules. |
104
+ | `src/openai.ts` | Adapter for OpenAI and OpenAI-compatible servers (Responses and Chat Completions APIs). |
105
+ | `src/anthropic.ts` | Adapter for Claude through the official Anthropic SDK. |
106
+ | `src/route.ts` | Roles that use different models (explore, compact, review) and a fallback chain when a provider is overloaded. |
107
+ | `src/router.ts` | Per-step routing between a strong and a cheap model, with the prompt-cache cost in the decision. |
108
+ | `src/ratelimit.ts` | A shared gate that keeps requests under the provider's per-minute limits. See [Budgets](budgets.md#rate-limits-requests-and-tokens-per-minute). |
109
+ | `src/ledger.ts` | The claims ledger: records edits and commands and checks the final summary. |
110
+ | `src/progress.ts` | Stagnation detection from objective signals (failing-test counts, new edits). |
111
+ | `src/review.ts` | The independent reviewer: acceptance criteria before the work, a check before it is accepted. |
112
+ | `src/approver.ts` | An optional second opinion on risky calls, from a cheaper model. It can approve but not refuse. |
113
+ | `src/permissions.ts` | Permission rules and the safety floor that holds in every mode. |
114
+ | `src/hooks.ts` | Lifecycle hooks: your commands at fixed points of the loop. |
115
+ | `src/plugins.ts` | Plugins: harness behaviour written as code (tools, hooks, eval tasks, variants). |
116
+ | `src/sandbox.ts` | OS-level sandbox for shell commands (Seatbelt on macOS, Bubblewrap on Linux). |
117
+ | `src/snapshot.ts` | Reversible shell commands: records the working tree as a git tree object before a command runs. |
118
+ | `src/secrets.ts` | Masks secrets in tool output before the model sees it. |
119
+ | `src/shell.ts` | Chooses which shell runs commands (a POSIX shell, including on Windows). |
120
+ | `src/procs.ts` | Background processes: start a server, read its output later, stop it. |
121
+ | `src/schema.ts` | A small JSON-schema checker and a tolerant JSON parser for model output. |
122
+ | `src/patch.ts` | Multi-file patches, applied all or nothing. |
123
+ | `src/diff.ts` | A line diff that makes unified hunks. |
124
+ | `src/lsp.ts` | Language-server diagnostics after edits. |
125
+ | `src/codeindex.ts` | A structural index of the repository: symbols, imports and where symbols are used. |
126
+ | `src/guidance.ts` | Loads project instruction files (`AGENTS.md`, with `CLAUDE.md` and `GEMINI.md` as fallbacks). |
127
+ | `src/skills.ts` | Skills: folders with a `SKILL.md` that the model loads when needed. |
128
+ | `src/agents.ts` | Custom subagents defined as markdown files. |
129
+ | `src/memory.ts` | Memory: ranked notes, merging, a light graph, learning from outcomes, an archive. |
130
+ | `src/embed.ts` | Optional embeddings for memory search. |
131
+ | `src/bundle.ts` | Portable session bundles: handoff note, memory and changed files. |
132
+ | `src/dynamic.ts` | Tools the agent defines as shell commands, and the live tool registry (MCP). |
133
+ | `src/images.ts` | Reads an image file for models that accept images. |
134
+ | `src/web.ts` | Web search and URL fetch tools. |
135
+ | `src/worktree.ts` | Git worktrees: an isolated copy of the repository for an agent. |
136
+ | `src/parallel.ts` | Several agents at once, each in its own worktree (best-of-N and parts). |
137
+ | `src/gitflow.ts` | Review and pull-request helpers around git and the GitHub CLI. |
138
+ | `src/live.ts` | Live view of a run in the browser, on `127.0.0.1` only. |
139
+ | `src/acp.ts` | Agent Client Protocol server, so an editor can host Amness. |
140
+ | `src/schedule.ts` | Recurring headless runs through cron lines. |
141
+ | `src/trace.ts` | Tracing: every loop event goes to a local JSONL file, and optionally to an OpenTelemetry collector. |
142
+ | `src/replay.ts` | Replays a recorded trace through the current harness without a model or tools. |
143
+ | `src/mining.ts` | Failure mining: groups failures from traces and turns one into an eval task. |
144
+ | `src/evals.ts` | The eval runner: variants and ablations. |
145
+ | `src/evalcases.ts` | Small built-in eval tasks and a loader for your own cases. |
146
+ | `src/hardcases.ts` | Harder built-in eval tasks. |
147
+ | `src/suites.ts` | Public-benchmark style suites (SWE-bench format) as eval cases. |
148
+ | `src/stats.ts` | Small statistics for comparing variants. Tasks are the unit of analysis. |
149
+ | `src/profiles.ts` | Harness profiles: settings that depend on the model. |
150
+ | `src/tune.ts` | `amness tune`: bounded changes to the harness settings, kept only if they hold on unseen tasks. |
151
+
152
+ ### `cli/` : the command line and the terminal interface
153
+
154
+ | File | What it does |
155
+ |---|---|
156
+ | `cli/amness.ts` | The entry point: chat (default), headless `run`, the classic menu, `demo`. It picks the full-screen or inline view. |
157
+ | `cli/headless.ts` | Non-interactive mode for CI, scripts and containers: `amness run`. |
158
+ | `cli/config.ts` | Reads and writes the configuration file. |
159
+ | `cli/model.ts` | Builds the model for a configuration. All models for one endpoint and key share one rate-limit gate. |
160
+ | `cli/detect.ts` | Guesses the command that proves the project works (the finish gate). |
161
+ | `cli/state.ts` | Small remembered state: last project, verify command per project, task history. |
162
+ | `cli/format.ts` | One readable trace line per harness event. |
163
+ | `cli/ui.ts` | A small terminal UI without dependencies: colours, boxes, menus, prompts, spinner. |
164
+ | `cli/demo.ts` | The guided demo: a scripted model that makes mistakes so every guard fires. No API key. |
165
+ | `cli/evalcli.ts` | `amness eval`: compare harness variants with statistics. |
166
+ | `cli/schedulecli.ts` | `amness schedule`: recurring headless runs through cron. |
167
+ | `cli/acpcli.ts` | `amness acp`: serve the Agent Client Protocol on stdin and stdout. |
168
+
169
+ ### `cli/tui/` : the chat interface
170
+
171
+ | File | What it does |
172
+ |---|---|
173
+ | `cli/tui/app.ts` | The `App`: one persistent conversation with input box, streaming output, approvals and slash commands. |
174
+ | `cli/tui/commands.ts` | The slash commands. |
175
+ | `cli/tui/custom.ts` | Your own slash commands, from markdown files. |
176
+ | `cli/tui/complete.ts` | Autocomplete for `/commands` and `@file` mentions. |
177
+ | `cli/tui/mentions.ts` | Turns `@path` mentions into attached file contents. |
178
+ | `cli/tui/editor.ts` | The multi-line input editor, as a state machine with no terminal input or output. |
179
+ | `cli/tui/keys.ts` | Turns raw terminal input into key events (keys, mouse, paste). |
180
+ | `cli/tui/session.ts` | Saves conversations per project and handles undo and rewind of file changes. |
181
+ | `cli/tui/blocks.ts` | The retained transcript: structured blocks that the full-screen view draws from. |
182
+ | `cli/tui/full.ts` | The full-screen view: header, transcript, sidebar, input, modals, toasts and mouse handling. |
183
+ | `cli/tui/cards.ts` | Card renderers: each transcript block becomes rows of styled cells. |
184
+ | `cli/tui/sidebar.ts` | The right-hand panel with the state of the session. |
185
+ | `cli/tui/modal.ts` | Modal dialogs for the full-screen view. |
186
+ | `cli/tui/overlays.ts` | Pieces drawn in place of the input box in the inline view: choices, pickers, help. |
187
+ | `cli/tui/canvas.ts` | A grid of character cells. `flush()` writes only the cells that changed. |
188
+ | `cli/tui/ansi.ts` | Converts between ANSI strings and styled cells. |
189
+ | `cli/tui/rich.ts` | Row-building helpers and the markdown style for the full-screen view. |
190
+ | `cli/tui/themes.ts` | The colour themes for the full-screen view. |
191
+ | `cli/tui/theme.ts` | Colour support for the inline view (truecolor, 256, 16, none). |
192
+ | `cli/tui/brand.ts` | Provider and model symbols, and the logo. |
193
+ | `cli/tui/diffview.ts` | The diff viewer: line numbers, tints, syntax colours, split layout. |
194
+ | `cli/tui/markdown.ts` | Converts markdown to ANSI lines for assistant messages. |
195
+ | `cli/tui/text.ts` | Text basics: strip ANSI, display width, wrapping. |
196
+ | `cli/tui/screen.ts` | The inline renderer: finished output goes to normal scrollback, and a small live region at the bottom is redrawn. |
197
+ | `cli/tui/view.ts` | Pure rendering helpers for the chat UI. They return lines and never touch the terminal. |
198
+ | `cli/tui/pager.ts` | The full-transcript viewer (Ctrl+O). |
199
+ | `cli/tui/clipboard.ts` | Copies text to your clipboard (OSC 52 and the platform tool). |
200
+ | `cli/tui/pricing.ts` | Rough price and context-window tables for the footer. They are estimates. |
201
+ | `cli/tui/tokens.ts` | The "where did my tokens go" report. |
202
+
203
+ ## Understand how the full-screen interface draws
204
+
205
+ **What it is.** The full-screen view draws the whole screen from one grid of cells.
206
+
207
+ **How it works.**
208
+
209
+ 1. Agent events reach `App.onEvent()` in `cli/tui/app.ts`. They are written into the block store (`BlockStore` in `cli/tui/blocks.ts`).
210
+ 2. Each block is drawn into rows of styled cells (`cli/tui/cards.ts`). `cli/tui/full.ts` keeps the result in a cache and reuses it while the block's signature is the same.
211
+ 3. `draw()` in `cli/tui/full.ts` places the header, transcript, sidebar, status line, input and footer on a `Canvas`. The sidebar shows only when the terminal is at least 104 columns wide.
212
+ 4. `Canvas.flush()` in `cli/tui/canvas.ts` returns only the cells that changed since the last frame. `draw()` wraps that text in synchronized-output markers (`ESC[?2026h` and `ESC[?2026l`), so you do not see a half-drawn frame.
213
+ 5. While the view runs, it turns on the alternate screen, mouse reporting (SGR 1006) and bracketed paste. It turns them off on exit. Copying uses OSC 52.
214
+ 6. When you resize the terminal, `invalidate()` clears the cache and forces a full repaint.
215
+
216
+ The inline renderer (`cli/tui/screen.ts`) uses the same `App` and the same agent events. Both views show the same behaviour.
217
+
218
+ **Limits and common problems.**
219
+
220
+ - Synchronized output and OSC 52 depend on your terminal. If your terminal ignores them, you may see flicker or copying may not reach your clipboard. Not verified on every terminal.
221
+ - Tests for the canvas, keys and full-screen view are in `test/tui-canvas.test.ts`, `test/tui-keys.test.ts` and `test/tui-full.test.ts`.
222
+
223
+ ## Next steps
224
+
225
+ - [The agent loop](agent-loop.md): follow one turn through these files.
226
+ - [Use it as a library](library.md): use `src/` without the UI.
227
+ - [Plugins](plugins.md): add behaviour as code.
228
+ - [Development and limitations](development.md): run the tests.