@abaigram/abaikit 0.0.0-stage → 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 +15 -2
- package/abai/identity.md +1 -0
- package/abai/learnabai.md +474 -0
- package/abai/use.md +1 -0
- package/abaigram.yaml +208 -0
- package/dist/abaify.d.ts +68 -0
- package/dist/abaify.d.ts.map +1 -0
- package/dist/abaify.js +310 -0
- package/dist/abaify.js.map +1 -0
- package/dist/abaigram.d.ts +18 -0
- package/dist/abaigram.d.ts.map +1 -0
- package/dist/abaigram.js +153 -0
- package/dist/abaigram.js.map +1 -0
- package/dist/app.d.ts +19 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +29 -0
- package/dist/app.js.map +1 -0
- package/dist/bin.d.ts +45 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +274 -0
- package/dist/bin.js.map +1 -0
- package/dist/check.d.ts +119 -0
- package/dist/check.d.ts.map +1 -0
- package/dist/check.js +416 -0
- package/dist/check.js.map +1 -0
- package/dist/command.d.ts +41 -0
- package/dist/command.d.ts.map +1 -0
- package/dist/command.js +179 -0
- package/dist/command.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/io.d.ts +12 -0
- package/dist/io.d.ts.map +1 -0
- package/dist/io.js +20 -0
- package/dist/io.js.map +1 -0
- package/dist/learnabai.d.ts +60 -0
- package/dist/learnabai.d.ts.map +1 -0
- package/dist/learnabai.js +197 -0
- package/dist/learnabai.js.map +1 -0
- package/dist/new.d.ts +26 -0
- package/dist/new.d.ts.map +1 -0
- package/dist/new.js +87 -0
- package/dist/new.js.map +1 -0
- package/dist/repo.d.ts +99 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +482 -0
- package/dist/repo.js.map +1 -0
- package/dist/run.d.ts +6 -0
- package/dist/run.d.ts.map +1 -0
- package/dist/run.js +47 -0
- package/dist/run.js.map +1 -0
- package/dist/subject.d.ts +15 -0
- package/dist/subject.d.ts.map +1 -0
- package/dist/subject.js +53 -0
- package/dist/subject.js.map +1 -0
- package/dist/template-ts.d.ts +21 -0
- package/dist/template-ts.d.ts.map +1 -0
- package/dist/template-ts.js +322 -0
- package/dist/template-ts.js.map +1 -0
- package/dist/version.d.ts +3 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/examples/hello/abai/identity.md +1 -0
- package/examples/hello/abaigram.yaml +24 -0
- package/examples/hello/hello.ts +18 -0
- package/package.json +40 -5
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joon Hwa Jung
|
|
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
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @abaigram/abaikit
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The abai authoring tool, itself an abaigram. A coding agent uses it to
|
|
4
|
+
make a program an abaigram under the contract, `spec/abaigrammar.md` in
|
|
5
|
+
the [abai repository](https://github.com/jjbits/abai). Installed with
|
|
6
|
+
`npm install --save-dev @abaigram/abaikit`, the command is `abaikit`.
|
|
7
|
+
|
|
8
|
+
- `abaikit learnabai` prints the guide the agent follows; `--skill`
|
|
9
|
+
installs it as a Claude Code skill.
|
|
10
|
+
- `abaikit check <repo>` verifies a repository's conformance and its
|
|
11
|
+
level; it needs no model.
|
|
12
|
+
- `abaikit new <name> --lang ts` creates a fresh abaigram.
|
|
13
|
+
- `abaikit abaify <repo>` retrofits an existing repository, adding and
|
|
14
|
+
never removing.
|
|
15
|
+
- `abaikit run <repo> -- ARGS` starts the repository's program.
|
|
16
|
+
- `abaikit abai ...` is the standard agent entry of abaikit itself.
|
package/abai/identity.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
You are the agent fused into abaikit, the abai authoring tool. You help a person create an abaigram, retrofit an existing repository into one, and verify conformance. You change a repository only through the repo.* capabilities, which add and never remove; you say why in every act; and you run check before you call anything done.
|
|
@@ -0,0 +1,474 @@
|
|
|
1
|
+
# learnabai: abai for a coding agent
|
|
2
|
+
|
|
3
|
+
abaigrammar {{abaigrammar}} · abai stub {{stub}} · abaikit {{abaikit}} · TypeScript
|
|
4
|
+
|
|
5
|
+
You are reading this because someone wants a program turned into an
|
|
6
|
+
abaigram, or a new one made, and you are the agent doing it. Read section
|
|
7
|
+
1 once. Follow section 5 step by step; every step ends in a command that
|
|
8
|
+
proves it. Keep sections 2 and 8 at hand while you edit. `abaikit
|
|
9
|
+
learnabai --section <id>` prints one section; the ids are in the headings.
|
|
10
|
+
|
|
11
|
+
## 1. What an abaigram is (what)
|
|
12
|
+
|
|
13
|
+
An abaigram is an ordinary program that includes the abai library (the
|
|
14
|
+
"stub") and thereby carries an agent: the agent ships with the program,
|
|
15
|
+
can run at install and during use, and observes and steers the program
|
|
16
|
+
only through channels the program declares. The contract is
|
|
17
|
+
`abaigrammar.md`; this guide is its TypeScript instantiation in practice.
|
|
18
|
+
|
|
19
|
+
Two halves, one boundary. The stub lives in the program. The harness is
|
|
20
|
+
the agent's runtime: a model, a tool set, a user channel. The boundary is
|
|
21
|
+
a function call inside the process (turns mode) or a local socket between
|
|
22
|
+
two processes (sidecar mode). It means the same thing either way.
|
|
23
|
+
|
|
24
|
+
Five channels, declared in a manifest the program builds in code:
|
|
25
|
+
|
|
26
|
+
- `events`: the program tells the agent something happened (push).
|
|
27
|
+
- `cells`: shared state the agent reads; a writable cell is a `knob` the
|
|
28
|
+
agent may set within its declared range.
|
|
29
|
+
- `capabilities`: functions the agent may call, each with a policy.
|
|
30
|
+
- `decisions`: the program stops and asks which way to go; every decision
|
|
31
|
+
has a default and a timeout, so the program never waits forever.
|
|
32
|
+
- `turns`: the program hands the agent a bounded moment to act.
|
|
33
|
+
|
|
34
|
+
Plus `lifecycle` (pause, resume, stop, restart, status: what a supervising
|
|
35
|
+
harness may do to the process) and `attention` (what wakes the agent, and
|
|
36
|
+
a budget on it).
|
|
37
|
+
|
|
38
|
+
Three modes. `turns`: one process, one thread, the agent runs only inside
|
|
39
|
+
a touchpoint; the simplest, the natural first rung for a command-line
|
|
40
|
+
program. `sidecar`: the harness is a separate process, attached through
|
|
41
|
+
a socket; the reference mode, the one with the whole lifecycle channel.
|
|
42
|
+
`thread` is in the contract but not in this instantiation yet. A manifest
|
|
43
|
+
declares the modes it supports; the first is the default; `ABAI_MODE`
|
|
44
|
+
overrides at startup.
|
|
45
|
+
|
|
46
|
+
Principles that bind everything you do:
|
|
47
|
+
|
|
48
|
+
1. The program owns `main()`. The library is included; it wraps nothing
|
|
49
|
+
and replaces no entry point.
|
|
50
|
+
2. The agent is invited: it acts through declared channels at declared
|
|
51
|
+
touchpoints and nowhere else.
|
|
52
|
+
3. The program runs without the agent. No harness, a failed harness, a
|
|
53
|
+
missing model: the program is the plain program. Defaults everywhere.
|
|
54
|
+
4. Declared, not discovered: the agent learns the program from the
|
|
55
|
+
manifest and its `doc` fields.
|
|
56
|
+
5. Control has policy (`auto`, `confirm`, `forbid`) and leaves an audit
|
|
57
|
+
trail.
|
|
58
|
+
6. It costs the program nothing it did not ask for: a detached channel
|
|
59
|
+
operation is a load or two; attached, nothing on the program's thread
|
|
60
|
+
allocates, locks or blocks except the touchpoints the program itself
|
|
61
|
+
placed.
|
|
62
|
+
|
|
63
|
+
How a person meets the agent: `<prog> abai` opens a session on the
|
|
64
|
+
terminal; `<prog> abai run -- ARGS` starts the program under the harness
|
|
65
|
+
with the whole lifecycle channel; `<prog> abai mcp` serves the agent's
|
|
66
|
+
tools to an MCP client such as Claude Code, whose own agent then is the
|
|
67
|
+
program's. All of that comes from a few inserted lines at the top of
|
|
68
|
+
`main()` (section 3).
|
|
69
|
+
|
|
70
|
+
## 2. Declaring one in TypeScript (declare)
|
|
71
|
+
|
|
72
|
+
Install (section 5 has the exact command for this machine):
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
npm install {{abai_spec}} {{harness_spec}}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
One module declares the abaigram and exports it. Everything below is
|
|
79
|
+
real API; copy what you need and delete the rest.
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// src/abai.ts
|
|
83
|
+
import { fileURLToPath } from "node:url";
|
|
84
|
+
import { createAbaigram } from "@abaigram/abai-stub";
|
|
85
|
+
|
|
86
|
+
export const app = createAbaigram(
|
|
87
|
+
{
|
|
88
|
+
name: "myprog", // the program's name; also the manifest's
|
|
89
|
+
entry: "node dist/main.js abai", // how the entry is reached: a runnable command line
|
|
90
|
+
modes: ["turns", "sidecar"], // supported; the first is the default
|
|
91
|
+
content: { identity: "abai/identity.md" }, // the prompts; identity is required
|
|
92
|
+
check: { run: "node dist/main.js --demo" }, // a short command that exercises the channels
|
|
93
|
+
cells: {
|
|
94
|
+
"queue.depth": { type: "int", access: "read", doc: "Jobs waiting." },
|
|
95
|
+
"worker.count": { // a knob: readwrite with a default and a range
|
|
96
|
+
type: "int", access: "readwrite", default: 4, range: [1, 64],
|
|
97
|
+
doc: "Active workers. The pool resizes at the next batch.",
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
events: {
|
|
101
|
+
"job.failed": {
|
|
102
|
+
payload: { job_id: { type: "string", max: 64 }, attempt: "int" },
|
|
103
|
+
wakes: "attempt gt 2", // "always", "never", or <field> eq|ne|gt|lt <json>
|
|
104
|
+
doc: "A job failed; past two attempts the agent is woken.",
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
capabilities: {
|
|
108
|
+
"job.retry": {
|
|
109
|
+
params: { job_id: "string" },
|
|
110
|
+
result: { ok: "bool" },
|
|
111
|
+
effect: "mutate", // "read" or "mutate"
|
|
112
|
+
policy: "confirm", // "auto" | "confirm" | "forbid"; mutate defaults to confirm
|
|
113
|
+
dispatch: "immediate", // run now. The default is "safepoint": the call waits for
|
|
114
|
+
// app.safepoint(), so a program that never calls safepoint()
|
|
115
|
+
// must declare "immediate", or the call stays pending and
|
|
116
|
+
// check never reaches L3
|
|
117
|
+
timeout: "30s",
|
|
118
|
+
example: { job_id: "job-0001" }, // check calls this; make it safe
|
|
119
|
+
doc: "Requeue a failed job once.",
|
|
120
|
+
handler: async ({ job_id }) => ({ ok: await retry(String(job_id)) }),
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
decisions: {
|
|
124
|
+
on_backlog: {
|
|
125
|
+
context: { depth: "int", workers: "int" },
|
|
126
|
+
options: ["scale_up", "shed_oldest", "wait"],
|
|
127
|
+
default: "wait",
|
|
128
|
+
timeout: "5s",
|
|
129
|
+
doc: "Asked when the backlog crosses the high-water mark.",
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
turns: {
|
|
133
|
+
end_of_batch: { timeout: "30s", doc: "After each batch. The agent may tune worker.count." },
|
|
134
|
+
},
|
|
135
|
+
lifecycle: {
|
|
136
|
+
pause: "cooperative", // "cooperative" | "os" | "none"
|
|
137
|
+
stop: { graceful: "10s", handler: () => shutdown() },
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
// the directory holding abaigram.yaml and abai/: one level up from src/abai.ts and from dist/abai.js;
|
|
141
|
+
// count the levels from the built file if the build nests deeper (dist/src/abai.js is two)
|
|
142
|
+
{ root: fileURLToPath(new URL("..", import.meta.url)) },
|
|
143
|
+
);
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
Using it from the program's own code, wherever the facts already are.
|
|
147
|
+
Obtain the handles once, at startup; `set`, `get` and `emit` are the cheap
|
|
148
|
+
calls. The import is in the repository's own style: `./abai.js` under
|
|
149
|
+
NodeNext (tsc resolves it to `abai.ts`), `./abai.ts` only where the project
|
|
150
|
+
runs or rewrites `.ts` imports.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import { app } from "./abai.js";
|
|
154
|
+
|
|
155
|
+
const depth = app.cell<number>("queue.depth");
|
|
156
|
+
const workers = app.knob<number>("worker.count");
|
|
157
|
+
const failed = app.event("job.failed");
|
|
158
|
+
|
|
159
|
+
depth.set(queue.length); // one store, always cheap
|
|
160
|
+
const n = workers.get(); // the agent's setting, or the default
|
|
161
|
+
failed.emit({ job_id, attempt }); // nothing happens when no harness is attached
|
|
162
|
+
const choice = await app.decide<string>("on_backlog", { depth: queue.length, workers: n }); // "wait" when nobody answers
|
|
163
|
+
await app.turn("end_of_batch"); // returns at once when nobody is there
|
|
164
|
+
app.safepoint(); // where "safepoint" calls and a pause take effect
|
|
165
|
+
await app.attach(); // optional: ask for the agent now
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
abai is ESM-only. In a CommonJS repository load it inside the async main:
|
|
169
|
+
`const { createAbaigram, runEntry } = await import("@abaigram/abai-stub");` (Node 22.12 and
|
|
170
|
+
later can also `require("@abaigram/abai-stub")`). Under `verbatimModuleSyntax` the imports
|
|
171
|
+
above are all value imports and need no change; a type you import, such as
|
|
172
|
+
`Abaigram`, needs `import type`.
|
|
173
|
+
|
|
174
|
+
Types: `int`, `float`, `bool`, `enum` (with `enum: [...]`), `string`,
|
|
175
|
+
`object` (with `fields`), `list` (with `items`); sized types carry `max`.
|
|
176
|
+
A schema field is a bare type name or `{ type, max, enum, fields, items,
|
|
177
|
+
nullable, doc }`. A knob is `access: "readwrite"` with a `default`; a
|
|
178
|
+
scalar knob lands in shared state, a sized one is delivered to
|
|
179
|
+
`app.knob(name).onChange(handler)` at a safepoint. Capabilities may also
|
|
180
|
+
be bound late: `app.capability("job.retry", handler)`.
|
|
181
|
+
|
|
182
|
+
Policy (section 10 of the contract): `auto` runs on the agent's say,
|
|
183
|
+
`confirm` asks the person first (under an MCP client, the client's own
|
|
184
|
+
permission prompt is the person's consent), `forbid` is declared but never
|
|
185
|
+
runnable. Every act is written to the audit log with the agent's reason.
|
|
186
|
+
|
|
187
|
+
Cost: detached, every operation above is a load or a store. Keep your own
|
|
188
|
+
handlers cheap too; the agent's model calls never run on the program's
|
|
189
|
+
thread in sidecar mode, and in turns mode they run only inside the
|
|
190
|
+
touchpoints you placed.
|
|
191
|
+
|
|
192
|
+
## 3. The standard entry (entry)
|
|
193
|
+
|
|
194
|
+
Every abaigram answers `<prog> abai ...`. The stub provides it; the
|
|
195
|
+
program hands it the arguments first thing in `main()`:
|
|
196
|
+
|
|
197
|
+
```ts
|
|
198
|
+
import { runEntry } from "@abaigram/abai-stub";
|
|
199
|
+
import { app } from "./abai.js";
|
|
200
|
+
|
|
201
|
+
async function main(): Promise<void> {
|
|
202
|
+
const code = await runEntry(app, process.argv.slice(2));
|
|
203
|
+
if (code >= 0) {
|
|
204
|
+
process.exitCode = code; // an abai verb ran and said what it had to say
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
// -1: no abai verb; the program's own argument handling follows, untouched
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
In a module that already runs at top level, the same two statements stand
|
|
212
|
+
on their own: `const code = await runEntry(app, process.argv.slice(2)); if
|
|
213
|
+
(code >= 0) process.exit(code);`. Prefer `process.exitCode` where you can:
|
|
214
|
+
`process.exit` can cut a large `manifest --json` short on a pipe, and
|
|
215
|
+
`abaikit check` reads the entry through one.
|
|
216
|
+
|
|
217
|
+
Added lines, nothing removed: the program's own argument parsing follows
|
|
218
|
+
untouched, and a plain invocation reaches it exactly as before.
|
|
219
|
+
|
|
220
|
+
The verbs the entry serves:
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
<prog> abai open a session (needs a configured model)
|
|
224
|
+
<prog> abai install run the install touchpoint
|
|
225
|
+
<prog> abai run -- ARGS sidecar launch: the harness starts "<prog> ARGS" as its child
|
|
226
|
+
<prog> abai config machine-level model and credential settings (~/.abai)
|
|
227
|
+
<prog> abai doctor offline: manifest, entry, content, modes; online: the model
|
|
228
|
+
<prog> abai manifest print the effective manifest
|
|
229
|
+
<prog> abai mcp serve the agent's tools to an MCP client over stdio
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
The manifest exists twice and must agree: in code (the source of truth at
|
|
233
|
+
runtime) and as `abaigram.yaml` at the repository root, generated by the
|
|
234
|
+
program itself and checked in:
|
|
235
|
+
|
|
236
|
+
```
|
|
237
|
+
<entry> manifest > abaigram.yaml
|
|
238
|
+
<entry> doctor --offline
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
The `entry` field is a command line that runs in the repository root; the
|
|
242
|
+
tools reach the program through it. `node dist/main.js abai`, `npx myprog
|
|
243
|
+
abai`, `./bin/myprog abai` are all fine; `myprog abai` only once `myprog`
|
|
244
|
+
is on the PATH.
|
|
245
|
+
|
|
246
|
+
Content lives in `abai/` at the root unless the manifest says otherwise:
|
|
247
|
+
`abai/identity.md` (required: who the agent is for this program and how
|
|
248
|
+
it should behave), `abai/install.md`, `abai/use.md`, `abai/turns/<name>.md`.
|
|
249
|
+
The harness composes the system prompt from `identity`, the mode, and
|
|
250
|
+
every declaration rendered with its `doc`. Good `doc` fields are most of
|
|
251
|
+
the work of good content.
|
|
252
|
+
|
|
253
|
+
## 4. Conformance levels and what check demands (levels)
|
|
254
|
+
|
|
255
|
+
No level needs a model. `abaikit check <repo>` verifies them with the
|
|
256
|
+
null harness, a harness that attaches and drives nothing (passive) or
|
|
257
|
+
drives every declared knob and capability by its declared default and
|
|
258
|
+
`example` (active, the "exercise run").
|
|
259
|
+
|
|
260
|
+
| Level | Name | Requires |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| L1 | reachable | The stub is included; `<entry> manifest` works; `abaigram.yaml` exists, validates and agrees with the code; `abai/identity.md` exists; the offline `doctor` passes. Checked without running the program beyond its entry. |
|
|
263
|
+
| L2 | observable | L1, plus `check.run`, plus at least one cell written or event emitted during the exercise run. |
|
|
264
|
+
| L3 | steerable | L2, plus one capability the exercise calls successfully with its `example`, or one knob whose write lands. |
|
|
265
|
+
| L4 | deliberative | L3, plus one decision the program reaches during the exercise run. |
|
|
266
|
+
|
|
267
|
+
L1 is what `<entry> doctor --offline` checks; `abaikit check` repeats it
|
|
268
|
+
from outside and adds the levels above.
|
|
269
|
+
|
|
270
|
+
`check.run` is a command, run by the shell in the repository root, that
|
|
271
|
+
exercises the program's channels and exits: a demo mode, a short batch, a
|
|
272
|
+
dry run. `check` runs it with `ABAI_HARNESS=exercise` (the active null
|
|
273
|
+
harness, which also stands in for the person's consent on `confirm`
|
|
274
|
+
acts) under each declared mode (`ABAI_MODE`), and reads the report the
|
|
275
|
+
stub prints at exit. It then measures: the same command with
|
|
276
|
+
`ABAI_HARNESS=off` (nothing attaches) and `ABAI_HARNESS=null` (the
|
|
277
|
+
passive harness), side by side; a declared `check.overhead` (such as
|
|
278
|
+
`"5%"`) fails the check when the null run is slower than that. Every
|
|
279
|
+
declaration the exercise could not reach is named in the report,
|
|
280
|
+
capabilities without an `example` included. The exercise and measurement
|
|
281
|
+
runs attach the null harness, so the audit log the manifest names
|
|
282
|
+
receives their records: keep that file out of version control, or point
|
|
283
|
+
`audit.path` outside the checkout.
|
|
284
|
+
|
|
285
|
+
```
|
|
286
|
+
abaikit check <repo> # the level reached, modes, overhead, unexercised, violations
|
|
287
|
+
abaikit check <repo> --level 3 # exit non-zero below level 3
|
|
288
|
+
abaikit check <repo> --json
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
A program is at the level `check` can demonstrate, not the level you
|
|
292
|
+
intended. If a channel is only reached by a path `check.run` does not
|
|
293
|
+
take, change `check.run`, not the claim.
|
|
294
|
+
|
|
295
|
+
## 5. The retrofit, step by step (retrofit)
|
|
296
|
+
|
|
297
|
+
Follow the repository's own guidelines and conventions first: its
|
|
298
|
+
`AGENTS.md` or `CONTRIBUTING.md`, its module system (ESM or CommonJS,
|
|
299
|
+
TypeScript or JavaScript), its linter and formatter, its test command.
|
|
300
|
+
Everything you add is additive: new files, inserted lines, two added
|
|
301
|
+
dependencies. You never rewrite `main()`, never delete a line, never
|
|
302
|
+
reorder existing code.
|
|
303
|
+
|
|
304
|
+
Step 0. Know the program. Find the package manifest, the entry file the
|
|
305
|
+
`bin` or `main` points at, how it is built and run, and where its state
|
|
306
|
+
already lives (a queue, a counter, a config). Verify: you can name the
|
|
307
|
+
file where the program's own argument handling starts, and the command
|
|
308
|
+
that runs the program.
|
|
309
|
+
|
|
310
|
+
Step 1. Add the dependencies, in the repository root.
|
|
311
|
+
|
|
312
|
+
```
|
|
313
|
+
npm install {{abai_spec}} {{harness_spec}}
|
|
314
|
+
```
|
|
315
|
+
{{workspace_note}}
|
|
316
|
+
Verify, in the repository root:
|
|
317
|
+
`node -e "import('@abaigram/abai-stub').then(m => console.log(m.ABAI_STUB_VERSION))"`
|
|
318
|
+
prints `{{stub}}`.
|
|
319
|
+
|
|
320
|
+
Step 2. Declare the abaigram in a new module (`src/abai.ts`, or
|
|
321
|
+
`src/abai.js` in a JavaScript repository) as in section 2, with only
|
|
322
|
+
`name`, a runnable `entry`, `modes: ["turns", "sidecar"]`, `content` and
|
|
323
|
+
`root` to begin with. When the program's real entry is a plain launcher
|
|
324
|
+
that runs without the build (a `bin/x.mjs` that imports `dist/`), put the
|
|
325
|
+
declaration beside it as a `.mjs` module the launcher can import, so the
|
|
326
|
+
entry answers `manifest` and `doctor` before anything is built. Verify: build as the repository does, then
|
|
327
|
+
`node -e "import('./dist/abai.js')"` (the built path) runs without error.
|
|
328
|
+
|
|
329
|
+
Step 3. Hook the entry. In the file where `main()` starts, insert the
|
|
330
|
+
lines of section 3 before the program's own argument handling. The entry
|
|
331
|
+
runs the built program, so from here on every change is: build (the
|
|
332
|
+
repository's build script), then `<entry> manifest > abaigram.yaml`, then
|
|
333
|
+
verify. Verify: `<entry> manifest` prints YAML, and the program's own
|
|
334
|
+
usage (`<prog> --help`, or whatever it answered before) is unchanged.
|
|
335
|
+
|
|
336
|
+
Step 4. Write `abai/identity.md`: who the agent is for this program, what
|
|
337
|
+
it is for, how it should behave, in a few sentences. Verify:
|
|
338
|
+
`<entry> doctor --offline` prints an `identity` line that begins with `ok`.
|
|
339
|
+
|
|
340
|
+
Step 5. Generate the manifest file and reach L1.
|
|
341
|
+
|
|
342
|
+
```
|
|
343
|
+
<entry> manifest > abaigram.yaml
|
|
344
|
+
abaikit check .
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
Verify: the report says level 1. Commit `abaigram.yaml`, `abai/`, the new
|
|
348
|
+
module, the inserted lines, `package.json` and its lock file; nothing else
|
|
349
|
+
changed.
|
|
350
|
+
|
|
351
|
+
Step 6. Observe: L2. Declare one or two cells and events where the program
|
|
352
|
+
already has the facts (a depth, a count, a failure), and set or emit them
|
|
353
|
+
in the existing code paths, one line each. Add `check: { run: "<a short
|
|
354
|
+
command that exercises them>" }` to the declaration, a mode of the
|
|
355
|
+
program that runs for seconds and exits (a `--demo` flag you add, a small
|
|
356
|
+
fixed input, a dry run). Build, regenerate `abaigram.yaml`, verify: `abaikit check .
|
|
357
|
+
--level 2`.
|
|
358
|
+
|
|
359
|
+
Step 7. Steer: L3. Declare one knob the program already reads as a
|
|
360
|
+
setting (with its current value as the `default` and a safe `range`), or
|
|
361
|
+
one capability with a handler and an `example` whose call is harmless.
|
|
362
|
+
Build, regenerate, verify: `abaikit check . --level 3`; the report names
|
|
363
|
+
the knob as landed or the capability as called.
|
|
364
|
+
|
|
365
|
+
Step 8. Deliberate: L4. At a real fork in the program (retry or give up,
|
|
366
|
+
scale or wait), declare a decision with `options`, a `default` that is
|
|
367
|
+
what the program does today, and a short `timeout`; insert `const choice
|
|
368
|
+
= await app.decide("on_retry", { ... })` above the fork and add a branch
|
|
369
|
+
on `choice`, the existing path staying as the branch the default chooses.
|
|
370
|
+
Make sure `check.run` reaches the fork. Build, regenerate, verify:
|
|
371
|
+
`abaikit check . --level 4`.
|
|
372
|
+
|
|
373
|
+
Step 9. Meet the agent. With a model configured (`<entry> config set
|
|
374
|
+
provider ...`, `<entry> config set model ...`, `<entry> config set key
|
|
375
|
+
...`, then `<entry> doctor`), open `<entry>` and ask what it sees; or run
|
|
376
|
+
`<entry> run -- ARGS` and watch it supervise; or serve `<entry> mcp` to
|
|
377
|
+
Claude Code (section 6). Say what you added and why in the commit
|
|
378
|
+
message and, if the repository keeps one, its changelog.
|
|
379
|
+
|
|
380
|
+
If `abaikit check` reports a violation, read its location and message:
|
|
381
|
+
they name the file or the declaration and what the contract wanted.
|
|
382
|
+
|
|
383
|
+
## 6. Running an abaigram (use)
|
|
384
|
+
|
|
385
|
+
Session on the terminal, the person talking to the agent while the
|
|
386
|
+
program runs (turns mode in-process; sidecar attaches to or starts the
|
|
387
|
+
harness binary):
|
|
388
|
+
|
|
389
|
+
```
|
|
390
|
+
<entry>
|
|
391
|
+
```
|
|
392
|
+
|
|
393
|
+
Launch under supervision, the harness as the parent with the whole
|
|
394
|
+
lifecycle channel (pause, resume, stop, restart, status) and the
|
|
395
|
+
program's output observed from outside:
|
|
396
|
+
|
|
397
|
+
```
|
|
398
|
+
<entry> run -- ARGS
|
|
399
|
+
abaikit run <repo> -- ARGS # the same, from outside the repository
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
An MCP client as the agent, no model configured anywhere: the client's
|
|
403
|
+
own agent uses the program's tools, and the client's permission prompt is
|
|
404
|
+
the person's consent.
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
{ "mcpServers": { "myprog": { "command": "node", "args": ["/abs/path/to/dist/main.js", "abai", "mcp"] } } }
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
(An absolute path, or run `claude` from the repository root: the client
|
|
411
|
+
starts the command from wherever it is.)
|
|
412
|
+
|
|
413
|
+
```
|
|
414
|
+
claude -p "what is the program doing?" --mcp-config mcp.json --strict-mcp-config --allowedTools "mcp__myprog__*"
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
The machine-level model configuration, shared by every abaigram on the
|
|
418
|
+
machine, lives under `~/.abai/`:
|
|
419
|
+
|
|
420
|
+
```
|
|
421
|
+
<entry> config set provider anthropic|freetoken
|
|
422
|
+
<entry> config set model <id>
|
|
423
|
+
<entry> config set key <provider> # read without echo
|
|
424
|
+
<entry> config set url <provider> <base url>
|
|
425
|
+
<entry> doctor # offline checks, then the model
|
|
426
|
+
```
|
|
427
|
+
|
|
428
|
+
Environment variables, all optional: `ABAI_MODE` (`turns` or `sidecar`,
|
|
429
|
+
overriding the manifest's first mode), `ABAI_HARNESS` (`off`, `null`,
|
|
430
|
+
`exercise`: check runs only; never set it for real use), `ABAI_HOME` (the
|
|
431
|
+
configuration directory, default `~/.abai`), `ABAI_RUN_DIR` (descriptors
|
|
432
|
+
of running programs, default `~/.abai/run`), `ABAI_HARNESS_BIN` (the
|
|
433
|
+
sidecar harness binary to spawn), `ABAI_HARNESS_MODULE` (the in-process
|
|
434
|
+
harness module to import), `ABAI_CHANNEL` (`terminal` or `none`),
|
|
435
|
+
`ABAI_ATTACH_WAIT_MS` (how long a touchpoint waits for a harness).
|
|
436
|
+
|
|
437
|
+
The audit log (`audit.path` in the manifest; default
|
|
438
|
+
`~/.abai/audit/<name>.jsonl`) records every act of control with its
|
|
439
|
+
reason, consent and outcome. Read it when you wonder what the agent did.
|
|
440
|
+
|
|
441
|
+
## 7. Where this instantiation departs from the contract (deviations)
|
|
442
|
+
|
|
443
|
+
The first four are in every manifest this stub writes, the last when
|
|
444
|
+
`sidecar` is declared, so an agent reading the manifest sees them too:
|
|
445
|
+
|
|
446
|
+
{{deviations}}
|
|
447
|
+
|
|
448
|
+
## 8. What never to do (never)
|
|
449
|
+
|
|
450
|
+
- Never rewrite, wrap or replace `main()`. The entry hook is a few
|
|
451
|
+
inserted lines at its top; the program's own flow follows them.
|
|
452
|
+
- Never delete or reorder existing lines. A retrofit is additive; a
|
|
453
|
+
reviewer should see only added lines and new files.
|
|
454
|
+
- Never put the stub behind a condition. The entry must be reachable on
|
|
455
|
+
every invocation, or `check` cannot reach L1.
|
|
456
|
+
- Never leave a capability on the default `safepoint` dispatch in a
|
|
457
|
+
program that never calls `app.safepoint()`: the call stays pending and
|
|
458
|
+
`check` never reaches L3.
|
|
459
|
+
- Never block in a handler or a `set`. Long work belongs behind an
|
|
460
|
+
`immediate`-dispatched async capability, or in a job the handler
|
|
461
|
+
enqueues.
|
|
462
|
+
- Never declare an `example` whose call changes real state. The exercise
|
|
463
|
+
run calls it; so will a curious agent.
|
|
464
|
+
- Never declare a knob without a safe `default` and `range`, a decision
|
|
465
|
+
without the `default` the program chooses today, or a turn without a
|
|
466
|
+
`timeout`.
|
|
467
|
+
- Never put secrets or credentials in content or `doc` fields; they are
|
|
468
|
+
the prompt.
|
|
469
|
+
- Never claim a level `check` did not report, and never change a level by
|
|
470
|
+
editing `abaigram.yaml` by hand: it is generated, and `check` compares
|
|
471
|
+
it with the code.
|
|
472
|
+
- Never make `check.run` depend on the network, credentials, or state it
|
|
473
|
+
does not create itself.
|
|
474
|
+
- Never touch `node_modules`, build output, or files `.gitignore` names.
|
package/abai/use.md
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
A session with abaikit is about one repository at a time. Start by listing it and reading its package manifest and entry file; propose before you write; after every write, run check and read its report.
|