@ssheleg/agent-stack 0.7.2 → 0.8.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.
- package/CHANGELOG.md +66 -0
- package/README.md +26 -4
- package/package.json +1 -1
- package/plugins/agent-stack/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-stack/skills/agent-harness/SKILL.md +161 -0
- package/plugins/agent-stack/skills/agent-harness/references/audit.md +141 -0
- package/plugins/agent-stack/skills/agent-harness/references/layers.md +100 -0
- package/plugins/agent-stack/skills/agent-harness/references/system-prompt.md +127 -0
- package/plugins/agent-stack/skills/agent-harness/references/techniques.md +115 -0
- package/plugins/agent-stack/skills/agent-harness/references/tools.md +154 -0
- package/plugins/agent-stack/skills/agent-harness/scripts/audit_agent.py +299 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.8.0] — 2026-08-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`agent-harness`, a fourth skill — the layer between the loop and the model.** The pack
|
|
8
|
+
could wire an agent (`agent-orchestrator`), prove it behaved (`agent-evals`) and connect
|
|
9
|
+
it to other processes (`agent-interop`), and said nothing about **what the agent is
|
|
10
|
+
told**. Five references and a scanner. It runs in both directions: building a harness and
|
|
11
|
+
auditing somebody else's are the same checklist read forwards and backwards, which is why
|
|
12
|
+
the audit lives here rather than in a sixth skill.
|
|
13
|
+
|
|
14
|
+
It is a fourth skill rather than a section because `agent-orchestrator`'s body is
|
|
15
|
+
**489 lines / ~4761 tokens** — already past the 4750 working limit — and could not absorb
|
|
16
|
+
a paragraph, let alone a layer.
|
|
17
|
+
|
|
18
|
+
- **`references/system-prompt.md`** — the right altitude (hardcoded branches on one side,
|
|
19
|
+
vague hope on the other), what actually belongs in a system prompt in order of behaviour
|
|
20
|
+
bought, **enumerating the vocabulary** so an agent stops inventing `pending` and `to-do` in
|
|
21
|
+
the same run, injecting what the model cannot know, and flexible-while-learning versus
|
|
22
|
+
strict-in-production. Plus the three things reasoning models changed: **do not add
|
|
23
|
+
chain-of-thought** (it can degrade instruction-following), give goals rather than
|
|
24
|
+
procedures, and treat reasoning effort as a per-stage dial.
|
|
25
|
+
|
|
26
|
+
- **`references/tools.md`** — the agent–computer interface. Fewer tools than instinct
|
|
27
|
+
suggests, namespacing, and the description as the product: a worked before/after where the
|
|
28
|
+
strong version names *when*, *what it costs*, *how to narrow*, and **the neighbouring tool
|
|
29
|
+
it is confused with** — the highest-value sentence in a tool definition and the one almost
|
|
30
|
+
nobody writes. Then meaning over identifiers, token efficiency as a correctness issue,
|
|
31
|
+
errors that teach, and **poka-yoke** — changing the interface so the wrong call cannot be
|
|
32
|
+
made.
|
|
33
|
+
|
|
34
|
+
- **`references/techniques.md`** — fifteen techniques with a **verdict each for a production
|
|
35
|
+
loop**, not a benchmark score. ReAct is the agent loop and its under-quoted failure is that
|
|
36
|
+
non-informative results derail it; reflection is strong exactly where a cheap objective
|
|
37
|
+
signal exists and is a second opinion from the same source where it does not; Tree of
|
|
38
|
+
Thoughts is almost never worth its combinatorics. Ends with an ordered five-question
|
|
39
|
+
chooser.
|
|
40
|
+
|
|
41
|
+
- **`references/layers.md`** — the question that resolves most framework arguments (*which
|
|
42
|
+
layer am I working at*), what a harness owns, and the design position that **permission
|
|
43
|
+
boundaries usually belong to the environment**: a harness that also claims to be a sandbox
|
|
44
|
+
is claiming a guarantee it cannot keep from inside the same process.
|
|
45
|
+
|
|
46
|
+
- **`references/audit.md`** — seven tracks, three evidence tiers (**measured / documented /
|
|
47
|
+
judgement**, never inflated), computed priority, and a report shape that ends in a plan
|
|
48
|
+
rather than a score. The finding that ends most audits early is stated first: no evals
|
|
49
|
+
makes everything downstream unfalsifiable, including the audit.
|
|
50
|
+
|
|
51
|
+
- **`scripts/audit_agent.py`** — the mechanical half. Five conservative detectors
|
|
52
|
+
(unbounded loop, empty tool description, swallowed error, missing timeout, duplicated
|
|
53
|
+
model literal), each requiring the file to show **two** independent signs of an agent, each
|
|
54
|
+
finding carrying `file:line`. It always prints **what it cannot see** and a **denominator**,
|
|
55
|
+
because `read: 1` alone looks like a broken pass while `1 of 4261` is itself a finding.
|
|
56
|
+
Virtualenvs are skipped by their `pyvenv.cfg` marker rather than by name — a real
|
|
57
|
+
repository met during testing kept 4249 of its 4261 files in `myenv/`, and was excluded
|
|
58
|
+
only because `site-packages` happened to be listed too.
|
|
59
|
+
|
|
60
|
+
### Changed
|
|
61
|
+
|
|
62
|
+
- **`PROTOCOL_PINNED` now covers `agent-harness`.** Its references document guidance that
|
|
63
|
+
moves, so each carries `**Spec pinned:** … · read <date>` and the build fails without it.
|
|
64
|
+
- **CI runs the scanner's own self-test**, and asserts that a real-tree run discloses both
|
|
65
|
+
its blind-spot list and its denominator — a scanner that could stop disclosing would be a
|
|
66
|
+
scanner nobody could calibrate.
|
|
67
|
+
|
|
68
|
+
|
|
3
69
|
## v0.7.2 — the plants say whether they landed, and two of them were not
|
|
4
70
|
|
|
5
71
|
Eight negative self-tests asserted inline, in Python, that their edit had happened —
|
package/README.md
CHANGED
|
@@ -9,9 +9,10 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
|
|
|
9
9
|
|
|
10
10
|
## What is in here
|
|
11
11
|
|
|
12
|
-
|
|
13
|
-
behaves, `agent-interop` for everything it talks to outside its own process
|
|
14
|
-
|
|
12
|
+
Four skills — `agent-orchestrator` for wiring the loop, `agent-evals` for proving it
|
|
13
|
+
behaves, `agent-interop` for everything it talks to outside its own process,
|
|
14
|
+
`agent-harness` for what it is **told** — and sixteen references they load on demand,
|
|
15
|
+
plus one scanner.
|
|
15
16
|
|
|
16
17
|
**The orchestrator** (`SKILL.md`) — what the agent reads first:
|
|
17
18
|
|
|
@@ -56,6 +57,23 @@ undoes both), `mcp-ship.md` (mounting, and the 404 that is really a double path)
|
|
|
56
57
|
an API gateway does not). Plus a link map, and a verdict on each neighbouring standard —
|
|
57
58
|
ACP, AGNTCY, AP2, Agent Skills — so an agent stops guessing.
|
|
58
59
|
|
|
60
|
+
**The harness skill** (`agent-harness/SKILL.md`) — the layer between the loop and the
|
|
61
|
+
model, and the one where most agent bugs actually live: *the biggest performance
|
|
62
|
+
improvements often come from clearly explaining tool usage in the system prompt*, and *even
|
|
63
|
+
small refinements to tool descriptions can yield dramatic improvements*. Before adding a
|
|
64
|
+
retry or a sub-agent, it asks four questions about the text. Five references —
|
|
65
|
+
`system-prompt.md` (right altitude, enumerated vocabulary, and the three things reasoning
|
|
66
|
+
models changed — starting with **do not add chain-of-thought**), `tools.md` (the
|
|
67
|
+
agent–computer interface, with a worked before/after and poka-yoke), `techniques.md`
|
|
68
|
+
(fifteen techniques, a verdict each **for production** rather than a benchmark),
|
|
69
|
+
`layers.md` (which layer you are building at, and why permission boundaries are usually the
|
|
70
|
+
environment's job), `audit.md` (seven tracks, evidence tiers, a plan instead of a score).
|
|
71
|
+
|
|
72
|
+
It runs in both directions: **building a harness and auditing somebody else's are one
|
|
73
|
+
checklist read forwards and backwards.** `scripts/audit_agent.py` is the mechanical half —
|
|
74
|
+
five conservative detectors, and it always prints what it *cannot* see plus a denominator,
|
|
75
|
+
so its silence is never read as a pass.
|
|
76
|
+
|
|
59
77
|
**`references/context-engineering.md`** — what the loop gives up when the window
|
|
60
78
|
runs out: the five-rung compaction ladder and why to re-measure between rungs,
|
|
61
79
|
the tool-pair boundary invariant, typed carryover blocks copied across the
|
|
@@ -139,6 +157,10 @@ audit row has to carry to prove a control was on.
|
|
|
139
157
|
trajectory rather than a final answer, turning a production failure into a
|
|
140
158
|
permanent fixture, calibrating a judge, gating a release on offline evals.
|
|
141
159
|
|
|
160
|
+
`agent-harness`: writing or fixing a system prompt, shaping tools so the model picks the
|
|
161
|
+
right one, choosing between ReAct, reflection, planning and voting — or auditing an agent
|
|
162
|
+
system somebody else built. Not the loop's plumbing, its evals, or its protocols.
|
|
163
|
+
|
|
142
164
|
`agent-interop`: building or consuming an MCP server, exposing or calling another
|
|
143
165
|
agent over A2A, publishing to the MCP Registry, or putting a gateway in front of
|
|
144
166
|
agent traffic. Not for designing one server's tool set — that is a design problem,
|
|
@@ -147,7 +169,7 @@ construction, which is `make-skill`. That boundary runs both ways: `make-skill`
|
|
|
147
169
|
what changes *because you are writing a skill*, and the protocol itself is described
|
|
148
170
|
here and nowhere else in the family.
|
|
149
171
|
|
|
150
|
-
None of the
|
|
172
|
+
None of the four triggers for a single LLM call in a script or for prompt wording —
|
|
151
173
|
that is not an orchestrator, and pulling this much doctrine for it is how a skill
|
|
152
174
|
teaches you to route around it.
|
|
153
175
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Production patterns for AI agent orchestrators \u2014 tool-calling loops, multi-stage pipelines with checkpoints, LLM provider routing with fallback, four-layer memory with confidence decay \u2014 plus the wallet side of reselling LLM access. This package is the installer CLI.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"agent-stack": "bin/agent-stack.js"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "agent-stack",
|
|
3
3
|
"displayName": "Agent Stack",
|
|
4
4
|
"description": "Two skills: agent-orchestrator \u2014 tool-calling loops, multi-stage pipelines with checkpoints, provider routing with fallback, four-layer memory, context engineering, plus the wallet side of reselling LLM access; and agent-evals \u2014 run/trace/thread evals, judges, and fixtures grown from production.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.8.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-harness
|
|
3
|
+
description: >-
|
|
4
|
+
Use when the question is what the agent is TOLD rather than how its loop is wired — writing
|
|
5
|
+
or fixing a system prompt, shaping tools so the model actually picks the right one,
|
|
6
|
+
deciding whether a job wants a workflow or an agent, or choosing between ReAct, reflection,
|
|
7
|
+
planning and voting. Also the audit direction: reviewing an agent system somebody else
|
|
8
|
+
built, with tracks, evidence tiers and a prioritized plan instead of a score, plus a
|
|
9
|
+
scanner for the defects that are mechanically visible. Triggers - "system prompt", "tool
|
|
10
|
+
description", "the agent picks the wrong tool", "agent loops forever", "prompt engineering",
|
|
11
|
+
"ReAct", "reflection", "workflow or agent", "audit this agent", "review our agent system",
|
|
12
|
+
"системный промпт", "агент не вызывает тул", "аудит агента", "воркфлоу или агент". Not for
|
|
13
|
+
the loop's plumbing, its evals, or the protocols it speaks — those are the sibling skills.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Agent harness — what the agent is told, and how to audit what someone else told theirs
|
|
17
|
+
|
|
18
|
+
`agent-orchestrator` wires the loop. `agent-evals` proves it behaves. `agent-interop` gets
|
|
19
|
+
it talking to other processes. **This skill is the layer between them and the model: the
|
|
20
|
+
prompt, the tools, and the shape of the work.**
|
|
21
|
+
|
|
22
|
+
It runs in both directions. Building one and auditing one are the same checklist read
|
|
23
|
+
forwards and backwards, which is why they live together here.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Rule zero — most agent bugs are prompt bugs wearing a stack trace
|
|
28
|
+
|
|
29
|
+
The instinct when an agent misbehaves is to change the code. The measured reality, in every
|
|
30
|
+
source this skill was built from, is that the largest behavioural changes come from the
|
|
31
|
+
text: **"the biggest performance improvements often come from clearly explaining tool usage
|
|
32
|
+
in the system prompt"**, and **"even small refinements to tool descriptions can yield
|
|
33
|
+
dramatic improvements."**
|
|
34
|
+
|
|
35
|
+
Before adding a retry, a router, or a sub-agent, check in this order:
|
|
36
|
+
|
|
37
|
+
1. **Does the tool description say when to use it, not just what it does?**
|
|
38
|
+
2. **Does the system prompt name the vocabulary?** An agent told to track status will invent
|
|
39
|
+
`pending` and `to-do` and `done` and `completed` in the same run unless the allowed values
|
|
40
|
+
are enumerated.
|
|
41
|
+
3. **Does the agent know today's date?** A model with a training cutoff will answer from
|
|
42
|
+
memory rather than search unless the current date is injected.
|
|
43
|
+
4. **Is the instruction flexible where it should be strict?** *"Use the tools in the order
|
|
44
|
+
that makes most sense to you"* is right while you are learning the task and wrong in
|
|
45
|
+
production, where *"you MUST execute a web search for each task"* is what stops a step
|
|
46
|
+
from being skipped.
|
|
47
|
+
|
|
48
|
+
Only then reach for architecture. Reaching for it first is how a prompt defect becomes a
|
|
49
|
+
permanent structural cost.
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Workflow or agent — decide this before anything else
|
|
54
|
+
|
|
55
|
+
An **agent** dynamically directs its own process. A **workflow** follows predefined code
|
|
56
|
+
paths. The choice is not about sophistication; it is about whether the number of steps is
|
|
57
|
+
knowable in advance.
|
|
58
|
+
|
|
59
|
+
| Build a workflow when | Build an agent when |
|
|
60
|
+
|---|---|
|
|
61
|
+
| requirements are clear and stable | the task is open-ended or exploratory |
|
|
62
|
+
| predictability and explicit control matter | flexibility outweighs predictability |
|
|
63
|
+
| debugging and cost control are priorities | adaptive reasoning across variables is needed |
|
|
64
|
+
| you can name every step now | step count is unpredictable and cannot be hardcoded |
|
|
65
|
+
|
|
66
|
+
**Start at the simplest thing that works, and stop there.** An agent adds latency, cost and
|
|
67
|
+
a class of failure a workflow does not have — it needs *trust in its own decisions*. Pay for
|
|
68
|
+
that only where a fixed path genuinely cannot be written.
|
|
69
|
+
|
|
70
|
+
### The five workflow patterns, before you reach for autonomy
|
|
71
|
+
|
|
72
|
+
| Pattern | Shape | Reach for it when |
|
|
73
|
+
|---|---|---|
|
|
74
|
+
| **Prompt chaining** | sequential calls, each on the last output, with programmatic checks between | the task decomposes into fixed steps — outline then draft, draft then translate |
|
|
75
|
+
| **Routing** | classify the input, send it to a specialist | categories are distinct and each wants its own prompt |
|
|
76
|
+
| **Parallelization** | *sectioning* (independent subtasks at once) or *voting* (same task N times) | subtasks are independent, or confidence needs more than one sample |
|
|
77
|
+
| **Orchestrator–workers** | a central model decomposes and delegates, then synthesizes | the subtasks **cannot be predefined** — this is the honest boundary with routing |
|
|
78
|
+
| **Evaluator–optimizer** | one model produces, another critiques, loop | clear evaluation criteria exist and iteration measurably helps |
|
|
79
|
+
|
|
80
|
+
**Orchestrator–workers versus routing is the distinction people get wrong.** Routing picks
|
|
81
|
+
from a known set. Orchestration invents the set per request. If you can enumerate the
|
|
82
|
+
branches, you wanted routing and it is cheaper.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## References
|
|
87
|
+
|
|
88
|
+
Each opens with its own **Load this when** line and a revision stamp — this material moves,
|
|
89
|
+
and `test/validate.py` fails the build on a reference that does not say when it was read.
|
|
90
|
+
|
|
91
|
+
| File | Read it when |
|
|
92
|
+
|---|---|
|
|
93
|
+
| [`references/system-prompt.md`](references/system-prompt.md) | you are **writing or fixing the prompt** — altitude, structure, vocabulary, dynamic context, and what changes for reasoning models |
|
|
94
|
+
| [`references/tools.md`](references/tools.md) | the model **picks the wrong tool, or none** — the agent–computer interface: how many, named how, described how, returning what |
|
|
95
|
+
| [`references/techniques.md`](references/techniques.md) | you are choosing between **ReAct, reflection, voting, planning** and the rest — every entry carries a verdict for production, not a benchmark score |
|
|
96
|
+
| [`references/layers.md`](references/layers.md) | deciding **what your harness owns** — kernel, workbench and product layers, and why permission boundaries are usually somebody else's job |
|
|
97
|
+
| [`references/audit.md`](references/audit.md) | reviewing **an agent system you did not build** — seven tracks, evidence tiers, and a prioritized plan |
|
|
98
|
+
|
|
99
|
+
**`scripts/audit_agent.py`** — the mechanical half of the audit. It finds what is visible
|
|
100
|
+
without understanding intent (an unbounded loop, a tool with no description, a swallowed
|
|
101
|
+
tool error, a hardcoded model, a missing timeout) and **prints the list of things it cannot
|
|
102
|
+
see**, so its silence is never read as a pass.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Auditing an agent system — the short version
|
|
107
|
+
|
|
108
|
+
The long version is `references/audit.md`. The shape:
|
|
109
|
+
|
|
110
|
+
1. **Run the scanner first.** It is cheap, and its blind-spot list tells you what the rest of
|
|
111
|
+
the audit must cover by hand.
|
|
112
|
+
2. **Walk the seven tracks** — prompt, tools, control flow, context, failure, permission,
|
|
113
|
+
evidence — and record a finding only with an observation attached.
|
|
114
|
+
3. **Tier every recommendation** by what backs it: measured here, documented upstream, or
|
|
115
|
+
judgement.
|
|
116
|
+
4. **Output a prioritized plan, not a score.** A number tells nobody what to change on
|
|
117
|
+
Monday. This is the same rule `agent-evals` applies to eval rubrics and
|
|
118
|
+
`seo-aeo-audit` to sites.
|
|
119
|
+
|
|
120
|
+
**The finding that ends most audits early:** the system has no evals. Everything downstream
|
|
121
|
+
is then unfalsifiable — including this audit. Say so first, and make it the first item.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## Boundaries
|
|
126
|
+
|
|
127
|
+
**Against `agent-orchestrator`.** That skill owns the loop's *plumbing*: iteration guards,
|
|
128
|
+
trimming, sub-agent dispatch, provider routing, memory layers, checkpoints. This one owns
|
|
129
|
+
what the model is *told*. They meet in one place:
|
|
130
|
+
`agent-orchestrator/references/context-engineering.md` covers **compaction** — what to drop
|
|
131
|
+
when the window fills — while this skill's `system-prompt.md` covers what to put there in
|
|
132
|
+
the first place. Filling and emptying, two files.
|
|
133
|
+
|
|
134
|
+
**Against `agent-evals`.** That skill measures whether an agent behaves, from execution
|
|
135
|
+
records. This one reviews how it was *built*, from its source and prompts. An audit that
|
|
136
|
+
finds no evals hands over to it; an eval suite that keeps failing on the same axis hands
|
|
137
|
+
back here.
|
|
138
|
+
|
|
139
|
+
**Against `agent-interop`.** MCP, A2A, the registry, gateways — the wire between processes.
|
|
140
|
+
Tool *descriptions* are here; tool *protocol* is there.
|
|
141
|
+
|
|
142
|
+
**Not covered:** model choice and pricing (see the `claude-api` skill for Anthropic's), the
|
|
143
|
+
wallet under resale (`agent-orchestrator/references/llm-proxy-billing.md`), and RAG
|
|
144
|
+
retrieval quality, which is a search problem this skill only touches where it enters the
|
|
145
|
+
prompt.
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Checklist — a harness worth shipping
|
|
150
|
+
|
|
151
|
+
- [ ] Workflow-versus-agent decided deliberately, and the simpler option was actually tried
|
|
152
|
+
- [ ] System prompt at the **right altitude** — heuristics, not hardcoded branches, not vague hope
|
|
153
|
+
- [ ] Every status, category and enum the agent must produce is **enumerated in the prompt**
|
|
154
|
+
- [ ] Today's date, and any other volatile context, injected rather than assumed
|
|
155
|
+
- [ ] Tools: a few high-impact ones, namespaced, each described as if to a new colleague
|
|
156
|
+
- [ ] Tool responses carry **meaning, not identifiers**, and are paginated or truncated by default
|
|
157
|
+
- [ ] Tool errors **teach the next attempt** instead of restating a stack trace
|
|
158
|
+
- [ ] One technique chosen per problem, with a reason — not ReAct because it was in a paper
|
|
159
|
+
- [ ] Sub-agents return **distilled summaries**, not transcripts
|
|
160
|
+
- [ ] The agent can be observed: which tool, which arguments, which observation, how many tokens
|
|
161
|
+
- [ ] An eval exists before the prompt is tuned, or the tuning is folklore
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# Auditing an agent system
|
|
2
|
+
|
|
3
|
+
**Load this when:** reviewing an agent system you did not build — a due-diligence pass, an
|
|
4
|
+
inherited codebase, or a "why is this unreliable" investigation.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** the tracks below are this pack's synthesis of Anthropic agent guidance, `promptingguide.ai` and the sibling skills · read 2026-08-14
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- What this audit produces
|
|
11
|
+
- Run the scanner first
|
|
12
|
+
- Seven tracks
|
|
13
|
+
- Evidence tiers
|
|
14
|
+
- Priority, computed
|
|
15
|
+
- The report
|
|
16
|
+
- Traps
|
|
17
|
+
|
|
18
|
+
## What this audit produces
|
|
19
|
+
|
|
20
|
+
**A prioritized change plan, with an observation behind every finding.** Not a score.
|
|
21
|
+
|
|
22
|
+
A number compresses away the only useful information — *what to change on Monday* — and
|
|
23
|
+
invites arguing about the number. This is the same rule `agent-evals` applies to eval
|
|
24
|
+
rubrics and `seo-aeo-audit` applies to sites, and it is the family's position rather than a
|
|
25
|
+
preference: **pass/fail with a named failure condition beats a scalar that names no fix.**
|
|
26
|
+
|
|
27
|
+
## Run the scanner first
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
python3 scripts/audit_agent.py <path> # human-readable
|
|
31
|
+
python3 scripts/audit_agent.py <path> --json # machine-readable
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
It finds only what is mechanically visible, and **it prints the list of what it cannot see.**
|
|
35
|
+
Read that list: it is the agenda for the manual half. A scanner that goes quiet is reporting
|
|
36
|
+
its own blindness, and an audit that stops there has audited the scanner.
|
|
37
|
+
|
|
38
|
+
## Seven tracks
|
|
39
|
+
|
|
40
|
+
Walk them in order. Later tracks assume earlier ones.
|
|
41
|
+
|
|
42
|
+
### 1 — Prompt
|
|
43
|
+
|
|
44
|
+
- Is there a system prompt under version control, or is it a string literal edited in place?
|
|
45
|
+
- **Altitude**: hardcoded branches (brittle) or vague hope (useless)? See `system-prompt.md`.
|
|
46
|
+
- Are the values the agent must emit **enumerated**, or invented per run?
|
|
47
|
+
- Is volatile context — the date above all — **injected**?
|
|
48
|
+
- Is tool *policy* in the prompt, or only tool *schema*?
|
|
49
|
+
- Has it been pruned, or only appended to since the first incident?
|
|
50
|
+
|
|
51
|
+
### 2 — Tools
|
|
52
|
+
|
|
53
|
+
- How many? Can the team say, without hesitating, which applies to a borderline case?
|
|
54
|
+
- Are descriptions written **for a model choosing under uncertainty**, or for a human reading docs?
|
|
55
|
+
- Do responses return **meaning or identifiers**?
|
|
56
|
+
- Is there a default limit on response size, or only an optional one?
|
|
57
|
+
- Do errors **name the next action**?
|
|
58
|
+
- Are destructive tools guarded by shape (`confirm: true`, absolute paths, enums) rather than by instruction?
|
|
59
|
+
|
|
60
|
+
### 3 — Control flow
|
|
61
|
+
|
|
62
|
+
- **Workflow or agent — and was that decided, or defaulted?** An agent where a chain would do is the most expensive finding on this list.
|
|
63
|
+
- Is there a **bounded iteration guard**, and what happens at the bound — a partial answer, or nothing?
|
|
64
|
+
- Are retries and fallbacks **multiplied**? Three providers × three retries is nine calls for one prompt.
|
|
65
|
+
- Is there loop detection, or does a repeated near-identical tool call run until the budget does?
|
|
66
|
+
- Do sub-agents return **distilled summaries** or transcripts?
|
|
67
|
+
|
|
68
|
+
### 4 — Context
|
|
69
|
+
|
|
70
|
+
- Does anything measure window usage **before** a request fails?
|
|
71
|
+
- Is there a compaction strategy, and does it preserve **decisions and open questions** rather than the discussion?
|
|
72
|
+
- Can a large tool result be **offloaded** and referenced, or does it land in the window whole?
|
|
73
|
+
- Is memory a design, or the conversation history by default?
|
|
74
|
+
|
|
75
|
+
### 5 — Failure
|
|
76
|
+
|
|
77
|
+
- What happens when a tool errors — is it distinguished from a tool returning nothing?
|
|
78
|
+
- Under MCP, is `isError: true` handled, or does a 200 read as success?
|
|
79
|
+
- Is there a timeout on every external call?
|
|
80
|
+
- Is degradation **honest** — does the user learn the answer is partial?
|
|
81
|
+
- Is there a path where the agent silently does nothing and reports success?
|
|
82
|
+
|
|
83
|
+
### 6 — Permission
|
|
84
|
+
|
|
85
|
+
- **Which layer owns the boundary?** If the harness delegates it (`layers.md`), audit the surroundings instead of filing a finding.
|
|
86
|
+
- Is tool access differentiated per caller, or is one credential shared by every path?
|
|
87
|
+
- Is tool output treated as **untrusted input**?
|
|
88
|
+
- Can an audit row prove a control was applied — does it carry the **policy version**?
|
|
89
|
+
- Is there a deterministic limit anywhere consequential, or only probabilistic content checks?
|
|
90
|
+
|
|
91
|
+
### 7 — Evidence
|
|
92
|
+
|
|
93
|
+
- **Are there evals?** If not, this is finding number one and everything else is unfalsifiable.
|
|
94
|
+
- Do they judge the **trajectory**, or only the final answer?
|
|
95
|
+
- Has any production failure become a permanent fixture?
|
|
96
|
+
- Is a judge calibrated against human labels, or trusted because it is a judge?
|
|
97
|
+
- Can a past run be replayed — is the execution record durable?
|
|
98
|
+
|
|
99
|
+
## Evidence tiers
|
|
100
|
+
|
|
101
|
+
Every finding carries one, and the tier is part of the finding:
|
|
102
|
+
|
|
103
|
+
| Tier | Means | Example |
|
|
104
|
+
|---|---|---|
|
|
105
|
+
| **Measured** | observed here, in this system, with the observation attached | "`agent.py:212` — the `while` has no bound; a repeated call ran 47 times in the log at `logs/2026-08-02`" |
|
|
106
|
+
| **Documented** | the upstream source says so, and this system contradicts it | "descriptions restate the name; Anthropic's tool guidance calls this the highest-leverage fix" |
|
|
107
|
+
| **Judgement** | experience, no measurement available here | "two tools look interchangeable to us" |
|
|
108
|
+
|
|
109
|
+
**Never present judgement as measured.** A finding whose tier is honest survives the meeting
|
|
110
|
+
where it is challenged; one that is inflated loses the whole report.
|
|
111
|
+
|
|
112
|
+
## Priority, computed
|
|
113
|
+
|
|
114
|
+
`P = blast × confidence / effort`
|
|
115
|
+
|
|
116
|
+
- **blast** 3 = a user of the system is harmed · 2 = the operator · 1 = a future maintainer
|
|
117
|
+
- **confidence** 3 = measured · 2 = documented · 1 = judgement
|
|
118
|
+
- **effort** 1 = under an hour · 2 = a session · 3 = its own project
|
|
119
|
+
|
|
120
|
+
Computed, not felt — so a dramatic finding nobody can act on ranks below a boring one that
|
|
121
|
+
is fixed before lunch, and the ranking can be argued with on its inputs.
|
|
122
|
+
|
|
123
|
+
## The report
|
|
124
|
+
|
|
125
|
+
1. **One paragraph** — what the system is, which layer, and the single most important thing.
|
|
126
|
+
2. **The scanner output**, including its blind-spot list, verbatim.
|
|
127
|
+
3. **Findings by track**, each with observation, tier and priority.
|
|
128
|
+
4. **The plan** — ordered by P, with the first three items sized so they can start immediately.
|
|
129
|
+
5. **What was not looked at**, and why. An audit that does not say where it stopped is read
|
|
130
|
+
as complete.
|
|
131
|
+
|
|
132
|
+
## Traps
|
|
133
|
+
|
|
134
|
+
- **Auditing the code and not the prompt.** The prompt is the largest behavioural surface and
|
|
135
|
+
is often not in the repository at all — ask where it lives before concluding it is fine.
|
|
136
|
+
- **Filing "no permission model" against a harness that delegates by design.** Check the
|
|
137
|
+
layer first (`layers.md`).
|
|
138
|
+
- **Grading instead of planning.** A score ends the conversation the audit was meant to start.
|
|
139
|
+
- **Confusing "no evals" with "not measured yet."** It is the root finding; put it first,
|
|
140
|
+
because every other conclusion inherits it.
|
|
141
|
+
- **Reading a silent scanner as a clean system.** It is silent about what it can see.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# Layers — what your harness owns, and what it must not
|
|
2
|
+
|
|
3
|
+
**Load this when:** deciding what to build versus adopt, or comparing agent frameworks and
|
|
4
|
+
finding the comparison keeps sliding.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** the three-layer reading of Pi / Goose / OpenCode (gist `AIMOWAY/bd8007c8`); Anthropic agent guidance · read 2026-08-14
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- The question that resolves most framework arguments
|
|
11
|
+
- Three layers
|
|
12
|
+
- What a harness owns
|
|
13
|
+
- What a harness should delegate
|
|
14
|
+
- Choosing your layer
|
|
15
|
+
- Traps
|
|
16
|
+
|
|
17
|
+
## The question that resolves most framework arguments
|
|
18
|
+
|
|
19
|
+
"Which of these agent frameworks is better" is usually unanswerable because the candidates
|
|
20
|
+
sit at different heights. The question that resolves it:
|
|
21
|
+
|
|
22
|
+
> **Which layer of the agent stack am I trying to work at?**
|
|
23
|
+
|
|
24
|
+
Compare within a layer. Across layers, the comparison is a category error, and the argument
|
|
25
|
+
will not converge no matter how long it runs.
|
|
26
|
+
|
|
27
|
+
## Three layers
|
|
28
|
+
|
|
29
|
+
| Layer | What it is | Shape | Best when |
|
|
30
|
+
|---|---|---|---|
|
|
31
|
+
| **Kernel / harness** | the agent loop itself — runtime, LLM API abstraction, tool dispatch, a terminal UI | a toolkit you build *on* | you are building an agent product, or studying how agents actually work |
|
|
32
|
+
| **Workbench / orchestration** | a local environment: desktop app, CLI and API, extensions, workflows across many kinds of work | a product you *extend* | you want capability now, across coding, research and automation, without owning the loop |
|
|
33
|
+
| **Product agent** | a domain agent — most visibly coding: explore, plan, edit, test, with built-in modes | a product you *use* | the domain is the one it was built for |
|
|
34
|
+
|
|
35
|
+
The distinction is not quality. A kernel is *supposed* to be smaller than a workbench; that
|
|
36
|
+
is what makes it legible.
|
|
37
|
+
|
|
38
|
+
## What a harness owns
|
|
39
|
+
|
|
40
|
+
If you are building at the kernel layer, these are yours and nobody else's:
|
|
41
|
+
|
|
42
|
+
1. **The loop** — iterate, dispatch tools, decide when to stop. With a bounded iteration
|
|
43
|
+
guard, because an unbounded loop is the defect that costs money while looking like work.
|
|
44
|
+
2. **The model boundary** — one abstraction over providers, so the loop does not know which
|
|
45
|
+
vendor answered. See `agent-orchestrator` §6.
|
|
46
|
+
3. **Tool dispatch and the ACI** — registration, schemas, parallel versus sequential
|
|
47
|
+
execution, error surfacing. See `tools.md`.
|
|
48
|
+
4. **Context accounting** — knowing how full the window is *before* the request fails, and
|
|
49
|
+
what to do about it. See `agent-orchestrator/references/context-engineering.md`.
|
|
50
|
+
5. **Observability** — which tool, which arguments, which observation, how many tokens. If
|
|
51
|
+
this is missing, every other item becomes unfalsifiable.
|
|
52
|
+
6. **The interrupt/resume contract** — one mechanism, not two. See
|
|
53
|
+
`agent-orchestrator/references/runtime.md`.
|
|
54
|
+
|
|
55
|
+
## What a harness should delegate
|
|
56
|
+
|
|
57
|
+
The interesting stance, and it is a design position rather than an omission:
|
|
58
|
+
|
|
59
|
+
**Permission boundaries usually belong to the environment, not the harness.** Pi states this
|
|
60
|
+
explicitly — it provides the kernel and delegates sandboxing and permissions to whatever
|
|
61
|
+
surrounds it. Read as a weakness, it looks like a missing feature. Read as architecture, it
|
|
62
|
+
is a clear statement: *a harness that also claims to be a sandbox is claiming a guarantee it
|
|
63
|
+
cannot keep*, because it runs in the same process as the code it would be confining.
|
|
64
|
+
|
|
65
|
+
Two consequences worth stating plainly:
|
|
66
|
+
|
|
67
|
+
- **A harness advertising "safe tool execution" without an OS-level or container boundary is
|
|
68
|
+
advertising a preference, not a control.** MCP says the same about roots: servers *SHOULD*
|
|
69
|
+
respect them, and real enforcement is OS permissions and sandboxing.
|
|
70
|
+
- **Deciding the layer decides the audit.** If the harness delegates permission, the audit
|
|
71
|
+
looks at what surrounds it, and "the harness has no permission model" stops being a
|
|
72
|
+
finding and becomes a question about the deployment.
|
|
73
|
+
|
|
74
|
+
Also usually delegated: identity and secrets (a credential store, never the loop), durable
|
|
75
|
+
state (a database or a file system, not the conversation), and policy (see
|
|
76
|
+
`agent-orchestrator/references/governance.md` — permission, not protocol).
|
|
77
|
+
|
|
78
|
+
## Choosing your layer
|
|
79
|
+
|
|
80
|
+
- **Building a product with agentic features** → adopt a workbench or embed a kernel; do not
|
|
81
|
+
write a third loop. Loops are commodity; your tools and prompt are not.
|
|
82
|
+
- **Building an agent platform others build on** → the kernel layer is yours, and the ACI is
|
|
83
|
+
your product surface.
|
|
84
|
+
- **Automating your own work** → a product agent, and stop. The most common expensive mistake
|
|
85
|
+
is building a kernel to solve a workbench problem.
|
|
86
|
+
|
|
87
|
+
**The test that catches the mistake early:** if you cannot name a behaviour you need that the
|
|
88
|
+
layer above does not provide, you are building the layer for its own sake.
|
|
89
|
+
|
|
90
|
+
## Traps
|
|
91
|
+
|
|
92
|
+
- **Comparing across layers**, then choosing on a benchmark that only makes sense within one.
|
|
93
|
+
- **Writing a loop because the loop is the interesting part.** It is a week; the tools and
|
|
94
|
+
the prompt are the year.
|
|
95
|
+
- **Claiming a security boundary the layer cannot enforce.** Say what is enforced and by
|
|
96
|
+
what; a sandbox protects the host, not the sandbox.
|
|
97
|
+
- **Adopting a workbench and then fighting its opinions.** Its opinions are the product; if
|
|
98
|
+
you disagree with enough of them, you wanted the kernel.
|
|
99
|
+
- **Assuming the layer is stable.** These projects move; check what the current version
|
|
100
|
+
actually owns before designing around a division of labour you read about once.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# The system prompt — altitude, vocabulary, and what changes for reasoning models
|
|
2
|
+
|
|
3
|
+
**Load this when:** writing or fixing an agent's system prompt, or explaining why the same
|
|
4
|
+
prompt behaves differently across runs.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** Anthropic context-engineering and agent guidance; `promptingguide.ai` agents/* and guides/reasoning-llms · read 2026-08-14
|
|
7
|
+
|
|
8
|
+
## Contents
|
|
9
|
+
|
|
10
|
+
- The right altitude
|
|
11
|
+
- What actually belongs in there
|
|
12
|
+
- Enumerate the vocabulary
|
|
13
|
+
- Inject what the model cannot know
|
|
14
|
+
- Flexible while learning, strict in production
|
|
15
|
+
- Structure, examples, and the cost of formatting
|
|
16
|
+
- Reasoning models change three things
|
|
17
|
+
- Traps
|
|
18
|
+
|
|
19
|
+
## The right altitude
|
|
20
|
+
|
|
21
|
+
A system prompt fails in two directions and the middle is narrower than it looks.
|
|
22
|
+
|
|
23
|
+
**Too low** — hardcoded if-then branches for every case. It works on the cases you wrote and
|
|
24
|
+
is brittle everywhere else, and each new case costs another branch. You are writing a
|
|
25
|
+
program in prose, badly.
|
|
26
|
+
|
|
27
|
+
**Too high** — vague guidance that assumes a shared understanding the model does not have.
|
|
28
|
+
"Be helpful and use good judgement" tells it nothing it did not already believe.
|
|
29
|
+
|
|
30
|
+
The target: **specific enough to guide behaviour, flexible enough to give the model strong
|
|
31
|
+
heuristics.** A useful test — could a competent new colleague follow this without asking a
|
|
32
|
+
clarifying question, and without being insulted? If they would ask, it is too high. If they
|
|
33
|
+
would feel micromanaged into a corner where their judgement cannot help, it is too low.
|
|
34
|
+
|
|
35
|
+
## What actually belongs in there
|
|
36
|
+
|
|
37
|
+
In rough order of how much behaviour each buys:
|
|
38
|
+
|
|
39
|
+
1. **Tool usage instructions.** Not the tool schema — the *policy*. When to reach for which,
|
|
40
|
+
what order usually makes sense, what to do when one fails. The largest measured gains come
|
|
41
|
+
from here, and it is the part most teams leave to the schema alone.
|
|
42
|
+
2. **The role and its boundaries** — what this agent is for, and what it must hand off.
|
|
43
|
+
3. **The vocabulary** it must produce (below).
|
|
44
|
+
4. **Volatile context** it cannot know (below).
|
|
45
|
+
5. **Failure instructions** — what to do when a tool errors, when data is missing, when the
|
|
46
|
+
task is impossible. Absent, the model invents a recovery, and inventions are not uniform.
|
|
47
|
+
6. **Output contract** — shape, not prose about shape.
|
|
48
|
+
|
|
49
|
+
## Enumerate the vocabulary
|
|
50
|
+
|
|
51
|
+
**Be explicit about allowed values.** An agent asked to track task status will produce
|
|
52
|
+
`pending` in one turn and `to-do` in the next, `completed` here and `done` there — and any
|
|
53
|
+
code reading those strings now has a bug that appears intermittently and reads as
|
|
54
|
+
flakiness.
|
|
55
|
+
|
|
56
|
+
This generalises past status: every category, label, severity, priority or state the agent
|
|
57
|
+
emits should appear as an enumerated set in the prompt, or in the tool's parameter `enum`,
|
|
58
|
+
or both. It is the cheapest determinism available.
|
|
59
|
+
|
|
60
|
+
## Inject what the model cannot know
|
|
61
|
+
|
|
62
|
+
**Today's date is the canonical example**, and its absence has a specific failure signature:
|
|
63
|
+
the agent answers from training data instead of searching, confidently and with no error.
|
|
64
|
+
Inject the date and the behaviour changes without another word of instruction.
|
|
65
|
+
|
|
66
|
+
The general rule: anything volatile that the model would otherwise fill from memory —
|
|
67
|
+
current date, environment, tenant, available capabilities, the user's locale — is injected,
|
|
68
|
+
not assumed. A capability-aware prompt that lists only the tools actually connected beats a
|
|
69
|
+
static prompt describing tools that may be absent.
|
|
70
|
+
|
|
71
|
+
## Flexible while learning, strict in production
|
|
72
|
+
|
|
73
|
+
The same instruction should not survive the whole lifecycle.
|
|
74
|
+
|
|
75
|
+
- **While you are still learning what good looks like:** *"Use the tools in the order that
|
|
76
|
+
makes most sense to you."* This surfaces what the model thinks the task is, which is the
|
|
77
|
+
information you need.
|
|
78
|
+
- **Once the sequence is known and a skipped step is a defect:** *"You MUST execute a web
|
|
79
|
+
search for each task."* Flexibility here buys nothing and costs a silently missing step.
|
|
80
|
+
|
|
81
|
+
Teams get stuck at the first form because it felt elegant, then debug an agent that
|
|
82
|
+
"sometimes forgets" — which is not forgetting, it is permission.
|
|
83
|
+
|
|
84
|
+
## Structure, examples, and the cost of formatting
|
|
85
|
+
|
|
86
|
+
**Structure inputs and outputs** with delimiters, XML tags or JSON. Clear segmentation
|
|
87
|
+
reduces the class of error where the model treats data as instruction.
|
|
88
|
+
|
|
89
|
+
**Examples are worth more than description**, and the mistake is quantity. Curate a few
|
|
90
|
+
**diverse, canonical** examples rather than an exhaustive list of edge cases — the latter
|
|
91
|
+
reads as a lookup table and the model generalises from it badly.
|
|
92
|
+
|
|
93
|
+
**But formatting has a cost.** Keep formats close to natural internet text where you can;
|
|
94
|
+
elaborate escaping, deeply nested structures and unusual syntaxes spend the model's
|
|
95
|
+
attention on parsing rather than the task. Ask whether a human writing this by hand would
|
|
96
|
+
choose the format. If not, it is overhead.
|
|
97
|
+
|
|
98
|
+
## Reasoning models change three things
|
|
99
|
+
|
|
100
|
+
Treating a reasoning model like a completion model is now a common and expensive mistake.
|
|
101
|
+
|
|
102
|
+
1. **Do not add chain-of-thought instructions.** Native reasoning already happens.
|
|
103
|
+
Explicit step-by-step prompting is redundant and **can hurt instruction-following** —
|
|
104
|
+
which is the opposite of what the person adding it intends.
|
|
105
|
+
2. **Give goals, not procedures.** Be explicit about the high-level outcome and let the
|
|
106
|
+
model plan the route. Procedural micro-steps fight the thing you are paying for.
|
|
107
|
+
3. **Reasoning effort is a dial that did not exist before** — low/medium/high trades cost
|
|
108
|
+
against accuracy per call, so it is a per-stage decision, not a global setting.
|
|
109
|
+
|
|
110
|
+
Two more, worth knowing before you architect around them: **few-shot is still useful, but
|
|
111
|
+
mainly for output *format***, not for teaching the task; and **tool-calling remains weaker
|
|
112
|
+
in most reasoning models**, which is why the common shape is a reasoning model for planning
|
|
113
|
+
and a different one for execution.
|
|
114
|
+
|
|
115
|
+
## Traps
|
|
116
|
+
|
|
117
|
+
- **Growing the prompt instead of fixing it.** Every incident adds a sentence; nothing is
|
|
118
|
+
ever removed; a year later nobody can say which line does work. Prune on the same schedule
|
|
119
|
+
you add.
|
|
120
|
+
- **Describing tools twice**, in the schema and in the prompt, with the two drifting. Put
|
|
121
|
+
*policy* in the prompt and *contract* in the schema, and say which is which.
|
|
122
|
+
- **A prompt that assumes a tool exists.** Capability-aware assembly, or an explicit
|
|
123
|
+
fallback; never a promise the runtime may not keep.
|
|
124
|
+
- **Tuning the prompt with no eval.** You are optimising against the last thing you noticed.
|
|
125
|
+
See `agent-evals`.
|
|
126
|
+
- **One prompt for planning and execution.** Separation of concerns measurably improves
|
|
127
|
+
reliability and lets a cheaper model take the mechanical half.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# The technique catalogue, with a verdict on each
|
|
2
|
+
|
|
3
|
+
**Load this when:** choosing between ReAct, reflection, voting, planning and the rest — or
|
|
4
|
+
being asked why the system does not use one that appeared in a paper.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** `promptingguide.ai` techniques/* and guides/*; Anthropic agent guidance · read 2026-08-14
|
|
7
|
+
|
|
8
|
+
**How to read the verdicts.** Every technique here works somewhere; the column says whether
|
|
9
|
+
it earns its cost *in a production agent loop*, which is a narrower question than whether it
|
|
10
|
+
raised a benchmark. Costs are real: each of these multiplies calls, latency or context, and
|
|
11
|
+
several of them were measured on single-turn QA rather than a long-running agent.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- The catalogue
|
|
16
|
+
- ReAct, in detail
|
|
17
|
+
- Reflection, in detail
|
|
18
|
+
- Voting and self-consistency
|
|
19
|
+
- What reasoning models made redundant
|
|
20
|
+
- Choosing one
|
|
21
|
+
|
|
22
|
+
## The catalogue
|
|
23
|
+
|
|
24
|
+
| Technique | What it is | Verdict for a production agent loop |
|
|
25
|
+
|---|---|---|
|
|
26
|
+
| **Zero-shot** | instruction alone | **Default.** Start here; everything below is a cost you must justify |
|
|
27
|
+
| **Few-shot** | input→output exemplars | **Yes, for format.** Curate a few diverse canonical examples. It teaches shape far better than it teaches judgement, and an exhaustive list makes the model brittle |
|
|
28
|
+
| **Chain-of-thought** | "think step by step" | **Legacy on reasoning models — actively harmful there** (it can degrade instruction-following). Still useful on non-reasoning models for arithmetic and multi-constraint tasks |
|
|
29
|
+
| **ReAct** | interleaved thought → action → observation | **Yes — this is the agent loop.** Most harnesses implement it without naming it. See below for what it does not fix |
|
|
30
|
+
| **Reflexion / self-critique** | actor, evaluator, self-reflection, with episodic memory | **Selectively.** Real gains where a *cheap objective signal* exists — tests pass, query runs, schema validates. Without one, the model grades its own homework |
|
|
31
|
+
| **Self-consistency** | sample N, take the majority | **Rarely.** N× cost and latency for a single answer; needs a well-defined answer to vote on. Use for a high-stakes classification, not for a whole trajectory |
|
|
32
|
+
| **Tree of Thoughts** | explore and prune a branching search | **Almost never in production.** Combinatorial cost, and the pruning heuristic is usually the hard part you have not solved. A planner plus a bounded retry gets most of the value |
|
|
33
|
+
| **ART** (automatic reasoning + tool use) | select exemplars and tools from a task library automatically | **Watch.** The idea — a library of trajectories rather than a hand-written prompt — is where prompt maintenance is heading; the tooling is not settled |
|
|
34
|
+
| **Prompt chaining** | fixed sequence with checks between | **Yes — and prefer it over an agent** wherever the steps are knowable |
|
|
35
|
+
| **Meta prompting** | prompt about the structure of the task, not its content | **Occasionally.** Useful for generating scaffolds; not a loop technique |
|
|
36
|
+
| **Generate-knowledge** | elicit facts first, then answer | **No.** Retrieval solves the same problem with grounding; this invents plausible knowledge |
|
|
37
|
+
| **RAG** | retrieve, then generate | **Yes, and it is a search problem.** Its quality lives outside this skill; what belongs here is *just-in-time* retrieval — see below |
|
|
38
|
+
| **Just-in-time retrieval** | hold lightweight identifiers (paths, queries, links); load at runtime via tools | **Yes.** The scalable default: it mirrors how people work, and keeps the window for reasoning rather than for data |
|
|
39
|
+
| **Structured note-taking** | write notes to durable memory outside the window, read them back | **Yes for long tasks.** Persistent memory at low overhead, and it survives compaction — which a summary of the discussion does not |
|
|
40
|
+
| **Sub-agents** | specialists returning distilled summaries | **Yes where the sub-task has its own context need.** The value is the *isolated window*; a sub-agent returns a 1–2k-token distilled summary, never a transcript |
|
|
41
|
+
|
|
42
|
+
## ReAct, in detail
|
|
43
|
+
|
|
44
|
+
**Thought → Action → Observation**, repeated. Reasoning traces and task actions generated in
|
|
45
|
+
an interleaved way, so the plan updates on what the world actually returned.
|
|
46
|
+
|
|
47
|
+
What it fixes: plain chain-of-thought is isolated from external information and therefore
|
|
48
|
+
suffers **fact hallucination and error propagation** — it reasons confidently past a wrong
|
|
49
|
+
premise. ReAct grounds each step in an observation.
|
|
50
|
+
|
|
51
|
+
**What it does not fix, and this is under-quoted:**
|
|
52
|
+
|
|
53
|
+
- It **constrains reasoning flexibility** compared with free-form CoT — the format itself is
|
|
54
|
+
a cost.
|
|
55
|
+
- **Non-informative results derail it.** A search returning nothing useful leaves the model
|
|
56
|
+
struggling to reformulate, and it will loop on near-identical queries. This is the failure
|
|
57
|
+
your iteration guard exists for, and it is why tool errors must teach (`tools.md`).
|
|
58
|
+
- It is strongest **combined** with CoT and self-consistency rather than alone — which is
|
|
59
|
+
the honest reading of the paper and rarely the reading in a blog post.
|
|
60
|
+
|
|
61
|
+
## Reflection, in detail
|
|
62
|
+
|
|
63
|
+
Three roles, and naming them separates the ones people conflate:
|
|
64
|
+
|
|
65
|
+
| Role | Does | In practice |
|
|
66
|
+
|---|---|---|
|
|
67
|
+
| **Actor** | generates text and actions, produces a trajectory | your existing loop |
|
|
68
|
+
| **Evaluator** | scores the trajectory | **the part that decides whether this works at all** |
|
|
69
|
+
| **Self-reflection** | turns the score into verbal guidance stored for next time | an extra call, plus memory |
|
|
70
|
+
|
|
71
|
+
**The evaluator is the whole question.** Where the signal is objective and cheap — the test
|
|
72
|
+
suite ran, the SQL executed, the JSON validated, the build passed — reflection is one of the
|
|
73
|
+
strongest available techniques. Where the evaluator is the same model judging its own
|
|
74
|
+
output with no ground truth, you have added cost and a confident second opinion.
|
|
75
|
+
|
|
76
|
+
Stated limitations worth carrying: it depends on **accurate self-evaluation**, its memory is
|
|
77
|
+
typically a **sliding window**, and it struggles where correctness is non-deterministic.
|
|
78
|
+
|
|
79
|
+
## Voting and self-consistency
|
|
80
|
+
|
|
81
|
+
Sample the same task several times and take the majority. It genuinely reduces variance —
|
|
82
|
+
and it multiplies cost and latency by N, needs a discrete answer to vote on, and does
|
|
83
|
+
nothing for a long trajectory where the runs diverge at step three.
|
|
84
|
+
|
|
85
|
+
Reach for it on a **single high-stakes decision** — a routing classification, a safety
|
|
86
|
+
judgement, an extraction that everything downstream depends on. Not on a whole agent run.
|
|
87
|
+
|
|
88
|
+
## What reasoning models made redundant
|
|
89
|
+
|
|
90
|
+
A live shift, and it invalidates a lot of otherwise-good advice:
|
|
91
|
+
|
|
92
|
+
- **Do not instruct step-by-step thinking.** It is native, and explicit CoT can hurt
|
|
93
|
+
instruction-following.
|
|
94
|
+
- **Give high-level goals rather than procedures**, and let planning happen inside the model.
|
|
95
|
+
- **Reasoning effort** is now a per-call dial, which is a cheaper knob than most of the
|
|
96
|
+
techniques above.
|
|
97
|
+
- But **tool-calling stays weaker** in most reasoning models — so the common production shape
|
|
98
|
+
is a reasoning model that plans and a different model that executes tools. That is
|
|
99
|
+
*separation of concerns*, and it also lets the cheap half be cheap.
|
|
100
|
+
|
|
101
|
+
## Choosing one
|
|
102
|
+
|
|
103
|
+
Ask in this order, and stop at the first yes:
|
|
104
|
+
|
|
105
|
+
1. **Are the steps knowable?** → prompt chaining, not an agent.
|
|
106
|
+
2. **Is there a cheap objective signal?** → evaluator–optimizer or reflection.
|
|
107
|
+
3. **Does the sub-task need its own window?** → a sub-agent returning a distilled summary.
|
|
108
|
+
4. **Is one decision disproportionately expensive to get wrong?** → voting, on that decision only.
|
|
109
|
+
5. **Otherwise** → a plain ReAct loop with a bounded iteration guard, and spend the effort on
|
|
110
|
+
the tools and the prompt instead. That is where the measured gains are.
|
|
111
|
+
|
|
112
|
+
**The anti-pattern this section exists to prevent:** adopting a technique because it appears
|
|
113
|
+
in a paper, without naming the signal it consumes or the cost it adds. If you cannot say
|
|
114
|
+
what the evaluator measures, you are not doing reflection — you are paying for a second
|
|
115
|
+
opinion from the same source.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Tools — the agent–computer interface
|
|
2
|
+
|
|
3
|
+
**Load this when:** the model picks the wrong tool, calls none, calls one with bad arguments,
|
|
4
|
+
or you are deciding what to expose in the first place.
|
|
5
|
+
|
|
6
|
+
**Spec pinned:** Anthropic *Writing tools for agents* and *Building effective agents*; `promptingguide.ai` agents/function-calling · read 2026-08-14
|
|
7
|
+
|
|
8
|
+
Anthropic's framing is worth adopting whole: this is the **agent–computer interface**, and it
|
|
9
|
+
deserves the same craft a human interface gets. Most teams spend their effort on the model
|
|
10
|
+
and none on the ACI, then conclude the model is bad at tool use.
|
|
11
|
+
|
|
12
|
+
## Contents
|
|
13
|
+
|
|
14
|
+
- Fewer tools than you think
|
|
15
|
+
- Namespacing
|
|
16
|
+
- The description is the product
|
|
17
|
+
- Return meaning, not identifiers
|
|
18
|
+
- Token efficiency is a correctness issue
|
|
19
|
+
- Errors that teach
|
|
20
|
+
- Poka-yoke: make the wrong call impossible
|
|
21
|
+
- Evaluating tools
|
|
22
|
+
- Traps
|
|
23
|
+
|
|
24
|
+
## Fewer tools than you think
|
|
25
|
+
|
|
26
|
+
**More tools do not lead to better outcomes.** The reflex — wrap every API endpoint, ship
|
|
27
|
+
forty tools, let the model choose — produces an agent that chooses badly, because selection
|
|
28
|
+
degrades with the size of the set and every definition costs context.
|
|
29
|
+
|
|
30
|
+
Build **a few thoughtful tools targeting specific high-impact workflows.** The test:
|
|
31
|
+
consolidate where a human would. `search_and_summarize` beats `search` + `fetch` +
|
|
32
|
+
`summarize` when the three are always used together, because it removes two decisions and
|
|
33
|
+
two round trips.
|
|
34
|
+
|
|
35
|
+
**The honest check, and it is brutal:** *if your engineers cannot definitively say which
|
|
36
|
+
tool applies to a case, the model cannot either.* Ambiguity between two tools is a design
|
|
37
|
+
defect, not a prompting problem.
|
|
38
|
+
|
|
39
|
+
## Namespacing
|
|
40
|
+
|
|
41
|
+
Group related tools under a common prefix — `asana_search`, `asana_create_task`,
|
|
42
|
+
`jira_search`. Boundaries become visible, and the model stops crossing services by accident.
|
|
43
|
+
|
|
44
|
+
This also matters at the federation layer: behind a gateway, tool names commonly gain a
|
|
45
|
+
prefix from their source server, and a name that changes between sessions invalidates every
|
|
46
|
+
prompt and eval that referenced it. See `agent-interop/references/gateway.md`.
|
|
47
|
+
|
|
48
|
+
## The description is the product
|
|
49
|
+
|
|
50
|
+
**Even small refinements to tool descriptions yield dramatic improvements.** The rule that
|
|
51
|
+
makes them good: **write as if explaining to a new team member**, and make implicit context
|
|
52
|
+
explicit.
|
|
53
|
+
|
|
54
|
+
A description must answer **when and why**, not only what:
|
|
55
|
+
|
|
56
|
+
```jsonc
|
|
57
|
+
// weak — restates the name, and says nothing about choosing it
|
|
58
|
+
{ "name": "search_users", "description": "Searches for users." }
|
|
59
|
+
|
|
60
|
+
// strong — the model can now decide
|
|
61
|
+
{ "name": "search_users",
|
|
62
|
+
"description": "Find users by name, email or team. Use this before any operation \
|
|
63
|
+
that needs a user ID — IDs are never guessable. Returns at most 20 matches; narrow \
|
|
64
|
+
with `team` rather than paging when you can. Do NOT use for the current user: \
|
|
65
|
+
`get_current_user` is cheaper and always correct." }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Note what the strong version carries: **when to reach for it, what it costs, how to narrow,
|
|
69
|
+
and the neighbouring tool it is confused with.** That last clause is the highest-value
|
|
70
|
+
sentence in most tool descriptions and almost nobody writes it.
|
|
71
|
+
|
|
72
|
+
**Parameters carry their own guidance.** Use `enum` to constrain values rather than
|
|
73
|
+
describing the constraint in prose, give examples in parameter descriptions, and mark
|
|
74
|
+
required versus optional honestly — an optional parameter the tool actually needs is a
|
|
75
|
+
silent failure.
|
|
76
|
+
|
|
77
|
+
## Return meaning, not identifiers
|
|
78
|
+
|
|
79
|
+
Prioritise **contextual relevance over flexibility**. A response of
|
|
80
|
+
`{"id": "u_8f3a", "gid": "1209...", "rid": 44}` gives the model nothing to reason with; it
|
|
81
|
+
will echo identifiers into prose and hallucinate what they mean. Return
|
|
82
|
+
`{"name": "Ada Lovelace", "team": "Platform", "id": "u_8f3a"}` — the id stays for the next
|
|
83
|
+
call, the meaning arrives for the reasoning.
|
|
84
|
+
|
|
85
|
+
## Token efficiency is a correctness issue
|
|
86
|
+
|
|
87
|
+
Not merely a cost issue: a tool that returns 40,000 tokens of JSON has consumed the window
|
|
88
|
+
the agent needed to finish the task, and no amount of history trimming recovers it (see
|
|
89
|
+
`agent-orchestrator/references/context-engineering.md` → *tool-output offload*).
|
|
90
|
+
|
|
91
|
+
Build in **pagination, range selection, filtering and truncation — with sensible defaults**.
|
|
92
|
+
The default matters more than the capability: an agent will rarely opt into a limit it was
|
|
93
|
+
not given.
|
|
94
|
+
|
|
95
|
+
## Errors that teach
|
|
96
|
+
|
|
97
|
+
A tool error is a turn in a conversation. Compare:
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
Error: 422 Unprocessable Entity
|
|
101
|
+
```
|
|
102
|
+
```
|
|
103
|
+
Error: `due_date` must be ISO-8601 (e.g. 2026-08-14). You sent "next friday".
|
|
104
|
+
Call `resolve_date` first, or pass an absolute date.
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The second costs nothing extra and converts a dead end into a recovery. **Return informative
|
|
108
|
+
messages that help the agent recover or try an alternative** — naming the alternative is the
|
|
109
|
+
part that gets skipped.
|
|
110
|
+
|
|
111
|
+
Two structural notes: under MCP a failed tool arrives as a *successful* response carrying
|
|
112
|
+
`isError: true`, so code that only catches transport exceptions treats every tool failure as
|
|
113
|
+
a success containing an apology (`agent-interop/references/mcp.md`). And in code-mode
|
|
114
|
+
harnesses, generated wrappers should convert that into a thrown exception so model-authored
|
|
115
|
+
code can `try`/`catch`.
|
|
116
|
+
|
|
117
|
+
## Poka-yoke: make the wrong call impossible
|
|
118
|
+
|
|
119
|
+
Borrowed from manufacturing, and the highest-leverage idea here: **change the interface so
|
|
120
|
+
the mistake cannot be made**, rather than documenting the mistake.
|
|
121
|
+
|
|
122
|
+
- Absolute paths instead of relative ones, when relative paths get resolved against a
|
|
123
|
+
directory the agent guessed.
|
|
124
|
+
- An `enum` instead of a free-text field with a list of valid values in the description.
|
|
125
|
+
- One tool that does the two-step correctly instead of two tools that must be ordered.
|
|
126
|
+
- A required `confirm: true` on a destructive action, so a partially-formed call fails
|
|
127
|
+
closed.
|
|
128
|
+
|
|
129
|
+
## Evaluating tools
|
|
130
|
+
|
|
131
|
+
Tools deserve **thorough documentation and testing**, and testing means running the agent
|
|
132
|
+
against real tasks and reading which tool it picked, with what arguments, and what came
|
|
133
|
+
back. Enable intermediate-step visibility and look for the three recurring faults:
|
|
134
|
+
|
|
135
|
+
| Symptom | Almost always |
|
|
136
|
+
|---|---|
|
|
137
|
+
| wrong tool chosen | two descriptions do not distinguish themselves; add the "do NOT use for…" clause |
|
|
138
|
+
| bad arguments | the parameter description assumes context the model does not have, or the type is too loose |
|
|
139
|
+
| result misread | the response returned identifiers, or too much, or both |
|
|
140
|
+
|
|
141
|
+
Fix the interface, not the prompt, when the fault is in this table.
|
|
142
|
+
|
|
143
|
+
## Traps
|
|
144
|
+
|
|
145
|
+
- **Wrapping the API you have** instead of designing the tools the agent needs.
|
|
146
|
+
- **A description written for a human reading docs** rather than a model choosing under
|
|
147
|
+
uncertainty.
|
|
148
|
+
- **Unbounded responses** with an optional `limit` nobody sets.
|
|
149
|
+
- **Errors that are true and useless.** `null is not an object` names the symptom the model
|
|
150
|
+
can do nothing with.
|
|
151
|
+
- **Treating tool output as trusted.** It is attacker-controlled input if the server is; the
|
|
152
|
+
specification says descriptions and annotations are untrusted unless the server is.
|
|
153
|
+
- **Adding a tool to fix a prompt problem.** The set grows, selection degrades, and the
|
|
154
|
+
original defect is still there.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Mechanical half of an agent-system audit.
|
|
3
|
+
|
|
4
|
+
Finds only what is visible WITHOUT understanding intent, and prints what it cannot see —
|
|
5
|
+
because a scanner that goes quiet is reporting its own blindness, and an audit that stops
|
|
6
|
+
at a silent scanner has audited the scanner.
|
|
7
|
+
|
|
8
|
+
python3 audit_agent.py <path> human-readable
|
|
9
|
+
python3 audit_agent.py <path> --json machine-readable
|
|
10
|
+
python3 audit_agent.py --self-test plant each defect, require each to be found
|
|
11
|
+
|
|
12
|
+
Zero dependencies. Python 3.9+.
|
|
13
|
+
|
|
14
|
+
Design rule, and the reason this file is short: every detector is CONSERVATIVE. A false
|
|
15
|
+
positive costs more than a miss here, because an audit report that cries wolf is discarded
|
|
16
|
+
whole — and the seven tracks in `references/audit.md` cover by hand everything this cannot
|
|
17
|
+
reach. Detectors therefore require corroboration (the file must look agent-related) and
|
|
18
|
+
each finding carries `file:line` so a human can disagree with it in one click.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
import argparse
|
|
22
|
+
import json
|
|
23
|
+
import os
|
|
24
|
+
import re
|
|
25
|
+
import sys
|
|
26
|
+
|
|
27
|
+
# A file is "agent-related" only if it shows two independent signs. One is a coincidence:
|
|
28
|
+
# plenty of code says "message" or "prompt" without being an agent loop.
|
|
29
|
+
AGENTISH = [
|
|
30
|
+
re.compile(r"\btool[_ ]?call", re.I),
|
|
31
|
+
re.compile(r"\btools\s*=|\"tools\"\s*:|'tools'\s*:"),
|
|
32
|
+
re.compile(r"\bsystem[_ ]?prompt", re.I),
|
|
33
|
+
re.compile(r"\b(anthropic|openai|litellm|langchain|langgraph|bedrock|mistral)\b", re.I),
|
|
34
|
+
re.compile(r"\bfunction[_ ]?call", re.I),
|
|
35
|
+
re.compile(r"\bmessages\s*=\s*\[|\"messages\"\s*:"),
|
|
36
|
+
]
|
|
37
|
+
CODE_EXT = {".py", ".js", ".mjs", ".cjs", ".ts", ".tsx", ".jsx"}
|
|
38
|
+
SKIP_DIRS = {".git", "node_modules", "venv", ".venv", "__pycache__", "dist", "build",
|
|
39
|
+
".next", "target", "vendor", ".tox", "site-packages"}
|
|
40
|
+
MAX_BYTES = 400_000 # a generated bundle is not worth reading, and skews everything
|
|
41
|
+
|
|
42
|
+
# Model ids that are usually hardcoded by accident. Deliberately not exhaustive: this is a
|
|
43
|
+
# smell detector, and the finding says "pin it deliberately", not "this id is wrong".
|
|
44
|
+
MODEL_LITERAL = re.compile(
|
|
45
|
+
r"[\"']((?:claude|gpt|gemini|llama|mistral|deepseek|qwen)[-\w.]*\d[\w.-]*)[\"']", re.I)
|
|
46
|
+
|
|
47
|
+
FINDINGS = []
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def add(kind, path, line, detail, fix):
|
|
51
|
+
FINDINGS.append({"check": kind, "file": path, "line": line, "detail": detail, "fix": fix})
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def agentish(text):
|
|
55
|
+
return sum(1 for p in AGENTISH if p.search(text)) >= 2
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def walk(root):
|
|
59
|
+
for dirpath, dirnames, filenames in os.walk(root):
|
|
60
|
+
# Skip a virtualenv by its MARKER, not by its name. `venv`/`.venv` in SKIP_DIRS
|
|
61
|
+
# only catches the conventional names; a real repository met during testing used
|
|
62
|
+
# `myenv/`, holding 4249 of its 4261 code files, and was excluded only because
|
|
63
|
+
# `site-packages` happened to be listed too. Right by accident is not right.
|
|
64
|
+
dirnames[:] = [d for d in dirnames
|
|
65
|
+
if d not in SKIP_DIRS and not d.startswith(".")
|
|
66
|
+
and not os.path.exists(os.path.join(dirpath, d, "pyvenv.cfg"))]
|
|
67
|
+
for fn in filenames:
|
|
68
|
+
if os.path.splitext(fn)[1] not in CODE_EXT:
|
|
69
|
+
continue
|
|
70
|
+
full = os.path.join(dirpath, fn)
|
|
71
|
+
try:
|
|
72
|
+
if os.path.getsize(full) > MAX_BYTES:
|
|
73
|
+
continue
|
|
74
|
+
with open(full, encoding="utf-8", errors="replace") as fh:
|
|
75
|
+
text = fh.read()
|
|
76
|
+
except OSError:
|
|
77
|
+
continue
|
|
78
|
+
yield os.path.relpath(full, root), text
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------ detectors
|
|
82
|
+
|
|
83
|
+
def check_unbounded_loop(rel, text, lines):
|
|
84
|
+
"""`while True` in an agent file with no visible iteration bound.
|
|
85
|
+
|
|
86
|
+
Conservative twice over: the file must be agent-related, AND the file must not mention
|
|
87
|
+
any bound at all. A loop with `max_iter` somewhere else in the file is left alone.
|
|
88
|
+
"""
|
|
89
|
+
if re.search(r"max[_ ]?iter|max[_ ]?steps|max[_ ]?turns|iteration_limit|for\s+\w+\s+in\s+range\(",
|
|
90
|
+
text, re.I):
|
|
91
|
+
return
|
|
92
|
+
for i, l in enumerate(lines, 1):
|
|
93
|
+
if re.search(r"^\s*while\s+(True|true|1)\s*[:)]|^\s*while\s*\(\s*true\s*\)", l):
|
|
94
|
+
add("unbounded-loop", rel, i,
|
|
95
|
+
"`while True` in an agent file with no iteration bound anywhere in it",
|
|
96
|
+
"Add a max-iteration guard that composes a partial answer at the bound, "
|
|
97
|
+
"rather than returning nothing")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def check_tool_without_description(rel, text, lines):
|
|
101
|
+
"""A tool declared with an empty or missing description.
|
|
102
|
+
|
|
103
|
+
Only fires on an explicit empty string — a missing key is too easy to get wrong across
|
|
104
|
+
frameworks, and a wrong finding here is worse than a missed one.
|
|
105
|
+
"""
|
|
106
|
+
for i, l in enumerate(lines, 1):
|
|
107
|
+
if re.search(r"[\"']description[\"']\s*:\s*[\"']\s*[\"']", l) or \
|
|
108
|
+
re.search(r"\bdescription\s*=\s*[\"']\s*[\"']", l):
|
|
109
|
+
add("tool-no-description", rel, i,
|
|
110
|
+
"a tool description is the empty string",
|
|
111
|
+
"Describe WHEN and WHY to use it, and name the neighbouring tool it is "
|
|
112
|
+
"confused with — the highest-leverage sentence in a tool definition")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def check_swallowed_error(rel, text, lines):
|
|
116
|
+
"""An exception caught and discarded inside an agent file."""
|
|
117
|
+
for i, l in enumerate(lines, 1):
|
|
118
|
+
nxt = lines[i] if i < len(lines) else ""
|
|
119
|
+
if re.search(r"^\s*except[^\n]*:\s*$", l) and re.search(r"^\s*pass\s*$", nxt):
|
|
120
|
+
add("swallowed-error", rel, i,
|
|
121
|
+
"`except: pass` — the failure is invisible to the loop and to the model",
|
|
122
|
+
"Return an error the agent can act on; a tool error is a turn in the "
|
|
123
|
+
"conversation, not a silence")
|
|
124
|
+
if re.search(r"catch\s*\([^)]*\)\s*\{\s*\}", l):
|
|
125
|
+
add("swallowed-error", rel, i,
|
|
126
|
+
"empty `catch` block — the failure is discarded",
|
|
127
|
+
"Surface it to the loop; an error that teaches the next attempt costs "
|
|
128
|
+
"nothing extra")
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def check_no_timeout(rel, text, lines):
|
|
132
|
+
"""An outbound HTTP call with no timeout, in an agent file."""
|
|
133
|
+
for i, l in enumerate(lines, 1):
|
|
134
|
+
if re.search(r"\brequests\.(get|post|put|patch|delete)\s*\(", l) and "timeout" not in l:
|
|
135
|
+
add("no-timeout", rel, i,
|
|
136
|
+
"`requests` call with no `timeout=` — a hung provider hangs the agent",
|
|
137
|
+
"Set an explicit timeout on every external call, and decide what the loop "
|
|
138
|
+
"does when it fires")
|
|
139
|
+
if re.search(r"\burllib\.request\.urlopen\s*\(", l) and "timeout" not in l:
|
|
140
|
+
add("no-timeout", rel, i, "`urlopen` with no `timeout=`",
|
|
141
|
+
"Set an explicit timeout on every external call")
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def check_hardcoded_model(rel, text, lines):
|
|
145
|
+
"""A model id as a literal, in more than one place — the smell is duplication."""
|
|
146
|
+
hits = []
|
|
147
|
+
for i, l in enumerate(lines, 1):
|
|
148
|
+
if l.lstrip().startswith(("#", "//", "*")):
|
|
149
|
+
continue
|
|
150
|
+
m = MODEL_LITERAL.search(l)
|
|
151
|
+
if m:
|
|
152
|
+
hits.append((i, m.group(1)))
|
|
153
|
+
if len(hits) >= 2:
|
|
154
|
+
i, name = hits[0]
|
|
155
|
+
add("hardcoded-model", rel, i,
|
|
156
|
+
f"model id {name!r} appears as a literal {len(hits)}× in this file",
|
|
157
|
+
"Resolve the model from configuration at one boundary; three levels — request, "
|
|
158
|
+
"tenant, system default — is the shape that bills correctly")
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
CHECKS = [check_unbounded_loop, check_tool_without_description, check_swallowed_error,
|
|
162
|
+
check_no_timeout, check_hardcoded_model]
|
|
163
|
+
|
|
164
|
+
# What no static pass can reach. Printed every run, never suppressed.
|
|
165
|
+
BLIND = [
|
|
166
|
+
"whether the SYSTEM PROMPT is at the right altitude — or whether it is in this repo at all",
|
|
167
|
+
"whether two tool descriptions actually distinguish themselves to a model",
|
|
168
|
+
"whether the workflow/agent choice was made deliberately or defaulted to an agent",
|
|
169
|
+
"whether retries and fallbacks MULTIPLY (three providers x three retries is nine calls)",
|
|
170
|
+
"whether compaction preserves decisions and open questions, or keeps the discussion",
|
|
171
|
+
"whether tool output is treated as untrusted input",
|
|
172
|
+
"whether evals exist, judge trajectories, and are calibrated",
|
|
173
|
+
"whether an audit row could prove a control was applied (policy version)",
|
|
174
|
+
"everything in a language this pass does not read, and everything in configuration",
|
|
175
|
+
]
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def scan(root):
|
|
179
|
+
seen_files = considered = 0
|
|
180
|
+
for rel, text in walk(root):
|
|
181
|
+
considered += 1
|
|
182
|
+
if not agentish(text):
|
|
183
|
+
continue
|
|
184
|
+
seen_files += 1
|
|
185
|
+
lines = text.splitlines()
|
|
186
|
+
for c in CHECKS:
|
|
187
|
+
c(rel, text, lines)
|
|
188
|
+
return seen_files, considered
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def report_text(root, seen, considered):
|
|
192
|
+
# The denominator is not decoration. "read: 1" alone looks like a broken pass; "1 of
|
|
193
|
+
# 4261" says the repository is mostly not an agent, which is a finding in itself when
|
|
194
|
+
# somebody called it one.
|
|
195
|
+
out = [f"agent-audit: {root}",
|
|
196
|
+
f" code files considered: {considered}",
|
|
197
|
+
f" of those, agent-related: {seen}"]
|
|
198
|
+
if not seen:
|
|
199
|
+
out.append(" NOTHING READ — no file showed two independent signs of an agent loop.")
|
|
200
|
+
out.append(" That is a fact about this pass, not about the system. Check the path,")
|
|
201
|
+
out.append(" and whether the agent lives in a language or a config this cannot read.")
|
|
202
|
+
out.append("")
|
|
203
|
+
if FINDINGS:
|
|
204
|
+
out.append(f"FINDINGS ({len(FINDINGS)}) — each is a smell with a location, not a verdict:")
|
|
205
|
+
for f in FINDINGS:
|
|
206
|
+
out.append(f" {f['file']}:{f['line']} [{f['check']}]")
|
|
207
|
+
out.append(f" {f['detail']}")
|
|
208
|
+
out.append(f" fix: {f['fix']}")
|
|
209
|
+
else:
|
|
210
|
+
out.append("FINDINGS (0) — nothing mechanically visible.")
|
|
211
|
+
out.append("")
|
|
212
|
+
out.append("THIS PASS CANNOT SEE — the manual half of the audit, and it is the larger half:")
|
|
213
|
+
for b in BLIND:
|
|
214
|
+
out.append(f" - {b}")
|
|
215
|
+
out.append("")
|
|
216
|
+
out.append("Walk the seven tracks in references/audit.md. Silence above is not a pass.")
|
|
217
|
+
return "\n".join(out)
|
|
218
|
+
|
|
219
|
+
|
|
220
|
+
def self_test():
|
|
221
|
+
"""Plant each defect and require the matching check to fire.
|
|
222
|
+
|
|
223
|
+
A detector nobody has watched fire is not evidence that it works, and every plant
|
|
224
|
+
asserts it changed something so a reworded fixture fails HERE rather than reporting a
|
|
225
|
+
healthy checker as broken.
|
|
226
|
+
"""
|
|
227
|
+
import tempfile
|
|
228
|
+
header = ("import requests\n"
|
|
229
|
+
"system_prompt = 'x'\n"
|
|
230
|
+
"tools = [{'name': 't', 'description': 'does a thing'}]\n"
|
|
231
|
+
"messages = []\n")
|
|
232
|
+
cases = {
|
|
233
|
+
"unbounded-loop": header + "while True:\n pass\n",
|
|
234
|
+
"tool-no-description": header + "T = [{'name': 'a', 'description': ''}]\n",
|
|
235
|
+
"swallowed-error": header + "try:\n x = 1\nexcept Exception:\n pass\n",
|
|
236
|
+
"no-timeout": header + "r = requests.get('https://example.com')\n",
|
|
237
|
+
"hardcoded-model": header + "a = 'claude-opus-4'\nb = 'claude-opus-4'\n",
|
|
238
|
+
}
|
|
239
|
+
failures = 0
|
|
240
|
+
for kind, body in cases.items():
|
|
241
|
+
FINDINGS.clear()
|
|
242
|
+
with tempfile.TemporaryDirectory() as d:
|
|
243
|
+
p = os.path.join(d, "agent.py")
|
|
244
|
+
with open(p, "w", encoding="utf-8") as fh:
|
|
245
|
+
fh.write(body)
|
|
246
|
+
assert agentish(body), f"PLANT DID NOT LAND: fixture for {kind} is not agent-related"
|
|
247
|
+
scan(d)
|
|
248
|
+
got = {f["check"] for f in FINDINGS}
|
|
249
|
+
if kind in got:
|
|
250
|
+
print(f" OK {kind}: detected")
|
|
251
|
+
else:
|
|
252
|
+
print(f" FAIL {kind}: NOT detected (found {sorted(got) or 'nothing'})")
|
|
253
|
+
failures += 1
|
|
254
|
+
# and a clean file must produce nothing, or every finding above is noise
|
|
255
|
+
FINDINGS.clear()
|
|
256
|
+
with tempfile.TemporaryDirectory() as d:
|
|
257
|
+
with open(os.path.join(d, "agent.py"), "w", encoding="utf-8") as fh:
|
|
258
|
+
fh.write(header + "for _ in range(10):\n pass\n"
|
|
259
|
+
"r = requests.get('https://example.com', timeout=5)\n")
|
|
260
|
+
scan(d)
|
|
261
|
+
if FINDINGS:
|
|
262
|
+
print(f" FAIL clean file produced {len(FINDINGS)} finding(s): "
|
|
263
|
+
f"{[f['check'] for f in FINDINGS]}")
|
|
264
|
+
failures += 1
|
|
265
|
+
else:
|
|
266
|
+
print(" OK clean file: silent")
|
|
267
|
+
print(f"\nself-test: {len(cases) + 1 - failures}/{len(cases) + 1} passed")
|
|
268
|
+
return 1 if failures else 0
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
def main(argv):
|
|
272
|
+
ap = argparse.ArgumentParser(description="Mechanical half of an agent-system audit.")
|
|
273
|
+
ap.add_argument("path", nargs="?", default=".")
|
|
274
|
+
ap.add_argument("--json", action="store_true")
|
|
275
|
+
ap.add_argument("--self-test", action="store_true")
|
|
276
|
+
a = ap.parse_args(argv)
|
|
277
|
+
|
|
278
|
+
if a.self_test:
|
|
279
|
+
return self_test()
|
|
280
|
+
|
|
281
|
+
root = os.path.abspath(a.path)
|
|
282
|
+
if not os.path.isdir(root):
|
|
283
|
+
print(f"error: {a.path} is not a directory", file=sys.stderr)
|
|
284
|
+
return 2
|
|
285
|
+
seen, considered = scan(root)
|
|
286
|
+
if a.json:
|
|
287
|
+
print(json.dumps({"root": root, "files_considered": considered,
|
|
288
|
+
"files_agent_related": seen, "findings": FINDINGS,
|
|
289
|
+
"cannot_see": BLIND}, indent=2))
|
|
290
|
+
else:
|
|
291
|
+
print(report_text(root, seen, considered))
|
|
292
|
+
# Findings are smells, not failures: exit 0 so this composes in a pipeline, and let the
|
|
293
|
+
# human decide. A non-zero exit here would turn an audit into a gate it was never
|
|
294
|
+
# calibrated to be.
|
|
295
|
+
return 0
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
if __name__ == "__main__":
|
|
299
|
+
sys.exit(main(sys.argv[1:]))
|