ai-runtime-engine 1.1.0 → 1.2.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 +36 -0
- package/README.md +63 -409
- package/dist/cli/cli.js +3 -1
- package/dist/cli/commands/config.js +6 -4
- package/dist/cli/commands/executions.d.ts +2 -0
- package/dist/cli/commands/executions.js +8 -1
- package/dist/cli/commands/init.js +7 -4
- package/dist/cli/commands/phase2.js +6 -2
- package/dist/cli/interactive/repl.js +20 -0
- package/dist/cli/interactive/session.js +15 -5
- package/dist/runtime/runtime.d.ts +5 -1
- package/dist/runtime/runtime.js +28 -8
- package/docs/GUIDE.md +3 -2
- package/docs/README.md +9 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,40 @@ All notable changes to `ai-runtime` are documented here. The format follows
|
|
|
5
5
|
Versioning](https://semver.org/). Development history and rationale live in
|
|
6
6
|
[docs/DECISIONS.md](docs/DECISIONS.md) and [docs/PROGRESS.md](docs/PROGRESS.md).
|
|
7
7
|
|
|
8
|
+
## [1.2.0] — 2026-08-31
|
|
9
|
+
|
|
10
|
+
Closes the known gaps between what the CLI/REPL exposed and what the Runtime supported. Additive and
|
|
11
|
+
backward compatible, with three small behavior corrections noted under Changed.
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **Approve / deny from the terminal** — `resume-execution --approve` / `--deny`, and REPL `/approve <id>`
|
|
16
|
+
/ `/deny <id>`. A plan that stops at `waiting_for_approval` can now be advanced or cancelled from the
|
|
17
|
+
CLI or interactive terminal (previously reachable only programmatically or via a host `ApprovalProvider`).
|
|
18
|
+
- **Live progress in the interactive terminal** — the REPL now prints concise mode/routing/fallback lines
|
|
19
|
+
during a run by subscribing to lifecycle events (it always claimed to, but never did).
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- **`chat` mode now honors `--dry-run` / `dryRun`** — a chat dry run makes **no** model call and captures
|
|
24
|
+
**no** memory; it reports what it would send. (Orchestration modes already did; chat used to run live.)
|
|
25
|
+
- **`ai-runtime config`** now resolves `.ai-runtime/config.yaml` through the runtime loader, matching
|
|
26
|
+
every other command. It previously used the legacy root-only loader and reported "no config file found"
|
|
27
|
+
even when the canonical file was in use.
|
|
28
|
+
- **`ai-runtime init`** now scaffolds the canonical `.ai-runtime/config.yaml` (same location as `setup`)
|
|
29
|
+
instead of a root `ai-runtime.yaml`. A root `ai-runtime.yaml` still works as a fallback if present.
|
|
30
|
+
- **`ai-runtime telemetry`** empty-state message now explains that telemetry is buffered in-process and
|
|
31
|
+
points to `telemetry: { sink: file }` for history that survives across CLI invocations.
|
|
32
|
+
|
|
33
|
+
## [1.1.1] — 2026-08-31
|
|
34
|
+
|
|
35
|
+
Documentation only — no code changes.
|
|
36
|
+
|
|
37
|
+
- Rewrote the README as a short, scannable landing page (~400 → ~140 lines) and removed relative links to
|
|
38
|
+
bundled docs / `LICENSE` / `CHANGELOG`, which show "no page found" on the npm package page (the source
|
|
39
|
+
repo is private). The full docs still ship in the package's `docs/` folder.
|
|
40
|
+
- Added npm version / Node / license badges and a top-of-page `npm install` line.
|
|
41
|
+
|
|
8
42
|
## [1.1.0] — 2026-08-29
|
|
9
43
|
|
|
10
44
|
Closes out the two items 1.0 deferred. Additive and backward compatible.
|
|
@@ -73,6 +107,8 @@ Initial release: the provider-agnostic AI **router** — capability-based routin
|
|
|
73
107
|
scoring, evidence validation, fallback, health tracking, learning-based scoring, multi-model verification,
|
|
74
108
|
budgets, MCP tools, OpenAPI-based adapter generation, and the `AI` class + CLI.
|
|
75
109
|
|
|
110
|
+
[1.2.0]: https://github.com/pavankhandelwal21/ai-runtime/releases/tag/v1.2.0
|
|
111
|
+
[1.1.1]: https://github.com/pavankhandelwal21/ai-runtime/releases/tag/v1.1.1
|
|
76
112
|
[1.1.0]: https://github.com/pavankhandelwal21/ai-runtime/releases/tag/v1.1.0
|
|
77
113
|
[1.0.0]: https://github.com/pavankhandelwal21/ai-runtime/releases/tag/v1.0.0
|
|
78
114
|
[0.1.0]: https://github.com/pavankhandelwal21/ai-runtime/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -1,127 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# AI Runtime
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/ai-runtime-engine)
|
|
4
|
+
[](https://nodejs.org)
|
|
5
|
+
[](https://www.npmjs.com/package/ai-runtime-engine)
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
```ts
|
|
10
|
-
import { Runtime } from 'ai-runtime-engine';
|
|
11
|
-
|
|
12
|
-
const runtime = await Runtime.load(); // reads your config + .env
|
|
13
|
-
const result = await runtime.run({ input: 'Fix the failing tests' });
|
|
14
|
-
console.log(result.response?.text);
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
> **The idea:** providers are replaceable; capabilities are durable. You never have to know *which*
|
|
18
|
-
> provider handled a request — you describe what you want, and the runtime picks the right model and runs it.
|
|
19
|
-
|
|
20
|
-
> 📘 **New here?** Start with the **[hands-on Guide](docs/GUIDE.md)** — install to first run, with runnable
|
|
21
|
-
> examples you can try offline (no API key needed).
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## Contents
|
|
26
|
-
|
|
27
|
-
- [What it is](#what-it-is)
|
|
28
|
-
- [Requirements](#requirements)
|
|
29
|
-
- [Install](#install)
|
|
30
|
-
- [Quick start](#quick-start)
|
|
31
|
-
- [How it works](#how-it-works)
|
|
32
|
-
- [How to use it](#how-to-use-it) — modes, terminal, memory, skills, orchestration, comparison, learning
|
|
33
|
-
- [Configuration](#configuration)
|
|
34
|
-
- [What you need to do](#what-you-need-to-do) — a getting-started checklist
|
|
35
|
-
- [CLI commands](#cli-commands)
|
|
36
|
-
- [Package & distribution](#package--distribution)
|
|
37
|
-
- [Security & privacy](#security--privacy)
|
|
38
|
-
- [Troubleshooting](#troubleshooting)
|
|
39
|
-
- [Further reading](#further-reading)
|
|
40
|
-
- [Development](#development)
|
|
41
|
-
|
|
42
|
-
---
|
|
43
|
-
|
|
44
|
-
## What it is
|
|
45
|
-
|
|
46
|
-
Hardcoding one AI provider means vendor lock-in, availability risk, and painful migrations — and different
|
|
47
|
-
providers are better at different things (reasoning, speed, cost, privacy), while any of them can be
|
|
48
|
-
rate-limited, out of quota, or down.
|
|
49
|
-
|
|
50
|
-
`ai-runtime` separates **provider support** from **provider availability**. Configure whatever you have
|
|
51
|
-
once; then, on every request, the runtime:
|
|
52
|
-
|
|
53
|
-
1. figures out **what kind of work** it is (a quick answer, a plan, a multi-step task, a comparison…),
|
|
54
|
-
2. **routes** it to the best model you have that can actually do it (by capability, health, privacy, cost),
|
|
55
|
-
3. **executes** through one normalized interface and **validates** the result,
|
|
56
|
-
4. **falls back** to the next-best model if something fails, and
|
|
57
|
-
5. returns a clean result with a full trace of how it decided.
|
|
58
|
-
|
|
59
|
-
Around that router sits the **Runtime**: an interactive terminal, conversation history, durable memory,
|
|
60
|
-
context building, a skill/tool system with permissions, planning and orchestration, resumable long-running
|
|
61
|
-
tasks, model comparison, and learning from outcomes. Everything runs over native `fetch` — **no provider
|
|
62
|
-
SDKs**.
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Requirements
|
|
67
|
-
|
|
68
|
-
- **Node.js 22 or newer.**
|
|
69
|
-
- Git is optional (used to detect your workspace).
|
|
70
|
-
- At least one AI provider — a cloud API key, or a local model via [Ollama](https://ollama.com) (no key needed).
|
|
71
|
-
|
|
72
|
-
---
|
|
73
|
-
|
|
74
|
-
## Install
|
|
75
|
-
|
|
76
|
-
The npm package is **`ai-runtime-engine`**. Installing it gives you both a **library** and the
|
|
77
|
-
**`ai-runtime`** command-line tool (the command name is `ai-runtime` — a package name need not match its
|
|
78
|
-
command name).
|
|
7
|
+
**A provider-agnostic AI runtime — point it at whatever AI providers you have, and it routes each request
|
|
8
|
+
to the best available model.** On top of that it adds an interactive terminal, memory, skills, tools,
|
|
9
|
+
planning, orchestration, comparison, and learning.
|
|
79
10
|
|
|
80
11
|
```bash
|
|
81
|
-
# in your project (library + local CLI via `npx ai-runtime`)
|
|
82
12
|
npm install ai-runtime-engine
|
|
83
|
-
|
|
84
|
-
# or install globally to get the `ai-runtime` command on your PATH
|
|
85
|
-
npm install -g ai-runtime-engine
|
|
86
13
|
```
|
|
87
14
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
```ts
|
|
91
|
-
import { Runtime, AI } from 'ai-runtime-engine';
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
> **Naming:** the **product** is *AI Runtime*, the **npm package** is `ai-runtime-engine`, and the **CLI
|
|
95
|
-
> command** is `ai-runtime`. The main API is `Runtime`; the lower-level router is `AI`.
|
|
15
|
+
> **Naming:** the product is **AI Runtime**, the npm package is **`ai-runtime-engine`**, and the CLI command
|
|
16
|
+
> is **`ai-runtime`**. In code you use `Runtime` (the high-level API) and `AI` (the low-level router).
|
|
96
17
|
|
|
97
18
|
---
|
|
98
19
|
|
|
99
20
|
## Quick start
|
|
100
21
|
|
|
101
|
-
|
|
22
|
+
**In the terminal** (install globally, or use `npx ai-runtime …`):
|
|
102
23
|
|
|
103
24
|
```bash
|
|
104
|
-
npm install -g ai-runtime-engine
|
|
105
|
-
|
|
106
|
-
ai-runtime
|
|
107
|
-
#
|
|
108
|
-
ai-runtime doctor # check that a provider is configured and reachable
|
|
109
|
-
ai-runtime # open the interactive terminal — just type what you want
|
|
25
|
+
npm install -g ai-runtime-engine
|
|
26
|
+
ai-runtime setup # scaffolds config + .env.example (asks before each write)
|
|
27
|
+
ai-runtime doctor # checks a provider is configured and reachable
|
|
28
|
+
ai-runtime # opens the interactive terminal — just type what you want
|
|
110
29
|
```
|
|
111
30
|
|
|
112
|
-
|
|
31
|
+
**In code:**
|
|
113
32
|
|
|
114
33
|
```ts
|
|
115
34
|
import { Runtime } from 'ai-runtime-engine';
|
|
116
35
|
|
|
117
|
-
const runtime = await Runtime.load(
|
|
118
|
-
const r = await runtime.run({ input: '
|
|
36
|
+
const runtime = await Runtime.load(); // reads your config + .env
|
|
37
|
+
const r = await runtime.run({ input: 'Fix the failing tests' });
|
|
119
38
|
console.log(r.response?.text);
|
|
120
39
|
```
|
|
121
40
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
Use the built-in mock provider to see everything work without any credentials — great for tests and demos:
|
|
41
|
+
**Try it offline (no API key):** the built-in mock provider runs the whole pipeline with no credentials.
|
|
125
42
|
|
|
126
43
|
```ts
|
|
127
44
|
import { Runtime, MockProvider, makeModel } from 'ai-runtime-engine';
|
|
@@ -139,142 +56,31 @@ console.log(r.response?.text, r.routing?.selected); // includes the full routi
|
|
|
139
56
|
|
|
140
57
|
## How it works
|
|
141
58
|
|
|
142
|
-
|
|
143
|
-
a workspace); the runtime decides *how* to use them.
|
|
144
|
-
|
|
145
|
-
```
|
|
146
|
-
runtime.run({ input })
|
|
147
|
-
│
|
|
148
|
-
▼ resolve MODE (auto-detected, or set by you): chat · plan · execute · orchestrate · agent · compare · debug
|
|
149
|
-
│
|
|
150
|
-
▼ build the model context under a token budget (workspace summary, memory, your request)
|
|
151
|
-
│
|
|
152
|
-
▼ route to the best model ──► the ONE router: filter (capability/health/privacy/cost) → score → pick
|
|
153
|
-
│ → execute → validate → fall back on failure
|
|
154
|
-
│
|
|
155
|
-
▼ for multi-step work: plan → run tools/skills as a DAG (succeed a step only on evidence) → replan on failure
|
|
156
|
-
│
|
|
157
|
-
▼ result { ok, response, routing trace, + plan / execution / comparison / memory as relevant }
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
Two principles hold everywhere:
|
|
59
|
+
You describe *what you want*; the runtime picks *which model* runs it. A few ideas carry the whole design:
|
|
161
60
|
|
|
61
|
+
- **Providers are replaceable; capabilities are durable.** You route by what a task needs (reasoning, JSON,
|
|
62
|
+
a big context window…), never by a provider name.
|
|
162
63
|
- **One router.** Every model call — chat, planning, comparison, a skill's own call — goes through the same
|
|
163
|
-
router
|
|
164
|
-
- **
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
64
|
+
router: filter (capability · health · privacy · cost) → score → pick → execute → validate → fall back.
|
|
65
|
+
- **Modes** decide *what kind of work* it is: `chat`, `plan`, `execute`, `orchestrate`, `agent`, `compare`,
|
|
66
|
+
`debug`. `auto` (the default) detects it from your request.
|
|
67
|
+
- **`Runtime` vs `AI`.** `Runtime` is the high-level API you'll use; `AI` is the bare router underneath it.
|
|
68
|
+
- **Evidence over claims.** A step succeeds only with proof (a tool returned ok, a validator passed) — never
|
|
69
|
+
because a model said "done."
|
|
169
70
|
|
|
170
71
|
---
|
|
171
72
|
|
|
172
|
-
##
|
|
173
|
-
|
|
174
|
-
Load the runtime once, then `run()` requests. It resolves a **mode** and dispatches.
|
|
175
|
-
|
|
176
|
-
```ts
|
|
177
|
-
const runtime = await Runtime.load({ workspace: process.cwd() });
|
|
178
|
-
runtime.ai.registerProvider(myProvider); // register providers the same way as the router
|
|
179
|
-
const r = await runtime.run({ input: 'Explain this stack trace: …' });
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
### Modes
|
|
183
|
-
|
|
184
|
-
Every run resolves to one of eight modes. **`auto`** (the default) detects intent; you can force any mode
|
|
185
|
-
per run (`{ mode: 'plan' }`), in config (`runtime.defaultMode`), or via `AI_DEFAULT_MODE`. Precedence:
|
|
186
|
-
per-run > config > env > detection — an explicit mode is never overridden.
|
|
187
|
-
|
|
188
|
-
| Mode | What it does |
|
|
189
|
-
|---|---|
|
|
190
|
-
| `chat` | A single conversational answer (memory- and workspace-aware). |
|
|
191
|
-
| `plan` | Produce a structured plan; execute nothing. |
|
|
192
|
-
| `execute` | Run a planned sequence once. |
|
|
193
|
-
| `orchestrate` | Plan → execute → observe → replan until done or a limit is hit. |
|
|
194
|
-
| `agent` | A longer autonomous loop with hard stops (iterations, budget, permissions). |
|
|
195
|
-
| `debug` | Evidence-first investigation (gather → hypotheses → validate → conclude). |
|
|
196
|
-
| `compare` | Fan the same task across models and analyze the results. |
|
|
197
|
-
|
|
198
|
-
Mode is *what kind of work*; **autonomy** is *how much independent action* — `chat`/`plan` need no approval,
|
|
199
|
-
`execute`/`orchestrate` ask before acting, `agent` runs autonomously within limits.
|
|
200
|
-
|
|
201
|
-
### Interactive terminal
|
|
202
|
-
|
|
203
|
-
Bare `ai-runtime` opens a REPL: type natural language, or use slash commands to inspect and steer —
|
|
204
|
-
`/mode`, `/status`, `/info`, `/providers`, `/models`, `/skills [discover]`, `/tools`, `/permissions`,
|
|
205
|
-
`/memory`, `/conversations`, `/resume`, `/executions`, `/compare`, `/feedback`, `/learning`, `/doctor`,
|
|
206
|
-
`/cleanup`, `/dry-run`. For CI, `ai-runtime run "<input>" [--mode m] [--dry-run]` is the one-shot equivalent.
|
|
207
|
-
|
|
208
|
-
### Conversations & memory
|
|
73
|
+
## What you can do
|
|
209
74
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
**Skills** are reusable capabilities that compose tools. Beyond the built-ins, the runtime **loads your
|
|
221
|
-
own local skills**:
|
|
222
|
-
|
|
223
|
-
- Drop a manifest (`*.skill.yaml`) or a JS module (`*.skill.mjs`) into **`.ai-runtime/skills/`** — auto-loaded.
|
|
224
|
-
- `ai-runtime skills --discover` scans the rest of your repo and **reports** other skill files without
|
|
225
|
-
loading them (you enable one deliberately).
|
|
226
|
-
- Install a **skill pack** (any npm package that exports `skills`) and name it under `skills.packages`.
|
|
227
|
-
|
|
228
|
-
```yaml
|
|
229
|
-
# .ai-runtime/skills/read-changelog.skill.yaml — a declarative skill (composes tools, no code)
|
|
230
|
-
id: read-changelog
|
|
231
|
-
version: 1.0.0
|
|
232
|
-
name: Read changelog
|
|
233
|
-
description: read CHANGELOG.md
|
|
234
|
-
steps:
|
|
235
|
-
- tool: filesystem
|
|
236
|
-
input: { op: read, path: CHANGELOG.md }
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
### Planning, orchestration & dry-run
|
|
240
|
-
|
|
241
|
-
For multi-step work, the runtime plans (using only your registered skills/tools), runs the plan as a DAG,
|
|
242
|
-
succeeds a step only on evidence, and replans on failure. **Dry-run** (`run --dry-run`, `/dry-run`) shows
|
|
243
|
-
the plan and what it *would* touch — with zero changes made.
|
|
244
|
-
|
|
245
|
-
### Persistent executions & resume
|
|
246
|
-
|
|
247
|
-
Longer runs are saved before they start and can be resumed. Resume never blindly continues: it re-checks
|
|
248
|
-
your workspace (git state, touched files, config) and replans if anything drifted. `/executions`,
|
|
249
|
-
`/resume-execution <id>`, `/pause`, `/cancel`.
|
|
250
|
-
|
|
251
|
-
### Comparison
|
|
252
|
-
|
|
253
|
-
`compare` mode (or `runtime.compare(...)`) runs the same task across models and shows where they agree,
|
|
254
|
-
differ, and contradict each other. If you supply a way to *check* each answer (e.g. run the tests), the one
|
|
255
|
-
that passes wins — evidence beats opinion.
|
|
256
|
-
|
|
257
|
-
### Steering which models get used
|
|
258
|
-
|
|
259
|
-
- **Exclude = a hard filter.** An excluded provider/model is **never** selected (it still shows in the
|
|
260
|
-
routing trace as `⊘ excluded by user configuration`).
|
|
261
|
-
- **Prefer = a soft preference.** A preferred model is nudged up, but a preferred-but-incapable/unhealthy
|
|
262
|
-
one still loses.
|
|
263
|
-
- **Neither configured = normal routing** across all eligible models.
|
|
264
|
-
|
|
265
|
-
Set them in config `routing:`, per run (`runtime.run({ input, routing: {…} })`), or via environment
|
|
266
|
-
variables — they combine, so an exclusion set anywhere always holds:
|
|
267
|
-
|
|
268
|
-
```bash
|
|
269
|
-
AI_EXCLUDE_PROVIDERS=ollama
|
|
270
|
-
AI_EXCLUDE_MODELS=gemini:model-a,groq:model-b # provider:model, or a bare model id
|
|
271
|
-
AI_PREFER_PROVIDERS=gemini
|
|
272
|
-
AI_PREFER_MODELS=groq:model-c
|
|
273
|
-
```
|
|
274
|
-
|
|
275
|
-
**Learning** — the runtime learns which providers/skills work from real outcomes and your `/feedback`, and
|
|
276
|
-
turns that into *soft* preferences only. **Learning can never override an exclusion** (nor a privacy rule,
|
|
277
|
-
a pin, a required capability, or the budget) — a guarantee proven by a dedicated test.
|
|
75
|
+
- **Chat, plan, or run multi-step tasks** — preview any run with `--dry-run` (or `dryRun: true`) to see the
|
|
76
|
+
plan and what it *would* touch, with zero changes.
|
|
77
|
+
- **Compare models** — `compare` mode (or `runtime.compare(...)`) runs a task across several models and
|
|
78
|
+
reports agreement, differences, and contradictions; a validator you supply lets evidence pick the winner.
|
|
79
|
+
- **Remember facts** — durable, scoped memory that's retrieved when relevant (or turn it off with
|
|
80
|
+
stateless mode).
|
|
81
|
+
- **Add your own skills** — drop a manifest into `.ai-runtime/skills/`, or install a skill pack from npm.
|
|
82
|
+
- **Steer routing** — hard-`exclude` or soft-`prefer` providers/models via config, per-run, or env vars.
|
|
83
|
+
- Everything that acts is **permission-gated and workspace-jailed** (see [Safety](#safety)).
|
|
278
84
|
|
|
279
85
|
---
|
|
280
86
|
|
|
@@ -284,9 +90,9 @@ Three places, and only these:
|
|
|
284
90
|
|
|
285
91
|
| Path | What | In git? |
|
|
286
92
|
|---|---|---|
|
|
287
|
-
| `.ai-runtime/config.yaml` | Project config —
|
|
288
|
-
| `.env` | The secret **values** for
|
|
289
|
-
| `~/.ai-runtime/` | Local state — conversations, memory, executions, cache
|
|
93
|
+
| `.ai-runtime/config.yaml` | Project config — settings + providers (env-var **names**). | ✅ commit it |
|
|
94
|
+
| `.env` | The secret **values** for those env vars. | ❌ gitignored |
|
|
95
|
+
| `~/.ai-runtime/` | Local state — conversations, memory, executions, cache. Override with `AI_RUNTIME_HOME`. | ❌ never in git |
|
|
290
96
|
|
|
291
97
|
A minimal config:
|
|
292
98
|
|
|
@@ -297,193 +103,41 @@ runtime:
|
|
|
297
103
|
providers:
|
|
298
104
|
- id: gemini
|
|
299
105
|
kind: gemini
|
|
300
|
-
apiKeyEnv: GEMINI_API_KEY
|
|
301
|
-
- id: ollama
|
|
106
|
+
apiKeyEnv: GEMINI_API_KEY # the NAME of the env var — never the value
|
|
107
|
+
- id: ollama # local, no key required
|
|
302
108
|
kind: ollama
|
|
303
109
|
```
|
|
304
110
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
`
|
|
308
|
-
|
|
309
|
-
**Optional runtime variables** — all optional; unset means the built-in default:
|
|
310
|
-
|
|
311
|
-
| Variable | Effect |
|
|
312
|
-
|---|---|
|
|
313
|
-
| `AI_DEFAULT_MODE` | Default mode when a run doesn't set one (`auto` by default). |
|
|
314
|
-
| `AI_DEFAULT_STRATEGY` | Default routing strategy. |
|
|
315
|
-
| `AI_CONTEXT_MAX_TOKENS` | Token budget for the compiled model context. |
|
|
316
|
-
| `AI_MAX_COST_USD` · `AI_MAX_CALLS` | Per-run spend / call guardrails. |
|
|
317
|
-
| `AI_MEMORY_ENABLED` | Set `false` to disable memory. |
|
|
318
|
-
| `AI_LEARNING_ENABLED` | Set `false` to disable outcome learning. |
|
|
319
|
-
| `AI_EXCLUDE_PROVIDERS` · `AI_EXCLUDE_MODELS` | Hard-exclude providers / models (see [steering](#steering-which-models-get-used)). |
|
|
320
|
-
| `AI_PREFER_PROVIDERS` · `AI_PREFER_MODELS` | Soft-prefer providers / models. |
|
|
321
|
-
| `AI_CONVERSATION_RETENTION_DAYS` · `AI_EXECUTION_RETENTION_DAYS` · `AI_ARTIFACT_RETENTION_DAYS` | Retention for `cleanup` (unset = keep everything). |
|
|
322
|
-
| `AI_RUNTIME_HOME` | Override the `~/.ai-runtime/` state directory. |
|
|
323
|
-
| `AI_TELEMETRY_ENABLED` | *Reserved — not yet honored by the runtime.* Telemetry is metadata-only and set in config. |
|
|
324
|
-
|
|
325
|
-
`ai-runtime setup` writes the config and `.env.example` for you (asking first), and `ai-runtime info` shows
|
|
326
|
-
everything resolved. For the full router config (routing strategies, scoring weights, privacy posture,
|
|
327
|
-
telemetry, self-declared providers), see [docs/router.md](docs/router.md).
|
|
328
|
-
|
|
329
|
-
### Providers
|
|
330
|
-
|
|
331
|
-
If no provider is configured, `ai-runtime setup` and `ai-runtime doctor` walk you through adding one.
|
|
332
|
-
Recommended starters:
|
|
333
|
-
|
|
334
|
-
| Provider | Type | Key | Where to get it |
|
|
335
|
-
|---|---|---|---|
|
|
336
|
-
| **Google Gemini** | Cloud API | `GEMINI_API_KEY` | https://aistudio.google.com/apikey |
|
|
337
|
-
| **Groq** | Cloud API | `GROQ_API_KEY` | https://console.groq.com/keys |
|
|
338
|
-
| **OpenRouter** | Cloud API (aggregator) | `OPENROUTER_API_KEY` | https://openrouter.ai/keys |
|
|
339
|
-
| **Ollama** | Local (no key) | — | https://ollama.com |
|
|
340
|
-
|
|
341
|
-
Cloud providers need an API key; **Ollama** runs models locally with no key. Availability, models, and any
|
|
342
|
-
free/paid tiers are set by each provider — check their site for current terms.
|
|
111
|
+
Optional env vars (all optional): `AI_DEFAULT_MODE`, `AI_CONTEXT_MAX_TOKENS`, `AI_MAX_COST_USD`,
|
|
112
|
+
`AI_MAX_CALLS`, `AI_MEMORY_ENABLED`, `AI_LEARNING_ENABLED`, `AI_EXCLUDE_PROVIDERS` / `AI_EXCLUDE_MODELS` /
|
|
113
|
+
`AI_PREFER_PROVIDERS` / `AI_PREFER_MODELS`, `AI_*_RETENTION_DAYS`, `AI_RUNTIME_HOME`. Run `ai-runtime setup`
|
|
114
|
+
to scaffold everything and `ai-runtime info` to see it resolved.
|
|
343
115
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
## What you need to do
|
|
347
|
-
|
|
348
|
-
A getting-started checklist:
|
|
349
|
-
|
|
350
|
-
1. **Install** — Node 22+, then `npm install -g ai-runtime-engine` (or add it to a project). Verify with
|
|
351
|
-
`ai-runtime --help`.
|
|
352
|
-
2. **Scaffold** — run `ai-runtime setup`. It creates `.ai-runtime/config.yaml`, `.env.example`, and
|
|
353
|
-
`.gitignore` entries (asking before each write).
|
|
354
|
-
3. **Add a provider** — either put a real key in `.env` (e.g. `GEMINI_API_KEY=…`), or install
|
|
355
|
-
[Ollama](https://ollama.com) for a local model with no key.
|
|
356
|
-
4. **Check it** — `ai-runtime doctor` confirms a provider is configured and reachable.
|
|
357
|
-
5. **Use it** — `ai-runtime` (terminal), `ai-runtime run "…"` (one-shot), or `Runtime.load().run(…)` (code).
|
|
358
|
-
6. **Go further (optional)** — add skills to `.ai-runtime/skills/`, grant write/shell permissions under
|
|
359
|
-
`permissions:` when you want the runtime to act, and set `routing:` to steer model selection.
|
|
116
|
+
Recommended providers: **Google Gemini** (`GEMINI_API_KEY`), **Groq** (`GROQ_API_KEY`), **OpenRouter**
|
|
117
|
+
(`OPENROUTER_API_KEY`), and **Ollama** (local, no key). Cloud providers need a key; Ollama runs locally.
|
|
360
118
|
|
|
361
119
|
---
|
|
362
120
|
|
|
363
|
-
##
|
|
364
|
-
|
|
365
|
-
The commands you'll use day to day:
|
|
366
|
-
|
|
367
|
-
| Command | What it does |
|
|
368
|
-
|---|---|
|
|
369
|
-
| `ai-runtime` | Open the interactive terminal. |
|
|
370
|
-
| `ai-runtime setup` | Detect the workspace and scaffold config (asks before writing). |
|
|
371
|
-
| `ai-runtime run "<input>" [--mode m] [--dry-run] [--json]` | Run one request (one-shot, for scripts/CI). |
|
|
372
|
-
| `ai-runtime info [--json]` | Read-only summary: workspace, providers, skills, tools, memory, storage. |
|
|
373
|
-
| `ai-runtime doctor [--json]` | Diagnose config, `.env` (by name), providers, models, store, permissions, git. |
|
|
374
|
-
| `ai-runtime skills [--discover]` | List available skills; `--discover` reports skill files elsewhere in the repo. |
|
|
375
|
-
| `ai-runtime cleanup [--dry-run] [--yes]` | Enforce retention, drop the cache, validate the store. |
|
|
376
|
-
| `ai-runtime executions` · `resume-execution <id>` | List and resume persisted executions. |
|
|
121
|
+
## Safety
|
|
377
122
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
### Inside the interactive terminal
|
|
383
|
-
|
|
384
|
-
Type a request in plain language, or use a slash command to inspect and steer. The commands that exist
|
|
385
|
-
today:
|
|
386
|
-
|
|
387
|
-
```
|
|
388
|
-
/help show all commands
|
|
389
|
-
/status /info workspace, mode, provider count · full runtime summary
|
|
390
|
-
/mode [name] show or set the mode (auto|chat|plan|…)
|
|
391
|
-
/providers list providers + access state
|
|
392
|
-
/providers/all models list every provider's models
|
|
393
|
-
/providers/<id> [models|capabilities|health] inspect one provider
|
|
394
|
-
/providers/setup how to add a provider (keys / Ollama)
|
|
395
|
-
/models list all known models
|
|
396
|
-
/skills [discover] /tools /permissions
|
|
397
|
-
/memory [list|search <q>|delete <id>|clear]
|
|
398
|
-
/conversations /resume <id>
|
|
399
|
-
/executions /resume-execution <id> /pause <id> /cancel <id>
|
|
400
|
-
/compare <request> compare across all providers
|
|
401
|
-
/feedback <text> /learning record feedback · show what was learned
|
|
402
|
-
/dry-run toggle dry-run (plan only, no changes)
|
|
403
|
-
/doctor /cleanup /config diagnostics · retention preview · resolved config
|
|
404
|
-
/clear /exit
|
|
405
|
-
```
|
|
123
|
+
Everything read through tools, files, the web, or MCP is treated as **data, never instructions**.
|
|
124
|
+
Filesystem access is jailed to your workspace; shell is allowlist-first with a destructive denylist; writes
|
|
125
|
+
and network are off by default. Secrets are named by env var only and redacted from all output — the npm
|
|
126
|
+
package contains only the runtime code, never your keys, memory, or local state.
|
|
406
127
|
|
|
407
128
|
---
|
|
408
129
|
|
|
409
|
-
##
|
|
410
|
-
|
|
411
|
-
| | |
|
|
412
|
-
|---|---|
|
|
413
|
-
| **Product** | AI Runtime |
|
|
414
|
-
| **npm package** | `ai-runtime-engine` |
|
|
415
|
-
| **CLI command** | `ai-runtime` |
|
|
416
|
-
| **Source repository** | private |
|
|
417
|
-
| **Project config** | `.ai-runtime/config.yaml` (committed) |
|
|
418
|
-
| **Secrets** | `.env` (gitignored) |
|
|
419
|
-
| **Runtime state** | `~/.ai-runtime/` (never committed) |
|
|
420
|
-
|
|
421
|
-
The public npm package contains the **distributed runtime code** (compiled `dist/`), this README, and the
|
|
422
|
-
changelog. It **does not** contain any of your data — no API keys, `.env`, memory, conversations, execution
|
|
423
|
-
state, local indexes, cache, or other runtime state; those live only on your machine under `~/.ai-runtime/`
|
|
424
|
-
and `.env`, and are never part of the package.
|
|
130
|
+
## Documentation
|
|
425
131
|
|
|
426
|
-
|
|
427
|
-
runnable code — publishing to npm distributes the compiled implementation to anyone who installs it. It
|
|
428
|
-
does not hide the implementation from package consumers; keeping the *repository* private only keeps the
|
|
429
|
-
source history and development materials private.
|
|
132
|
+
This package bundles fuller docs in its `docs/` folder (open them after installing):
|
|
430
133
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
-
|
|
436
|
-
|
|
437
|
-
- Filesystem access is **jailed to your workspace** (tested against `..`, absolute paths, and symlinks).
|
|
438
|
-
Shell is allowlist-first with a destructive-command denylist; writes, commit/push, and network are
|
|
439
|
-
each gated and off by default.
|
|
440
|
-
- **Secrets are named by env var only** in config; an inline key is rejected at load, and every value is
|
|
441
|
-
redacted at every egress — including the terminal and logs.
|
|
442
|
-
- **Privacy:** high-sensitivity input is kept off cloud providers unless you explicitly allow it.
|
|
443
|
-
|
|
444
|
-
Full details in [docs/security.md](docs/security.md).
|
|
445
|
-
|
|
446
|
-
---
|
|
134
|
+
- **`docs/GUIDE.md`** — a hands-on, study-friendly guide: install → first run → how it works → worked
|
|
135
|
+
examples, runnable offline.
|
|
136
|
+
- **`docs/router.md`** — the engine reference: config keys, the `run()` API and `AI` class, providers,
|
|
137
|
+
routing internals.
|
|
138
|
+
- **`docs/architecture.md`** — how the router and Runtime layers fit together.
|
|
139
|
+
- **`docs/security.md`** — the full security model.
|
|
447
140
|
|
|
448
|
-
##
|
|
449
|
-
|
|
450
|
-
- **"No compatible AI provider is available"** — run `ai-runtime doctor`: it shows which provider keys are
|
|
451
|
-
set (by name), which providers authenticated, and how many models are usable. Add a key to `.env`, or a
|
|
452
|
-
provider to config; with none, `ai-runtime setup` lists free/local options (Ollama needs no key).
|
|
453
|
-
- **A provider is never chosen** — check `doctor` for its health and `/providers` for its access state; a
|
|
454
|
-
provider you excluded shows as `⊘ excluded by user configuration` in the routing trace.
|
|
455
|
-
- **Writes or shell are refused** — that's deny-by-default; grant them under `permissions:` in config
|
|
456
|
-
(destructive shell always asks). Use `--dry-run` to preview what a run would touch.
|
|
457
|
-
- **A local skill isn't loading** — `ai-runtime skills --discover` shows what was found and any load errors;
|
|
458
|
-
module skills load only from `.ai-runtime/skills/` or a configured `skills.paths`.
|
|
459
|
-
- **Nothing persists** — you may be in stateless mode (`persistence: 'disabled'`) or have `AI_*_ENABLED`
|
|
460
|
-
toggles off; `ai-runtime info` shows storage status.
|
|
461
|
-
|
|
462
|
-
---
|
|
463
|
-
|
|
464
|
-
## Further reading
|
|
465
|
-
|
|
466
|
-
- **[docs/GUIDE.md](docs/GUIDE.md)** — a hands-on, study-friendly guide: install → first run → how it works
|
|
467
|
-
→ worked examples, runnable offline.
|
|
468
|
-
- **[docs/architecture.md](docs/architecture.md)** — how the two layers fit together, the one-router
|
|
469
|
-
invariant, and the lifecycle of a request.
|
|
470
|
-
- **[docs/router.md](docs/router.md)** — the router engine reference: config keys, strategies, scoring,
|
|
471
|
-
providers, the `AI` API, and advanced features.
|
|
472
|
-
- **[docs/security.md](docs/security.md)** — the full security model.
|
|
473
|
-
- **[CHANGELOG.md](CHANGELOG.md)** — version history.
|
|
474
|
-
|
|
475
|
-
---
|
|
476
|
-
|
|
477
|
-
## Development
|
|
478
|
-
|
|
479
|
-
TypeScript, ESM, Node ≥ 22. The test suite is fully offline and deterministic.
|
|
480
|
-
|
|
481
|
-
```bash
|
|
482
|
-
npm install
|
|
483
|
-
npm run typecheck # tsc --noEmit (strict)
|
|
484
|
-
npm test # node:test — offline, no keys, no network
|
|
485
|
-
npm run build # tsc → dist/ (ESM + .d.ts)
|
|
486
|
-
npm run cli -- info # run the CLI from source
|
|
487
|
-
```
|
|
141
|
+
## License
|
|
488
142
|
|
|
489
|
-
|
|
143
|
+
ISC.
|
package/dist/cli/cli.js
CHANGED
|
@@ -20,7 +20,7 @@ import { skillsCommand } from './commands/skills.js';
|
|
|
20
20
|
import { startRepl } from './interactive/repl.js';
|
|
21
21
|
import { printError } from './render.js';
|
|
22
22
|
const program = new Command();
|
|
23
|
-
program.name('ai-runtime').description('Universal, provider-agnostic AI Runtime & Orchestration Platform').version('1.
|
|
23
|
+
program.name('ai-runtime').description('Universal, provider-agnostic AI Runtime & Orchestration Platform').version('1.2.0');
|
|
24
24
|
const configOpt = ['-c, --config <path>', 'path to an ai-runtime config file'];
|
|
25
25
|
// Bare `ai-runtime` (no subcommand) opens the interactive terminal. `allowExcessArguments(false)` keeps
|
|
26
26
|
// a mistyped subcommand (e.g. `ai-runtime porviders`) failing fast instead of silently opening the REPL.
|
|
@@ -48,6 +48,8 @@ program
|
|
|
48
48
|
.description('Resume a persisted execution (reconciles the workspace first)')
|
|
49
49
|
.option(...configOpt)
|
|
50
50
|
.option('--answer <text>', 'answer a pending clarification')
|
|
51
|
+
.option('--approve', 'approve a plan that is waiting for approval, then continue')
|
|
52
|
+
.option('--deny', 'deny a plan waiting for approval (cancels the execution)')
|
|
51
53
|
.action((id, o) => resumeExecutionCommand(id, o));
|
|
52
54
|
program.command('init').description('Scaffold ai-runtime.yaml, .env.example, and .gitignore entries').action(() => initCommand());
|
|
53
55
|
program
|
|
@@ -3,16 +3,18 @@
|
|
|
3
3
|
* defaults). Shows weights, strategy, policy posture, and timeouts. No secrets are read or printed.
|
|
4
4
|
*/
|
|
5
5
|
import { resolveConfig } from '../../config/defaults.js';
|
|
6
|
-
import {
|
|
6
|
+
import { loadRuntimeConfig } from '../../runtime/config.js';
|
|
7
7
|
import { print } from '../render.js';
|
|
8
8
|
export function configCommand(options) {
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
// Use the runtime loader so `.ai-runtime/config.yaml` is honored (root `ai-runtime.yaml` still works
|
|
10
|
+
// as a fallback) — matching every other command, instead of the legacy root-only loader.
|
|
11
|
+
const loaded = loadRuntimeConfig({ workspaceRoot: process.cwd(), ...(options.config ? { explicitPath: options.config } : {}) });
|
|
12
|
+
const resolved = resolveConfig(loaded.config.router);
|
|
11
13
|
if (options.json) {
|
|
12
14
|
print(JSON.stringify(resolved, null, 2));
|
|
13
15
|
return;
|
|
14
16
|
}
|
|
15
|
-
print(
|
|
17
|
+
print(loaded.configFile ? `Resolved config (${loaded.configFile}):` : 'Resolved defaults (no config file found):');
|
|
16
18
|
print(` providers: ${resolved.providers.length}`);
|
|
17
19
|
print(` strategy: ${resolved.strategy}`);
|
|
18
20
|
print(` timeoutMs: ${resolved.timeoutMs}`);
|
|
@@ -14,10 +14,17 @@ export async function executionsCommand(options) {
|
|
|
14
14
|
}
|
|
15
15
|
export async function resumeExecutionCommand(id, options) {
|
|
16
16
|
const rt = await Runtime.load({ ...(options.config ? { config: options.config } : {}) });
|
|
17
|
-
|
|
17
|
+
// --approve proceeds past a waiting-for-approval gate; --deny cancels it. Neither → leave the decision.
|
|
18
|
+
const approve = options.approve ? true : options.deny ? false : undefined;
|
|
19
|
+
const r = await rt.resumeExecution(id, {
|
|
20
|
+
...(options.answer ? { clarificationAnswer: options.answer } : {}),
|
|
21
|
+
...(approve !== undefined ? { approve } : {}),
|
|
22
|
+
});
|
|
18
23
|
if (r.response?.text)
|
|
19
24
|
print(r.response.text);
|
|
20
25
|
print(`status: ${r.status}`);
|
|
26
|
+
if (r.status === 'waiting_for_approval')
|
|
27
|
+
print('(waiting for approval — re-run with --approve to proceed or --deny to cancel)');
|
|
21
28
|
if (r.clarification)
|
|
22
29
|
print(`? ${r.clarification.question}`);
|
|
23
30
|
if (!r.ok)
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* `ai-runtime init` — scaffold a config, an env template, and gitignore entries. Never writes secrets;
|
|
3
3
|
* the config carries only env-var NAMES, and `.env` is added to .gitignore.
|
|
4
4
|
*/
|
|
5
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
-
import { resolve } from 'node:path';
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { dirname, resolve } from 'node:path';
|
|
7
7
|
import { print } from '../render.js';
|
|
8
8
|
const EXAMPLE_YAML = `# ai-runtime configuration. Secret VALUES live in .env (gitignored); this file names env vars only.
|
|
9
9
|
strategy: best
|
|
@@ -48,6 +48,7 @@ function writeIfAbsent(file, contents) {
|
|
|
48
48
|
print(` • ${file} already exists — left unchanged`);
|
|
49
49
|
return;
|
|
50
50
|
}
|
|
51
|
+
mkdirSync(dirname(full), { recursive: true });
|
|
51
52
|
writeFileSync(full, contents);
|
|
52
53
|
print(` ✓ wrote ${file}`);
|
|
53
54
|
}
|
|
@@ -65,11 +66,13 @@ function ensureGitignore() {
|
|
|
65
66
|
}
|
|
66
67
|
export function initCommand() {
|
|
67
68
|
print('Initializing ai-runtime...');
|
|
68
|
-
|
|
69
|
+
// Write the canonical `.ai-runtime/config.yaml` (the location the runtime loader prefers) so `init`
|
|
70
|
+
// and `setup` scaffold the same file. A root `ai-runtime.yaml` still works as a fallback if present.
|
|
71
|
+
writeIfAbsent('.ai-runtime/config.yaml', EXAMPLE_YAML);
|
|
69
72
|
writeIfAbsent('.env.example', EXAMPLE_ENV);
|
|
70
73
|
ensureGitignore();
|
|
71
74
|
print('\nNext steps:');
|
|
72
75
|
print(' 1. Copy .env.example to .env and fill in the keys you have');
|
|
73
76
|
print(' 2. Run `ai-runtime doctor` to verify configuration');
|
|
74
|
-
print(' 3. Run `ai-runtime
|
|
77
|
+
print(' 3. Run `ai-runtime` to open the interactive terminal (or `ai-runtime run "<task>"`)');
|
|
75
78
|
}
|
|
@@ -65,8 +65,12 @@ export function telemetryCommand(opts) {
|
|
|
65
65
|
const events = ai.telemetryEvents();
|
|
66
66
|
const limit = opts.limit ? Number(opts.limit) : 20;
|
|
67
67
|
const recent = events.slice(-limit);
|
|
68
|
-
if (recent.length === 0)
|
|
69
|
-
|
|
68
|
+
if (recent.length === 0) {
|
|
69
|
+
print('No telemetry recorded in this session.');
|
|
70
|
+
print('(Telemetry is buffered in-process, so a fresh CLI invocation starts empty. For telemetry that');
|
|
71
|
+
print(' persists across processes, set `telemetry: { sink: file }` in your config.)');
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
70
74
|
for (const e of recent)
|
|
71
75
|
print(JSON.stringify(e));
|
|
72
76
|
}
|
|
@@ -15,10 +15,30 @@ function banner(rt) {
|
|
|
15
15
|
print(`mode: auto · ${rt.ai.providers().length} providers · type /help or a request, /exit to quit`);
|
|
16
16
|
print('');
|
|
17
17
|
}
|
|
18
|
+
/** A concise, redacted progress line for a lifecycle event — the REPL's live feedback during a run. */
|
|
19
|
+
function progressLine(e) {
|
|
20
|
+
switch (e.type) {
|
|
21
|
+
case 'mode.selected':
|
|
22
|
+
return e.source === 'auto' ? ` · mode: ${e.selected} (auto, ${Math.round(e.confidence * 100)}%)` : ` · mode: ${e.selected}`;
|
|
23
|
+
case 'provider.selected':
|
|
24
|
+
return ` · routed → ${e.providerId}/${e.model}`;
|
|
25
|
+
case 'provider.failed':
|
|
26
|
+
return ` · ${e.providerId}/${e.model} failed${e.category ? ` (${e.category})` : ''} — falling back`;
|
|
27
|
+
default:
|
|
28
|
+
return undefined; // runtime.started / clarification.requested / run.completed are covered by the result block
|
|
29
|
+
}
|
|
30
|
+
}
|
|
18
31
|
/** Start the interactive session. Resolves when the user exits (or stdin closes). */
|
|
19
32
|
export async function startRepl(configPath) {
|
|
20
33
|
const rt = await Runtime.load({ ...(configPath ? { config: configPath } : {}) });
|
|
21
34
|
const session = new ReplSession(rt);
|
|
35
|
+
// Live progress: print concise routing/mode lines as they happen during a run. The events are already
|
|
36
|
+
// redacted; a throwing observer can never break a run (the emitter swallows sink errors).
|
|
37
|
+
rt.on((e) => {
|
|
38
|
+
const line = progressLine(e);
|
|
39
|
+
if (line)
|
|
40
|
+
print(line);
|
|
41
|
+
});
|
|
22
42
|
banner(rt);
|
|
23
43
|
const rl = createInterface({ input: process.stdin, output: process.stdout, prompt: '> ' });
|
|
24
44
|
rl.prompt();
|
|
@@ -36,6 +36,8 @@ const HELP = [
|
|
|
36
36
|
' /resume <id> resume a conversation',
|
|
37
37
|
' /executions list persisted executions',
|
|
38
38
|
' /resume-execution <id> resume an execution',
|
|
39
|
+
' /approve <id> approve an execution waiting for approval, then continue',
|
|
40
|
+
' /deny <id> deny an execution waiting for approval (cancels it)',
|
|
39
41
|
' /pause <id> pause an execution',
|
|
40
42
|
' /cancel <id> cancel an execution',
|
|
41
43
|
' /config show the resolved configuration',
|
|
@@ -141,6 +143,10 @@ export class ReplSession {
|
|
|
141
143
|
return this.executionsList();
|
|
142
144
|
case 'resume-execution':
|
|
143
145
|
return this.resumeExecution(args[0]);
|
|
146
|
+
case 'approve':
|
|
147
|
+
return this.resumeExecution(args[0], true);
|
|
148
|
+
case 'deny':
|
|
149
|
+
return this.resumeExecution(args[0], false);
|
|
144
150
|
case 'pause':
|
|
145
151
|
return args[0] ? { lines: [this.runtime.pauseExecution(args[0]) ? `paused ${args[0]}` : `cannot pause '${args[0]}'`] } : { lines: ['usage: /pause <execution-id>'] };
|
|
146
152
|
case 'cancel':
|
|
@@ -165,8 +171,10 @@ export class ReplSession {
|
|
|
165
171
|
lines.push(`[${result.error.category}] ${result.error.message}`);
|
|
166
172
|
if (result.mode.executed !== result.mode.selected)
|
|
167
173
|
lines.push(`(mode: ${result.mode.selected} → ${result.mode.executed})`);
|
|
168
|
-
if (result.status === 'waiting_for_approval')
|
|
169
|
-
|
|
174
|
+
if (result.status === 'waiting_for_approval') {
|
|
175
|
+
const execId = result.execution?.id;
|
|
176
|
+
lines.push(execId ? `(approval required — /approve ${execId} to proceed, or /deny ${execId})` : '(approval required — approve the execution via /executions then /approve <id>)');
|
|
177
|
+
}
|
|
170
178
|
if (result.status === 'failed')
|
|
171
179
|
lines.push('(did not complete)');
|
|
172
180
|
if (result.clarification)
|
|
@@ -249,14 +257,16 @@ export class ReplSession {
|
|
|
249
257
|
return { lines: ['no executions yet.'] };
|
|
250
258
|
return { lines: ['Executions:', ...list.map((e) => ` ${e.id} [${e.status}] ${e.mode}: ${e.goal.slice(0, 50)}`)] };
|
|
251
259
|
}
|
|
252
|
-
async resumeExecution(id) {
|
|
260
|
+
async resumeExecution(id, approve) {
|
|
253
261
|
if (!id)
|
|
254
|
-
return { lines: [
|
|
255
|
-
const r = await this.runtime.resumeExecution(id);
|
|
262
|
+
return { lines: [`usage: /${approve === undefined ? 'resume-execution' : approve ? 'approve' : 'deny'} <execution-id>`] };
|
|
263
|
+
const r = await this.runtime.resumeExecution(id, approve !== undefined ? { approve } : {});
|
|
256
264
|
const lines = [];
|
|
257
265
|
if (r.response?.text)
|
|
258
266
|
lines.push(r.response.text);
|
|
259
267
|
lines.push(`status: ${r.status}`);
|
|
268
|
+
if (r.status === 'waiting_for_approval')
|
|
269
|
+
lines.push('(waiting for approval — /approve ' + id + ' to proceed, /deny ' + id + ' to cancel)');
|
|
260
270
|
if (r.clarification)
|
|
261
271
|
lines.push(`? ${r.clarification.question}`);
|
|
262
272
|
return { lines };
|
|
@@ -186,6 +186,10 @@ export declare class Runtime {
|
|
|
186
186
|
private contextBlocks;
|
|
187
187
|
/** Best-effort estimator calibration from provider-reported input usage (estimates never claim to be exact). */
|
|
188
188
|
private calibrate;
|
|
189
|
-
/**
|
|
189
|
+
/**
|
|
190
|
+
* Capture an explicit "remember …" fact and retrieve relevant facts for context. Off when disabled.
|
|
191
|
+
* `capture` is false on a dry run — retrieval is read-only, but writing a fact is a mutation the dry
|
|
192
|
+
* run must not perform.
|
|
193
|
+
*/
|
|
190
194
|
private applyMemory;
|
|
191
195
|
}
|
package/dist/runtime/runtime.js
CHANGED
|
@@ -350,8 +350,9 @@ export class Runtime {
|
|
|
350
350
|
const clarification = buildClarification(modeResult);
|
|
351
351
|
if (clarification)
|
|
352
352
|
this.emitter.emit({ type: 'clarification.requested', runId, question: clarification.question });
|
|
353
|
-
// Memory:
|
|
354
|
-
|
|
353
|
+
// Memory: retrieve relevant facts into context, and (unless this is a dry run) capture an explicit
|
|
354
|
+
// "remember …". A dry run performs zero mutations, so it retrieves but never writes.
|
|
355
|
+
const memTrace = this.applyMemory(text, !policy.dryRun);
|
|
355
356
|
// Compile the model context (workspace summary + memory facts + user system) under a token budget.
|
|
356
357
|
const budget = resolveContextBudget({
|
|
357
358
|
...(req.context?.maxTokens !== undefined ? { perRun: req.context.maxTokens } : {}),
|
|
@@ -360,6 +361,19 @@ export class Runtime {
|
|
|
360
361
|
});
|
|
361
362
|
const compiled = compileContext(this.contextBlocks(req, context.workspace, memTrace?.retrieved), { budgetTokens: budget, estimator: this.estimator });
|
|
362
363
|
const contextReport = { metrics: compiled.metrics, validation: compiled.validation };
|
|
364
|
+
// Dry-run: chat's only "action" is the model call itself, so a dry run makes NO call. It reports
|
|
365
|
+
// what it would send (mode, compiled-context size) and performs zero mutations.
|
|
366
|
+
if (policy.dryRun) {
|
|
367
|
+
const status = clarification ? 'waiting_for_clarification' : 'completed';
|
|
368
|
+
this.emitter.emit({ type: 'run.completed', runId, ok: true, status, confidence: 1 });
|
|
369
|
+
const preview = `[dry-run] chat — no model call made. Would compile ${compiled.metrics.compiledTokens} context token(s) (budget ${budget}) and send one request to the best-matching provider. Run without --dry-run to execute.`;
|
|
370
|
+
const result = { ok: true, runId, mode: resolution, status, response: { text: preview }, context: contextReport, artifacts: [] };
|
|
371
|
+
if (clarification)
|
|
372
|
+
result.clarification = clarification;
|
|
373
|
+
if (memTrace)
|
|
374
|
+
result.memory = memTrace;
|
|
375
|
+
return result;
|
|
376
|
+
}
|
|
363
377
|
const runRequest = buildChatRequest(req, strategy, compiled.system || undefined, routing);
|
|
364
378
|
const runResult = await this._ai.run(runRequest);
|
|
365
379
|
this.calibrate(runRequest.system, text, runResult);
|
|
@@ -673,16 +687,22 @@ export class Runtime {
|
|
|
673
687
|
if (tokens && tokens > 0)
|
|
674
688
|
this.estimator.calibrate((system?.length ?? 0) + inputText.length, tokens);
|
|
675
689
|
}
|
|
676
|
-
/**
|
|
677
|
-
|
|
690
|
+
/**
|
|
691
|
+
* Capture an explicit "remember …" fact and retrieve relevant facts for context. Off when disabled.
|
|
692
|
+
* `capture` is false on a dry run — retrieval is read-only, but writing a fact is a mutation the dry
|
|
693
|
+
* run must not perform.
|
|
694
|
+
*/
|
|
695
|
+
applyMemory(text, capture = true) {
|
|
678
696
|
if (!this.memoryEnabled())
|
|
679
697
|
return undefined;
|
|
680
698
|
try {
|
|
681
699
|
const trace = { retrieved: [] };
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
700
|
+
if (capture) {
|
|
701
|
+
const candidate = classifyMemory(text);
|
|
702
|
+
if (candidate) {
|
|
703
|
+
const rec = this._memory.remember(candidate);
|
|
704
|
+
trace.captured = { id: rec.id, scope: rec.scope };
|
|
705
|
+
}
|
|
686
706
|
}
|
|
687
707
|
trace.retrieved = this._memory.search(text, { limit: 3 }).map((h) => h.text);
|
|
688
708
|
if (trace.retrieved.length === 0 && !trace.captured)
|
package/docs/GUIDE.md
CHANGED
|
@@ -66,8 +66,9 @@ You can run the whole pipeline with a **mock provider** — no credentials, no n
|
|
|
66
66
|
```js
|
|
67
67
|
import { Runtime, MockProvider, makeModel } from 'ai-runtime-engine';
|
|
68
68
|
|
|
69
|
-
// A fresh runtime
|
|
70
|
-
|
|
69
|
+
// A fresh runtime in stateless mode, so this demo writes nothing to disk.
|
|
70
|
+
// (Persistence is ON by default; pass { persistence: 'disabled' } to turn it off.)
|
|
71
|
+
const runtime = new Runtime(undefined, { persistence: 'disabled' });
|
|
71
72
|
|
|
72
73
|
// Register one fake provider with one fake model that can "reason" and output "text".
|
|
73
74
|
runtime.ai.registerProvider(new MockProvider({
|
package/docs/README.md
CHANGED
|
@@ -13,8 +13,15 @@ with the `ai-runtime-engine` package:
|
|
|
13
13
|
permissions, credentials, and the learning safety rail.
|
|
14
14
|
|
|
15
15
|
Additional development records — the append-only decision log (`DECISIONS.md`), the phase-by-phase build
|
|
16
|
-
log (`PROGRESS.md`),
|
|
17
|
-
repository and are not part of
|
|
16
|
+
log (`PROGRESS.md`), the release checklist (`NPM_PUBLISHING.md`), and the complete project **handbook**
|
|
17
|
+
(`handbook/00-index.md` and its chapters) — live in the (private) source repository and are not part of
|
|
18
|
+
the published package. The handbook is the deepest reference: architecture, a file-by-file source tour,
|
|
19
|
+
the CLI/REPL, the npm story, testing, and maintenance.
|
|
20
|
+
|
|
21
|
+
> Note: this index file (`docs/README.md`) itself *does* end up in the npm tarball, because npm always
|
|
22
|
+
> includes any `README*` file regardless of the `files` allowlist. It only links to shipped docs, so that
|
|
23
|
+
> is harmless — but keep anything private out of `README*`-named files. (The handbook index is named
|
|
24
|
+
> `00-index.md`, not `README.md`, for exactly this reason.)
|
|
18
25
|
|
|
19
26
|
Per-subsystem detail is documented at the source: each module under `src/**` opens with a doc comment
|
|
20
27
|
describing its contract and invariants, and the README's [How to use it](../README.md#how-to-use-it)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-runtime-engine",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "AI Runtime — a provider-agnostic AI runtime and orchestration platform. Point it at whatever AI providers you have; it routes each task to the best available model. Ships the `ai-runtime` CLI and the `Runtime`/`AI` library API.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "ISC",
|