@ghostmind-dev/agent 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +141 -0
- package/dist/budget.d.ts +39 -0
- package/dist/budget.d.ts.map +1 -0
- package/dist/budget.js +34 -0
- package/dist/budget.js.map +1 -0
- package/dist/consult.d.ts +25 -0
- package/dist/consult.d.ts.map +1 -0
- package/dist/consult.js +80 -0
- package/dist/consult.js.map +1 -0
- package/dist/events.d.ts +119 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +24 -0
- package/dist/events.js.map +1 -0
- package/dist/guide.d.ts +103 -0
- package/dist/guide.d.ts.map +1 -0
- package/dist/guide.js +268 -0
- package/dist/guide.js.map +1 -0
- package/dist/hooks.d.ts +80 -0
- package/dist/hooks.d.ts.map +1 -0
- package/dist/hooks.js +56 -0
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +33 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/jev.d.ts +74 -0
- package/dist/jev.d.ts.map +1 -0
- package/dist/jev.js +82 -0
- package/dist/jev.js.map +1 -0
- package/dist/loop.d.ts +142 -0
- package/dist/loop.d.ts.map +1 -0
- package/dist/loop.js +550 -0
- package/dist/loop.js.map +1 -0
- package/dist/model.d.ts +173 -0
- package/dist/model.d.ts.map +1 -0
- package/dist/model.js +205 -0
- package/dist/model.js.map +1 -0
- package/dist/seams.d.ts +137 -0
- package/dist/seams.d.ts.map +1 -0
- package/dist/seams.js +45 -0
- package/dist/seams.js.map +1 -0
- package/dist/skills.d.ts +36 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +168 -0
- package/dist/skills.js.map +1 -0
- package/dist/testing.d.ts +36 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +60 -0
- package/dist/testing.js.map +1 -0
- package/dist/tools.d.ts +114 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +136 -0
- package/dist/tools.js.map +1 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ghostmind
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# @ghostmind-dev/agent
|
|
2
|
+
|
|
3
|
+
The engine every app builds its own agent from. The loop, and everything it needs to run safely:
|
|
4
|
+
|
|
5
|
+
- a worker model that calls tools, through OpenRouter;
|
|
6
|
+
- **Jev**, a calibrated decision model, guiding the worker at checkpoints;
|
|
7
|
+
- budgets, including a USD cap;
|
|
8
|
+
- `go` / `pause` / `stop` hooks;
|
|
9
|
+
- an append-only event log.
|
|
10
|
+
|
|
11
|
+
It is a **library**: import it, give it tools, run it. No server, no service, no connection to call. Zero runtime dependencies, Node ≥ 22.18, one credential: `OPENROUTER_API_KEY`.
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
@ghostmind-dev/agent (engine: loop, model + Jev, guide, budget, hooks, events)
|
|
15
|
+
/ | \
|
|
16
|
+
an app's agent another app's an ensemble work node
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
**Share the engine, not the agent.** What makes an agent *yours* is its tools, prompts, memory and approvals, and each app keeps those. What every app needs, and what is hard to get right, is the loop: the guards that stop a model looping, stalling, inventing tool calls or overspending. That lives here once.
|
|
20
|
+
|
|
21
|
+
## Quick start
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { openrouter, runAgent, type AgentTool } from '@ghostmind-dev/agent';
|
|
25
|
+
|
|
26
|
+
const weather: AgentTool = {
|
|
27
|
+
name: 'get_weather',
|
|
28
|
+
description: 'The forecast for a city today.',
|
|
29
|
+
parameters: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'] },
|
|
30
|
+
run: async ({ city }) => ({ city, forecast: 'rain, 12°C' }),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const result = await runAgent({
|
|
34
|
+
provider: openrouter({ model: '<an OpenRouter model id>' }),
|
|
35
|
+
task: { goal: 'Will it rain in Montreal?', expectation: 'One sentence.' },
|
|
36
|
+
toolsets: [{ name: 'weather', description: 'Forecasts.', tools: [weather] }],
|
|
37
|
+
budget: { maxUsd: 0.05 },
|
|
38
|
+
});
|
|
39
|
+
// result: { status, reason, answer, steps, toolCalls, cost, messages }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`npm run example` runs [`examples/two-tools.mts`](examples/two-tools.mts): a read tool, a write tool that asks for approval, and the event log. By default it uses a scripted model and spends $0. Add `-- --live <model-id>` for a real run.
|
|
43
|
+
|
|
44
|
+
## The loop
|
|
45
|
+
|
|
46
|
+
Ported from a production agent's loop, where each guard answers a failure its evaluation found:
|
|
47
|
+
|
|
48
|
+
| Guard | What it prevents |
|
|
49
|
+
|---|---|
|
|
50
|
+
| Every tool offered at every step | A router that offered only some tools made the model say "I have no tool for that" |
|
|
51
|
+
| Step cap; the last step answers with tools visible but not callable (`toolChoice: 'none'`) | Running forever; removing the tools instead makes models write tool markup as text |
|
|
52
|
+
| Tool-call cap | Endless lookups |
|
|
53
|
+
| USD cap (`budget.maxUsd`) | Overspending. Ends with status `limit` and a reason naming the figure |
|
|
54
|
+
| A transient reminder at the end of the context | Weak models losing the thread (sent once, never kept) |
|
|
55
|
+
| Leaked tool-call markup read back into real calls | Models that write their calls as text (DSML, `<invoke>`, GLM `arg_key`) |
|
|
56
|
+
| Empty reply sent back twice, then reported | Blank answers |
|
|
57
|
+
| Identical reads run once (sorted-key JSON) | Doom loops |
|
|
58
|
+
| A "you may have enough" note every few reads | Over-searching |
|
|
59
|
+
| Long results capped | Context blow-up |
|
|
60
|
+
| Guide reads (skills) spend no tool call and give the step back | Preparation eating the budget |
|
|
61
|
+
| A declined change ends the run on the person's move | Retrying what the person refused |
|
|
62
|
+
|
|
63
|
+
Statuses: `done`, `needs_person`, `unfinished`, `limit`, `stopped`, `paused`, `greeted`, `chatted`, with `reason` saying why whenever it isn't obvious.
|
|
64
|
+
|
|
65
|
+
## Jev: the decider and the guide
|
|
66
|
+
|
|
67
|
+
Two models, two jobs. The **worker** writes, plans and calls tools. **Jev** (TypeSafe's System One model, reached through OpenRouter's `/systemone`) never writes. It answers typed questions with calibrated probabilities (yes/no, one of N, a score), in well under a second, for about $0.00002. Probabilities, not prose, so the worker can't talk it into agreeing.
|
|
68
|
+
|
|
69
|
+
Because it is that cheap, the loop asks Jev wherever a step is a judgement rather than a generation:
|
|
70
|
+
|
|
71
|
+
- **Opening:** is there a task at all? A greeting gets the app's welcome and a thank-you gets one short reply, neither paying for the loop.
|
|
72
|
+
- **Skills:** which guides, if any, the task needs. One choice question covers every skill.
|
|
73
|
+
- **`ask_jev`:** offered to the worker at every step, for "which existing option fits", "is this a duplicate", "how relevant".
|
|
74
|
+
- **The guide:** checkpoints the harness asks itself, so a weaker model gets through uncertainty. The model doesn't have to think of asking.
|
|
75
|
+
|
|
76
|
+
### The guide
|
|
77
|
+
|
|
78
|
+
| Checkpoint | Question (atomic; the state is the task and the latest step only) | Low probability → |
|
|
79
|
+
|---|---|---|
|
|
80
|
+
| `after_tool` | Does this result move toward the request? | A steer rides with the next call |
|
|
81
|
+
| `before_write` | Is this change something the request asks for? | Held back **once**, with the doubt; made again, it goes on to approval |
|
|
82
|
+
| `before_answer` | Does this answer respond to the request? | Sent back (a nudge) |
|
|
83
|
+
| `hesitation` (empty reply, repeated call, leaked markup) | Which next step helps most? (choice over the tools + "answer now") | A steer naming the step |
|
|
84
|
+
|
|
85
|
+
The guide **steers; it does not overrule.** Every question avoids Jev's documented weaknesses: counting, numbers, dates, long context and multi-hop reasoning. The verdict node this engine's source loop dropped judged a whole transcript and asked whether "every fact and number" was supported. That combination is Jev's weak spot, and the probable reason the node was dropped.
|
|
86
|
+
|
|
87
|
+
**Guidance follows the model.** `guidance: 'auto'` is the default:
|
|
88
|
+
|
|
89
|
+
1. **Start** from what the provider knows of the model (`provider.card()`, which reads OpenRouter's live catalogue: price, context size, tool support). Expensive models start `light`; cheap or small ones start `close`.
|
|
90
|
+
2. **Adapt** within the run. Repeated low probabilities tighten the level; a streak of confident passes loosens it. Every change is a `guidance` event.
|
|
91
|
+
3. **Learn** across runs: a per-model profile (`guide.profiles`, e.g. `memoryProfiles()` or `fileProfiles(path)`) records how often Jev corrected the model and whether corrected runs still succeeded. After three runs, the profile sets the next run's starting level.
|
|
92
|
+
|
|
93
|
+
Levels: `off` · `light` (before writes and answers) · `normal` (+ every 3rd tool result, hesitation) · `close` (every tool result). A named level is fixed. A number `N` checks after every Nth result. Every checkpoint is a `checkpoint` event (question, probability, action, level), and its cost counts toward the budget. None run past the USD cap.
|
|
94
|
+
|
|
95
|
+
`npm run bench` compares guidance `off` against `close` on the same scenarios. The default dry run proves the wiring for $0. `node bench/guidance.mts --live <model-id> --runs N` is the real measure, and it spends money.
|
|
96
|
+
|
|
97
|
+
App-owned variants of the same idea: `jevStopCheck(provider, checks)` and `jevToolGate(provider, options)` put Jev behind the hooks.
|
|
98
|
+
|
|
99
|
+
## Hooks
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
hooks: {
|
|
103
|
+
beforeStep(ctx) // go · pause (→ 'paused') · stop (→ 'stopped')
|
|
104
|
+
toolGate(ctx) // go · { verdict: 'go', args } · { verdict: 'go', result } · pause · stop
|
|
105
|
+
rewriteOutput(ctx) // the text the model sees instead (redact, trim, annotate)
|
|
106
|
+
stopCheck(ctx) // nothing/stop = accept · { verdict: 'go', reason } = back to work · pause = needs_person
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
The same `go | pause | stop` vocabulary as ensemble's `guard(step)`, so one supervisor speaks both. `combineHooks(...)` stacks several (the first objection wins; rewrites chain). A paused run resumes by running again with `result.messages` as `history`: every tool call in it has a result.
|
|
111
|
+
|
|
112
|
+
## Events
|
|
113
|
+
|
|
114
|
+
`onEvent` streams events; `log: eventLog()` keeps them. The rule: **if the model saw it, it is logged.** Every message added to the model's context (the system prompt, the task, each tool result as the model received it, and the transient reminders and steers that never stay in `messages`) is a `context` event. Entries are numbered, timestamped, deep-copied and frozen. `toJSONL()` and `since(seq)` are for tailing; `onAppend` writes them anywhere.
|
|
115
|
+
|
|
116
|
+
## The seams: what an app provides
|
|
117
|
+
|
|
118
|
+
| Seam | What it is |
|
|
119
|
+
|---|---|
|
|
120
|
+
| `provider: ModelProvider` | `chat` (the worker), optional `decide` (Jev), optional `card` (what's known of a model). `openrouter()` in an app, `scriptedModel()` in a test |
|
|
121
|
+
| `toolsets`, `alwaysTools` | `AgentTool`s. A tool that changes something has `write.describe(args)`, the sentence the person approves. `foldTools()` folds many actions into one `{ action, args }` tool. `mcpToolset(session)` turns an MCP session (ensemble's `connect()` shape) into tools |
|
|
122
|
+
| `prompts: Partial<PromptPack>` | Every word the engine says: system rules, reminder, answer-now, nudges, opening paths |
|
|
123
|
+
| `memory: MemoryStore` | `recall()` facts into the system prompt (remembering is the app's own tool) |
|
|
124
|
+
| `session: { store: SessionStore, id }` or `history` | Earlier turns |
|
|
125
|
+
| `approve(request)` | Asks the person. Without it, every change is refused |
|
|
126
|
+
| `skills: SkillSource` | `inlineSkills([...])` or `dirSkills(dir)` (Agent Skills format). Jev picks; `use_skill` and `read_skill_file` load |
|
|
127
|
+
| `postProcessors` | Transform the final answer (an app's own link repair, redaction) |
|
|
128
|
+
| `subagentTool(spec)` | A whole agent as one tool: isolated transcript, cost rolled up into the parent's cap |
|
|
129
|
+
|
|
130
|
+
## The boundary rule
|
|
131
|
+
|
|
132
|
+
`src/` never names an app: no app database, no app link format, no app wording in prompts. If a line of the engine would only make sense for one app, it belongs behind a seam. `test/boundary.test.mts` enforces it, along with zero runtime dependencies and no hardcoded model ids. Jev's `jev-latest` alias is the one default.
|
|
133
|
+
|
|
134
|
+
## Development
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
npm test # all suites, offline, $0 (builds first)
|
|
138
|
+
npm run typecheck
|
|
139
|
+
npm run example # the two-tool agent, scripted
|
|
140
|
+
npm run bench # guidance off vs close, dry run
|
|
141
|
+
```
|
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budgets — every run is bounded, and says which bound it hit.
|
|
3
|
+
*
|
|
4
|
+
* Steps, tool calls and nudges came from Potion's Talk, each tuned against a failure seen in its
|
|
5
|
+
* evaluation. The USD cap is new: Talk had none, though it already summed what each call cost —
|
|
6
|
+
* OpenRouter bills `usage.cost` per call, worker and Jev alike, so a cap is a comparison, not an
|
|
7
|
+
* estimate. A run that hits it ends with status 'limit' and a reason naming the figure, never a
|
|
8
|
+
* silent truncation.
|
|
9
|
+
*/
|
|
10
|
+
export interface Budget {
|
|
11
|
+
/** Worker turns. Default 8. The last one always answers, with tools shown but not callable. */
|
|
12
|
+
maxSteps?: number;
|
|
13
|
+
/** Tool calls across the run. Default 12. Once spent, the next step answers with what it has. */
|
|
14
|
+
maxToolCalls?: number;
|
|
15
|
+
/** How many times a stopCheck may send the model back. Default 2. */
|
|
16
|
+
maxNudges?: number;
|
|
17
|
+
/** USD across the run (worker, decisions, and whatever tools report through `spend`). Default: none. */
|
|
18
|
+
maxUsd?: number;
|
|
19
|
+
/** Extra steps granted by guide tools (reading a skill is preparation, not work). Default 4. */
|
|
20
|
+
guideSteps?: number;
|
|
21
|
+
/** Every this many reads, a tool result carries a "you may have enough — answer" note. Default 4. */
|
|
22
|
+
readNudgeAt?: number;
|
|
23
|
+
/** A tool result longer than this is truncated before the model sees it. Default 12,000 chars. */
|
|
24
|
+
resultCap?: number;
|
|
25
|
+
}
|
|
26
|
+
export declare const DEFAULT_BUDGET: {
|
|
27
|
+
maxSteps: number;
|
|
28
|
+
maxToolCalls: number;
|
|
29
|
+
maxNudges: number;
|
|
30
|
+
maxUsd: number;
|
|
31
|
+
guideSteps: number;
|
|
32
|
+
readNudgeAt: number;
|
|
33
|
+
resultCap: number;
|
|
34
|
+
};
|
|
35
|
+
export type Limits = Required<Budget>;
|
|
36
|
+
export declare function limits(budget?: Budget): Limits;
|
|
37
|
+
/** The reason a run must stop for money, or null while there is budget left. */
|
|
38
|
+
export declare function overBudget(spent: number, maxUsd: number): string | null;
|
|
39
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,WAAW,MAAM;IACrB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iGAAiG;IACjG,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,qEAAqE;IACrE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wGAAwG;IACxG,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gGAAgG;IAChG,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qGAAqG;IACrG,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kGAAkG;IAClG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,cAAc;;;;;;;;CAQC,CAAC;AAE7B,MAAM,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEtC,wBAAgB,MAAM,CAAC,MAAM,GAAE,MAAW,GAAG,MAAM,CAIlD;AAKD,gFAAgF;AAChF,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGvE"}
|
package/dist/budget.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Budgets — every run is bounded, and says which bound it hit.
|
|
3
|
+
*
|
|
4
|
+
* Steps, tool calls and nudges came from Potion's Talk, each tuned against a failure seen in its
|
|
5
|
+
* evaluation. The USD cap is new: Talk had none, though it already summed what each call cost —
|
|
6
|
+
* OpenRouter bills `usage.cost` per call, worker and Jev alike, so a cap is a comparison, not an
|
|
7
|
+
* estimate. A run that hits it ends with status 'limit' and a reason naming the figure, never a
|
|
8
|
+
* silent truncation.
|
|
9
|
+
*/
|
|
10
|
+
export const DEFAULT_BUDGET = {
|
|
11
|
+
maxSteps: 8,
|
|
12
|
+
maxToolCalls: 12,
|
|
13
|
+
maxNudges: 2,
|
|
14
|
+
maxUsd: Infinity,
|
|
15
|
+
guideSteps: 4,
|
|
16
|
+
readNudgeAt: 4,
|
|
17
|
+
resultCap: 12_000,
|
|
18
|
+
};
|
|
19
|
+
export function limits(budget = {}) {
|
|
20
|
+
const out = { ...DEFAULT_BUDGET };
|
|
21
|
+
for (const [k, v] of Object.entries(budget))
|
|
22
|
+
if (v !== undefined)
|
|
23
|
+
out[k] = v;
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/** Money is compared in whole micro-dollars so float sums never stop a run a hair early or late. */
|
|
27
|
+
const micro = (usd) => Math.round(usd * 1e6);
|
|
28
|
+
/** The reason a run must stop for money, or null while there is budget left. */
|
|
29
|
+
export function overBudget(spent, maxUsd) {
|
|
30
|
+
if (!Number.isFinite(maxUsd) || micro(spent) < micro(maxUsd))
|
|
31
|
+
return null;
|
|
32
|
+
return `Budget reached: spent $${spent.toFixed(6)} of the $${maxUsd.toFixed(6)} cap.`;
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../src/budget.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAmBH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,QAAQ,EAAE,CAAC;IACX,YAAY,EAAE,EAAE;IAChB,SAAS,EAAE,CAAC;IACZ,MAAM,EAAE,QAAQ;IAChB,UAAU,EAAE,CAAC;IACb,WAAW,EAAE,CAAC;IACd,SAAS,EAAE,MAAM;CACS,CAAC;AAI7B,MAAM,UAAU,MAAM,CAAC,SAAiB,EAAE;IACxC,MAAM,GAAG,GAAG,EAAE,GAAG,cAAc,EAAE,CAAC;IAClC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAyC;QAAE,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACrH,OAAO,GAAG,CAAC;AACb,CAAC;AAED,oGAAoG;AACpG,MAAM,KAAK,GAAG,CAAC,GAAW,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,CAAC;AAErD,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,MAAc;IACtD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1E,OAAO,0BAA0B,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC;AACxF,CAAC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Two ways for the worker to ask for help mid-task.
|
|
3
|
+
*
|
|
4
|
+
* ask_jev — Jev, the decision model: a yes/no (probability), a pick from options (with every
|
|
5
|
+
* option's probability), or a score on a scale. Fast, calibrated, a fraction of a
|
|
6
|
+
* cent. Meant to be offered at EVERY step (`alwaysTools`): whenever an answer is a
|
|
7
|
+
* judgement, not prose — which existing option fits, is this a duplicate, how relevant.
|
|
8
|
+
* ask_model — another model, consulted in plain text with no tools. Only from an allowlist the app
|
|
9
|
+
* gives, because which models a person may spend on is the app's decision.
|
|
10
|
+
*
|
|
11
|
+
* What each costs is reported through the tool context's `spend`, so it counts toward the run's
|
|
12
|
+
* total and its USD cap.
|
|
13
|
+
*/
|
|
14
|
+
import type { ModelProvider } from './model.ts';
|
|
15
|
+
import { type AgentTool } from './tools.ts';
|
|
16
|
+
export declare function askJevTool(model: ModelProvider): AgentTool;
|
|
17
|
+
export interface AskModelOptions {
|
|
18
|
+
/** The models that may be consulted: id → label. */
|
|
19
|
+
models: Record<string, string>;
|
|
20
|
+
/** The consulted model's system line. */
|
|
21
|
+
system?: string;
|
|
22
|
+
maxTokens?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function askModelTool(model: ModelProvider, options: AskModelOptions): AgentTool;
|
|
25
|
+
//# sourceMappingURL=consult.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consult.d.ts","sourceRoot":"","sources":["../src/consult.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,KAAK,EAAE,aAAa,EAAY,MAAM,YAAY,CAAC;AAC1D,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAE3D,wBAAgB,UAAU,CAAC,KAAK,EAAE,aAAa,GAAG,SAAS,CA6C1D;AAED,MAAM,WAAW,eAAe;IAC9B,oDAAoD;IACpD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,yCAAyC;IACzC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,SAAS,CAsBtF"}
|
package/dist/consult.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { forgivingArgs } from "./tools.js";
|
|
2
|
+
export function askJevTool(model) {
|
|
3
|
+
if (!model.decide)
|
|
4
|
+
throw new Error('ask_jev needs a ModelProvider with decide().');
|
|
5
|
+
const decide = model.decide.bind(model);
|
|
6
|
+
return {
|
|
7
|
+
name: 'ask_jev',
|
|
8
|
+
description: 'Ask Jev, a fast decision model, one typed question about some state and get probabilities back (no prose). kind "yes_no": the probability of yes. kind "choice": pick one of `options` (an array of labels, or { label: description }), with every option\'s probability — use it to match a phrase to what EXISTS instead of guessing. kind "score": a position on `options` read as ordered levels, low to high. Use it freely whenever the answer is a judgement.',
|
|
9
|
+
parameters: {
|
|
10
|
+
type: 'object',
|
|
11
|
+
properties: {
|
|
12
|
+
question: { type: 'string', description: 'The question, referring to fields of `state` in backticks, e.g. "Which of `options` fits `item.title`?"' },
|
|
13
|
+
state: { description: 'What Jev judges: text or an object' },
|
|
14
|
+
kind: { type: 'string', enum: ['yes_no', 'choice', 'score'] },
|
|
15
|
+
options: { description: 'For choice / score: labels (array) or { label: description }' },
|
|
16
|
+
},
|
|
17
|
+
required: ['question', 'state', 'kind'],
|
|
18
|
+
},
|
|
19
|
+
run: async (args, ctx) => {
|
|
20
|
+
// Models often send `options` and `state` as JSON text; read them as what they mean.
|
|
21
|
+
const a = forgivingArgs(args);
|
|
22
|
+
const kind = String(a.kind);
|
|
23
|
+
const raw = a.options;
|
|
24
|
+
const labels = Array.isArray(raw)
|
|
25
|
+
? Object.fromEntries(raw.map(o => [String(o), null]))
|
|
26
|
+
: raw && typeof raw === 'object'
|
|
27
|
+
? Object.fromEntries(Object.entries(raw).map(([k, v]) => [k, v == null ? null : String(v)]))
|
|
28
|
+
: {};
|
|
29
|
+
let q;
|
|
30
|
+
if (kind === 'yes_no')
|
|
31
|
+
q = { type: 'noul', instructions: String(a.question) };
|
|
32
|
+
else if (kind === 'choice') {
|
|
33
|
+
if (Object.keys(labels).length < 2)
|
|
34
|
+
throw new Error('A choice needs at least two options');
|
|
35
|
+
q = { type: 'choice', instructions: String(a.question), criteria: labels };
|
|
36
|
+
}
|
|
37
|
+
else if (kind === 'score') {
|
|
38
|
+
const levels = Object.entries(labels).map(([k, v]) => (v ? `${k}: ${v}` : k));
|
|
39
|
+
if (levels.length < 2)
|
|
40
|
+
throw new Error('A score needs at least two ordered levels in `options`');
|
|
41
|
+
q = { type: 'score', instructions: String(a.question), criteria: levels.slice(0, 10) };
|
|
42
|
+
}
|
|
43
|
+
else
|
|
44
|
+
throw new Error('kind must be yes_no, choice or score');
|
|
45
|
+
const { answers, cost } = await decide(a.state ?? '', { q }, ctx.signal);
|
|
46
|
+
ctx.spend(cost);
|
|
47
|
+
const ans = answers.q;
|
|
48
|
+
if (ans.type === 'noul')
|
|
49
|
+
return { yes: Number(ans.noul.toFixed(3)) };
|
|
50
|
+
if (ans.type === 'choice')
|
|
51
|
+
return { choice: ans.choice, confidence: Number(ans.confidence.toFixed(3)), probabilities: ans.probabilities };
|
|
52
|
+
return { score: Number(ans.score.toFixed(3)), confidence: Number(ans.confidence.toFixed(3)), levels: ans.legend, probabilities: ans.probabilities };
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export function askModelTool(model, options) {
|
|
57
|
+
const ids = Object.keys(options.models);
|
|
58
|
+
return {
|
|
59
|
+
name: 'ask_model',
|
|
60
|
+
description: `Send one self-contained prompt to another model and get its text answer (it has no tools and sees nothing else). Only when the person asked for that model. Models: ${ids.map(id => `${id} (${options.models[id]})`).join(', ')}.`,
|
|
61
|
+
parameters: { type: 'object', properties: { model: { type: 'string', enum: ids }, prompt: { type: 'string' } }, required: ['model', 'prompt'] },
|
|
62
|
+
run: async (a, ctx) => {
|
|
63
|
+
const id = String(a.model ?? '');
|
|
64
|
+
if (!ids.includes(id))
|
|
65
|
+
throw new Error(`"${id}" is not offered. Models: ${ids.join(', ')}`);
|
|
66
|
+
const reply = await model.chat({
|
|
67
|
+
model: id,
|
|
68
|
+
maxTokens: options.maxTokens ?? 2000,
|
|
69
|
+
signal: ctx.signal,
|
|
70
|
+
messages: [
|
|
71
|
+
{ role: 'system', content: options.system ?? "You are consulted by another agent on a person's behalf. Answer the prompt directly and concisely." },
|
|
72
|
+
{ role: 'user', content: String(a.prompt ?? '') },
|
|
73
|
+
],
|
|
74
|
+
});
|
|
75
|
+
ctx.spend(reply.cost);
|
|
76
|
+
return { model: id, answer: reply.message.content ?? '' };
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=consult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consult.js","sourceRoot":"","sources":["../src/consult.ts"],"names":[],"mappings":"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"}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Events — what happened in a run, and the log that keeps it.
|
|
3
|
+
*
|
|
4
|
+
* The rule: IF THE MODEL SAW IT, IT IS LOGGED. Every message added to the model's context emits a
|
|
5
|
+
* `context` event — the system prompt, the task, each tool result as the model received it (after
|
|
6
|
+
* truncation and rewriting), and the transient lines (reminders, "answer now") that are sent once and
|
|
7
|
+
* never stay in the conversation. A run can therefore be replayed and audited from its log alone,
|
|
8
|
+
* which is not true of `messages`, where transient lines leave no trace.
|
|
9
|
+
*
|
|
10
|
+
* The log is append-only in the strict sense: an entry is a deep copy, frozen, numbered in order.
|
|
11
|
+
* Nothing that happens later in the run — a message mutated, a result rewritten — can change what
|
|
12
|
+
* was recorded.
|
|
13
|
+
*/
|
|
14
|
+
import type { ChatMessage } from './model.ts';
|
|
15
|
+
import type { AgentResult, AgentTask } from './seams.ts';
|
|
16
|
+
import type { Verdict } from './hooks.ts';
|
|
17
|
+
import type { Checkpoint, GuidanceLevel } from './guide.ts';
|
|
18
|
+
export type AgentEvent = {
|
|
19
|
+
type: 'run_start';
|
|
20
|
+
task: AgentTask;
|
|
21
|
+
model: string | null;
|
|
22
|
+
} | {
|
|
23
|
+
type: 'context';
|
|
24
|
+
message: ChatMessage;
|
|
25
|
+
transient: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
type: 'opening';
|
|
28
|
+
path: 'task' | 'greeting' | 'small_talk';
|
|
29
|
+
confidence: number;
|
|
30
|
+
} | {
|
|
31
|
+
type: 'skills';
|
|
32
|
+
names: string[];
|
|
33
|
+
} | {
|
|
34
|
+
type: 'step';
|
|
35
|
+
step: number;
|
|
36
|
+
answerNow: boolean;
|
|
37
|
+
} | {
|
|
38
|
+
type: 'delta';
|
|
39
|
+
text: string;
|
|
40
|
+
} | {
|
|
41
|
+
type: 'message';
|
|
42
|
+
text: string;
|
|
43
|
+
} | {
|
|
44
|
+
type: 'tool_call';
|
|
45
|
+
id: string;
|
|
46
|
+
name: string;
|
|
47
|
+
args: Record<string, unknown>;
|
|
48
|
+
} | {
|
|
49
|
+
type: 'approval';
|
|
50
|
+
id: string;
|
|
51
|
+
tool: string;
|
|
52
|
+
summary: string;
|
|
53
|
+
args: Record<string, unknown>;
|
|
54
|
+
} | {
|
|
55
|
+
type: 'approval_result';
|
|
56
|
+
id: string;
|
|
57
|
+
approved: boolean;
|
|
58
|
+
} | {
|
|
59
|
+
type: 'tool_result';
|
|
60
|
+
id: string;
|
|
61
|
+
name: string;
|
|
62
|
+
ok: boolean;
|
|
63
|
+
result: string;
|
|
64
|
+
} | {
|
|
65
|
+
type: 'hook';
|
|
66
|
+
hook: 'beforeStep' | 'toolGate' | 'stopCheck';
|
|
67
|
+
verdict: Verdict;
|
|
68
|
+
reason: string | null;
|
|
69
|
+
} | {
|
|
70
|
+
type: 'nudge';
|
|
71
|
+
reason: string;
|
|
72
|
+
} | {
|
|
73
|
+
type: 'spend';
|
|
74
|
+
usd: number;
|
|
75
|
+
total: number;
|
|
76
|
+
on: string;
|
|
77
|
+
}
|
|
78
|
+
/** A Jev checkpoint the guide asked: the question, the probability (or chosen step) and what the loop did. */
|
|
79
|
+
| {
|
|
80
|
+
type: 'checkpoint';
|
|
81
|
+
at: Checkpoint;
|
|
82
|
+
question: string;
|
|
83
|
+
p: number | null;
|
|
84
|
+
choice?: string;
|
|
85
|
+
action: 'pass' | 'steer' | 'hold' | 'send_back' | 'none';
|
|
86
|
+
level: GuidanceLevel;
|
|
87
|
+
}
|
|
88
|
+
/** The guidance level was set (from: null) or changed, and why. */
|
|
89
|
+
| {
|
|
90
|
+
type: 'guidance';
|
|
91
|
+
from: GuidanceLevel | null;
|
|
92
|
+
level: GuidanceLevel;
|
|
93
|
+
reason: string;
|
|
94
|
+
} | {
|
|
95
|
+
type: 'finished';
|
|
96
|
+
result: AgentResult;
|
|
97
|
+
};
|
|
98
|
+
export interface LoggedEvent {
|
|
99
|
+
/** 1, 2, 3… in the order they happened. */
|
|
100
|
+
seq: number;
|
|
101
|
+
/** ISO time. */
|
|
102
|
+
at: string;
|
|
103
|
+
event: AgentEvent;
|
|
104
|
+
}
|
|
105
|
+
export interface EventLog {
|
|
106
|
+
append(event: AgentEvent): LoggedEvent;
|
|
107
|
+
entries(): readonly LoggedEvent[];
|
|
108
|
+
/** Entries after `seq` — for a reader that tails the log. */
|
|
109
|
+
since(seq: number): readonly LoggedEvent[];
|
|
110
|
+
/** One JSON object per line. */
|
|
111
|
+
toJSONL(): string;
|
|
112
|
+
}
|
|
113
|
+
export interface EventLogOptions {
|
|
114
|
+
/** Called with each entry as it is appended — write it to a file, a database, a socket. */
|
|
115
|
+
onAppend?: (entry: LoggedEvent) => void;
|
|
116
|
+
now?: () => Date;
|
|
117
|
+
}
|
|
118
|
+
export declare function eventLog(options?: EventLogOptions): EventLog;
|
|
119
|
+
//# sourceMappingURL=events.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"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"}
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
function deepFreeze(value) {
|
|
2
|
+
if (value && typeof value === 'object' && !Object.isFrozen(value)) {
|
|
3
|
+
Object.freeze(value);
|
|
4
|
+
for (const v of Object.values(value))
|
|
5
|
+
deepFreeze(v);
|
|
6
|
+
}
|
|
7
|
+
return value;
|
|
8
|
+
}
|
|
9
|
+
export function eventLog(options = {}) {
|
|
10
|
+
const list = [];
|
|
11
|
+
const now = options.now ?? (() => new Date());
|
|
12
|
+
return {
|
|
13
|
+
append(event) {
|
|
14
|
+
const entry = deepFreeze({ seq: list.length + 1, at: now().toISOString(), event: structuredClone(event) });
|
|
15
|
+
list.push(entry);
|
|
16
|
+
options.onAppend?.(entry);
|
|
17
|
+
return entry;
|
|
18
|
+
},
|
|
19
|
+
entries: () => list.slice(),
|
|
20
|
+
since: seq => list.filter(e => e.seq > seq),
|
|
21
|
+
toJSONL: () => list.map(e => JSON.stringify(e)).join('\n') + (list.length ? '\n' : ''),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=events.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAwDA,SAAS,UAAU,CAAI,KAAQ;IAC7B,IAAI,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAClE,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC;YAAE,UAAU,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAQD,MAAM,UAAU,QAAQ,CAAC,UAA2B,EAAE;IACpD,MAAM,IAAI,GAAkB,EAAE,CAAC;IAC/B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;IAC9C,OAAO;QACL,MAAM,CAAC,KAAK;YACV,MAAM,KAAK,GAAG,UAAU,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3G,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YACjB,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;YAC1B,OAAO,KAAK,CAAC;QACf,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE;QAC3B,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;QAC3C,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;KACvF,CAAC;AACJ,CAAC"}
|
package/dist/guide.d.ts
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { AgentEvent } from './events.ts';
|
|
2
|
+
import type { ModelCard, ModelProvider } from './model.ts';
|
|
3
|
+
import type { AgentStatus, AgentTask } from './seams.ts';
|
|
4
|
+
import type { AgentTool } from './tools.ts';
|
|
5
|
+
export type GuidanceLevel = 'off' | 'light' | 'normal' | 'close';
|
|
6
|
+
/** 'auto' starts from the model and adapts; a named level is fixed; a number N checks after every Nth tool result, fixed. */
|
|
7
|
+
export type Guidance = 'auto' | GuidanceLevel | number;
|
|
8
|
+
export type Checkpoint = 'after_tool' | 'before_write' | 'before_answer' | 'hesitation';
|
|
9
|
+
export interface Thresholds {
|
|
10
|
+
/** after_tool: below this, steer. Default 0.35. */
|
|
11
|
+
onTrack: number;
|
|
12
|
+
/** before_write: below this, hold the change back once. Default 0.3. */
|
|
13
|
+
write: number;
|
|
14
|
+
/** before_answer: below this, send back. Default 0.35. */
|
|
15
|
+
answer: number;
|
|
16
|
+
/** At or above this a pass counts as confident (for loosening). Default 0.85. */
|
|
17
|
+
confident: number;
|
|
18
|
+
/** This many lows in a row tighten the level. Default 2. */
|
|
19
|
+
tightenAfter: number;
|
|
20
|
+
/** This many confident passes in a row loosen it. Default 4. */
|
|
21
|
+
loosenAfter: number;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_THRESHOLDS: Thresholds;
|
|
24
|
+
/** What each level checks. `afterTool` is a period: every Nth tool result (0 = never). */
|
|
25
|
+
export declare const LEVELS: Record<GuidanceLevel, {
|
|
26
|
+
afterTool: number;
|
|
27
|
+
beforeWrite: boolean;
|
|
28
|
+
beforeAnswer: boolean;
|
|
29
|
+
hesitation: boolean;
|
|
30
|
+
}>;
|
|
31
|
+
export interface GuideProfile {
|
|
32
|
+
model: string;
|
|
33
|
+
runs: number;
|
|
34
|
+
/** Runs that ended 'done'. */
|
|
35
|
+
successes: number;
|
|
36
|
+
checkpoints: number;
|
|
37
|
+
/** Checkpoints below their threshold. */
|
|
38
|
+
lows: number;
|
|
39
|
+
/** Runs where the guide steered at least once, and how many of those still ended 'done'. */
|
|
40
|
+
correctedRuns: number;
|
|
41
|
+
correctedSuccesses: number;
|
|
42
|
+
/** The level the last run ended at. */
|
|
43
|
+
lastLevel: GuidanceLevel;
|
|
44
|
+
}
|
|
45
|
+
export interface ProfileStore {
|
|
46
|
+
get(model: string): Promise<GuideProfile | null>;
|
|
47
|
+
put(profile: GuideProfile): Promise<void>;
|
|
48
|
+
}
|
|
49
|
+
export declare function memoryProfiles(): ProfileStore & {
|
|
50
|
+
profiles: Map<string, GuideProfile>;
|
|
51
|
+
};
|
|
52
|
+
/** Profiles in one JSON file (written atomically). Good for a CLI or a single server. */
|
|
53
|
+
export declare function fileProfiles(path: string): ProfileStore;
|
|
54
|
+
/**
|
|
55
|
+
* From what the catalogue says of a model. Price is the strongest cheap signal of capability a
|
|
56
|
+
* catalogue gives; a small context or no tool support tightens one more notch. Unknown → normal.
|
|
57
|
+
*/
|
|
58
|
+
export declare function levelFromCard(card: ModelCard | null): {
|
|
59
|
+
level: GuidanceLevel;
|
|
60
|
+
why: string;
|
|
61
|
+
};
|
|
62
|
+
/** From a model's history, once there is enough of it (3+ runs); null otherwise. */
|
|
63
|
+
export declare function levelFromProfile(p: GuideProfile | null): {
|
|
64
|
+
level: GuidanceLevel;
|
|
65
|
+
why: string;
|
|
66
|
+
} | null;
|
|
67
|
+
export interface GuideOptions {
|
|
68
|
+
guidance?: Guidance;
|
|
69
|
+
thresholds?: Partial<Thresholds>;
|
|
70
|
+
profiles?: ProfileStore;
|
|
71
|
+
}
|
|
72
|
+
export interface GuideDeps {
|
|
73
|
+
provider: ModelProvider;
|
|
74
|
+
model: string | undefined;
|
|
75
|
+
task: AgentTask;
|
|
76
|
+
signal: AbortSignal;
|
|
77
|
+
emit(event: AgentEvent): void;
|
|
78
|
+
spend(usd: number, on: string): void;
|
|
79
|
+
/** False once the run is over its USD cap: checkpoints are skipped then. */
|
|
80
|
+
affordable(): boolean;
|
|
81
|
+
}
|
|
82
|
+
export interface Guide {
|
|
83
|
+
readonly level: GuidanceLevel;
|
|
84
|
+
readonly active: boolean;
|
|
85
|
+
start(): Promise<void>;
|
|
86
|
+
/** After a batch of tool results: a steer for the next call, or null. */
|
|
87
|
+
afterTools(results: {
|
|
88
|
+
tool: string;
|
|
89
|
+
args: Record<string, unknown>;
|
|
90
|
+
result: string;
|
|
91
|
+
ok: boolean;
|
|
92
|
+
}[]): Promise<string | null>;
|
|
93
|
+
/** Before a change: a held-back result to hand the model instead, or null to proceed. */
|
|
94
|
+
beforeWrite(tool: string, args: Record<string, unknown>, change: string, key: string): Promise<string | null>;
|
|
95
|
+
/** Before accepting the final answer: a reason to send it back, or null. */
|
|
96
|
+
beforeAnswer(answer: string | null): Promise<string | null>;
|
|
97
|
+
/** When the model hesitates: a steer naming the likeliest next step, or null. */
|
|
98
|
+
hesitation(why: 'empty_reply' | 'repeated_call' | 'leaked_call', last: string, tools: AgentTool[]): Promise<string | null>;
|
|
99
|
+
finish(status: AgentStatus): Promise<void>;
|
|
100
|
+
}
|
|
101
|
+
export declare const ANSWER_NOW = "answer_now";
|
|
102
|
+
export declare function createGuide(options: GuideOptions, deps: GuideDeps): Guide;
|
|
103
|
+
//# sourceMappingURL=guide.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guide.d.ts","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"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"}
|