@possumtech/rummy 0.2.1

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.
Files changed (120) hide show
  1. package/.env.example +55 -0
  2. package/LICENSE +21 -0
  3. package/PLUGINS.md +302 -0
  4. package/README.md +41 -0
  5. package/SPEC.md +524 -0
  6. package/lang/en.json +34 -0
  7. package/migrations/001_initial_schema.sql +226 -0
  8. package/package.json +54 -0
  9. package/service.js +143 -0
  10. package/src/agent/AgentLoop.js +553 -0
  11. package/src/agent/ContextAssembler.js +29 -0
  12. package/src/agent/KnownStore.js +254 -0
  13. package/src/agent/ProjectAgent.js +101 -0
  14. package/src/agent/ResponseHealer.js +134 -0
  15. package/src/agent/TurnExecutor.js +457 -0
  16. package/src/agent/XmlParser.js +247 -0
  17. package/src/agent/known_checks.sql +42 -0
  18. package/src/agent/known_queries.sql +80 -0
  19. package/src/agent/known_store.sql +161 -0
  20. package/src/agent/messages.js +17 -0
  21. package/src/agent/prompt_queue.sql +39 -0
  22. package/src/agent/runs.sql +114 -0
  23. package/src/agent/schemes.sql +3 -0
  24. package/src/agent/sessions.sql +51 -0
  25. package/src/agent/tokens.js +28 -0
  26. package/src/agent/turns.sql +36 -0
  27. package/src/hooks/HookRegistry.js +72 -0
  28. package/src/hooks/Hooks.js +115 -0
  29. package/src/hooks/PluginContext.js +116 -0
  30. package/src/hooks/RummyContext.js +181 -0
  31. package/src/hooks/ToolRegistry.js +83 -0
  32. package/src/llm/LlmProvider.js +107 -0
  33. package/src/llm/OllamaClient.js +88 -0
  34. package/src/llm/OpenAiClient.js +80 -0
  35. package/src/llm/OpenRouterClient.js +78 -0
  36. package/src/llm/XaiClient.js +113 -0
  37. package/src/plugins/ask_user/README.md +18 -0
  38. package/src/plugins/ask_user/ask_user.js +48 -0
  39. package/src/plugins/ask_user/docs.md +2 -0
  40. package/src/plugins/cp/README.md +18 -0
  41. package/src/plugins/cp/cp.js +55 -0
  42. package/src/plugins/cp/docs.md +2 -0
  43. package/src/plugins/current/README.md +14 -0
  44. package/src/plugins/current/current.js +48 -0
  45. package/src/plugins/engine/README.md +12 -0
  46. package/src/plugins/engine/engine.sql +18 -0
  47. package/src/plugins/engine/turn_context.sql +51 -0
  48. package/src/plugins/env/README.md +14 -0
  49. package/src/plugins/env/docs.md +2 -0
  50. package/src/plugins/env/env.js +32 -0
  51. package/src/plugins/file/README.md +25 -0
  52. package/src/plugins/file/file.js +85 -0
  53. package/src/plugins/get/README.md +19 -0
  54. package/src/plugins/get/docs.md +6 -0
  55. package/src/plugins/get/get.js +53 -0
  56. package/src/plugins/hedberg/README.md +72 -0
  57. package/src/plugins/hedberg/docs.md +9 -0
  58. package/src/plugins/hedberg/edits.js +65 -0
  59. package/src/plugins/hedberg/hedberg.js +89 -0
  60. package/src/plugins/hedberg/matcher.js +181 -0
  61. package/src/plugins/hedberg/normalize.js +41 -0
  62. package/src/plugins/hedberg/patterns.js +452 -0
  63. package/src/plugins/hedberg/sed.js +48 -0
  64. package/src/plugins/helpers.js +22 -0
  65. package/src/plugins/index.js +180 -0
  66. package/src/plugins/instructions/README.md +11 -0
  67. package/src/plugins/instructions/instructions.js +37 -0
  68. package/src/plugins/instructions/preamble.md +12 -0
  69. package/src/plugins/known/README.md +18 -0
  70. package/src/plugins/known/docs.md +3 -0
  71. package/src/plugins/known/known.js +57 -0
  72. package/src/plugins/mv/README.md +18 -0
  73. package/src/plugins/mv/docs.md +2 -0
  74. package/src/plugins/mv/mv.js +56 -0
  75. package/src/plugins/previous/README.md +15 -0
  76. package/src/plugins/previous/previous.js +50 -0
  77. package/src/plugins/progress/README.md +17 -0
  78. package/src/plugins/progress/progress.js +44 -0
  79. package/src/plugins/prompt/README.md +16 -0
  80. package/src/plugins/prompt/prompt.js +45 -0
  81. package/src/plugins/rm/README.md +18 -0
  82. package/src/plugins/rm/docs.md +4 -0
  83. package/src/plugins/rm/rm.js +51 -0
  84. package/src/plugins/rpc/README.md +45 -0
  85. package/src/plugins/rpc/rpc.js +587 -0
  86. package/src/plugins/set/README.md +32 -0
  87. package/src/plugins/set/docs.md +4 -0
  88. package/src/plugins/set/set.js +268 -0
  89. package/src/plugins/sh/README.md +18 -0
  90. package/src/plugins/sh/docs.md +2 -0
  91. package/src/plugins/sh/sh.js +32 -0
  92. package/src/plugins/skills/README.md +25 -0
  93. package/src/plugins/skills/skills.js +175 -0
  94. package/src/plugins/store/README.md +20 -0
  95. package/src/plugins/store/docs.md +5 -0
  96. package/src/plugins/store/store.js +52 -0
  97. package/src/plugins/summarize/README.md +18 -0
  98. package/src/plugins/summarize/docs.md +4 -0
  99. package/src/plugins/summarize/summarize.js +24 -0
  100. package/src/plugins/telemetry/README.md +19 -0
  101. package/src/plugins/telemetry/rpc_log.sql +28 -0
  102. package/src/plugins/telemetry/telemetry.js +186 -0
  103. package/src/plugins/unknown/README.md +23 -0
  104. package/src/plugins/unknown/docs.md +5 -0
  105. package/src/plugins/unknown/unknown.js +31 -0
  106. package/src/plugins/update/README.md +18 -0
  107. package/src/plugins/update/docs.md +4 -0
  108. package/src/plugins/update/update.js +24 -0
  109. package/src/server/ClientConnection.js +228 -0
  110. package/src/server/RpcRegistry.js +52 -0
  111. package/src/server/SocketServer.js +43 -0
  112. package/src/sql/file_constraints.sql +15 -0
  113. package/src/sql/functions/countTokens.js +7 -0
  114. package/src/sql/functions/hedmatch.js +8 -0
  115. package/src/sql/functions/hedreplace.js +8 -0
  116. package/src/sql/functions/hedsearch.js +8 -0
  117. package/src/sql/functions/schemeOf.js +7 -0
  118. package/src/sql/functions/slugify.js +6 -0
  119. package/src/sql/v_model_context.sql +101 -0
  120. package/src/sql/v_run_log.sql +23 -0
