amness 0.0.0-stage → 0.1.2
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/LICENSE +21 -0
- package/README.md +237 -2
- package/dist/cli/acpcli.d.ts +7 -0
- package/dist/cli/acpcli.js +14 -0
- package/dist/cli/amness.d.ts +2 -0
- package/dist/cli/amness.js +694 -0
- package/dist/cli/benchcli.d.ts +5 -0
- package/dist/cli/benchcli.js +160 -0
- package/dist/cli/config.d.ts +79 -0
- package/dist/cli/config.js +111 -0
- package/dist/cli/demo.d.ts +13 -0
- package/dist/cli/demo.js +44 -0
- package/dist/cli/detect.d.ts +2 -0
- package/dist/cli/detect.js +20 -0
- package/dist/cli/evalcli.d.ts +15 -0
- package/dist/cli/evalcli.js +198 -0
- package/dist/cli/format.d.ts +5 -0
- package/dist/cli/format.js +55 -0
- package/dist/cli/headless.d.ts +2 -0
- package/dist/cli/headless.js +169 -0
- package/dist/cli/keystore.d.ts +9 -0
- package/dist/cli/keystore.js +108 -0
- package/dist/cli/model.d.ts +11 -0
- package/dist/cli/model.js +19 -0
- package/dist/cli/plugincli.d.ts +2 -0
- package/dist/cli/plugincli.js +176 -0
- package/dist/cli/schedulecli.d.ts +2 -0
- package/dist/cli/schedulecli.js +66 -0
- package/dist/cli/state.d.ts +11 -0
- package/dist/cli/state.js +19 -0
- package/dist/cli/tui/ansi.d.ts +38 -0
- package/dist/cli/tui/ansi.js +142 -0
- package/dist/cli/tui/app.d.ts +328 -0
- package/dist/cli/tui/app.js +1358 -0
- package/dist/cli/tui/blocks.d.ts +151 -0
- package/dist/cli/tui/blocks.js +128 -0
- package/dist/cli/tui/brand.d.ts +9 -0
- package/dist/cli/tui/brand.js +16 -0
- package/dist/cli/tui/canvas.d.ts +92 -0
- package/dist/cli/tui/canvas.js +199 -0
- package/dist/cli/tui/cards.d.ts +44 -0
- package/dist/cli/tui/cards.js +215 -0
- package/dist/cli/tui/clipboard.d.ts +5 -0
- package/dist/cli/tui/clipboard.js +29 -0
- package/dist/cli/tui/commands.d.ts +10 -0
- package/dist/cli/tui/commands.js +650 -0
- package/dist/cli/tui/complete.d.ts +39 -0
- package/dist/cli/tui/complete.js +113 -0
- package/dist/cli/tui/custom.d.ts +9 -0
- package/dist/cli/tui/custom.js +34 -0
- package/dist/cli/tui/diffview.d.ts +25 -0
- package/dist/cli/tui/diffview.js +130 -0
- package/dist/cli/tui/editor.d.ts +60 -0
- package/dist/cli/tui/editor.js +230 -0
- package/dist/cli/tui/full.d.ts +86 -0
- package/dist/cli/tui/full.js +630 -0
- package/dist/cli/tui/keys.d.ts +31 -0
- package/dist/cli/tui/keys.js +134 -0
- package/dist/cli/tui/markdown.d.ts +37 -0
- package/dist/cli/tui/markdown.js +155 -0
- package/dist/cli/tui/mentions.d.ts +15 -0
- package/dist/cli/tui/mentions.js +48 -0
- package/dist/cli/tui/modal.d.ts +26 -0
- package/dist/cli/tui/modal.js +107 -0
- package/dist/cli/tui/overlays.d.ts +98 -0
- package/dist/cli/tui/overlays.js +237 -0
- package/dist/cli/tui/pager.d.ts +15 -0
- package/dist/cli/tui/pager.js +66 -0
- package/dist/cli/tui/pricing.d.ts +9 -0
- package/dist/cli/tui/pricing.js +23 -0
- package/dist/cli/tui/rich.d.ts +16 -0
- package/dist/cli/tui/rich.js +72 -0
- package/dist/cli/tui/screen.d.ts +30 -0
- package/dist/cli/tui/screen.js +62 -0
- package/dist/cli/tui/session.d.ts +77 -0
- package/dist/cli/tui/session.js +103 -0
- package/dist/cli/tui/sidebar.d.ts +84 -0
- package/dist/cli/tui/sidebar.js +114 -0
- package/dist/cli/tui/text.d.ts +15 -0
- package/dist/cli/tui/text.js +94 -0
- package/dist/cli/tui/theme.d.ts +59 -0
- package/dist/cli/tui/theme.js +148 -0
- package/dist/cli/tui/themes.d.ts +56 -0
- package/dist/cli/tui/themes.js +80 -0
- package/dist/cli/tui/tokens.d.ts +39 -0
- package/dist/cli/tui/tokens.js +71 -0
- package/dist/cli/tui/view.d.ts +60 -0
- package/dist/cli/tui/view.js +178 -0
- package/dist/cli/ui.d.ts +34 -0
- package/dist/cli/ui.js +224 -0
- package/dist/src/acp.d.ts +28 -0
- package/dist/src/acp.js +111 -0
- package/dist/src/agents.d.ts +23 -0
- package/dist/src/agents.js +71 -0
- package/dist/src/anthropic.d.ts +30 -0
- package/dist/src/anthropic.js +91 -0
- package/dist/src/approver.d.ts +15 -0
- package/dist/src/approver.js +40 -0
- package/dist/src/bench.d.ts +67 -0
- package/dist/src/bench.js +201 -0
- package/dist/src/bundle.d.ts +36 -0
- package/dist/src/bundle.js +55 -0
- package/dist/src/chaos.d.ts +15 -0
- package/dist/src/chaos.js +26 -0
- package/dist/src/code.d.ts +187 -0
- package/dist/src/code.js +590 -0
- package/dist/src/codeindex.d.ts +65 -0
- package/dist/src/codeindex.js +300 -0
- package/dist/src/deep.d.ts +62 -0
- package/dist/src/deep.js +177 -0
- package/dist/src/diff.d.ts +8 -0
- package/dist/src/diff.js +74 -0
- package/dist/src/dynamic.d.ts +42 -0
- package/dist/src/dynamic.js +133 -0
- package/dist/src/embed.d.ts +8 -0
- package/dist/src/embed.js +25 -0
- package/dist/src/evalcases.d.ts +11 -0
- package/dist/src/evalcases.js +196 -0
- package/dist/src/evals.d.ts +197 -0
- package/dist/src/evals.js +340 -0
- package/dist/src/gitflow.d.ts +27 -0
- package/dist/src/gitflow.js +56 -0
- package/dist/src/grade.d.ts +42 -0
- package/dist/src/grade.js +96 -0
- package/dist/src/guidance.d.ts +15 -0
- package/dist/src/guidance.js +68 -0
- package/dist/src/hardcases.d.ts +2 -0
- package/dist/src/hardcases.js +94 -0
- package/dist/src/harness.d.ts +411 -0
- package/dist/src/harness.js +725 -0
- package/dist/src/hooks.d.ts +44 -0
- package/dist/src/hooks.js +116 -0
- package/dist/src/images.d.ts +13 -0
- package/dist/src/images.js +27 -0
- package/dist/src/index.d.ts +57 -0
- package/dist/src/index.js +57 -0
- package/dist/src/ledger.d.ts +58 -0
- package/dist/src/ledger.js +78 -0
- package/dist/src/live.d.ts +16 -0
- package/dist/src/live.js +80 -0
- package/dist/src/lsp.d.ts +31 -0
- package/dist/src/lsp.js +147 -0
- package/dist/src/memory.d.ts +128 -0
- package/dist/src/memory.js +390 -0
- package/dist/src/metrics.d.ts +3 -0
- package/dist/src/metrics.js +42 -0
- package/dist/src/mining.d.ts +49 -0
- package/dist/src/mining.js +126 -0
- package/dist/src/openai.d.ts +50 -0
- package/dist/src/openai.js +307 -0
- package/dist/src/parallel.d.ts +66 -0
- package/dist/src/parallel.js +135 -0
- package/dist/src/patch.d.ts +45 -0
- package/dist/src/patch.js +219 -0
- package/dist/src/permissions.d.ts +46 -0
- package/dist/src/permissions.js +245 -0
- package/dist/src/plugins.d.ts +148 -0
- package/dist/src/plugins.js +225 -0
- package/dist/src/pluginscan.d.ts +8 -0
- package/dist/src/pluginscan.js +20 -0
- package/dist/src/procs.d.ts +41 -0
- package/dist/src/procs.js +94 -0
- package/dist/src/profiles.d.ts +36 -0
- package/dist/src/profiles.js +61 -0
- package/dist/src/progress.d.ts +29 -0
- package/dist/src/progress.js +75 -0
- package/dist/src/prompts.d.ts +10 -0
- package/dist/src/prompts.js +28 -0
- package/dist/src/ratelimit.d.ts +34 -0
- package/dist/src/ratelimit.js +116 -0
- package/dist/src/replay.d.ts +25 -0
- package/dist/src/replay.js +99 -0
- package/dist/src/review.d.ts +20 -0
- package/dist/src/review.js +49 -0
- package/dist/src/route.d.ts +9 -0
- package/dist/src/route.js +30 -0
- package/dist/src/router.d.ts +61 -0
- package/dist/src/router.js +126 -0
- package/dist/src/sandbox.d.ts +13 -0
- package/dist/src/sandbox.js +68 -0
- package/dist/src/schedule.d.ts +20 -0
- package/dist/src/schedule.js +59 -0
- package/dist/src/schema.d.ts +13 -0
- package/dist/src/schema.js +36 -0
- package/dist/src/secrets.d.ts +10 -0
- package/dist/src/secrets.js +41 -0
- package/dist/src/shell.d.ts +34 -0
- package/dist/src/shell.js +95 -0
- package/dist/src/skills.d.ts +20 -0
- package/dist/src/skills.js +112 -0
- package/dist/src/snapshot.d.ts +20 -0
- package/dist/src/snapshot.js +93 -0
- package/dist/src/stats.d.ts +24 -0
- package/dist/src/stats.js +62 -0
- package/dist/src/strategies.d.ts +16 -0
- package/dist/src/strategies.js +35 -0
- package/dist/src/suites.d.ts +30 -0
- package/dist/src/suites.js +112 -0
- package/dist/src/trace.d.ts +63 -0
- package/dist/src/trace.js +153 -0
- package/dist/src/tune.d.ts +38 -0
- package/dist/src/tune.js +49 -0
- package/dist/src/web.d.ts +19 -0
- package/dist/src/web.js +56 -0
- package/dist/src/worktree.d.ts +24 -0
- package/dist/src/worktree.js +85 -0
- package/docs/agent-loop.md +222 -0
- package/docs/architecture.md +228 -0
- package/docs/bench.md +292 -0
- package/docs/budgets.md +185 -0
- package/docs/build-your-own-harness.md +424 -0
- package/docs/cookbook.md +375 -0
- package/docs/development.md +150 -0
- package/docs/evaluation.md +450 -0
- package/docs/features.md +1320 -0
- package/docs/getting-started.md +205 -0
- package/docs/headless.md +188 -0
- package/docs/images/approval.png +0 -0
- package/docs/images/chat.png +0 -0
- package/docs/images/daylight.png +0 -0
- package/docs/images/loop.png +0 -0
- package/docs/images/palette.png +0 -0
- package/docs/images/running.png +0 -0
- package/docs/images/subagent-done.png +0 -0
- package/docs/images/subagent-running.png +0 -0
- package/docs/images/welcome.png +0 -0
- package/docs/index.md +25 -0
- package/docs/library.md +267 -0
- package/docs/plugins.md +691 -0
- package/docs/providers.md +175 -0
- package/docs/safety.md +250 -0
- package/docs/usage.md +348 -0
- package/docs/why.md +155 -0
- package/docs/writing-guide.md +39 -0
- package/package.json +66 -4
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
# Build your own harness
|
|
2
|
+
|
|
3
|
+
This tutorial starts with one small rule and ends with a custom harness that you have measured.
|
|
4
|
+
|
|
5
|
+
In this page you will learn:
|
|
6
|
+
|
|
7
|
+
- how to build a plugin step by step: a guardrail, a finish gate, a command, a status line and a loop rule
|
|
8
|
+
- how to add your own context rule and system prompt
|
|
9
|
+
- how to compare your harness with the built-in one, and how to read the result
|
|
10
|
+
- how to use the comparison as a check in CI, and what is safe to install
|
|
11
|
+
|
|
12
|
+
A *harness* is the code around the model: the loop, the tools, the rules and the checks. A *plugin* is a TypeScript file that changes the harness. You do not need to read the source code. You need Amness installed and a project folder (see [Getting started](getting-started.md)).
|
|
13
|
+
|
|
14
|
+
Each step ends with a check. Do the check before you go on.
|
|
15
|
+
|
|
16
|
+
## Step 1: Create a plugin and see it
|
|
17
|
+
|
|
18
|
+
**What you do.** Create a plugin file from a template and approve it.
|
|
19
|
+
|
|
20
|
+
1. In your project folder, run:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
amness plugin new my-harness
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Amness writes `.amness/plugins/my-harness.ts`. It never overwrites a file that exists.
|
|
27
|
+
|
|
28
|
+
2. Start Amness:
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
amness
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Amness asks if you trust the plugin files in this project. A plugin runs with your full permissions, so read the file first. Choose "Yes, trust this version".
|
|
35
|
+
|
|
36
|
+
3. Check that it loaded:
|
|
37
|
+
|
|
38
|
+
```text
|
|
39
|
+
/plugins
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
You will see a line with `my-harness` and its description. The template also adds a `/my-harness` command. Type it. You will see `hello from the my-harness plugin`.
|
|
43
|
+
|
|
44
|
+
**Important.** Amness approves the exact content of your plugin files. After you edit a file, the approval no longer matches. Run `/reload-plugins`: Amness asks you to approve the new version, then loads it.
|
|
45
|
+
|
|
46
|
+
For the next steps, you replace the whole file. The complete file is at the end of step 7. In each step you add one property to the exported object.
|
|
47
|
+
|
|
48
|
+
Start with this file. It is a plain object. You do not need to import anything from Amness.
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
export default {
|
|
52
|
+
name: "my-harness",
|
|
53
|
+
description: "no curl, tests before finish, read first, short old tool output",
|
|
54
|
+
};
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Step 2: Add a guardrail
|
|
58
|
+
|
|
59
|
+
**What it is.** A *hook* is a function that Amness calls at a fixed point in the loop. `PreToolUse` runs before each tool call. If it returns `{ block: "..." }`, the call does not run.
|
|
60
|
+
|
|
61
|
+
**Why.** A rule in code holds every time. A rule in the prompt can be forgotten.
|
|
62
|
+
|
|
63
|
+
Add this inside the object:
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
hooks: {
|
|
67
|
+
PreToolUse: ({ tool, args }: { tool: string; args: any }) =>
|
|
68
|
+
tool === "sh" && /\bcurl\b/.test(String(args.cmd))
|
|
69
|
+
? { block: "curl is not allowed here. Use the project tools instead." }
|
|
70
|
+
: undefined,
|
|
71
|
+
},
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`sh` is the tool that runs shell commands. Its argument is `cmd`.
|
|
75
|
+
|
|
76
|
+
**Check.** Restart Amness and approve. Then type:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
Run curl -s https://example.com in the shell and show me the first line.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The agent gets this as the tool result, and the chat marks the call as denied:
|
|
83
|
+
|
|
84
|
+
```text
|
|
85
|
+
BLOCKED BY A HOOK: curl is not allowed here. Use the project tools instead.
|
|
86
|
+
Do not retry the same call; choose another approach or explain what you could not do.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The words the agent writes next will differ each time.
|
|
90
|
+
|
|
91
|
+
**Limit.** This is a text match. The agent can run `wget` or `python -c` instead. A real network rule needs more patterns, or the sandbox (see [Safety and privacy](safety.md)). If you want a crash in your rule to block the call, add `failClosed: true` to the plugin.
|
|
92
|
+
|
|
93
|
+
## Step 3: Add a finish gate
|
|
94
|
+
|
|
95
|
+
**What it is.** A *finish gate* is a check that runs when the agent calls `finish`. If it fails, the agent must keep working.
|
|
96
|
+
|
|
97
|
+
**Why.** The gate sees what really happened, not only what the agent says. `GateCtx` has `changed` (the files changed in this task) and `testsAfterLastEdit` (whether a test or build command ran after the last edit).
|
|
98
|
+
|
|
99
|
+
Add this inside the object:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
gates: [{
|
|
103
|
+
name: "tests-after-edit",
|
|
104
|
+
check: (ctx: any) =>
|
|
105
|
+
ctx.changed.length > 0 && ctx.testsAfterLastEdit === false
|
|
106
|
+
? "you changed files but did not run the tests after the last edit"
|
|
107
|
+
: true,
|
|
108
|
+
}],
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**Check.** Restart Amness, approve, and ask for a small edit without tests:
|
|
112
|
+
|
|
113
|
+
```text
|
|
114
|
+
Add a comment line at the top of README.md. Do not run anything.
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
When the agent calls `finish`, it sees:
|
|
118
|
+
|
|
119
|
+
```text
|
|
120
|
+
REJECTED by a hook: gate "tests-after-edit" (plugin my-harness) found problems:
|
|
121
|
+
- you changed files but did not run the tests after the last edit
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**Limits.**
|
|
125
|
+
|
|
126
|
+
- A gate rejects the same task at most 2 times. Then the run finishes and Amness shows a warning. This stops a wrong gate from trapping the agent. Set `maxRejections` on the gate to change it.
|
|
127
|
+
- `changed` lists edits made with the file tools. Files changed by a shell command are not in it.
|
|
128
|
+
- `testsAfterLastEdit` can be `undefined` when Amness cannot tell. The rule above rejects only on `false`.
|
|
129
|
+
|
|
130
|
+
## Step 4: Add a slash command and a status line
|
|
131
|
+
|
|
132
|
+
**What it is.** A *slash command* is a `/name` command in the chat. The *status line* is a short text in the sidebar.
|
|
133
|
+
|
|
134
|
+
**Why.** A command starts a repeated job with one word. The status line shows a state you want to see all the time.
|
|
135
|
+
|
|
136
|
+
Add this inside the object:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
commands: [{
|
|
140
|
+
name: "tests",
|
|
141
|
+
help: "run npm test and show the result",
|
|
142
|
+
run: async (ctx: any) => {
|
|
143
|
+
const r = await ctx.run("npm test", 120_000);
|
|
144
|
+
ctx.say(r.code === 0 ? "tests pass" : "tests fail:\n" + r.output.slice(-1500));
|
|
145
|
+
},
|
|
146
|
+
}],
|
|
147
|
+
|
|
148
|
+
status: ({ running }: { running: boolean }) => (running ? "working" : "ready"),
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
`ctx.run` runs a shell command and returns `{ code, output }`. `ctx.say` prints a line that the agent does not see. A command name that a built-in command already uses is skipped. That is why this one is not called `check`.
|
|
152
|
+
|
|
153
|
+
**Check.** Restart Amness and approve. Type `/tests`. You will see `tests pass` or `tests fail:` with the end of the output. The sidebar shows `my-harness: ready`. If you do not see the sidebar, type `/sidebar`. The status function must be fast, because the sidebar calls it often.
|
|
154
|
+
|
|
155
|
+
## Step 5: Change the loop
|
|
156
|
+
|
|
157
|
+
**What it is.** A *loop strategy* decides when the model may use which tools. It uses the `PreModel` hook, which runs before each model request. It can return `tools` (the only tools allowed in this turn) and `then` (a message to send if the model answers with text only).
|
|
158
|
+
|
|
159
|
+
1. Try the built-in strategies first:
|
|
160
|
+
|
|
161
|
+
```text
|
|
162
|
+
/strategy plan-first
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
With `plan-first`, the model writes a numbered plan at step 0 and may not call tools. Try a task. Then try `/strategy explore-then-act`, which allows only read tools for the first 6 steps. Turn it off with `/strategy off`.
|
|
166
|
+
|
|
167
|
+
2. Now write your own rule. Add this to the `hooks` object from step 2, next to `PreToolUse`:
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
PreModel: ({ step }: { step: number }) =>
|
|
171
|
+
step === 0
|
|
172
|
+
? { context: "Write a short numbered plan first. Do not call tools in this reply.", tools: [], then: "Good. Now carry out the plan." }
|
|
173
|
+
: step < 3 ? { tools: ["list_files", "read_file", "grep", "repo_map", "find_refs", "impact", "finish"] } : undefined,
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
At step 0 the model gets no tools and must write a plan. `then` makes Amness ask again after the plan. Without `then`, a text-only reply would end the run. In steps 1 and 2, only read tools and `finish` are allowed. From step 3 on, all tools work.
|
|
177
|
+
|
|
178
|
+
**Check.** Restart Amness, approve, and give a task that needs an edit. In the first turns, the agent writes a plan and then only reads. If it calls `edit_file` too early, it gets `ERROR: edit_file is not allowed in this phase`.
|
|
179
|
+
|
|
180
|
+
**Limits.** The model still sees the full tool list. Amness enforces the limit when the model calls a tool. Your rule costs one extra model call per request.
|
|
181
|
+
|
|
182
|
+
## Step 6: Add a context rule and your own prompt
|
|
183
|
+
|
|
184
|
+
**What it is.** The `context` function runs before every model request. It gets the message list and `{ step, promptTokens, budget }`. It may return a new list. `budget` is the share of the token cap used so far, from 0 to 1.
|
|
185
|
+
|
|
186
|
+
**Why.** Old tool output fills the context window. This rule shortens old output when the budget is half used.
|
|
187
|
+
|
|
188
|
+
Add this inside the object:
|
|
189
|
+
|
|
190
|
+
```ts
|
|
191
|
+
context: (msgs: any[], { budget }: { budget: number }) =>
|
|
192
|
+
budget < 0.5
|
|
193
|
+
? undefined
|
|
194
|
+
: msgs.map((m, i) =>
|
|
195
|
+
i < msgs.length - 10 && m.role === "tool" && (m.content ?? "").length > 1000
|
|
196
|
+
? { ...m, content: m.content.slice(0, 500) + "\n[older output shortened]" }
|
|
197
|
+
: m),
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
The rule keeps every message. It only shortens the text of old tool results. This keeps each tool call together with its result. If you remove messages, keep them in pairs, or the provider can reject the request.
|
|
201
|
+
|
|
202
|
+
**Your own prompt.** A prompt in the registry is a file with a name you can test.
|
|
203
|
+
|
|
204
|
+
1. Create the file:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
mkdir -p .amness/prompts
|
|
208
|
+
echo "You are a careful coding agent. Make the smallest change that works. Say which command you ran to check it." > .amness/prompts/terse.md
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
2. In the chat, choose it:
|
|
212
|
+
|
|
213
|
+
```text
|
|
214
|
+
/prompt terse
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
The text replaces the whole system prompt for new conversations. `/prompt list` shows the ids. `/prompt off` brings back the built-in prompt. An id may use letters, numbers, `.`, `_` and `-`.
|
|
218
|
+
|
|
219
|
+
**Check.** Type `/prompt list`. You will see `terse`. Start a new conversation with `/clear` and ask what rules you gave it.
|
|
220
|
+
|
|
221
|
+
**Limit.** Your prompt replaces the built-in prompt. The built-in prompt has rules for tools and safety. Test a short prompt before you rely on it (step 7).
|
|
222
|
+
|
|
223
|
+
## Step 7: Measure it
|
|
224
|
+
|
|
225
|
+
So far you have a feeling that the harness is better. Now you measure it.
|
|
226
|
+
|
|
227
|
+
**What it is.** An *eval* runs the agent on tasks in fresh folders. A *held-out check* decides if each task is solved. The agent never sees the check. A *variant* is one harness setup. A *trial* is one more run of the same task.
|
|
228
|
+
|
|
229
|
+
**1. Write a task.** Add `cases` and `variants` inside the object:
|
|
230
|
+
|
|
231
|
+
```ts
|
|
232
|
+
cases: [{
|
|
233
|
+
name: "small-fix",
|
|
234
|
+
files: {
|
|
235
|
+
"math.js": "exports.add = (a, b) => a - b;\n",
|
|
236
|
+
"test.js": "const { add } = require('./math.js');\nif (add(2, 3) !== 5) { console.error('wrong'); process.exit(1); }\n",
|
|
237
|
+
},
|
|
238
|
+
task: "add() in math.js gives wrong results. Fix it. Run node test.js to check.",
|
|
239
|
+
verify: "node test.js",
|
|
240
|
+
check: async (dir: string) => {
|
|
241
|
+
const { execSync } = await import("node:child_process");
|
|
242
|
+
try {
|
|
243
|
+
execSync("node -e \"const {add}=require('./math.js'); process.exit(add(2,3)===5&&add(-4,1)===-3?0:1)\"", { cwd: dir, stdio: "pipe" });
|
|
244
|
+
return true;
|
|
245
|
+
} catch { return "add() still gives wrong results"; }
|
|
246
|
+
},
|
|
247
|
+
solution: { "math.js": "exports.add = (a, b) => a + b;\n" },
|
|
248
|
+
}],
|
|
249
|
+
|
|
250
|
+
variants: { guarded: { off: [], verify: true } },
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
The `check` function gets the workspace folder. It returns `true` to pass, or text that explains the failure. It tests one more value than `test.js` does, so the agent cannot pass by tuning for the file it can see. `solution` is a known good fix. The variant `guarded` is the full harness, plus the hooks, gates, context rule and prompt of this plugin. The plugin that defines a variant is always active in it.
|
|
254
|
+
|
|
255
|
+
**2. Check the task. This calls no model and costs nothing.**
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
amness eval --plugin .amness/plugins/my-harness.ts --selftest
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
The last line must be `ok small-fix`. Passing a file with `--plugin` counts as your approval of that file.
|
|
262
|
+
|
|
263
|
+
**3. Compare the variants.** This calls your model and costs tokens. Amness shows a plan and asks before it starts. Your task joins the 13 built-in tasks.
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
amness eval --plugin .amness/plugins/my-harness.ts --variants full,guarded --trials 3 --metrics diff-lines,tool-calls
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
That is 14 tasks, 2 variants and 3 trials: 84 sessions. Read the plan line before you accept.
|
|
270
|
+
|
|
271
|
+
**4. Read the table.**
|
|
272
|
+
|
|
273
|
+
- `pass [95% CI]` is the share of tasks solved. The *confidence interval* (CI) is the range where the true value probably lies.
|
|
274
|
+
- `tok/solved` is tokens per solved task. A *token* is a small piece of text that a model reads.
|
|
275
|
+
- Under it, Amness compares `guarded` with the first variant (`full`). `Δ pass` is the change in points. `W/L/T` counts tasks won, lost and tied. `verdict` is `better`, `worse` or `inconclusive`.
|
|
276
|
+
- The custom metrics table shows `diff-lines` (lines changed) and `tool-calls`. A smaller change that still passes is usually better.
|
|
277
|
+
|
|
278
|
+
`inconclusive` means the data cannot tell. It does not mean the variants are equal.
|
|
279
|
+
|
|
280
|
+
**Warning: the first runs are noisy.** A model gives different answers each time. Amness treats the task as the unit, so more trials do not help much. More tasks help. The report prints how large a pass-rate difference you can see. Amness computes it as `minDetectable` in `src/stats.ts`: about 2 divided by the square root of the number of tasks.
|
|
281
|
+
|
|
282
|
+
| Tasks | Smallest pass-rate difference you can detect |
|
|
283
|
+
|---|---|
|
|
284
|
+
| 14 | about 53 points |
|
|
285
|
+
| 25 | about 40 points |
|
|
286
|
+
| 44 | about 30 points |
|
|
287
|
+
| 98 | about 20 points |
|
|
288
|
+
|
|
289
|
+
With 14 tasks you will almost surely see `inconclusive` for pass rate. Use 3 or more trials to make the cost and metric numbers steadier. Add your own tasks from real failures (see [Evaluating the harness](evaluation.md#turn-a-real-failure-into-a-task)). Treat one run as a hint, not as proof.
|
|
290
|
+
|
|
291
|
+
To use the prompt in the comparison, add `prompt:terse` as a variant. For example: `--variants full,guarded,prompt:terse`.
|
|
292
|
+
|
|
293
|
+
### The complete plugin file
|
|
294
|
+
|
|
295
|
+
Put the pieces together. This file passed `--selftest`. Its hook, gate and context functions were also called directly with sample input. It was not run against a real model.
|
|
296
|
+
|
|
297
|
+
```ts
|
|
298
|
+
export default {
|
|
299
|
+
name: "my-harness",
|
|
300
|
+
description: "no curl, tests before finish, read first, short old tool output",
|
|
301
|
+
|
|
302
|
+
hooks: {
|
|
303
|
+
PreToolUse: ({ tool, args }: { tool: string; args: any }) =>
|
|
304
|
+
tool === "sh" && /\bcurl\b/.test(String(args.cmd))
|
|
305
|
+
? { block: "curl is not allowed here. Use the project tools instead." }
|
|
306
|
+
: undefined,
|
|
307
|
+
PreModel: ({ step }: { step: number }) =>
|
|
308
|
+
step === 0
|
|
309
|
+
? { context: "Write a short numbered plan first. Do not call tools in this reply.", tools: [], then: "Good. Now carry out the plan." }
|
|
310
|
+
: step < 3 ? { tools: ["list_files", "read_file", "grep", "repo_map", "find_refs", "impact", "finish"] } : undefined,
|
|
311
|
+
},
|
|
312
|
+
|
|
313
|
+
gates: [{
|
|
314
|
+
name: "tests-after-edit",
|
|
315
|
+
check: (ctx: any) =>
|
|
316
|
+
ctx.changed.length > 0 && ctx.testsAfterLastEdit === false
|
|
317
|
+
? "you changed files but did not run the tests after the last edit"
|
|
318
|
+
: true,
|
|
319
|
+
}],
|
|
320
|
+
|
|
321
|
+
commands: [{
|
|
322
|
+
name: "tests",
|
|
323
|
+
help: "run npm test and show the result",
|
|
324
|
+
run: async (ctx: any) => {
|
|
325
|
+
const r = await ctx.run("npm test", 120_000);
|
|
326
|
+
ctx.say(r.code === 0 ? "tests pass" : "tests fail:\n" + r.output.slice(-1500));
|
|
327
|
+
},
|
|
328
|
+
}],
|
|
329
|
+
|
|
330
|
+
status: ({ running }: { running: boolean }) => (running ? "working" : "ready"),
|
|
331
|
+
|
|
332
|
+
context: (msgs: any[], { budget }: { budget: number }) =>
|
|
333
|
+
budget < 0.5
|
|
334
|
+
? undefined
|
|
335
|
+
: msgs.map((m, i) =>
|
|
336
|
+
i < msgs.length - 10 && m.role === "tool" && (m.content ?? "").length > 1000
|
|
337
|
+
? { ...m, content: m.content.slice(0, 500) + "\n[older output shortened]" }
|
|
338
|
+
: m),
|
|
339
|
+
|
|
340
|
+
cases: [{
|
|
341
|
+
name: "small-fix",
|
|
342
|
+
files: {
|
|
343
|
+
"math.js": "exports.add = (a, b) => a - b;\n",
|
|
344
|
+
"test.js": "const { add } = require('./math.js');\nif (add(2, 3) !== 5) { console.error('wrong'); process.exit(1); }\n",
|
|
345
|
+
},
|
|
346
|
+
task: "add() in math.js gives wrong results. Fix it. Run node test.js to check.",
|
|
347
|
+
verify: "node test.js",
|
|
348
|
+
check: async (dir: string) => {
|
|
349
|
+
const { execSync } = await import("node:child_process");
|
|
350
|
+
try {
|
|
351
|
+
execSync("node -e \"const {add}=require('./math.js'); process.exit(add(2,3)===5&&add(-4,1)===-3?0:1)\"", { cwd: dir, stdio: "pipe" });
|
|
352
|
+
return true;
|
|
353
|
+
} catch { return "add() still gives wrong results"; }
|
|
354
|
+
},
|
|
355
|
+
solution: { "math.js": "exports.add = (a, b) => a + b;\n" },
|
|
356
|
+
}],
|
|
357
|
+
|
|
358
|
+
variants: { guarded: { off: [], verify: true } },
|
|
359
|
+
};
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
## Step 8: Use it as a regression gate and grade real sessions
|
|
363
|
+
|
|
364
|
+
**What it is.** A *regression gate* compares a new eval run with a saved one. It exits with code 1 when the result is clearly worse.
|
|
365
|
+
|
|
366
|
+
**Why.** You change the plugin, a prompt or the model. The gate tells you if something got worse.
|
|
367
|
+
|
|
368
|
+
1. Save a good run as your baseline:
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
amness eval --plugin .amness/plugins/my-harness.ts --variants guarded --trials 3 --out evals/baseline.json
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
2. In CI, run again and compare:
|
|
375
|
+
|
|
376
|
+
```bash
|
|
377
|
+
amness eval --plugin .amness/plugins/my-harness.ts --variants guarded --trials 3 --yes --gate evals/baseline.json
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Amness prints a `regression gate` block. The exit code is 0 when every variant is `ok`, and 1 when one fails.
|
|
381
|
+
|
|
382
|
+
The gate fails when the pass rate drops by more than 5 points (`--gate-tol`) and the whole interval is below zero. It also fails when tokens per solved task grow by more than 25 percent (`--gate-cost`). With few tasks, a real drop often gives only a warning. The gate is tested with scripted data. It has not been run in a real CI pipeline.
|
|
383
|
+
|
|
384
|
+
**Grade real sessions.** An eval uses tasks you wrote. `grade` scores sessions that really happened, from the traces in `.amness/traces`. It makes no model call.
|
|
385
|
+
|
|
386
|
+
```bash
|
|
387
|
+
amness plugin grade .amness/traces --plugin .amness/plugins/my-harness.ts
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
You will see one line per grader, such as `finished mean 62% over 8 session(s)`. The built-in graders are `finished`, `no-rejections`, `tool-errors`, `no-loops` and `verified-edits`. A plugin can add more with the `graders` field (see [Plugins](plugins.md#metrics-and-graders)). Note that `finished` scores 0 for a plain chat answer.
|
|
391
|
+
|
|
392
|
+
## Step 9: Stay safe
|
|
393
|
+
|
|
394
|
+
- **A plugin has your full permissions.** It can read files, run commands and use the network. The sandbox setting does not limit plugin code.
|
|
395
|
+
- **Trust prompt.** Before Amness asks, it shows one line per file about what the source seems to touch. For example: `my-harness.ts: runs shell commands`. This is a text scan, not a sandbox. A plugin can hide what it does. Read the file.
|
|
396
|
+
- **Approval is per content.** Any change to a project plugin file asks again. In headless runs, project plugins load only with `--trust-project`.
|
|
397
|
+
- **Add a plugin from someone else.** This copies the file into `.amness/plugins/` and does not run it:
|
|
398
|
+
|
|
399
|
+
```bash
|
|
400
|
+
amness plugin add ./their-plugin.ts
|
|
401
|
+
amness plugin add https://example.com/their-plugin.ts
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
It accepts a file or an `https` URL, rejects files over 300 KB or without `export default`, and never overwrites a file. It prints what the file seems to touch. Amness still asks for your approval the first time it starts.
|
|
405
|
+
|
|
406
|
+
## What you built
|
|
407
|
+
|
|
408
|
+
- A guardrail that blocks `curl` in shell commands.
|
|
409
|
+
- A finish gate that refuses to finish after edits without a test run.
|
|
410
|
+
- A `/tests` command and a status line.
|
|
411
|
+
- A loop rule: plan first, then read only, then act.
|
|
412
|
+
- A context rule and a prompt (`terse`).
|
|
413
|
+
- An eval task and a variant, compared with `full` on a table with intervals.
|
|
414
|
+
- A baseline file for a CI gate, and a way to grade real sessions.
|
|
415
|
+
|
|
416
|
+
None of these rules is shown to be better yet. The table tells you what the data can and cannot say.
|
|
417
|
+
|
|
418
|
+
## Next steps
|
|
419
|
+
|
|
420
|
+
- [Plugins](plugins.md): every plugin field, the hook events, memory backends and model routing.
|
|
421
|
+
- [Evaluating the harness](evaluation.md): suites, ablations, custom tasks and the measured results.
|
|
422
|
+
- [Cookbook](cookbook.md): short recipes.
|
|
423
|
+
- [examples/plugins/house-rules.ts](../examples/plugins/house-rules.ts): a plugin with a tool, a prompt rewrite, a task and two variants.
|
|
424
|
+
- [Safety and privacy](safety.md)
|