@possumtech/rummy 0.2.8 → 0.3.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/.env.example +11 -1
- package/EXCEPTIONS.md +46 -0
- package/PLUGINS.md +422 -188
- package/SPEC.md +284 -93
- package/migrations/001_initial_schema.sql +6 -4
- package/package.json +13 -5
- package/src/agent/AgentLoop.js +166 -15
- package/src/agent/ContextAssembler.js +18 -4
- package/src/agent/KnownStore.js +127 -13
- package/src/agent/ProjectAgent.js +4 -1
- package/src/agent/ResponseHealer.js +21 -1
- package/src/agent/TurnExecutor.js +365 -175
- package/src/agent/XmlParser.js +72 -39
- package/src/agent/known_store.sql +20 -4
- package/src/agent/schemes.sql +3 -0
- package/src/agent/tokens.js +6 -21
- package/src/agent/turns.sql +10 -1
- package/src/hooks/Hooks.js +18 -0
- package/src/hooks/PluginContext.js +14 -1
- package/src/hooks/RummyContext.js +16 -4
- package/src/hooks/ToolRegistry.js +83 -19
- package/src/llm/LlmProvider.js +27 -8
- package/src/llm/OpenAiClient.js +20 -0
- package/src/llm/OpenRouterClient.js +24 -2
- package/src/llm/XaiClient.js +47 -2
- package/src/plugins/ask_user/README.md +4 -4
- package/src/plugins/ask_user/ask_user.js +5 -5
- package/src/plugins/ask_user/ask_userDoc.js +29 -0
- package/src/plugins/budget/BudgetGuard.js +74 -0
- package/src/plugins/budget/README.md +43 -0
- package/src/plugins/budget/budget.js +79 -0
- package/src/plugins/cp/README.md +5 -4
- package/src/plugins/cp/cp.js +10 -6
- package/src/plugins/cp/cpDoc.js +29 -0
- package/src/plugins/current/README.md +4 -4
- package/src/plugins/current/current.js +9 -6
- package/src/plugins/engine/engine.sql +1 -8
- package/src/plugins/engine/turn_context.sql +4 -9
- package/src/plugins/env/README.md +3 -4
- package/src/plugins/env/env.js +5 -5
- package/src/plugins/env/envDoc.js +29 -0
- package/src/plugins/file/README.md +9 -12
- package/src/plugins/file/file.js +34 -35
- package/src/plugins/get/README.md +2 -2
- package/src/plugins/get/get.js +6 -5
- package/src/plugins/get/getDoc.js +41 -0
- package/src/plugins/hedberg/hedberg.js +2 -1
- package/src/plugins/hedberg/normalize.js +28 -0
- package/src/plugins/hedberg/patterns.js +25 -27
- package/src/plugins/hedberg/sed.js +17 -10
- package/src/plugins/index.js +66 -14
- package/src/plugins/instructions/README.md +6 -2
- package/src/plugins/instructions/instructions.js +20 -4
- package/src/plugins/instructions/preamble.md +9 -5
- package/src/plugins/known/README.md +10 -7
- package/src/plugins/known/known.js +29 -17
- package/src/plugins/known/knownDoc.js +33 -0
- package/src/plugins/mv/README.md +5 -4
- package/src/plugins/mv/mv.js +10 -6
- package/src/plugins/mv/mvDoc.js +31 -0
- package/src/plugins/persona/persona.js +78 -0
- package/src/plugins/previous/README.md +2 -2
- package/src/plugins/previous/previous.js +9 -6
- package/src/plugins/progress/progress.js +41 -15
- package/src/plugins/prompt/README.md +5 -5
- package/src/plugins/prompt/prompt.js +18 -13
- package/src/plugins/rm/README.md +4 -4
- package/src/plugins/rm/rm.js +5 -5
- package/src/plugins/rm/rmDoc.js +30 -0
- package/src/plugins/rpc/README.md +15 -28
- package/src/plugins/rpc/rpc.js +42 -77
- package/src/plugins/set/README.md +13 -12
- package/src/plugins/set/set.js +60 -5
- package/src/plugins/set/setDoc.js +45 -0
- package/src/plugins/sh/README.md +4 -4
- package/src/plugins/sh/sh.js +5 -5
- package/src/plugins/sh/shDoc.js +29 -0
- package/src/plugins/{skills/skills.js → skill/skill.js} +10 -51
- package/src/plugins/summarize/README.md +6 -5
- package/src/plugins/summarize/summarize.js +7 -6
- package/src/plugins/summarize/summarizeDoc.js +33 -0
- package/src/plugins/telemetry/telemetry.js +3 -1
- package/src/plugins/think/README.md +20 -0
- package/src/plugins/think/think.js +5 -0
- package/src/plugins/unknown/README.md +5 -5
- package/src/plugins/unknown/unknown.js +9 -7
- package/src/plugins/unknown/unknownDoc.js +31 -0
- package/src/plugins/update/README.md +3 -8
- package/src/plugins/update/update.js +7 -6
- package/src/plugins/update/updateDoc.js +33 -0
- package/src/server/RpcRegistry.js +52 -4
- package/src/sql/v_model_context.sql +16 -21
- package/src/plugins/ask_user/docs.md +0 -2
- package/src/plugins/cp/docs.md +0 -2
- package/src/plugins/env/docs.md +0 -4
- package/src/plugins/get/docs.md +0 -10
- package/src/plugins/known/docs.md +0 -3
- package/src/plugins/mv/docs.md +0 -2
- package/src/plugins/rm/docs.md +0 -6
- package/src/plugins/set/docs.md +0 -6
- package/src/plugins/sh/docs.md +0 -2
- package/src/plugins/skills/README.md +0 -25
- package/src/plugins/store/README.md +0 -20
- package/src/plugins/store/docs.md +0 -6
- package/src/plugins/store/store.js +0 -63
- package/src/plugins/summarize/docs.md +0 -4
- package/src/plugins/unknown/docs.md +0 -5
- package/src/plugins/update/docs.md +0 -4
package/PLUGINS.md
CHANGED
|
@@ -1,28 +1,89 @@
|
|
|
1
1
|
# PLUGINS.md — Plugin Development Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Every `<tag>` the model sees is a plugin. Every scheme is registered by
|
|
4
|
+
its owner. Every operation — model, client, plugin — flows through the
|
|
5
|
+
same tool handler. No exceptions without documentation in EXCEPTIONS.md.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
## §0 Quickstart
|
|
8
|
+
|
|
9
|
+
A complete tool plugin in four parts: register, handle, render, document.
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
// src/plugins/ping/ping.js
|
|
13
|
+
import docs from "./pingDoc.js";
|
|
14
|
+
|
|
15
|
+
export default class Ping {
|
|
16
|
+
#core;
|
|
17
|
+
|
|
18
|
+
constructor(core) {
|
|
19
|
+
this.#core = core;
|
|
20
|
+
core.ensureTool();
|
|
21
|
+
core.registerScheme({ category: "logging" });
|
|
22
|
+
core.on("handler", this.handler.bind(this));
|
|
23
|
+
core.on("full", this.full.bind(this));
|
|
24
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
25
|
+
docsMap.ping = docs;
|
|
26
|
+
return docsMap;
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async handler(entry, rummy) {
|
|
31
|
+
const now = new Date().toISOString();
|
|
32
|
+
await rummy.set({
|
|
33
|
+
path: entry.resultPath,
|
|
34
|
+
body: `pong ${now}`,
|
|
35
|
+
status: 200,
|
|
36
|
+
attributes: { path: entry.path },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
full(entry) {
|
|
41
|
+
return entry.body;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
9
45
|
|
|
10
46
|
```js
|
|
11
|
-
|
|
47
|
+
// src/plugins/ping/pingDoc.js
|
|
48
|
+
const LINES = [
|
|
49
|
+
["## ping",
|
|
50
|
+
"Header — model sees this as the tool name"],
|
|
51
|
+
["<ping/>",
|
|
52
|
+
"Simplest invocation — no path, no body"],
|
|
53
|
+
["* Returns server timestamp",
|
|
54
|
+
"One-line description of what the tool does"],
|
|
55
|
+
];
|
|
56
|
+
export default LINES.map(([text]) => text).join("\n");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Install external plugins via npm + env var:
|
|
60
|
+
|
|
61
|
+
```env
|
|
62
|
+
RUMMY_PLUGIN_PING=@myorg/rummy.ping
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## §1 Plugin Contract
|
|
12
66
|
|
|
67
|
+
A plugin is a directory under `src/plugins/` containing a `.js` file
|
|
68
|
+
that exports a default class. The class name matches the file name.
|
|
69
|
+
The constructor receives `core` (a PluginContext) — the plugin's
|
|
70
|
+
complete interface with the system.
|
|
71
|
+
|
|
72
|
+
```js
|
|
13
73
|
export default class MyTool {
|
|
14
74
|
#core;
|
|
15
75
|
|
|
16
76
|
constructor(core) {
|
|
17
77
|
this.#core = core;
|
|
18
|
-
core.
|
|
78
|
+
core.ensureTool();
|
|
79
|
+
core.registerScheme({ category: "logging" });
|
|
19
80
|
core.on("handler", this.handler.bind(this));
|
|
20
81
|
core.on("full", this.full.bind(this));
|
|
21
82
|
core.on("summary", this.summary.bind(this));
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
);
|
|
83
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
84
|
+
docsMap.mytool = docs;
|
|
85
|
+
return docsMap;
|
|
86
|
+
});
|
|
26
87
|
}
|
|
27
88
|
|
|
28
89
|
async handler(entry, rummy) {
|
|
@@ -30,7 +91,7 @@ export default class MyTool {
|
|
|
30
91
|
}
|
|
31
92
|
|
|
32
93
|
full(entry) {
|
|
33
|
-
return
|
|
94
|
+
return entry.body;
|
|
34
95
|
}
|
|
35
96
|
|
|
36
97
|
summary(entry) {
|
|
@@ -40,7 +101,7 @@ export default class MyTool {
|
|
|
40
101
|
```
|
|
41
102
|
|
|
42
103
|
File naming: `src/plugins/mytool/mytool.js`. Class name = file name.
|
|
43
|
-
Tool docs: `src/plugins/mytool/
|
|
104
|
+
Tool docs: `src/plugins/mytool/mytoolDoc.js` (annotated line arrays).
|
|
44
105
|
|
|
45
106
|
External plugins install via npm and load via `RUMMY_PLUGIN_*` env vars:
|
|
46
107
|
|
|
@@ -49,11 +110,11 @@ RUMMY_PLUGIN_WEB=@possumtech/rummy.web
|
|
|
49
110
|
RUMMY_PLUGIN_REPO=@possumtech/rummy.repo
|
|
50
111
|
```
|
|
51
112
|
|
|
52
|
-
## Unified API
|
|
113
|
+
## §2 Unified API
|
|
53
114
|
|
|
54
|
-
The model, the client, and plugins all use the same interface. Each
|
|
55
|
-
is a superset of the one below. `name` (model) = `method` (client)
|
|
56
|
-
method name (plugin). The params shape is the same at every tier.
|
|
115
|
+
The model, the client, and plugins all use the same interface. Each
|
|
116
|
+
tier is a superset of the one below. `name` (model) = `method` (client)
|
|
117
|
+
= method name (plugin). The params shape is the same at every tier.
|
|
57
118
|
|
|
58
119
|
```
|
|
59
120
|
Model: <rm path="file.txt"/> → { name: "rm", path: "file.txt" }
|
|
@@ -61,68 +122,121 @@ Client: { method: "rm", params: { path: "file.txt" } }
|
|
|
61
122
|
Plugin: rummy.rm({ path: "file.txt" })
|
|
62
123
|
```
|
|
63
124
|
|
|
64
|
-
|
|
125
|
+
All three tiers go through the same tool handler. Budget enforcement
|
|
126
|
+
applies equally. A client `get` is subject to the same budget check
|
|
127
|
+
as a model `<get>`.
|
|
128
|
+
|
|
129
|
+
## §3 Registration
|
|
65
130
|
|
|
66
131
|
All registration happens in the constructor via `core.on()`,
|
|
67
|
-
`core.filter()`, and `core.registerScheme()`.
|
|
132
|
+
`core.filter()`, `core.ensureTool()`, and `core.registerScheme()`.
|
|
68
133
|
|
|
69
|
-
### core.
|
|
134
|
+
### §3.1 core.ensureTool()
|
|
135
|
+
|
|
136
|
+
Declares this plugin as a model-facing tool. Required for the tool
|
|
137
|
+
to appear in the model's tool list. Called automatically by
|
|
138
|
+
`core.on("handler", ...)` but must be called explicitly for tools
|
|
139
|
+
without handlers (e.g., `summarize`, `update`, `unknown`).
|
|
140
|
+
|
|
141
|
+
### §3.2 core.registerScheme(config?)
|
|
70
142
|
|
|
71
143
|
Registers this plugin's scheme in the database. Called once in the
|
|
72
|
-
constructor.
|
|
144
|
+
constructor.
|
|
73
145
|
|
|
74
146
|
```js
|
|
75
147
|
core.registerScheme({
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
validStates: ["full", "proposed", "pass", "rejected", "error"],
|
|
79
|
-
category: "result", // "result", "file", "knowledge", "structural", "audit", "tool"
|
|
148
|
+
modelVisible: 1, // 1 or 0 — appears in v_model_context
|
|
149
|
+
category: "logging", // "data", "logging", "unknown", "prompt"
|
|
80
150
|
});
|
|
81
151
|
```
|
|
82
152
|
|
|
83
153
|
All fields optional. `core.registerScheme()` with no args gives a
|
|
84
154
|
sensible result-type scheme.
|
|
85
155
|
|
|
86
|
-
### core.on(event, callback, priority?)
|
|
156
|
+
### §3.3 core.on(event, callback, priority?)
|
|
87
157
|
|
|
88
|
-
| Event | Purpose |
|
|
89
|
-
|
|
90
|
-
| `"handler"` | Tool handler — called when model/client invokes this tool |
|
|
91
|
-
| `"full"` | Full fidelity — what the model sees
|
|
92
|
-
| `"summary"` | Summary fidelity — what the model sees
|
|
93
|
-
| `"turn.started"` | Turn beginning — write prompt/progress/instructions entries |
|
|
94
|
-
| `"turn.response"` | LLM responded — write audit entries, commit usage |
|
|
95
|
-
| `"turn.proposing"` | All dispatches done — materialize file edit proposals |
|
|
96
|
-
| `"entry.created"` | Entry created during dispatch |
|
|
97
|
-
| `"entry.changed"` |
|
|
98
|
-
| Any `"dotted.name"` | Resolves to the matching hook in the hook tree |
|
|
158
|
+
| Event | Payload | Purpose |
|
|
159
|
+
|-------|---------|---------|
|
|
160
|
+
| `"handler"` | `(entry, rummy)` | Tool handler — called when model/client invokes this tool |
|
|
161
|
+
| `"full"` | `(entry)` | Full fidelity projection — what the model sees at full |
|
|
162
|
+
| `"summary"` | `(entry)` | Summary fidelity projection — what the model sees at summary |
|
|
163
|
+
| `"turn.started"` | `(ctx)` | Turn beginning — write prompt/progress/instructions entries |
|
|
164
|
+
| `"turn.response"` | `(result, rummy)` | LLM responded — write audit entries, commit usage |
|
|
165
|
+
| `"turn.proposing"` | `(rummy)` | All dispatches done — materialize file edit proposals |
|
|
166
|
+
| `"entry.created"` | `({ runId, path, scheme })` | Entry created during dispatch |
|
|
167
|
+
| `"entry.changed"` | `({ runId, path, changeType })` | Entry content, fidelity, or status modified |
|
|
168
|
+
| Any `"dotted.name"` | varies | Resolves to the matching hook in the hook tree |
|
|
99
169
|
|
|
100
|
-
|
|
170
|
+
```js
|
|
171
|
+
// One-liner examples
|
|
172
|
+
core.on("handler", async (entry, rummy) => { /* tool logic */ });
|
|
173
|
+
core.on("full", (entry) => entry.body);
|
|
174
|
+
core.on("summary", (entry) => entry.body?.slice(0, 200));
|
|
175
|
+
core.on("turn.started", async (ctx) => { /* write entries */ });
|
|
176
|
+
core.on("turn.response", async (result, rummy) => { /* audit */ });
|
|
177
|
+
core.on("entry.changed", ({ runId, path, changeType }) => { /* react */ });
|
|
178
|
+
```
|
|
101
179
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
|
105
|
-
|
|
106
|
-
| `"
|
|
107
|
-
| `"
|
|
108
|
-
| `"
|
|
109
|
-
|
|
|
180
|
+
### §3.4 core.filter(name, callback, priority?)
|
|
181
|
+
|
|
182
|
+
| Filter | Signature | Purpose |
|
|
183
|
+
|--------|-----------|---------|
|
|
184
|
+
| `"instructions.toolDocs"` | `(docsMap) → docsMap` | Add tool documentation (docsMap pattern) |
|
|
185
|
+
| `"assembly.system"` | `(content, ctx) → content` | Contribute to system message |
|
|
186
|
+
| `"assembly.user"` | `(content, ctx) → content` | Contribute to user message |
|
|
187
|
+
| `"llm.messages"` | `(messages) → messages` | Transform final messages before LLM call |
|
|
188
|
+
| `"llm.response"` | `(response) → response` | Transform LLM response |
|
|
189
|
+
| Any `"dotted.name"` | varies | Resolves to the matching filter in the hook tree |
|
|
110
190
|
|
|
111
|
-
|
|
191
|
+
```js
|
|
192
|
+
// One-liner examples
|
|
193
|
+
core.filter("assembly.system", async (content, ctx) => {
|
|
194
|
+
return `${content}\n<mytag>${myData}</mytag>`;
|
|
195
|
+
}, 400);
|
|
196
|
+
core.filter("assembly.user", async (content, ctx) => {
|
|
197
|
+
return `${content}\n<status>${myStatus}</status>`;
|
|
198
|
+
}, 150);
|
|
199
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
200
|
+
docsMap.mytool = docs;
|
|
201
|
+
return docsMap;
|
|
202
|
+
});
|
|
203
|
+
```
|
|
112
204
|
|
|
113
|
-
|
|
114
|
-
Registered via the `instructions.toolDocs` filter in the constructor:
|
|
205
|
+
The `ctx` object passed to assembly filters:
|
|
115
206
|
|
|
116
207
|
```js
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
208
|
+
ctx = {
|
|
209
|
+
rows, // turn_context rows (materialized entries)
|
|
210
|
+
loopStartTurn, // First turn of current loop
|
|
211
|
+
type, // "ask" or "act"
|
|
212
|
+
tools, // Set of active tool names
|
|
213
|
+
contextSize, // Model context window size
|
|
214
|
+
lastContextTokens, // Assembled tokens from previous turn
|
|
215
|
+
}
|
|
121
216
|
```
|
|
122
217
|
|
|
123
|
-
###
|
|
218
|
+
### §3.5 Tool Docs
|
|
124
219
|
|
|
125
|
-
|
|
220
|
+
Each tool plugin has a `*Doc.js` file with annotated line arrays.
|
|
221
|
+
Text goes to the model. Rationale stays in source. Registered via
|
|
222
|
+
the `instructions.toolDocs` filter using the docsMap pattern:
|
|
223
|
+
|
|
224
|
+
```js
|
|
225
|
+
import docs from "./mytoolDoc.js";
|
|
226
|
+
|
|
227
|
+
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
228
|
+
docsMap.mytool = docs;
|
|
229
|
+
return docsMap;
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
The instructions plugin filters by the active tool set — tools
|
|
234
|
+
excluded by mode or flags are automatically omitted from the docs.
|
|
235
|
+
|
|
236
|
+
### §3.6 handler(entry, rummy)
|
|
237
|
+
|
|
238
|
+
The handler receives the parsed command entry and a per-turn
|
|
239
|
+
RummyContext:
|
|
126
240
|
|
|
127
241
|
```js
|
|
128
242
|
entry = {
|
|
@@ -130,196 +244,316 @@ entry = {
|
|
|
130
244
|
path, // Entry path ("set://src/app.js")
|
|
131
245
|
body, // Tag body text
|
|
132
246
|
attributes, // Parsed tag attributes
|
|
133
|
-
state, // Current state
|
|
134
247
|
resultPath, // Where to write the result
|
|
135
248
|
}
|
|
136
249
|
```
|
|
137
250
|
|
|
138
|
-
Multiple handlers per scheme. Lower priority runs first. Return
|
|
139
|
-
to stop the chain.
|
|
251
|
+
Multiple handlers per scheme. Lower priority runs first. Return
|
|
252
|
+
`false` to stop the chain.
|
|
253
|
+
|
|
254
|
+
### §3.7 full(entry) / summary(entry)
|
|
140
255
|
|
|
141
|
-
|
|
256
|
+
Returns the string the model sees for this tool's entries at the
|
|
257
|
+
given fidelity. Every tool MUST register `full`. `summary` is
|
|
258
|
+
optional — if unregistered, falls back to `attributes.summary`
|
|
259
|
+
(model-authored keyword description) or empty string.
|
|
142
260
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
in `<previous>` (completed loops). Every tool MUST register `full`.
|
|
146
|
-
`summary` is optional — if unregistered, the entry is empty at summary
|
|
147
|
-
fidelity.
|
|
261
|
+
At summary fidelity, `attributes.summary` is prepended above the
|
|
262
|
+
plugin's summary output automatically by ToolRegistry.view().
|
|
148
263
|
|
|
149
|
-
## Two Objects
|
|
264
|
+
## §4 Two Objects
|
|
150
265
|
|
|
151
266
|
Plugins interact with two objects at different scopes:
|
|
152
267
|
|
|
153
|
-
**PluginContext** (`
|
|
154
|
-
Used for registration (`on()`, `filter()`, `registerScheme()
|
|
155
|
-
|
|
156
|
-
|
|
268
|
+
**PluginContext** (`core`) — startup-scoped. Created once per plugin.
|
|
269
|
+
Used for registration (`on()`, `filter()`, `registerScheme()`,
|
|
270
|
+
`ensureTool()`). Available as `this.#core` throughout the plugin's
|
|
271
|
+
lifetime.
|
|
157
272
|
|
|
158
|
-
**RummyContext** (`rummy`
|
|
159
|
-
|
|
273
|
+
**RummyContext** (`rummy`) — turn-scoped. Passed to handlers per
|
|
274
|
+
invocation. Has tool verbs, per-turn state, database access.
|
|
160
275
|
|
|
161
|
-
### Tool Verbs (on RummyContext)
|
|
276
|
+
### §4.1 Tool Verbs (on RummyContext)
|
|
162
277
|
|
|
163
278
|
| Method | Effect |
|
|
164
279
|
|--------|--------|
|
|
165
|
-
| `rummy.set({ path, body,
|
|
166
|
-
| `rummy.get({ path })` | Promote to full
|
|
167
|
-
| `rummy.store({ path })` | Demote to stored state |
|
|
280
|
+
| `rummy.set({ path, body, status, fidelity, attributes })` | Create/update entry |
|
|
281
|
+
| `rummy.get({ path })` | Promote to full fidelity |
|
|
168
282
|
| `rummy.rm({ path })` | Delete permanently |
|
|
169
283
|
| `rummy.mv({ path, to })` | Move entry |
|
|
170
284
|
| `rummy.cp({ path, to })` | Copy entry |
|
|
171
285
|
|
|
172
|
-
### Query Methods
|
|
286
|
+
### §4.2 Query Methods
|
|
173
287
|
|
|
174
288
|
| Method | Returns |
|
|
175
289
|
|--------|---------|
|
|
176
|
-
| `rummy.getEntry(path)` | Full entry object |
|
|
177
290
|
| `rummy.getBody(path)` | Body text or null |
|
|
178
|
-
| `rummy.getState(path)` |
|
|
291
|
+
| `rummy.getState(path)` | Status code or null |
|
|
179
292
|
| `rummy.getAttributes(path)` | Parsed attributes `{}` |
|
|
180
293
|
| `rummy.getEntries(pattern, body?)` | Array of matching entries |
|
|
181
294
|
|
|
182
|
-
### Properties
|
|
295
|
+
### §4.3 Properties
|
|
296
|
+
|
|
297
|
+
| Property | Type | Scope |
|
|
298
|
+
|----------|------|-------|
|
|
299
|
+
| `rummy.entries` | KnownStore instance | Both |
|
|
300
|
+
| `rummy.db` | Database | Both |
|
|
301
|
+
| `rummy.runId` | Current run ID | RummyContext |
|
|
302
|
+
| `rummy.projectId` | Current project ID | Both |
|
|
303
|
+
| `rummy.sequence` | Current turn number | RummyContext |
|
|
304
|
+
| `rummy.contextSize` | Model context window | RummyContext |
|
|
305
|
+
| `rummy.noRepo` | Skip filesystem scanning | RummyContext |
|
|
306
|
+
|
|
307
|
+
## §5 Tool Display Order
|
|
183
308
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
| `rummy.name` | Plugin name (PluginContext only) |
|
|
187
|
-
| `rummy.entries` | KnownStore instance |
|
|
188
|
-
| `rummy.db` | Database |
|
|
189
|
-
| `rummy.runId` | Current run ID (RummyContext only) |
|
|
190
|
-
| `rummy.projectId` | Current project ID |
|
|
191
|
-
| `rummy.sequence` | Current turn number (RummyContext only) |
|
|
309
|
+
Tools are presented to the model in priority order:
|
|
310
|
+
gather → reason → act → communicate.
|
|
192
311
|
|
|
193
|
-
|
|
312
|
+
Defined in `ToolRegistry.TOOL_ORDER`. The `resolveForLoop(mode, flags)`
|
|
313
|
+
method handles all exclusions through one mechanism:
|
|
194
314
|
|
|
195
|
-
|
|
196
|
-
|
|
315
|
+
| Flag | Excludes |
|
|
316
|
+
|------|----------|
|
|
317
|
+
| `mode === "ask"` | `sh` |
|
|
318
|
+
| `noInteraction` | `ask_user` |
|
|
319
|
+
| `noWeb` | `search` |
|
|
320
|
+
|
|
321
|
+
## §6 Hedberg
|
|
322
|
+
|
|
323
|
+
The hedberg plugin exposes pattern matching and interpretation
|
|
324
|
+
utilities on `core.hooks.hedberg` for all plugins to use:
|
|
197
325
|
|
|
198
326
|
```js
|
|
199
|
-
const { match, search, replace, parseSed, parseEdits,
|
|
200
|
-
= core.hooks.hedberg;
|
|
327
|
+
const { match, search, replace, parseSed, parseEdits,
|
|
328
|
+
normalizeAttrs, generatePatch } = core.hooks.hedberg;
|
|
201
329
|
```
|
|
202
330
|
|
|
203
331
|
| Method | Purpose |
|
|
204
332
|
|--------|---------|
|
|
205
333
|
| `match(pattern, string)` | Full-string pattern match (glob, regex, literal) |
|
|
206
|
-
| `search(pattern, string)` | Substring search
|
|
207
|
-
| `replace(body, search, replacement, opts?)` | Apply replacement
|
|
208
|
-
| `parseSed(input)` | Parse sed syntax
|
|
209
|
-
| `parseEdits(content)` | Detect edit format (merge conflict, udiff,
|
|
334
|
+
| `search(pattern, string)` | Substring search |
|
|
335
|
+
| `replace(body, search, replacement, opts?)` | Apply replacement |
|
|
336
|
+
| `parseSed(input)` | Parse sed syntax (any delimiter) |
|
|
337
|
+
| `parseEdits(content)` | Detect edit format (merge conflict, udiff, sed) |
|
|
210
338
|
| `normalizeAttrs(attrs)` | Heal model attribute names |
|
|
211
339
|
| `generatePatch(path, old, new)` | Generate unified diff |
|
|
212
340
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
| Syntax | Type | Example |
|
|
216
|
-
|--------|------|---------|
|
|
217
|
-
| `s/old/new/flags` | Sed replace | `s/3000/8080/g` |
|
|
218
|
-
| `/pattern/flags` | Regex | `/\d+/g` |
|
|
219
|
-
| `$.path` | JSONPath | `$.config.port` |
|
|
220
|
-
| `//element` | XPath | `//div[@class]` |
|
|
221
|
-
| `*glob*` | Glob | `src/**/*.js` |
|
|
222
|
-
| Everything else | Literal | `port = 3000` |
|
|
223
|
-
|
|
224
|
-
## Events & Filters
|
|
341
|
+
## §7 Events & Filters
|
|
225
342
|
|
|
226
343
|
**Events** are fire-and-forget. All handlers run. Return values ignored.
|
|
227
344
|
**Filters** transform data through a chain. Lower priority runs first.
|
|
228
345
|
All hooks are async.
|
|
229
346
|
|
|
230
|
-
### Project Lifecycle
|
|
347
|
+
### §7.1 Project Lifecycle
|
|
231
348
|
|
|
232
|
-
| Hook | Type |
|
|
233
|
-
|
|
234
|
-
| `project.init.started` | event |
|
|
235
|
-
| `project.init.completed` | event |
|
|
349
|
+
| Hook | Type | When |
|
|
350
|
+
|------|------|------|
|
|
351
|
+
| `project.init.started` | event | Before project DB upsert |
|
|
352
|
+
| `project.init.completed` | event | After project created |
|
|
236
353
|
|
|
237
|
-
###
|
|
354
|
+
### §7.2 Run & Loop Lifecycle
|
|
238
355
|
|
|
239
|
-
| Hook | Type |
|
|
240
|
-
|
|
241
|
-
| `
|
|
242
|
-
| `
|
|
243
|
-
| `
|
|
244
|
-
| `
|
|
245
|
-
| `
|
|
246
|
-
| `
|
|
356
|
+
| Hook | Type | When |
|
|
357
|
+
|------|------|------|
|
|
358
|
+
| `run.created` | event | Run just created in DB |
|
|
359
|
+
| `ask.started` | event | Run requested in ask mode |
|
|
360
|
+
| `act.started` | event | Run requested in act mode |
|
|
361
|
+
| `loop.started` | event | Loop execution beginning |
|
|
362
|
+
| `run.config` | filter | Before run config applied |
|
|
363
|
+
| `run.progress` | event | Status change (thinking, processing) |
|
|
364
|
+
| `run.state` | event | After each turn — full state snapshot |
|
|
365
|
+
| `run.step.completed` | event | Turn resolved, no proposals pending |
|
|
366
|
+
| `loop.completed` | event | Loop execution finished (any exit path) |
|
|
367
|
+
| `ask.completed` | event | Ask run finished |
|
|
368
|
+
| `act.completed` | event | Act run finished |
|
|
247
369
|
|
|
248
|
-
###
|
|
249
|
-
|
|
250
|
-
| Hook | Type | Payload | When |
|
|
251
|
-
|------|------|---------|------|
|
|
252
|
-
| `ask.started` | event | `{ projectId, model, prompt, run }` | Run requested in ask mode |
|
|
253
|
-
| `act.started` | event | `{ projectId, model, prompt, run }` | Run requested in act mode |
|
|
254
|
-
| `run.config` | filter | Config object, `{ projectId }` | Before run config applied |
|
|
255
|
-
| `run.progress` | event | `{ run, turn, status }` | Status change (thinking, processing) |
|
|
256
|
-
| `run.state` | event | `{ run, turn, status, summary, history, unknowns, proposed, telemetry }` | After each turn |
|
|
257
|
-
| `run.step.completed` | event | `{ run, turn, flags }` | Turn resolved, no proposals pending |
|
|
258
|
-
| `ask.completed` | event | `{ projectId, run, status, turn }` | Ask run finished |
|
|
259
|
-
| `act.completed` | event | `{ projectId, run, status, turn }` | Act run finished |
|
|
260
|
-
|
|
261
|
-
### Turn Pipeline
|
|
370
|
+
### §7.3 Turn Pipeline
|
|
262
371
|
|
|
263
372
|
Hooks fire in this order every turn:
|
|
264
373
|
|
|
265
|
-
|
|
|
266
|
-
|
|
267
|
-
| `turn.started` | event |
|
|
268
|
-
| `
|
|
269
|
-
|
|
|
270
|
-
| `assembly.
|
|
271
|
-
| `
|
|
272
|
-
| `llm.messages` | filter |
|
|
273
|
-
| `llm.request.started` | event |
|
|
274
|
-
| `llm.response` | filter |
|
|
275
|
-
| `llm.request.completed` | event |
|
|
276
|
-
| `turn.response` | event |
|
|
277
|
-
| `
|
|
278
|
-
| `
|
|
279
|
-
|
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
374
|
+
| # | Hook | Type | When |
|
|
375
|
+
|---|------|------|------|
|
|
376
|
+
| 1 | `turn.started` | event | Plugins write prompt/progress/instructions entries |
|
|
377
|
+
| 2 | `context.materialized` | event | turn_context populated from v_model_context |
|
|
378
|
+
| 3 | `assembly.system` | filter | Build system message from entries |
|
|
379
|
+
| 4 | `assembly.user` | filter | Build user message from entries |
|
|
380
|
+
| 5 | `budget.enforce` | hook | Measure assembled tokens, 413 if over |
|
|
381
|
+
| 6 | `llm.messages` | filter | Transform messages before LLM call |
|
|
382
|
+
| 7 | `llm.request.started` | event | LLM call about to fire |
|
|
383
|
+
| 8 | `llm.response` | filter | Transform raw LLM response |
|
|
384
|
+
| 9 | `llm.request.completed` | event | LLM call finished |
|
|
385
|
+
| 10 | `turn.response` | event | Plugins write audit entries |
|
|
386
|
+
| 11 | `entry.recording` | filter | Before each entry is stored (validate/transform) |
|
|
387
|
+
| 12 | `tool.before` | event | Before tool handler dispatch |
|
|
388
|
+
| 13 | Tool handler dispatch | — | Lifecycle always, actions sequential |
|
|
389
|
+
| 14 | `tool.after` | event | After tool handler dispatch |
|
|
390
|
+
| 15 | `entry.created` | event | After each new entry dispatched |
|
|
391
|
+
| 16 | `entry.changed` | event | After entry content, fidelity, or status modified |
|
|
392
|
+
| 17 | `turn.proposing` | event | All dispatches done — materialize proposals |
|
|
393
|
+
| 18 | `turn.completed` | event | Turn fully resolved with final status |
|
|
394
|
+
|
|
395
|
+
### §7.4 Entry Events
|
|
396
|
+
|
|
397
|
+
| Hook | Type | When |
|
|
398
|
+
|------|------|------|
|
|
399
|
+
| `entry.recording` | filter | Before entry stored. Return `{ status: 4xx }` to reject. |
|
|
400
|
+
| `entry.created` | event | New entry added during dispatch |
|
|
401
|
+
| `entry.changed` | event | Entry content, fidelity, or status modified |
|
|
402
|
+
|
|
403
|
+
`entry.recording` is a filter — plugins can validate, transform, or
|
|
404
|
+
reject entries before they hit the store. Payload:
|
|
405
|
+
`{ scheme, path, body, attributes, status }`. Return the object
|
|
406
|
+
(modified or not). Set `status >= 400` to reject.
|
|
407
|
+
|
|
408
|
+
`entry.changed` fires on any mutation to an existing entry — body
|
|
409
|
+
update, fidelity change, status change, attribute update. Payload:
|
|
410
|
+
`{ runId, path, changeType }`. Subscribers include the budget plugin
|
|
411
|
+
(remeasure context) and the repo plugin (detect file changes on disk).
|
|
412
|
+
|
|
413
|
+
### §7.5 Budget
|
|
414
|
+
|
|
415
|
+
| Hook | Type | When |
|
|
416
|
+
|------|------|------|
|
|
417
|
+
| `budget.enforce` | hook | After assembly, before LLM call. Returns 413 if over context limit. |
|
|
418
|
+
|
|
419
|
+
The budget plugin measures `countTokens()` on assembled messages —
|
|
420
|
+
the actual content being sent to the LLM. No estimates, no DB token
|
|
421
|
+
math. The assembled message IS the measurement.
|
|
422
|
+
|
|
423
|
+
**DB tokens vs assembled tokens:** The `tokens` column on entries is
|
|
424
|
+
strictly for DISPLAY — showing token counts in `<knowns>` tags so
|
|
425
|
+
the model can reason about entry sizes. It is NEVER used for budget
|
|
426
|
+
decisions. Budget math uses only assembled message token counts.
|
|
427
|
+
These are two separate numbers that must never be conflated.
|
|
428
|
+
|
|
429
|
+
### §7.6 Client Notifications
|
|
430
|
+
|
|
431
|
+
| Hook | Type | When |
|
|
432
|
+
|------|------|------|
|
|
433
|
+
| `ui.render` | event | Text for client display |
|
|
434
|
+
| `ui.notify` | event | Status notification |
|
|
435
|
+
|
|
436
|
+
## §8 Entry Lifecycle
|
|
437
|
+
|
|
438
|
+
Every entry follows the same lifecycle regardless of origin:
|
|
439
|
+
|
|
440
|
+
1. **Created** — `known_entries` row with scheme, path, body, status
|
|
441
|
+
2. **Dispatched** — tool handler chain executes
|
|
442
|
+
3. **Status set** — handler sets 200, 202, 400, 413, etc.
|
|
443
|
+
4. **Materialized** — `v_model_context` projects into `turn_context`
|
|
444
|
+
5. **Assembled** — filter chain renders into system/user messages
|
|
445
|
+
6. **Visible** — model sees the entry in its context
|
|
446
|
+
|
|
447
|
+
Entries at `archive` fidelity skip steps 4-6 (invisible to model).
|
|
448
|
+
Entries at `index` fidelity render as path-only tags (no body).
|
|
449
|
+
Entries at `summary` fidelity render with `attributes.summary`
|
|
450
|
+
prepended above the plugin's summary view output.
|
|
451
|
+
|
|
452
|
+
## §9 Bundled Plugins
|
|
291
453
|
|
|
292
454
|
| Plugin | Type | Description |
|
|
293
455
|
|--------|------|-------------|
|
|
294
|
-
|
|
|
295
|
-
|
|
|
296
|
-
|
|
|
297
|
-
|
|
|
298
|
-
|
|
|
299
|
-
|
|
|
300
|
-
|
|
|
301
|
-
|
|
|
302
|
-
|
|
|
303
|
-
|
|
|
304
|
-
|
|
|
305
|
-
|
|
|
306
|
-
|
|
|
307
|
-
|
|
|
308
|
-
|
|
|
309
|
-
|
|
|
310
|
-
|
|
|
311
|
-
|
|
|
312
|
-
|
|
|
313
|
-
|
|
|
314
|
-
|
|
|
315
|
-
|
|
|
316
|
-
|
|
|
317
|
-
|
|
318
|
-
|
|
456
|
+
| `get` | Core tool | Load file/entry into context |
|
|
457
|
+
| `set` | Core tool | Edit file/entry, fidelity control |
|
|
458
|
+
| `known` | Core tool + Assembly | Save knowledge, render `<knowns>` section |
|
|
459
|
+
| `rm` | Core tool | Delete permanently |
|
|
460
|
+
| `mv` | Core tool | Move entry |
|
|
461
|
+
| `cp` | Core tool | Copy entry |
|
|
462
|
+
| `sh` | Core tool | Shell command (act mode only) |
|
|
463
|
+
| `env` | Core tool | Exploratory command |
|
|
464
|
+
| `ask_user` | Core tool | Ask the user |
|
|
465
|
+
| `search` | Core tool | Web search (via external plugin) |
|
|
466
|
+
| `summarize` | Structural | Signal completion |
|
|
467
|
+
| `update` | Structural | Signal continued work |
|
|
468
|
+
| `unknown` | Structural + Assembly | Register unknowns, render `<unknowns>` |
|
|
469
|
+
| `previous` | Assembly | Render `<previous>` loop history |
|
|
470
|
+
| `current` | Assembly | Render `<current>` active loop work |
|
|
471
|
+
| `progress` | Assembly | Render `<progress>` telemetry + warnings |
|
|
472
|
+
| `prompt` | Assembly | Render `<prompt mode="ask|act">` tag |
|
|
473
|
+
| `hedberg` | Utility | Pattern matching, interpretation, normalization |
|
|
474
|
+
| `instructions` | Internal | Preamble + tool docs + persona assembly |
|
|
475
|
+
| `file` | Internal | File entry projections and constraints |
|
|
476
|
+
| `rpc` | Internal | RPC method registration |
|
|
477
|
+
| `telemetry` | Internal | Audit entries, usage stats, reasoning_content |
|
|
478
|
+
| `budget` | Internal | Context ceiling enforcement (413), panic mode, BudgetGuard |
|
|
479
|
+
| `think` | Internal | Model reasoning tag (`model_visible = 0`) |
|
|
480
|
+
| `mcp` | Core tool | Model Context Protocol server management |
|
|
481
|
+
|
|
482
|
+
Removed: `crunch` (dead code, replaced by model-owned context management),
|
|
483
|
+
`store` (merged into `set` fidelity attributes).
|
|
484
|
+
|
|
485
|
+
## §10 External Plugins
|
|
319
486
|
|
|
320
487
|
| Plugin | Package | Description |
|
|
321
488
|
|--------|---------|-------------|
|
|
322
489
|
| Repo | `@possumtech/rummy.repo` | Git-aware file scanning and symbol extraction |
|
|
323
490
|
| Web | `@possumtech/rummy.web` | Web search and URL fetching via searxng |
|
|
324
491
|
|
|
325
|
-
Loaded via `RUMMY_PLUGIN_*` env vars.
|
|
492
|
+
Loaded via `RUMMY_PLUGIN_*` env vars. External plugins have access
|
|
493
|
+
to the same PluginContext API as bundled plugins.
|
|
494
|
+
|
|
495
|
+
## §11 RPC Methods
|
|
496
|
+
|
|
497
|
+
Client-facing JSON-RPC 2.0 over WebSocket. All tool methods go through
|
|
498
|
+
the same handler chain as model commands.
|
|
499
|
+
|
|
500
|
+
### §11.1 Wire Format
|
|
501
|
+
|
|
502
|
+
```json
|
|
503
|
+
// Request
|
|
504
|
+
{ "jsonrpc": "2.0", "id": 1, "method": "get", "params": { "path": "src/app.js", "run": "my_run" } }
|
|
505
|
+
|
|
506
|
+
// Success response
|
|
507
|
+
{ "jsonrpc": "2.0", "id": 1, "result": { "path": "src/app.js", "status": 200 } }
|
|
508
|
+
|
|
509
|
+
// Error response
|
|
510
|
+
{ "jsonrpc": "2.0", "id": 1, "error": { "code": -32600, "message": "Missing required param: path" } }
|
|
511
|
+
|
|
512
|
+
// Notification (server → client, no id)
|
|
513
|
+
{ "jsonrpc": "2.0", "method": "run/state", "params": { "run": "my_run", "status": 200 } }
|
|
514
|
+
```
|
|
515
|
+
|
|
516
|
+
### §11.2 Tool Methods (Unified API)
|
|
517
|
+
|
|
518
|
+
| Method | Params | Notes |
|
|
519
|
+
|--------|--------|-------|
|
|
520
|
+
| `get` | `{ path, run, persist?, readonly? }` | `persist` also sets file constraint |
|
|
521
|
+
| `set` | `{ path, body, run, attributes? }` | All entries go through handler chain |
|
|
522
|
+
| `rm` | `{ path, run }` | |
|
|
523
|
+
| `mv` | `{ path, to, run }` | |
|
|
524
|
+
| `cp` | `{ path, to, run }` | |
|
|
525
|
+
| `store` | `{ path, run?, persist?, ignore?, clear? }` | File constraints only — not a model tool |
|
|
526
|
+
| `getEntries` | `{ pattern?, body?, run?, limit?, offset? }` | Query entries |
|
|
527
|
+
|
|
528
|
+
### §11.3 Run Management
|
|
529
|
+
|
|
530
|
+
| Method | Params | Notes |
|
|
531
|
+
|--------|--------|-------|
|
|
532
|
+
| `startRun` | `{ model, temperature?, persona?, contextLimit? }` | Create run without prompt |
|
|
533
|
+
| `ask` | `{ model, prompt, run?, noInteraction?, noWeb?, noRepo? }` | |
|
|
534
|
+
| `act` | `{ model, prompt, run?, noInteraction?, noWeb?, noRepo? }` | |
|
|
535
|
+
| `run/resolve` | `{ run, resolution }` | Accept/reject proposals |
|
|
536
|
+
| `run/abort` | `{ run }` | Cancel active run |
|
|
537
|
+
| `run/config` | `{ run, contextLimit?, persona?, model? }` | Update run settings |
|
|
538
|
+
| `run/rename` | `{ run, name }` | Change run alias |
|
|
539
|
+
| `run/inject` | `{ run, message }` | Inject message into active turn |
|
|
540
|
+
|
|
541
|
+
### §11.4 Project Management
|
|
542
|
+
|
|
543
|
+
| Method | Params | Notes |
|
|
544
|
+
|--------|--------|-------|
|
|
545
|
+
| `init` | `{ name, projectRoot }` | Initialize project |
|
|
546
|
+
| `addModel` | `{ alias, actual, contextLength? }` | Register model |
|
|
547
|
+
| `removeModel` | `{ alias }` | Remove model |
|
|
548
|
+
| `getRuns` | `{ limit?, offset? }` | List runs |
|
|
549
|
+
| `getRun` | `{ run }` | Get single run details |
|
|
550
|
+
| `getModels` | `{}` | List models |
|
|
551
|
+
|
|
552
|
+
### §11.5 Notifications (server → client)
|
|
553
|
+
|
|
554
|
+
| Method | Payload |
|
|
555
|
+
|--------|---------|
|
|
556
|
+
| `run/state` | `{ run, status, turn, entries, ... }` |
|
|
557
|
+
| `run/progress` | `{ run, status }` |
|
|
558
|
+
| `ui/render` | `{ text }` |
|
|
559
|
+
| `ui/notify` | `{ message }` |
|