package/SPEC.md ADDED
@@ -0,0 +1,524 @@
1
+ # RUMMY: Architecture Specification
2
+
3
+ The authoritative reference for Rummy's design. The sacred prompt
4
+ The instructions plugin (`preamble.md` + tool docs) defines
5
+ model-facing behavior. This document defines everything else.
6
+
7
+ ---
8
+
9
+ ## 0. Design Philosophy: Events & Filters
10
+
11
+ Rummy is a hooks-and-filters system. Every structural seam in the
12
+ pipeline is a hookable checkpoint. Plugins subscribe to events
13
+ (fire-and-forget side effects) and filters (transformation chains
14
+ that thread a value through subscribers in priority order).
15
+
16
+ **Every `<tag>` the model sees is a plugin.** The `<known>` section
17
+ of the system message is rendered by the known plugin. The `<progress>`
18
+ section is rendered by the progress plugin. The `<ask>` tag is rendered
19
+ by the prompt plugin. No monolithic assembler decides what goes where.
20
+ Each plugin filters for its own data from the shared row set, renders
21
+ its section, and returns.
22
+
23
+ **Plugins compose, they don't coordinate.** A plugin subscribes to a
24
+ filter at a priority. It receives the accumulator value, appends its
25
+ contribution, and returns. It doesn't know what other plugins exist.
26
+ Priority determines ordering. Lower numbers run first.
27
+
28
+ **The core is a filter chain invocation.** The TurnExecutor computes
29
+ `loopStartTurn` (one value from one row), then calls
30
+ `assembly.system.filter(instructions, ctx)` and
31
+ `assembly.user.filter("", ctx)`. Everything else is plugins.
32
+
33
+ ---
34
+
35
+ ## 1. The Known Store
36
+
37
+ All model-facing state lives in `known_entries`. Files, knowledge, tool
38
+ results, skills, audit — everything is a keyed entry with a URI scheme,
39
+ body, attributes, and state.
40
+
41
+ ### 1.1 Schema
42
+
43
+ ```sql
44
+ known_entries (
45
+ id, run_id, turn, path, body, scheme, state, hash,
46
+ attributes, tokens, tokens_full, refs, write_count,
47
+ created_at, updated_at
48
+ )
49
+ ```
50
+
51
+ | Column | Purpose |
52
+ |--------|---------|
53
+ | `path` | Entry identity. Bare paths (`src/app.js`) or URIs (`known://auth`) |
54
+ | `body` | Tag body text. File content, tool output, skill docs. |
55
+ | `attributes` | Tag attributes as JSON. Handler-private workspace. `CHECK (json_valid)` |
56
+ | `scheme` | Generated from path via `schemeOf()`. Drives dispatch and view routing |
57
+ | `state` | Lifecycle stage. Determines model visibility |
58
+ | `hash` | SHA-256 for file change detection |
59
+ | `tokens` | Context cost at current state |
60
+ | `tokens_full` | Cost of raw body at full fidelity |
61
+ | `turn` | Freshness — when was this entry last touched |
62
+
63
+ ### 1.2 Schemes & States
64
+
65
+ Paths use URI scheme syntax. Bare paths (no `://`) are files.
66
+
67
+ **Files** (`scheme IS NULL`):
68
+
69
+ | State | Model sees |
70
+ |-------|-----------|
71
+ | `full` | File content in code fence |
72
+ | `index` | Path listed in File Index |
73
+ | `stored` | Invisible, retrievable via `<get>` |
74
+
75
+ **Knowledge** (`known://`, `unknown://`):
76
+
77
+ | State | Model sees |
78
+ |-------|-----------|
79
+ | `full` | Key — value in bullet list |
80
+ | `stored` | Key listed, no value |
81
+
82
+ **Tool results** (`set://`, `sh://`, `env://`, `rm://`, `ask_user://`,
83
+ `mv://`, `cp://`, `search://`, `get://`, `store://`):
84
+
85
+ All start at `full` state when recorded. Handlers set the final state:
86
+ `proposed`, `pass`, `rejected`, `error`, `pattern`, `read`, `stored`, `info`.
87
+
88
+ **Skills** (`skill://`): `full` or `stored`. Rendered in system message.
89
+
90
+ **Tools** (`tool://`): `full`, `model_visible = 0`. Internal plugin metadata.
91
+
92
+ **URLs** (`http://`, `https://`): `full`, `summary`, `stored`.
93
+
94
+ **Structural** (`summarize://`, `update://`): Status signals.
95
+
96
+ **Audit** (`system://`, `prompt://`, `ask://`, `act://`, `progress://`,
97
+ `reasoning://`, `model://`, `error://`, `user://`, `assistant://`,
98
+ `content://`): `info` state, `model_visible = 0` (hidden from model).
99
+
100
+ ### 1.3 State Validation
101
+
102
+ The `schemes` table is a bootstrap registry — 30 rows of static config.
103
+ INSERT/UPDATE triggers validate state against `schemes.valid_states`.
104
+ Plugins cannot bypass this (circular dependency prevents schemes as entries).
105
+
106
+ ### 1.4 UPSERT Semantics
107
+
108
+ INSERT OR REPLACE on `(run_id, path)`. Each write increments `write_count`.
109
+ Blank body is valid. Deletion uses `<rm>`, which removes the row entirely.
110
+
111
+ ---
112
+
113
+ ## 2. Relational Tables
114
+
115
+ The K/V store is the memory. Relational tables are the skeleton.
116
+
117
+ ```sql
118
+ projects (id, name UNIQUE, project_root, config_path, created_at)
119
+ models (id, alias UNIQUE, actual, context_length, created_at)
120
+ runs (id, project_id, parent_run_id, model, alias UNIQUE, status,
121
+ temperature, persona, context_limit, next_turn, created_at)
122
+ turns (id, run_id, sequence, prompt_tokens, completion_tokens,
123
+ total_tokens, cost, created_at)
124
+
125
+ file_constraints (id, project_id, pattern, visibility, created_at)
126
+ prompt_queue (id, run_id, mode, model, prompt, config, status, result)
127
+ rpc_log (id, project_id, method, rpc_id, params, result, error)
128
+ ```
129
+
130
+ **No sessions.** Runs belong to projects. Any client that knows the project
131
+ name can access any run. Temperature, persona, and context_limit are per-run.
132
+
133
+ **Models** are bootstrapped from `RUMMY_MODEL_*` env vars at startup (upsert).
134
+ Clients can add/remove models at runtime via RPC. No default model — the
135
+ client picks for every run.
136
+
137
+ ### 2.1 Run State Machine
138
+
139
+ ```
140
+ queued → running → proposed → running → completed
141
+ → completed
142
+ → failed → running
143
+ → aborted → running
144
+ ```
145
+
146
+ All terminal states allow transition back to `running`. Runs are long-lived.
147
+
148
+ ### 2.2 Prompt Queue
149
+
150
+ All prompts flow through `prompt_queue`. FIFO per run. One active at a time.
151
+ Abort stops the current prompt; pending prompts survive.
152
+
153
+ ---
154
+
155
+ ## 3. Entry-Driven Dispatch
156
+
157
+ ### 3.1 Unified API
158
+
159
+ Three callers, one interface. Each tier is a superset of the one below.
160
+
161
+ | Tier | Transport | Invocation shape |
162
+ |------|-----------|-----------------|
163
+ | Model | XML tags | `{ name: "rm", path: "file.txt" }` |
164
+ | Client | JSON-RPC | `{ method: "rm", params: { path: "file.txt" } }` |
165
+ | Plugin | PluginContext | `rummy.rm({ path: "file.txt" })` |
166
+
167
+ `name` (model) = `method` (client) = method name (plugin). The params
168
+ object is the same shape at every tier.
169
+
170
+ | Method | Model | Client | Plugin |
171
+ |--------|-------|--------|--------|
172
+ | `get`, `set`, `rm`, `mv`, `cp`, `sh`, `env`, `store` | ✓ | ✓ | ✓ |
173
+ | `known`, `unknown`, `ask_user`, `summarize`, `update` | ✓ | ✓ | ✓ |
174
+ | `ask`, `act`, `resolve`, `abort`, `startRun` | — | ✓ | ✓ |
175
+ | `getRuns`, `getModels`, `getEntries` | — | ✓ | ✓ |
176
+ | `on()`, `filter()`, db/store access | — | — | ✓ |
177
+
178
+ Model tier restrictions enforced by mode (ask removes act-only tools).
179
+ Client tier requires project init. Plugin tier has no restrictions.
180
+
181
+ ### 3.2 Dispatch Path
182
+
183
+ All three tiers feed the same handler chain:
184
+
185
+ ```
186
+ Model: XmlParser → { name, path, ... } → #record() → dispatch(scheme, entry, rummy)
187
+ Client: JSON-RPC → { method, params } → #record() → dispatch(scheme, entry, rummy)
188
+ Plugin: rummy.rm({ path }) → #record() → dispatch(scheme, entry, rummy)
189
+ ```
190
+
191
+ ### 3.3 Plugin Convention
192
+
193
+ A plugin is an instantiated class. The class name matches the file name.
194
+ The constructor receives `core` (a PluginContext) — the plugin's
195
+ complete interface with the system.
196
+
197
+ ```js
198
+ export default class Rm {
199
+ #core;
200
+
201
+ constructor(core) {
202
+ this.#core = core;
203
+ core.on("handler", this.handler.bind(this));
204
+ core.on("full", this.full.bind(this));
205
+ }
206
+
207
+ async handler(entry, rummy) {
208
+ // rummy here is per-turn RummyContext (not the startup PluginContext)
209
+ }
210
+
211
+ full(entry) {
212
+ return `# rm ${entry.attributes.path}`;
213
+ }
214
+ }
215
+ ```
216
+
217
+ **Two objects:**
218
+ - `this.#core` — PluginContext (startup). For registration: `on()`, `filter()`.
219
+ - `rummy` argument — RummyContext (per-turn). For runtime: tool verbs, queries.
220
+
221
+ **Plugin types:**
222
+ - **Tool plugins**: register `handler` + `full`/`summary`. Model-invokable.
223
+ - **Assembly plugins**: register `core.filter("assembly.system", ...)`. Own a packet tag.
224
+ - **Infrastructure plugins**: register `core.on("turn", ...)`. Background work.
225
+
226
+ A plugin can be multiple types. Known is a tool AND an assembly plugin.
227
+
228
+ ### 3.4 Mode Enforcement
229
+
230
+ All tools are available by default. In ask mode, the core removes
231
+ act-only tools (`sh`, file-scheme `set`) from the tool list. This is
232
+ a core concern — plugins do not declare their modes.
233
+
234
+ ---
235
+
236
+ ## 4. Message Structure
237
+
238
+ Two messages per turn. System = stable truth. User = active task.
239
+
240
+ ### 4.1 Packet Structure
241
+
242
+ ```
243
+ [system]
244
+ [instructions]
245
+ [sacred_prompt/]
246
+ [toolDescriptions/]
247
+ [persona/]
248
+ [skills/]
249
+ [/instructions]
250
+ <knowledge>
251
+ ...entries sorted by fidelity (index, summary, full), then by scheme
252
+ </knowledge>
253
+ <previous>
254
+ (pre-loop user prompt, model responses, agent warnings, and tools used, in order)
255
+ </previous>
256
+ <unknowns></unknowns>
257
+ [/system]
258
+ [user]
259
+ <current>
260
+ (current loop model responses, agent warnings, and tools used, in order)
261
+ </current>
262
+ <progress>the above actions have been performed on this user prompt:</progress>
263
+ <ask tools="..." warn="...">user prompt</ask>
264
+ — OR —
265
+ <act tools="...">user prompt</act>
266
+ [/user]
267
+ ```
268
+
269
+ **System** contains everything the model needs to know.
270
+ **User** contains everything the model needs to do.
271
+
272
+ The `<ask>`/`<act>` tag is present on every turn — first turn and
273
+ continuations alike. The model always sees its task. The active prompt
274
+ is extracted from its chronological position and placed last for maximum
275
+ recency. `<progress>` bridges the gap, narrating the causal relationship
276
+ between `<current>` (the work) and the prompt (the cause).
277
+
278
+ ### 4.2 Loops, Previous, and Current
279
+
280
+ A **loop** is one `ask` or `act` invocation and all its continuation
281
+ turns until summarize, fail, or abort.
282
+
283
+ **Previous** = all completed loops on this run. The user prompt, model
284
+ responses, tool results, agent warnings — the full chronicle in order.
285
+ Lives in the system message as established history. Omitted on the
286
+ first turn of the first loop.
287
+
288
+ **Current** = the active loop's work so far. Model responses, tool
289
+ results, agent warnings — in order. Does NOT include the user prompt
290
+ (one per loop, extracted to `<ask>`/`<act>`). Lives in the user
291
+ message as immediate context. Empty on the first turn of a loop.
292
+
293
+ When a new prompt arrives on an existing run, the prior loop's
294
+ `<current>` content plus its prompt move to `<previous>`. When a loop
295
+ continues (next turn), new results append to `<current>`.
296
+
297
+ ### 4.3 Key Entries
298
+
299
+ | Path | Lifetime | Body | Attributes |
300
+ |------|----------|------|-----------|
301
+ | `instructions://system` | One per run (mutable) | Empty (projection builds from preamble + plugins) | `{ persona }` |
302
+ | `system://N` | Audit, one per turn | Full assembled system message | — |
303
+ | `user://N` | Audit, one per turn | Full assembled user message | — |
304
+ | `assistant://N` | Audit, one per turn | Model's raw response | — |
305
+
306
+ `instructions://system` is the only mutable entry in this group. The
307
+ framework auto-populates `toolDescriptions` from tool registrations
308
+ that include `docs`. The instructions projection assembles the final
309
+ text from body + attributes.
310
+
311
+ ### 4.4 Materialization
312
+
313
+ Each turn:
314
+
315
+ 1. Write `instructions://system` (empty body, attributes = { persona })
316
+ 2. Run plugin hooks (`onTurn`) — plugins modify entries before the model sees them
317
+ 3. Project `instructions://system` → instructions text
318
+ 4. Query `v_model_context` VIEW → visible entries
319
+ 5. Project each entry through its tool's `full`/`summary` projection
320
+ 6. Insert projected rows into `turn_context`
321
+ 7. Invoke `assembly.system` filter chain (instructions text as base):
322
+ - Known plugin (priority 100) → `<known>` section
323
+ - Previous plugin (priority 200) → `<previous>` section
324
+ - Unknown plugin (priority 300) → `<unknowns>` section
325
+ 8. Invoke `assembly.user` filter chain (empty string as base):
326
+ - Current plugin (priority 100) → `<current>` section
327
+ - Progress plugin (priority 200) → `<progress>` section
328
+ - Prompt plugin (priority 300) → `<ask>`/`<act>` section
329
+ 9. Store as `system://N` and `user://N` audit entries
330
+
331
+ The VIEW determines visibility. State IS fidelity:
332
+ - `full` → body visible
333
+ - `summary` → body visible
334
+ - `index` → path listed, no content
335
+ - `stored` → invisible
336
+ - `proposed` → invisible (pending client)
337
+ - `model_visible = 0` → invisible (audit, tool, instructions)
338
+
339
+ ### 4.5 progress:// as Entry
340
+
341
+ The continuation prompt is a `progress://N` entry. Plugins can modify its
342
+ body before materialization.
343
+
344
+ ---
345
+
346
+ ## 5. RPC Protocol
347
+
348
+ JSON-RPC 2.0 over WebSocket. `discover` returns the live catalog.
349
+
350
+ ### 5.1 Methods
351
+
352
+ #### Protocol
353
+
354
+ | Method | Params |
355
+ |--------|--------|
356
+ | `ping` | — |
357
+ | `discover` | — |
358
+ | `init` | `{ name, projectRoot, configPath? }` |
359
+
360
+ #### Models
361
+
362
+ | Method | Params |
363
+ |--------|--------|
364
+ | `getModels` | `{ limit?, offset? }` |
365
+ | `addModel` | `{ alias, actual, contextLength? }` |
366
+ | `removeModel` | `{ alias }` |
367
+
368
+ #### Entry Operations (dispatched through handler chain)
369
+
370
+ | Method | Params |
371
+ |--------|--------|
372
+ | `read` | `{ path, run?, persist?, readonly? }` |
373
+ | `store` | `{ path, run?, persist?, ignore?, clear? }` |
374
+ | `write` | `{ run, path, body?, state?, attributes? }` |
375
+ | `delete` | `{ run, path }` |
376
+ | `getEntries` | `{ pattern?, body?, run?, limit?, offset? }` |
377
+
378
+ `persist` creates a project-level file constraint (operator privilege).
379
+ Without `persist`, operations dispatch through the handler chain.
380
+
381
+ #### Runs
382
+
383
+ | Method | Params |
384
+ |--------|--------|
385
+ | `startRun` | `{ model, temperature?, persona?, contextLimit? }` |
386
+ | `ask` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noContext?, fork? }` |
387
+ | `act` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noContext?, fork? }` |
388
+ | `run/resolve` | `{ run, resolution: { path, action, output? } }` |
389
+ | `run/abort` | `{ run }` |
390
+ | `run/rename` | `{ run, name }` |
391
+ | `run/inject` | `{ run, message }` |
392
+ | `run/config` | `{ run, temperature?, persona?, contextLimit?, model? }` |
393
+
394
+ `model` is required on `ask`, `act`, and `startRun`. No default.
395
+
396
+ #### Queries
397
+
398
+ | Method | Params |
399
+ |--------|--------|
400
+ | `getRuns` | `{ limit?, offset? }` |
401
+ | `getRun` | `{ run }` |
402
+
403
+ #### Skills & Personas
404
+
405
+ | Method | Params |
406
+ |--------|--------|
407
+ | `skill/add` | `{ run, name }` |
408
+ | `skill/remove` | `{ run, name }` |
409
+ | `getSkills` | `{ run }` |
410
+ | `listSkills` | — |
411
+ | `persona/set` | `{ run, name?, text? }` |
412
+ | `listPersonas` | — |
413
+
414
+ Skills loaded from `RUMMY_HOME/skills/{name}.md`. Personas from
415
+ `RUMMY_HOME/personas/{name}.md`.
416
+
417
+ ### 5.2 Notifications
418
+
419
+ | Notification | Scoped by |
420
+ |-------------|-----------|
421
+ | `run/state` | projectId |
422
+ | `run/progress` | projectId |
423
+ | `ui/render` | projectId |
424
+ | `ui/notify` | projectId |
425
+
426
+ ### 5.3 Resolution
427
+
428
+ | Resolution | Model signal | Outcome |
429
+ |-----------|-------------|---------|
430
+ | reject | any | `completed` — rejection stops the bus |
431
+ | accept | `<update>` | `running` — model has more work |
432
+ | accept | `<summarize>` | `completed` |
433
+ | accept | neither | `running` — healer decides |
434
+ | error | any | `running` — error state, model retries |
435
+
436
+ ---
437
+
438
+ ## 6. Plugin System
439
+
440
+ See [PLUGINS.md](PLUGINS.md) for the full plugin development guide,
441
+ including the RummyContext API, tool registration, handler chains,
442
+ projections, events, filters, and hedberg pattern library.
443
+
444
+ Each plugin has its own README at `src/plugins/{name}/README.md`.
445
+
446
+ ---
447
+
448
+ ## 7. Hedberg Editing Syntax
449
+
450
+ The model picks its preferred edit format. The parser understands all of them:
451
+
452
+ 1. Git merge conflict: `<<<<<<< SEARCH ... ======= ... >>>>>>> REPLACE`
453
+ 2. Replace-only: `======= ... >>>>>>> REPLACE`
454
+ 3. Unified diff: `@@ -1,3 +1,3 @@` with `-`/`+` lines
455
+ 4. Sed syntax: `s/old/new/flags`
456
+ 5. Claude XML: `<old_text>old</old_text><new_text>new</new_text>`
457
+ 6. JSON body: `{"search": "old", "replace": "new"}` or `{search="old", replace="new"}`
458
+ 7. XML attributes: `<set search="old" replace="new"/>`
459
+ 8. Full replacement: anything else becomes the new content
460
+
461
+ ---
462
+
463
+ ## 8. Response Healing
464
+
465
+ The server never throws on model output. Recovery order:
466
+
467
+ 1. Can we recover? Extract the data and continue.
468
+ 2. Can we warn? Log structured warnings.
469
+ 3. Did our structure cause this? Check formatting, prompts.
470
+ 4. Model drift is the LAST answer.
471
+
472
+ Termination protocol:
473
+ - `<summarize>` → run terminates
474
+ - `<update>` → run continues
475
+ - Both → summarize wins
476
+ - Neither + tools → stall counter
477
+ - Neither + plain text → healed to summarize
478
+ - Repeated commands → loop detection
479
+
480
+ ---
481
+
482
+ ## 9. Testing
483
+
484
+ | Tier | Location | LLM? |
485
+ |------|----------|------|
486
+ | Unit | `src/**/*.test.js` | No |
487
+ | Integration | `test/integration/` | No |
488
+ | Live | `test/live/` | Yes |
489
+ | E2E | `test/e2e/` | Yes |
490
+
491
+ E2E tests must NEVER mock the LLM. Environment cascade:
492
+ `.env.example` → `.env` → `.env.test`. Always use `npm run test:*`.
493
+
494
+ ---
495
+
496
+ ## 10. SQL Functions
497
+
498
+ | Function | Purpose |
499
+ |----------|---------|
500
+ | `schemeOf(path)` | Extract URI scheme |
501
+ | `countTokens(text)` | Token count (tiktoken o200k_base, `ceil(len/4)` fallback) |
502
+ | `hedmatch(pattern, string)` | Full-string pattern match (paths, equality) |
503
+ | `hedsearch(pattern, string)` | Substring pattern search (content filtering) |
504
+ | `hedreplace(pattern, replacement, string)` | Pattern-based replacement |
505
+ | `slugify(text)` | URI-encoded slug, max 80 chars |
506
+
507
+ See [PLUGINS.md](PLUGINS.md) for the hedberg pattern type reference.
508
+
509
+ ---
510
+
511
+ ## 11. Configuration
512
+
513
+ ```env
514
+ RUMMY_HOME=~/.rummy
515
+ RUMMY_MAX_TURNS=15
516
+ RUMMY_MAX_STALLS=3
517
+ RUMMY_MAX_REPETITIONS=3
518
+ RUMMY_RETENTION_DAYS=31
519
+ RUMMY_TEMPERATURE=0.7
520
+ RUMMY_DEBUG=false
521
+ ```
522
+
523
+ Model aliases: `RUMMY_MODEL_{alias}={provider/model}`. Seeded into
524
+ `models` table at startup.
package/lang/en.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "error.session_not_found": "Session '{sessionId}' not found.",
3
+ "error.project_not_found": "Project '{projectId}' not found.",
4
+ "error.run_not_found": "Run '{runId}' not found.",
5
+ "error.not_initialized": "Project not initialized.",
6
+ "error.method_not_found": "Method '{method}' not found.",
7
+ "error.rpc_timeout": "RPC '{method}' timed out after {timeout}ms.",
8
+ "error.run_name_invalid": "Name must match [a-z_]+.",
9
+ "error.run_name_taken": "Name '{name}' is already taken.",
10
+ "error.model_alias_unknown": "Unknown model alias '{alias}'. Define RUMMY_MODEL_{alias} in your environment.",
11
+ "error.openai_base_url_missing": "openai/ model requested but neither OPENAI_BASE_URL nor OPENAI_API_BASE is set.",
12
+ "error.openrouter_api_key_missing": "OpenRouter API key is missing. Please set OPENROUTER_API_KEY in your environment.",
13
+ "error.openrouter_auth": "OpenRouter Authentication Error: {status}. Please check your OPENROUTER_API_KEY.",
14
+ "error.openrouter_api": "OpenRouter API error: {status}",
15
+ "error.openrouter_catalog": "OpenRouter /models failed: {status}. Check your OPENROUTER_API_KEY.",
16
+ "error.openrouter_model_not_found": "Model '{model}' not found in OpenRouter catalog.",
17
+ "error.openrouter_no_context_length": "OpenRouter reports no context_length for model '{model}'.",
18
+ "error.ollama_api": "Ollama API error: {status}",
19
+ "error.ollama_show_failed": "Ollama /api/show failed: {status}. Is Ollama running at {baseUrl}?",
20
+ "error.ollama_no_context_length": "Ollama /api/show returned no context_length for model '{model}'.",
21
+ "error.ollama_unreachable": "Ollama /api/show unreachable after 3 attempts. Is Ollama running at {baseUrl}?",
22
+ "error.openai_api": "OpenAI-compatible API error: {status}",
23
+ "error.openai_models_failed": "OpenAI-compatible /v1/models failed: {status}. Is the server running at {baseUrl}?",
24
+ "error.openai_no_context_length": "OpenAI-compatible /v1/models returned no context size.",
25
+ "error.missing_summary": "missing required summary",
26
+ "error.unresolved_proposed": "Blocked: run has {count} unresolved proposed entries.",
27
+ "error.tool_already_registered": "Tool '{name}' already registered.",
28
+ "error.rpc_already_registered": "RPC method '{name}' already registered.",
29
+ "error.resolution_invalid": "Invalid resolution action: {action}. Use 'accept' or 'reject'.",
30
+ "error.xai_base_url_missing": "x.ai/ model requested but XAI_BASE_URL is not set.",
31
+ "error.xai_api_key_missing": "x.ai/ model requested but XAI_API_KEY is not set.",
32
+ "error.xai_auth": "xAI Authentication Error: {status}. Please check your XAI_API_KEY.",
33
+ "error.xai_api": "xAI API error: {status}"
34
+ }