bare-agent 0.16.1 → 0.18.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/README.md +36 -30
- package/bareagent.context.md +63 -2
- package/index.d.ts +7 -1
- package/index.js +12 -1
- package/package.json +5 -4
- package/src/bareguard-adapter.js +22 -4
- package/src/complexity.d.ts +12 -0
- package/src/complexity.js +33 -4
- package/src/evaluator.d.ts +147 -0
- package/src/evaluator.js +270 -0
- package/src/loop.d.ts +75 -4
- package/src/loop.js +233 -44
- package/src/mcp-bridge.js +11 -1
- package/src/memory.d.ts +10 -3
- package/src/memory.js +15 -4
- package/src/provider-anthropic.d.ts +13 -0
- package/src/provider-anthropic.js +36 -1
- package/src/provider-gemini.d.ts +70 -0
- package/src/provider-gemini.js +197 -0
- package/src/provider-openai.d.ts +10 -0
- package/src/provider-openai.js +20 -4
- package/src/providers.d.ts +2 -1
- package/src/providers.js +3 -0
- package/src/refine.d.ts +86 -0
- package/src/refine.js +65 -0
- package/src/remember.d.ts +118 -0
- package/src/remember.js +163 -0
- package/src/skills.d.ts +71 -0
- package/src/skills.js +200 -0
- package/src/stash.d.ts +44 -0
- package/src/stash.js +342 -0
- package/types/index.d.ts +57 -1
package/README.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
<img src="https://img.shields.io/badge/license-Apache%202.0-2a4f8c" alt="license: Apache 2.0">
|
|
18
18
|
</p>
|
|
19
19
|
|
|
20
|
-
**
|
|
20
|
+
**Lightweight agent orchestration. One required dep ([bareguard](https://npmjs.com/package/bareguard) ^0.9.0).**
|
|
21
21
|
|
|
22
22
|
Lightweight enough to understand completely. Complete enough to not reinvent wheels. Not a framework, not 50,000 lines of opinions — just composable building blocks for agents. Single-gate governance via bareguard: every tool call traverses one policy hook, one audit log, one budget cap.
|
|
23
23
|
|
|
@@ -62,38 +62,44 @@ and show me the wiring code.
|
|
|
62
62
|
|
|
63
63
|
## What's inside
|
|
64
64
|
|
|
65
|
-
Every piece works alone — take what you need, ignore the rest.
|
|
65
|
+
Every piece works alone — take what you need, ignore the rest. Two axes: **Act** (get work done) and **Verify** (check it, keep context clean), with **one gate** over both.
|
|
66
|
+
|
|
67
|
+
### Act — get work done
|
|
66
68
|
|
|
67
69
|
| Component | What it does |
|
|
68
70
|
|---|---|
|
|
69
|
-
| **Loop** | Think → act → observe
|
|
70
|
-
| **Planner** | Break a goal into a step DAG
|
|
71
|
-
| **assessComplexity** |
|
|
72
|
-
| **runPlan** |
|
|
73
|
-
| **
|
|
74
|
-
| **
|
|
75
|
-
| **
|
|
76
|
-
| **
|
|
77
|
-
| **
|
|
78
|
-
| **
|
|
79
|
-
| **
|
|
80
|
-
| **Stream** | Structured
|
|
81
|
-
| **
|
|
82
|
-
| **
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
|
87
|
-
|
|
88
|
-
| **
|
|
89
|
-
|
|
90
|
-
**
|
|
91
|
-
|
|
92
|
-
**
|
|
93
|
-
|
|
94
|
-
**
|
|
95
|
-
|
|
96
|
-
**
|
|
71
|
+
| **Loop** | Think → act → observe until done. Any LLM, your tools, per-run cost. Opt-in seams: `policy` (govern), `assemble` (context engineering), `trim` (bound the transcript) |
|
|
72
|
+
| **Planner** | Break a goal into a step DAG. Cached |
|
|
73
|
+
| **assessComplexity** | Rate a goal `simple`→`critical` from its text — no LLM. Gates whether to plan |
|
|
74
|
+
| **runPlan** | Run plan steps in parallel waves. Dependency-aware, per-step retry |
|
|
75
|
+
| **Memory** | Persist + recall across sessions via a swappable `Store` — zero-dep JSON, SQLite, or [litectx](https://npmjs.com/package/litectx) in a one-line swap |
|
|
76
|
+
| **StateMachine** | Task lifecycle: `pending → running → done / failed / waiting / cancelled` |
|
|
77
|
+
| **Scheduler** | Cron or relative triggers. Jobs survive restarts |
|
|
78
|
+
| **Checkpoint** | Human approval gate — bring your own transport |
|
|
79
|
+
| **Spawn** | Fork a child agent. One shared audit log + budget |
|
|
80
|
+
| **Defer** | Queue an action for a waker to fire later. Governed when emitted *and* when it fires |
|
|
81
|
+
| **Retry · CircuitBreaker · Fallback** | Resilience: backoff with jitter, fail-fast, provider failover |
|
|
82
|
+
| **Stream · Errors** | Structured JSONL events; typed error hierarchy |
|
|
83
|
+
| **Browsing · Mobile · Shell** | Hands: web ([barebrowse](https://npmjs.com/package/barebrowse)), Android + iOS ([baremobile](https://npmjs.com/package/baremobile)), cross-platform shell — library tools or token-thrifty CLI sessions |
|
|
84
|
+
| **MCP Bridge** | Auto-discover MCP servers from your IDE configs; expose as tools (bulk or meta-tools) |
|
|
85
|
+
|
|
86
|
+
### Verify — check the work, keep context clean *(the eval-assist suite)*
|
|
87
|
+
|
|
88
|
+
| Component | What it does |
|
|
89
|
+
|---|---|
|
|
90
|
+
| **Evaluator + refine** | Judge output by `predicate` (no tokens), `rubric` (an isolated adversarial grader), or `agentic` (a critic that exercises the live artifact). `refine` is the bounded generate→evaluate→regenerate loop |
|
|
91
|
+
| **SkillRegistry** | Surface skills on demand: one meta-tool catalog; activating a skill injects its instructions and unlocks its tools |
|
|
92
|
+
| **stash** | Compact finished work out of the live window (restorable), or auto-fold the middle under token pressure |
|
|
93
|
+
|
|
94
|
+
**Govern — one gate over both axes.** `wireGate(gate)` routes every LLM + tool call through one bareguard policy + audit + budget. Denied tools never reach the model; halts (turn / budget / content caps) exit cleanly. `require('bare-agent/bareguard')`
|
|
95
|
+
|
|
96
|
+
**Providers:** OpenAI-compatible (OpenAI, OpenRouter, Groq, vLLM, LM Studio), Anthropic, Gemini (native), Ollama, CLIPipe, Fallback — or bring your own (one `generate` method). All return the same shape; swap freely. Usage including prompt-cache tiers is normalized, so `result.metrics` reports honest cumulative tokens + cost — and `null`, never a silent `0`, for a model it couldn't price.
|
|
97
|
+
|
|
98
|
+
**Tools:** Any function is a tool — REST, MCP, CLI, shell. Built-in web + mobile (optional).
|
|
99
|
+
|
|
100
|
+
**Cross-language:** Run as a subprocess; talk JSONL over stdin/stdout from Python, Go, Rust, Ruby, or Java. Wrappers in [`contrib/`](contrib/README.md).
|
|
101
|
+
|
|
102
|
+
**Deps:** 1 required (`bareguard ^0.9.0`). Optional: `cron-parser`, `better-sqlite3`, `barebrowse`, `baremobile`, `wearehere`.
|
|
97
103
|
|
|
98
104
|
This table is the map, not the manual — per-component wiring and API detail live in the [Integration Guide](bareagent.context.md) and [Usage Guide](docs/02-features/usage-guide.md).
|
|
99
105
|
|
package/bareagent.context.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# bareagent — Integration Guide
|
|
2
2
|
|
|
3
3
|
> For AI assistants and developers wiring bareagent into a project.
|
|
4
|
-
> v0.
|
|
4
|
+
> v0.18.0 | Node.js >= 18 | one required dep (`bareguard ^0.9.0`) | Apache 2.0
|
|
5
5
|
>
|
|
6
6
|
> Full human guide with composition examples, design philosophy, and recipes: [Usage Guide](docs/02-features/usage-guide.md)
|
|
7
7
|
|
|
@@ -46,6 +46,10 @@ Eight entry points:
|
|
|
46
46
|
| Retry individual plan steps | runPlan({ stepRetry }) |
|
|
47
47
|
| Use a CLI tool as an LLM provider | CLIPipe |
|
|
48
48
|
| Health-check provider, store, and tools | Loop.validate() |
|
|
49
|
+
| Verify an agent's output (judge / grade / critic) | Evaluator + refine — `predicate` / `rubric` / `agentic` criteria |
|
|
50
|
+
| Offer skills on demand without bloating context | SkillRegistry — `skill_use` meta-tool + `skills.activeTools` thunk |
|
|
51
|
+
| Keep the context window lean (compact finished sub-tasks) | createStashSkill — register the skill + wire its `trim` into `Loop({ trim })` |
|
|
52
|
+
| Consolidate finished work into durable facts (across runs) | remember — distill harvested spans → write through any `Store` socket |
|
|
49
53
|
| Track cost per run | Automatic — `result.cost` and `loop:done` event |
|
|
50
54
|
| Catch typed errors programmatically | ProviderError, ToolError, TimeoutError, CircuitOpenError |
|
|
51
55
|
| Cache identical planner calls | Planner({ cacheTTL: 60000 }) |
|
|
@@ -142,6 +146,61 @@ const loop = new Loop({
|
|
|
142
146
|
});
|
|
143
147
|
```
|
|
144
148
|
|
|
149
|
+
## Wiring with remember (consolidate harvested spans → durable facts)
|
|
150
|
+
|
|
151
|
+
`remember` is the consolidation pass: it distills the spans `stash` folded out of the live transcript into durable facts and writes them through the **same `Store` socket** Memory uses (`store/search/get/delete`) — so it works with any backend (JsonFile, SQLite, litectx, or your own), with no backend coupling. One cheap LLM pass per span keeps only durable signal (decisions, config, identifiers, stable prefs) and drops chatter, superseded values, and unanswered questions. It composes *around* the Loop — never inside it.
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
const { remember, Memory } = require('bare-agent');
|
|
155
|
+
const { Anthropic } = require('bare-agent/providers');
|
|
156
|
+
const { JsonFile } = require('bare-agent/stores');
|
|
157
|
+
|
|
158
|
+
const memory = new Memory({ store: new JsonFile({ path: './facts.json' }) });
|
|
159
|
+
|
|
160
|
+
// `spans` are the harvested chunks stash evicted (lossless parks), or any finished-work transcript text.
|
|
161
|
+
const { facts } = await remember(spans, {
|
|
162
|
+
provider: new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY, model: 'claude-haiku-4-5' }),
|
|
163
|
+
store: memory, // writes each fact as { kind: 'fact', ... } via memory.store()
|
|
164
|
+
contract: 'only architecture + config decisions count as durable', // optional: steer "durable"
|
|
165
|
+
ctx, // optional: counts each write into result.metrics.memory.facts (disjoint from `stored`)
|
|
166
|
+
onLlmResult, // optional: forward distill-pass usage to a wired bareguard gate
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Budget visibility carries through `onLlmResult` (mirror of Evaluator); a governance `HaltError` from the provider propagates clean. Cheap by design — a small model and one pass per span.
|
|
171
|
+
|
|
172
|
+
**Security:** facts are model output over *untrusted* transcript content written to durable memory. The distiller refuses a direct "record this fact" injection (validated live), but treat recalled facts as untrusted **context**, not authority — gate them like any model output before a privileged action.
|
|
173
|
+
|
|
174
|
+
## Wiring with Skills + Stash (progressive disclosure + compaction)
|
|
175
|
+
|
|
176
|
+
Skills are operator-registered `{ name, description, instructions, tools }` bundles surfaced on demand: only a one-liner per skill sits in context until the agent calls `skill_use({ name })`, which injects the skill's instructions and unlocks its (namespaced) tools for the next round. Pass `skills.activeTools` (a bound thunk) as the Loop's `tools` — it is re-evaluated each round, so freshly-unlocked tools appear automatically. The gate still governs every unlocked tool; skills change discovery, not authorization.
|
|
177
|
+
|
|
178
|
+
The shipped reference skill is **stash** — compaction-first context hygiene. Its `trim` wires into `Loop({ trim })` and folds finished sub-tasks out of the live transcript (restorable), keeping long runs under budget. Pass a litectx instance as `ctx` for lossless verbatim parking + the `ctx.summarize` lossy path; set `compaction.ceilingTokens` to enable automatic token-pressure folding.
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
const { Loop, SkillRegistry, createStashSkill } = require('bare-agent');
|
|
182
|
+
const { Anthropic } = require('bare-agent/providers');
|
|
183
|
+
|
|
184
|
+
const { skill, trim } = createStashSkill({
|
|
185
|
+
// optional auto-compaction: fold the middle when measured input tokens cross 70% of the ceiling
|
|
186
|
+
compaction: { ceilingTokens: 150000, triggerAt: 0.7, strategy: 'summarize' },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
const skills = new SkillRegistry();
|
|
190
|
+
skills.register(skill); // catalog now lists "stash" in skill_use's description
|
|
191
|
+
|
|
192
|
+
const loop = new Loop({
|
|
193
|
+
provider: new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY }),
|
|
194
|
+
trim, // stash's fold runs here, at each round boundary
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Pass the bound thunk as tools; ctx carries litectx (lossless parking + summarizer).
|
|
198
|
+
await loop.run(messages, skills.activeTools, { ctx });
|
|
199
|
+
// The model: skill_use({name:'stash'}) → stash_checkpoint({label}) → …work… → stash_compact({label, reason})
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Folds are provider-safe by construction (whole rounds, alternation-preserving note pairs) and confirmed on the real Anthropic wire. Governance, budget visibility (`ctx.summarize` tokens forward to the gate), and `result.metrics.context.compactions` all flow through unchanged.
|
|
203
|
+
|
|
145
204
|
## Multi-agent: spawn + defer + wake (v0.9)
|
|
146
205
|
|
|
147
206
|
Three primitives, no framework. The "always-on" feeling of multi-agent
|
|
@@ -257,7 +316,9 @@ $HOME/IDE configs — NOT the project-cwd `./.mcp.json`** (v0.16.1): a checked-i
|
|
|
257
316
|
config in an untrusted repo would otherwise auto-spawn arbitrary commands. To
|
|
258
317
|
include the project config, pass `createMCPBridge({ includeProjectConfig: true })`,
|
|
259
318
|
or a `confirmServer` hook (which implies it, since the hook vets every command).
|
|
260
|
-
Explicitly-passed `configPaths` are honored verbatim.
|
|
319
|
+
Explicitly-passed `configPaths` are honored verbatim. So you're not left guessing,
|
|
320
|
+
discovery logs a one-line hint (v0.16.2) when a `./.mcp.json` is present but skipped —
|
|
321
|
+
naming the opt-in — rather than silently ignoring it. Pass `confirmServer(name,
|
|
261
322
|
def) => boolean` to approve each server **before its command is spawned** (return
|
|
262
323
|
`false` to skip it; a throw fails closed). When no `confirmServer` is set, the
|
|
263
324
|
bridge still trusts all *discovered* servers and prints a one-time warning naming
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import { Loop } from "./src/loop";
|
|
2
2
|
import { Planner } from "./src/planner";
|
|
3
|
+
import { Evaluator } from "./src/evaluator";
|
|
4
|
+
import { refine } from "./src/refine";
|
|
5
|
+
import { remember } from "./src/remember";
|
|
3
6
|
import { assessComplexity } from "./src/complexity";
|
|
7
|
+
import { isCritical } from "./src/complexity";
|
|
8
|
+
import { SkillRegistry } from "./src/skills";
|
|
9
|
+
import { createStashSkill } from "./src/stash";
|
|
4
10
|
import { StateMachine } from "./src/state";
|
|
5
11
|
import { Scheduler } from "./src/scheduler";
|
|
6
12
|
import { Checkpoint } from "./src/checkpoint";
|
|
@@ -23,4 +29,4 @@ import { TimeoutError } from "./src/errors";
|
|
|
23
29
|
import { ValidationError } from "./src/errors";
|
|
24
30
|
import { CircuitOpenError } from "./src/errors";
|
|
25
31
|
import { HaltError } from "./src/errors";
|
|
26
|
-
export { Loop, Planner, assessComplexity, StateMachine, Scheduler, Checkpoint, Memory, Stream, Retry, runPlan, CircuitBreaker, wireGate, defaultActionTranslator, toUnits, fromUnits, unitAssembler, unitTrimmer, harvestKey, BareAgentError, ProviderError, ToolError, TimeoutError, ValidationError, CircuitOpenError, HaltError };
|
|
32
|
+
export { Loop, Planner, Evaluator, refine, remember, assessComplexity, isCritical, SkillRegistry, createStashSkill, StateMachine, Scheduler, Checkpoint, Memory, Stream, Retry, runPlan, CircuitBreaker, wireGate, defaultActionTranslator, toUnits, fromUnits, unitAssembler, unitTrimmer, harvestKey, BareAgentError, ProviderError, ToolError, TimeoutError, ValidationError, CircuitOpenError, HaltError };
|
package/index.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
const { Loop } = require('./src/loop');
|
|
4
4
|
const { Planner } = require('./src/planner');
|
|
5
|
-
const {
|
|
5
|
+
const { Evaluator } = require('./src/evaluator');
|
|
6
|
+
const { refine } = require('./src/refine');
|
|
7
|
+
const { remember } = require('./src/remember');
|
|
8
|
+
const { assessComplexity, isCritical } = require('./src/complexity');
|
|
9
|
+
const { SkillRegistry } = require('./src/skills');
|
|
10
|
+
const { createStashSkill } = require('./src/stash');
|
|
6
11
|
const { StateMachine } = require('./src/state');
|
|
7
12
|
const { Scheduler } = require('./src/scheduler');
|
|
8
13
|
const { Checkpoint } = require('./src/checkpoint');
|
|
@@ -26,7 +31,13 @@ const {
|
|
|
26
31
|
module.exports = {
|
|
27
32
|
Loop,
|
|
28
33
|
Planner,
|
|
34
|
+
Evaluator,
|
|
35
|
+
refine,
|
|
36
|
+
remember,
|
|
29
37
|
assessComplexity,
|
|
38
|
+
isCritical,
|
|
39
|
+
SkillRegistry,
|
|
40
|
+
createStashSkill,
|
|
30
41
|
StateMachine,
|
|
31
42
|
Scheduler,
|
|
32
43
|
Checkpoint,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-agent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.0",
|
|
4
4
|
"files": [
|
|
5
5
|
"index.js",
|
|
6
6
|
"index.d.ts",
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"LICENSE",
|
|
14
14
|
"NOTICE"
|
|
15
15
|
],
|
|
16
|
-
"description": "Lightweight, composable agent orchestration for autonomous agents. Multi-agent primitives (spawn, defer, MCP meta-tools), single-gate governance via bareguard, cross-platform shell tools, MCP bridge.
|
|
16
|
+
"description": "Lightweight, composable agent orchestration for autonomous agents. Multi-agent primitives (spawn, defer, MCP meta-tools), single-gate governance via bareguard, cross-platform shell tools, MCP bridge. Lightweight core, one required dep.",
|
|
17
17
|
"license": "Apache-2.0",
|
|
18
18
|
"author": "hamr0",
|
|
19
19
|
"repository": {
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
"governance"
|
|
76
76
|
],
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"bareguard": "^0.
|
|
78
|
+
"bareguard": "^0.9.0"
|
|
79
79
|
},
|
|
80
80
|
"optionalDependencies": {
|
|
81
81
|
"barebrowse": "^0.5.0",
|
|
@@ -91,7 +91,8 @@
|
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
93
|
"scripts": {
|
|
94
|
-
"test": "node --test test/**/*.test.js",
|
|
94
|
+
"test": "node --test --test-force-exit test/**/*.test.js",
|
|
95
|
+
"test:unit": "node --test --test-force-exit \"test/!(integration*|*mcp*|spawn*).test.js\"",
|
|
95
96
|
"typecheck": "tsc --noEmit",
|
|
96
97
|
"prebuild:types": "node scripts/clean-types.js",
|
|
97
98
|
"build:types": "tsc",
|
package/src/bareguard-adapter.js
CHANGED
|
@@ -139,18 +139,36 @@ function wireGate(gate, options = {}) {
|
|
|
139
139
|
* @param {string|null} [arg.model]
|
|
140
140
|
* @param {string|null} [arg.provider]
|
|
141
141
|
* @param {Usage} [arg.usage]
|
|
142
|
-
* @param {number} [arg.costUsd]
|
|
142
|
+
* @param {number|null} [arg.costUsd]
|
|
143
|
+
* @param {('priced'|'unpriced')} [arg.pricing] - The meter's price verdict, forwarded VERBATIM
|
|
144
|
+
* (never synthesized here). Absent (older meter) ⇒ forwarded as undefined; bareguard's
|
|
145
|
+
* back-compat treats an absent flag as priced, keeping the contract back-compatible.
|
|
143
146
|
* @param {number|null} [arg.durationMs]
|
|
144
147
|
* @param {Ctx} [arg.ctx]
|
|
145
148
|
*/
|
|
146
|
-
const onLlmResult = async ({ model, provider, usage, costUsd, durationMs, ctx }) => {
|
|
149
|
+
const onLlmResult = async ({ model, provider, usage, costUsd, pricing, durationMs, ctx }) => {
|
|
147
150
|
// LLM rounds bypass actionTranslator — they always use the canonical
|
|
148
151
|
// {type:'llm'} action so budget rules can match without translator collusion.
|
|
149
152
|
await gate.record(
|
|
150
153
|
{ type: 'llm', args: { model: model || null, provider: provider || null }, _ctx: ctx ?? null },
|
|
151
154
|
{
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
// Forward costUsd AS-IS — a null (unpriced) cost must NOT coerce to 0 here. Coercing it
|
|
156
|
+
// tells the gate the round was "free" instead of "couldn't price", so an active
|
|
157
|
+
// budget.maxCostUsd cap silently accrues zero and never halts — the #3 silent-zero class
|
|
158
|
+
// (§3.7), reproduced on the adapter.
|
|
159
|
+
costUsd: costUsd ?? null,
|
|
160
|
+
// Forward the meter's price verdict VERBATIM — never synthesize it. bareguard treats an
|
|
161
|
+
// explicit pricing:'unpriced' as the SOLE trigger for the unpriced contract; a null cost
|
|
162
|
+
// WITHOUT pricing deliberately stays on bareguard's back-compat (?? 0 ⇒ priced) path. loop.js
|
|
163
|
+
// always sets pricing (loop.js:566), so faithful forwarding arms the contract on every real
|
|
164
|
+
// round. Manufacturing 'unpriced' from a bare null here would arm cases the gate intentionally
|
|
165
|
+
// leaves unarmed — the exact mirror of bareguard's budget-pricing round-trip. (§3.8 contract.)
|
|
166
|
+
pricing,
|
|
167
|
+
// ALL FOUR token tiers, not just input+output — cache read/creation are real consumed
|
|
168
|
+
// tokens; omitting them undercounts the gate's token axis on a cached run (L7). The token
|
|
169
|
+
// axis stays enforceable even when pricing is 'unpriced' — only the cost axis goes unknown.
|
|
170
|
+
tokens: (usage?.inputTokens || 0) + (usage?.outputTokens || 0)
|
|
171
|
+
+ (usage?.cacheReadTokens || 0) + (usage?.cacheCreationTokens || 0),
|
|
154
172
|
durationMs: durationMs ?? null,
|
|
155
173
|
},
|
|
156
174
|
);
|
package/src/complexity.d.ts
CHANGED
|
@@ -29,3 +29,15 @@ export type ComplexityResult = {
|
|
|
29
29
|
* @returns {ComplexityResult}
|
|
30
30
|
*/
|
|
31
31
|
export function assessComplexity(prompt: string): ComplexityResult;
|
|
32
|
+
/**
|
|
33
|
+
* The durable critical-safety floor, standalone. Returns `true` when a goal touches high-stakes
|
|
34
|
+
* work (security/production/compliance/financial incidents, or a securing action on a sensitive
|
|
35
|
+
* domain) — the same deterministic override `assessComplexity` applies, exported on its own so a
|
|
36
|
+
* consumer can gate extra scrutiny (e.g. the Evaluator's adversarial verification, Feature 1)
|
|
37
|
+
* WITHOUT pulling in the frozen 3-tier scorer. Deterministic, auditable, testable — not a
|
|
38
|
+
* capability estimate. Normalizes input (trims, length-caps, lowercases) then applies the override;
|
|
39
|
+
* non-string / blank input is `false`.
|
|
40
|
+
* @param {string} prompt - The goal to test.
|
|
41
|
+
* @returns {boolean}
|
|
42
|
+
*/
|
|
43
|
+
export function isCritical(prompt: string): boolean;
|
package/src/complexity.js
CHANGED
|
@@ -16,6 +16,15 @@
|
|
|
16
16
|
* and two calibrated thresholds. It is a heuristic — transparent and debuggable via `signals`, not
|
|
17
17
|
* a model. On the upstream validation corpus it lands ~89% (the fuller LLM-free original ~95%);
|
|
18
18
|
* the gap is long-tail ambiguity ("add a button" is genuinely context-dependent).
|
|
19
|
+
*
|
|
20
|
+
* FROZEN (eval-assist Feature 4, do not re-litigate). The 3-tier scorer is deletable scaffolding —
|
|
21
|
+
* the exact input-side routing a stronger model does better — so it is kept optional but NOT grown:
|
|
22
|
+
* **do not extend the keyword lists or add tiers.** The durable half is the deterministic critical
|
|
23
|
+
* override (`isCritical`), a safety FLOOR for high-stakes work (security/production/compliance/
|
|
24
|
+
* financial), exported standalone so a consumer can gate adversarial verification on it (the
|
|
25
|
+
* Evaluator, Feature 1) without the scorer. Adding critical keywords reopens the same treadmill the
|
|
26
|
+
* scorer froze, so the override list is frozen too. Input-side LLM subgoal-decomposition is a
|
|
27
|
+
* deliberate NO-BUILD (mechanical anchoring + cascading errors); the Planner is the durable form.
|
|
19
28
|
*/
|
|
20
29
|
|
|
21
30
|
const has = (/** @type {Set<string>} */ words, /** @type {Set<string>} */ set) =>
|
|
@@ -35,8 +44,12 @@ const CRIT_ACTIONS = ['fix', 'patch', 'investigate', 'secure', 'protect', 'mitig
|
|
|
35
44
|
const FINANCIAL = ['payment', 'transaction', 'billing', 'financial'];
|
|
36
45
|
const SECURE_ACTS = ['encrypt', 'secure', 'protect', 'audit'];
|
|
37
46
|
|
|
38
|
-
/**
|
|
39
|
-
|
|
47
|
+
/**
|
|
48
|
+
* Internal: the critical override over an already-lowercased string. Public callers use
|
|
49
|
+
* {@link isCritical}, which normalizes first; `assessComplexity` calls this on its lowered text.
|
|
50
|
+
* @param {string} s lowercased prompt
|
|
51
|
+
*/
|
|
52
|
+
function isCriticalLower(s) {
|
|
40
53
|
if (hasAny(s, CRIT_INCIDENT) || hasAny(s, CRIT_COMPLIANCE)) return true;
|
|
41
54
|
if (hasAny(s, SEC_CONTEXT) && hasAny(s, CRIT_ACTIONS)) return true; // e.g. "fix security ..."
|
|
42
55
|
if (hasAny(s, FINANCIAL) && hasAny(s, SECURE_ACTS)) return true; // e.g. "encrypt payment ..."
|
|
@@ -83,7 +96,7 @@ function assessComplexity(prompt) {
|
|
|
83
96
|
}
|
|
84
97
|
const text = prompt.trim().slice(0, MAX_ASSESS_LEN);
|
|
85
98
|
const lower = text.toLowerCase();
|
|
86
|
-
if (
|
|
99
|
+
if (isCriticalLower(lower)) {
|
|
87
100
|
return { level: 'critical', score: 100, needsPlanning: true, signals: ['critical_override'] };
|
|
88
101
|
}
|
|
89
102
|
|
|
@@ -146,4 +159,20 @@ function assessComplexity(prompt) {
|
|
|
146
159
|
return { level, score, needsPlanning: level !== 'simple', signals };
|
|
147
160
|
}
|
|
148
161
|
|
|
149
|
-
|
|
162
|
+
/**
|
|
163
|
+
* The durable critical-safety floor, standalone. Returns `true` when a goal touches high-stakes
|
|
164
|
+
* work (security/production/compliance/financial incidents, or a securing action on a sensitive
|
|
165
|
+
* domain) — the same deterministic override `assessComplexity` applies, exported on its own so a
|
|
166
|
+
* consumer can gate extra scrutiny (e.g. the Evaluator's adversarial verification, Feature 1)
|
|
167
|
+
* WITHOUT pulling in the frozen 3-tier scorer. Deterministic, auditable, testable — not a
|
|
168
|
+
* capability estimate. Normalizes input (trims, length-caps, lowercases) then applies the override;
|
|
169
|
+
* non-string / blank input is `false`.
|
|
170
|
+
* @param {string} prompt - The goal to test.
|
|
171
|
+
* @returns {boolean}
|
|
172
|
+
*/
|
|
173
|
+
function isCritical(prompt) {
|
|
174
|
+
if (typeof prompt !== 'string' || !prompt.trim()) return false;
|
|
175
|
+
return isCriticalLower(prompt.trim().slice(0, MAX_ASSESS_LEN).toLowerCase());
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { assessComplexity, isCritical };
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
export type Provider = import("../types").Provider;
|
|
2
|
+
export type ToolDef = import("../types").ToolDef;
|
|
3
|
+
/**
|
|
4
|
+
* The uniform outcome of an evaluation, across every criteria type.
|
|
5
|
+
*
|
|
6
|
+
* Tri-state `status` mirrors Anthropic Managed Agents "Outcomes" (`satisfied` /
|
|
7
|
+
* `needs_revision` / `failed`) — the distinction matters to `refine`: `needs_revision`
|
|
8
|
+
* is retryable, `failed` is terminal (stop spending). `pass` is derived (`status ===
|
|
9
|
+
* 'satisfied'`) so a boolean consumer never has to special-case the enum.
|
|
10
|
+
*/
|
|
11
|
+
export type Verdict = {
|
|
12
|
+
/**
|
|
13
|
+
* - Tri-state outcome.
|
|
14
|
+
*/
|
|
15
|
+
status: "satisfied" | "needs_revision" | "failed";
|
|
16
|
+
/**
|
|
17
|
+
* - Derived: `status === 'satisfied'`.
|
|
18
|
+
*/
|
|
19
|
+
pass: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* - 0–10 for the rubric path; null for predicate (pass/fail only).
|
|
22
|
+
*/
|
|
23
|
+
score: number | null;
|
|
24
|
+
/**
|
|
25
|
+
* - Why it failed / what to improve. '' when satisfied with no notes.
|
|
26
|
+
*/
|
|
27
|
+
critique: string;
|
|
28
|
+
/**
|
|
29
|
+
* - Concrete fixes (rubric may populate; [] otherwise).
|
|
30
|
+
*/
|
|
31
|
+
suggestions: string[];
|
|
32
|
+
};
|
|
33
|
+
export type EvaluatorOptions = {
|
|
34
|
+
/**
|
|
35
|
+
* - LLM provider — REQUIRED for the rubric and agentic paths; predicate needs none.
|
|
36
|
+
*/
|
|
37
|
+
provider?: import("../types").Provider | undefined;
|
|
38
|
+
/**
|
|
39
|
+
* - Override the adversarial grader system prompt (rubric path).
|
|
40
|
+
*/
|
|
41
|
+
prompt?: string | undefined;
|
|
42
|
+
/**
|
|
43
|
+
* - Override the adversarial tool-running critic system prompt (agentic path).
|
|
44
|
+
*/
|
|
45
|
+
agenticPrompt?: string | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* - The critic's SCOPED functional tools (`barebrowse`/`baremobile`) for the
|
|
48
|
+
* agentic path — what lets it exercise the live artifact rather than read text. Overridable per call via
|
|
49
|
+
* `EvaluateOptions.tools`. Ignored by predicate/rubric.
|
|
50
|
+
*/
|
|
51
|
+
tools?: import("../types").ToolDef[] | undefined;
|
|
52
|
+
};
|
|
53
|
+
export type Criteria = {
|
|
54
|
+
/**
|
|
55
|
+
* - Deterministic check, no tokens.
|
|
56
|
+
*/
|
|
57
|
+
predicate?: ((result: any) => boolean | Promise<boolean>) | undefined;
|
|
58
|
+
/**
|
|
59
|
+
* - Natural-language grading criteria an LLM scores. Exactly one of predicate|rubric|agentic.
|
|
60
|
+
*/
|
|
61
|
+
rubric?: string | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* - Instructions for a tool-running critic (D9): how to EXERCISE the live artifact
|
|
64
|
+
* (open it, click, read console/network) and what would make it fail. Runs an ISOLATED Loop with the scoped
|
|
65
|
+
* `tools`. The strongest verification — catches what only running the thing reveals. Exactly one of the three.
|
|
66
|
+
*/
|
|
67
|
+
agentic?: string | undefined;
|
|
68
|
+
/**
|
|
69
|
+
* - The shared, authoritative "definition of done" the grader judges against
|
|
70
|
+
* (A3 / D10). When present it is what success means — not the loose goal. Folded into the rubric/agentic prompt.
|
|
71
|
+
*/
|
|
72
|
+
contract?: string | undefined;
|
|
73
|
+
};
|
|
74
|
+
export type EvaluateOptions = {
|
|
75
|
+
/**
|
|
76
|
+
* - Budget hook.
|
|
77
|
+
* Judge-call tokens are real spend; forward them to the gate (BA1 lineage) so they count against budget and
|
|
78
|
+
* are never invisible. For the agentic path EVERY critic round forwards here (re-tagged `kind:'evaluate'`).
|
|
79
|
+
* A `HaltError` thrown here propagates as a clean governance exit. Wire `wireGate`'s.
|
|
80
|
+
*/
|
|
81
|
+
onLlmResult?: ((payload: {
|
|
82
|
+
usage: any;
|
|
83
|
+
model: string | null;
|
|
84
|
+
kind: "evaluate";
|
|
85
|
+
}) => any) | undefined;
|
|
86
|
+
/**
|
|
87
|
+
* - Per-call override of the agentic critic's scoped tools (else `EvaluatorOptions.tools`).
|
|
88
|
+
*/
|
|
89
|
+
tools?: import("../types").ToolDef[] | undefined;
|
|
90
|
+
/**
|
|
91
|
+
* - bareguard `policy` forwarded to the agentic critic's Loop — a tool-running
|
|
92
|
+
* critic MUST be bounded (turn/budget caps come from the gate; the Loop's HARD_ROUND_LIMIT is only a net).
|
|
93
|
+
*/
|
|
94
|
+
policy?: Function | undefined;
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Output-side judge — the mirror of `Planner` (input-side). Judges whether a result meets a goal, by a
|
|
98
|
+
* deterministic `predicate`, an LLM `rubric`, or a tool-running `agentic` critic, returning one uniform
|
|
99
|
+
* `Verdict`. The rubric and agentic paths run an ISOLATED adversarial critic (separate context + independent
|
|
100
|
+
* system prompt) — that isolation, not a feedback knob, is what defeats the self-evaluation trap. The agentic
|
|
101
|
+
* path additionally EXERCISES the artifact with scoped tools (it does not read the diff). Composes AROUND a
|
|
102
|
+
* Loop (never inside `loop.js`).
|
|
103
|
+
*
|
|
104
|
+
* Built flagged-and-deletable per D11 — opt-in by import; calibrate the rubric/prompt from execution traces.
|
|
105
|
+
*/
|
|
106
|
+
export class Evaluator {
|
|
107
|
+
/** @param {EvaluatorOptions} [options] */
|
|
108
|
+
constructor(options?: EvaluatorOptions);
|
|
109
|
+
provider: import("../types").Provider | null;
|
|
110
|
+
prompt: string;
|
|
111
|
+
agenticPrompt: string;
|
|
112
|
+
tools: import("../types").ToolDef[];
|
|
113
|
+
/**
|
|
114
|
+
* Judge `result` against `goal` by exactly one criteria type.
|
|
115
|
+
* @param {string} goal - The objective the result is judged against.
|
|
116
|
+
* @param {any} result - The output under judgment.
|
|
117
|
+
* @param {Criteria} criteria - Exactly one of `predicate` | `rubric` | `agentic` (none/more-than-one throws).
|
|
118
|
+
* @param {EvaluateOptions} [opts]
|
|
119
|
+
* @returns {Promise<Verdict>}
|
|
120
|
+
* @throws {ValidationError} not-exactly-one criteria supplied, or rubric/agentic requested with no provider.
|
|
121
|
+
*/
|
|
122
|
+
evaluate(goal: string, result: any, criteria: Criteria, opts?: EvaluateOptions): Promise<Verdict>;
|
|
123
|
+
/**
|
|
124
|
+
* Agentic path — run an ISOLATED tool-running critic Loop that exercises the artifact, then parse its
|
|
125
|
+
* final text into a `Verdict`. Isolation is by construction: a brand-new Loop with its own message array
|
|
126
|
+
* and the harsh `agenticPrompt` system prompt — a separate context window, never the generator's
|
|
127
|
+
* transcript (A1/D8). Budget visibility: every critic round forwards to `onLlmResult` (re-tagged
|
|
128
|
+
* `kind:'evaluate'`). A budget HALT during investigation re-throws as a clean `HaltError` (governance
|
|
129
|
+
* exit) so `refine` stops spending rather than misreading it as a verdict.
|
|
130
|
+
* @param {string} goal
|
|
131
|
+
* @param {any} result
|
|
132
|
+
* @param {string} instructions - The `agentic` criteria string: how to exercise the artifact.
|
|
133
|
+
* @param {string|null} contract
|
|
134
|
+
* @param {EvaluateOptions} opts
|
|
135
|
+
* @returns {Promise<Verdict>}
|
|
136
|
+
* @throws {ValidationError} no provider, or the critic loop errored / produced no parseable verdict.
|
|
137
|
+
* @throws {HaltError} a governance cap halted the critic mid-run.
|
|
138
|
+
*/
|
|
139
|
+
_evaluateAgentic(goal: string, result: any, instructions: string, contract: string | null, opts: EvaluateOptions): Promise<Verdict>;
|
|
140
|
+
/**
|
|
141
|
+
* Defensive JSON parse of a grader response into a `Verdict` (mirrors `Planner._parse`).
|
|
142
|
+
* @param {string} text
|
|
143
|
+
* @returns {Verdict}
|
|
144
|
+
* @throws {ValidationError} when no JSON object can be recovered (a `refine` loop can catch to abort).
|
|
145
|
+
*/
|
|
146
|
+
_parse(text: string): Verdict;
|
|
147
|
+
}
|