@possumtech/rummy 2.1.0 → 2.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 (140) hide show
  1. package/.env.example +40 -15
  2. package/.xai.key +1 -0
  3. package/PLUGINS.md +169 -53
  4. package/README.md +38 -32
  5. package/SPEC.md +366 -179
  6. package/bin/digest.js +1097 -0
  7. package/biome/no-fallbacks.grit +2 -2
  8. package/gemini.key +1 -0
  9. package/lang/en.json +10 -1
  10. package/migrations/001_initial_schema.sql +9 -2
  11. package/package.json +19 -8
  12. package/service.js +1 -0
  13. package/src/agent/AgentLoop.js +76 -26
  14. package/src/agent/ContextAssembler.js +2 -0
  15. package/src/agent/Entries.js +238 -60
  16. package/src/agent/ProjectAgent.js +44 -0
  17. package/src/agent/TurnExecutor.js +99 -30
  18. package/src/agent/XmlParser.js +206 -111
  19. package/src/agent/errors.js +35 -0
  20. package/src/agent/known_queries.sql +1 -1
  21. package/src/agent/known_store.sql +3 -42
  22. package/src/agent/materializeContext.js +30 -1
  23. package/src/agent/runs.sql +8 -18
  24. package/src/agent/tokens.js +0 -1
  25. package/src/agent/turns.sql +1 -0
  26. package/src/hooks/Hooks.js +26 -0
  27. package/src/hooks/RummyContext.js +12 -1
  28. package/src/lib/hedberg/README.md +60 -0
  29. package/src/lib/hedberg/hedberg.js +60 -0
  30. package/src/lib/hedberg/marker.js +158 -0
  31. package/src/{plugins → lib}/hedberg/matcher.js +1 -2
  32. package/src/llm/LlmProvider.js +41 -3
  33. package/src/llm/openaiStream.js +17 -0
  34. package/src/plugins/ask_user/ask_user.js +12 -2
  35. package/src/plugins/ask_user/ask_userDoc.md +1 -5
  36. package/src/plugins/budget/README.md +29 -24
  37. package/src/plugins/budget/budget.js +166 -110
  38. package/src/plugins/cli/README.md +3 -4
  39. package/src/plugins/cli/cli.js +31 -5
  40. package/src/plugins/cloudflare/cloudflare.js +136 -0
  41. package/src/plugins/cp/cp.js +41 -4
  42. package/src/plugins/cp/cpDoc.md +5 -6
  43. package/src/plugins/engine/engine.sql +1 -1
  44. package/src/plugins/env/README.md +5 -4
  45. package/src/plugins/env/env.js +7 -4
  46. package/src/plugins/env/envDoc.md +7 -8
  47. package/src/plugins/error/error.js +56 -15
  48. package/src/plugins/file/README.md +12 -3
  49. package/src/plugins/file/file.js +2 -2
  50. package/src/plugins/get/get.js +59 -36
  51. package/src/plugins/get/getDoc.md +10 -34
  52. package/src/plugins/google/google.js +115 -0
  53. package/src/plugins/hedberg/hedberg.js +13 -56
  54. package/src/plugins/helpers.js +66 -12
  55. package/src/plugins/index.js +1 -2
  56. package/src/plugins/instructions/README.md +44 -47
  57. package/src/plugins/instructions/instructions-system.md +44 -0
  58. package/src/plugins/instructions/instructions-user.md +53 -0
  59. package/src/plugins/instructions/instructions.js +58 -189
  60. package/src/plugins/known/README.md +6 -7
  61. package/src/plugins/known/known.js +24 -30
  62. package/src/plugins/log/log.js +41 -32
  63. package/src/plugins/mv/mv.js +40 -1
  64. package/src/plugins/mv/mvDoc.md +1 -8
  65. package/src/plugins/ollama/ollama.js +4 -3
  66. package/src/plugins/openai/openai.js +4 -3
  67. package/src/plugins/openrouter/openrouter.js +14 -4
  68. package/src/plugins/persona/README.md +11 -13
  69. package/src/plugins/persona/default.md +29 -0
  70. package/src/plugins/persona/persona.js +10 -66
  71. package/src/plugins/policy/policy.js +23 -22
  72. package/src/plugins/prompt/README.md +37 -27
  73. package/src/plugins/prompt/prompt.js +13 -19
  74. package/src/plugins/rm/rm.js +18 -0
  75. package/src/plugins/rm/rmDoc.md +5 -6
  76. package/src/plugins/rpc/rpc.js +3 -3
  77. package/src/plugins/set/set.js +205 -323
  78. package/src/plugins/set/setDoc.md +47 -17
  79. package/src/plugins/sh/README.md +6 -5
  80. package/src/plugins/sh/sh.js +8 -5
  81. package/src/plugins/sh/shDoc.md +7 -8
  82. package/src/plugins/skill/README.md +37 -14
  83. package/src/plugins/skill/skill.js +200 -101
  84. package/src/plugins/skill/skillDoc.js +3 -0
  85. package/src/plugins/skill/skillDoc.md +9 -0
  86. package/src/plugins/stream/README.md +7 -6
  87. package/src/plugins/stream/finalize.js +100 -0
  88. package/src/plugins/stream/stream.js +13 -45
  89. package/src/plugins/telemetry/telemetry.js +27 -4
  90. package/src/plugins/think/think.js +2 -3
  91. package/src/plugins/think/thinkDoc.md +2 -4
  92. package/src/plugins/unknown/README.md +1 -1
  93. package/src/plugins/unknown/unknown.js +17 -19
  94. package/src/plugins/update/update.js +4 -51
  95. package/src/plugins/update/updateDoc.md +21 -6
  96. package/src/plugins/xai/xai.js +68 -102
  97. package/src/plugins/yolo/yolo.js +102 -75
  98. package/src/sql/functions/hedmatch.js +1 -1
  99. package/src/sql/functions/hedreplace.js +1 -1
  100. package/src/sql/functions/hedsearch.js +1 -1
  101. package/src/sql/functions/slugify.js +16 -2
  102. package/BENCH_ENVIRONMENT.md +0 -230
  103. package/CLIENT_INTERFACE.md +0 -396
  104. package/last_run.txt +0 -5617
  105. package/scriptify/ask_run.js +0 -77
  106. package/scriptify/cache_probe.js +0 -66
  107. package/scriptify/cache_probe_grok.js +0 -74
  108. package/src/agent/budget.js +0 -33
  109. package/src/agent/config.js +0 -38
  110. package/src/plugins/hedberg/README.md +0 -71
  111. package/src/plugins/hedberg/docs.md +0 -0
  112. package/src/plugins/hedberg/edits.js +0 -55
  113. package/src/plugins/hedberg/normalize.js +0 -17
  114. package/src/plugins/hedberg/sed.js +0 -49
  115. package/src/plugins/instructions/instructions.md +0 -34
  116. package/src/plugins/instructions/instructions_104.md +0 -8
  117. package/src/plugins/instructions/instructions_105.md +0 -39
  118. package/src/plugins/instructions/instructions_106.md +0 -22
  119. package/src/plugins/instructions/instructions_107.md +0 -17
  120. package/src/plugins/instructions/instructions_108.md +0 -0
  121. package/src/plugins/known/knownDoc.js +0 -3
  122. package/src/plugins/known/knownDoc.md +0 -8
  123. package/src/plugins/unknown/unknownDoc.js +0 -3
  124. package/src/plugins/unknown/unknownDoc.md +0 -11
  125. package/turns/cli_1777462658211/turn_001.txt +0 -772
  126. package/turns/cli_1777462658211/turn_002.txt +0 -606
  127. package/turns/cli_1777462658211/turn_003.txt +0 -667
  128. package/turns/cli_1777462658211/turn_004.txt +0 -297
  129. package/turns/cli_1777462658211/turn_005.txt +0 -301
  130. package/turns/cli_1777462658211/turn_006.txt +0 -262
  131. package/turns/cli_1777465095132/turn_001.txt +0 -715
  132. package/turns/cli_1777465095132/turn_002.txt +0 -236
  133. package/turns/cli_1777465095132/turn_003.txt +0 -287
  134. package/turns/cli_1777465095132/turn_004.txt +0 -694
  135. package/turns/cli_1777465095132/turn_005.txt +0 -422
  136. package/turns/cli_1777465095132/turn_006.txt +0 -365
  137. package/turns/cli_1777465095132/turn_007.txt +0 -885
  138. package/turns/cli_1777465095132/turn_008.txt +0 -1277
  139. package/turns/cli_1777465095132/turn_009.txt +0 -736
  140. /package/src/{plugins → lib}/hedberg/patterns.js +0 -0
package/.env.example CHANGED
@@ -17,16 +17,25 @@ RUMMY_MMAP_MB=0
17
17
 
18
18
  # Agent Loop Limits — per-loop cap (turns within a single loop).
19
19
  # No per-run cap; a run can comprise many loops.
20
- RUMMY_MAX_LOOP_TURNS=99
20
+ RUMMY_MAX_LOOP_TURNS=999
21
21
  # Hard cap on commands per turn — high by design. The real cost
22
22
  # ceiling is the Token Budget; per-tool rate limits (e.g.
23
23
  # RUMMY_WEB_SEARCH_MAX) bound the expensive tools individually.
24
24
  RUMMY_MAX_COMMANDS=99
25
25
  # Per-turn cap on <search>. Refusals strike via 429.
26
26
  RUMMY_WEB_SEARCH_MAX=1
27
+ # Default candidate count per <search>. Brave caps at 20; the model can
28
+ # still narrow per-call via <search results="N">.
29
+ RUMMY_WEB_SEARCH_RESULTS=20
27
30
  RUMMY_MAX_STRIKES=3
28
31
  RUMMY_MIN_CYCLES=3
29
32
  RUMMY_MAX_CYCLE_PERIOD=4
33
+ # Free turns in an FCRM admin stage (Decomposition or Demotion) before
34
+ # the stagnation strike fires. Turn N+1 in the same admin stage emits a
35
+ # "N+1 turns in current stage" reminder and contributes to the strike
36
+ # streak (3 strikes → MAX_STRIKES → abandon). Distillation and Deployment
37
+ # are exempt — those phases can grind for many turns on hard tasks.
38
+ RUMMY_STAGNATION_FREE_TURNS=3
30
39
 
31
40
  # Hygiene
32
41
  # Days to keep completed/aborted runs before purging
@@ -35,16 +44,25 @@ RUMMY_RETENTION_DAYS=31
35
44
  # Timeouts (ms)
36
45
  RUMMY_RPC_TIMEOUT=30000
37
46
  RUMMY_FETCH_TIMEOUT=300000
47
+ RUMMY_WEB_FETCH_TIMEOUT=300000
38
48
  # Test harness — how long AuditClient waits for a single ask/act to reach
39
49
  # terminal status. Sized for full-context ingest on large-window models.
40
50
  RUMMY_TEST_RUN_TIMEOUT=3600000
41
- # rummy-cli watchdog — wall-clock budget for a one-shot CLI invocation.
42
- # Overridable per invocation via --RUMMY_RUN_TIMEOUT=<ms>.
43
- RUMMY_RUN_TIMEOUT=3600000
51
+ # rummy-cli watchdog — wall-clock budget for a single loop (one ask/act
52
+ # CLI invocation). Overridable per invocation via --RUMMY_LOOP_TIMEOUT=<ms>.
53
+ RUMMY_LOOP_TIMEOUT=86400000
44
54
 
45
55
  # Plugin module load watchdog.
46
56
  RUMMY_PLUGINS_LOAD_TIMEOUT=10000
47
57
 
58
+ # Per-entry storage cap (bytes). Generous by design — rummy is a
59
+ # memory-resident workspace, not a chat buffer — but bounded so a
60
+ # pathological capture (e.g. 100 MB of vim escape codes from a single
61
+ # <sh>) becomes a healthy 413 strike instead of an unbounded write.
62
+ # Enforced at the SQLite layer (entries.body CHECK) and surfaced to
63
+ # the model as an error.log entry the strike system can act on.
64
+ RUMMY_ENTRY_SIZE_MAX=104857600
65
+
48
66
  # LLM retry policy: time-bounded exponential backoff with full jitter.
49
67
  # DEADLINE is total wall-clock budget for an LLM call across all retries.
50
68
  # MAX_BACKOFF caps each inter-attempt sleep so a long deadline doesn't
@@ -55,9 +73,20 @@ RUMMY_LLM_MAX_BACKOFF=30000
55
73
  # Debug
56
74
  # RUMMY_DEBUG=true
57
75
 
58
- # Think tag: 1 = model uses <think> tags for reasoning (default)
59
- # 0 = disabled, model reasons via API reasoning_content field only
60
- RUMMY_THINK=1
76
+ # Reasoning request flag forwarded to the LLM provider's `think`
77
+ # parameter (gemma/llama.cpp: think=false skips server-side reasoning;
78
+ # ollama: same). 1 asks the provider to reason and surface reasoning
79
+ # content to rummy.
80
+ #
81
+ # Default 0 because forced reasoning on weak models (e.g. local gemma)
82
+ # burns the n_ctx ceiling on `<think>` blocks and triggers reasoning-
83
+ # runaway strikes. Opt in deliberately, per model.
84
+ #
85
+ # OpenRouter is intentionally orthogonal: rummy.web's openrouter plugin
86
+ # always sends `include_reasoning: true` (relay-level always-on
87
+ # telemetry — we pay the upstream cost regardless, so we keep the
88
+ # reasoning bytes). RUMMY_THINK does not apply there.
89
+ RUMMY_THINK=0
61
90
 
62
91
  # Budget
63
92
  # Fraction of context window used as ceiling. 0.9 = 90%, 10% reserved as headroom.
@@ -72,7 +101,7 @@ RUMMY_TOKEN_DIVISOR=2
72
101
 
73
102
  # Model Behavior
74
103
  # LLM temperature (0 = deterministic, 0.7 = creative). Client can override per-request.
75
- RUMMY_TEMPERATURE=0.5
104
+ RUMMY_TEMPERATURE=0.1
76
105
 
77
106
  # Run Attribute Defaults
78
107
  # Per-run attributes (passed in the run-creation set call) trump these.
@@ -97,7 +126,7 @@ RUMMY_X_TITLE=RUMMY
97
126
  # OPENAI_BASE_URL="http://127.0.0.1:11434"
98
127
  # OPENAI_API_KEY=
99
128
 
100
- # XAI_BASE_URL="https://api.x.ai/v1/responses"
129
+ # XAI_BASE_URL="https://api.x.ai/v1"
101
130
  # XAI_API_KEY=""
102
131
 
103
132
  # Model Aliases (Optional)
@@ -108,7 +137,7 @@ RUMMY_X_TITLE=RUMMY
108
137
  # RUMMY_MODEL_grok="xai/grok-4-1-fast-reasoning-latest"
109
138
  # RUMMY_MODEL_opus="openrouter/anthropic/claude-opus-4.6"
110
139
  # RUMMY_MODEL_gpro="openrouter/google/gemini-3.1-pro-preview"
111
- # RUMMY_MODEL_gemma="openai/gemma-4-26B-A4B-it-UD-Q3_K_XL.gguf"
140
+ # RUMMY_MODEL_gemma="openai/macher.gguf"
112
141
  # RUMMY_MODEL_qwen="ollama/qwen:7b"
113
142
 
114
143
  # Necessary for automated testing
@@ -116,11 +145,7 @@ RUMMY_X_TITLE=RUMMY
116
145
 
117
146
  # Web Search
118
147
 
119
- # RUMMY_SEARCH="searxng"
120
- # RUMMY_SEARXNG_URL="http://127.0.0.1:8888"
121
-
122
- # RUMMY_SEARCH="brave"
123
- # BRAVE_API_KEY=""
148
+ # RUMMY_WEB_SEARXNG_URL="http://127.0.0.1:8888"
124
149
 
125
150
  # External plugins: npm i -g <package>, then uncomment
126
151
  # RUMMY_PLUGIN_WEB="@possumtech/rummy.web"
package/.xai.key ADDED
@@ -0,0 +1 @@
1
+ export XAI_API_KEY="xai-oI0qbXtb0SUCLqgw3qeDPhIytrRs2YuPn3zGIV0l8XSygn1s5M1ZglxfbFHvCJUrIYrIbQvurCzkLpZA"
package/PLUGINS.md CHANGED
@@ -182,7 +182,7 @@ is a superset of what's below.
182
182
  | Event | Payload | Purpose |
183
183
  |-------|---------|---------|
184
184
  | `"handler"` | `(entry, rummy)` | Tool handler — called when model/client invokes this tool |
185
- | `"visible"` | `(entry)` | Visible-visibility projection — body shown in `<knowns>` / `<performed>` |
185
+ | `"visible"` | `(entry)` | Visible-visibility projection — body shown in `<visible>` (data) or `<log>` (logging) |
186
186
  | `"summarized"` | `(entry)` | Summarized-visibility projection — path + summary only (body hidden) |
187
187
  | `"turn.started"` | `({rummy, mode, prompt, loopIteration, isContinuation})` | Turn beginning — plugins write prompt/instructions entries |
188
188
  | `"turn.response"` | `({rummy, turn, result, responseMessage, content, commands, ...})` | LLM responded — write audit entries, commit usage |
@@ -253,7 +253,7 @@ the message. Current `assembly.user` registrations:
253
253
 
254
254
  | Priority | Block | Plugin | Mutates per turn? |
255
255
  |---|---|---|---|
256
- | 50 | `<summarized>` | `known.js` | Slow — only on new entry |
256
+ | 50 | `<summary>` | `known.js` | Slow — only on new entry |
257
257
  | 75 | `<visible>` | `known.js` | Fast — on every promote/demote |
258
258
  | 100 | `<log>` | `log.js` | Always — appends per action |
259
259
  | 200 | `<unknowns>` | `unknown.js` | On unknown lifecycle |
@@ -266,7 +266,7 @@ and predictable rendering position):
266
266
 
267
267
  | Range | Position | Use for |
268
268
  |---|---|---|
269
- | `0–49` | Top of user | Reserved (stable identity-tier blocks above `<summarized>`) |
269
+ | `0–49` | Top of user | Reserved (stable identity-tier blocks above `<summary>`) |
270
270
  | `50–99` | Codebase data surface | Don't add here — owned by `known.js` |
271
271
  | `100–149` | History tier | Action history, timeline-style content |
272
272
  | `150–199` | Open slot | Inter-history blocks (e.g. recent-decisions, tracked progress) |
@@ -378,10 +378,10 @@ full contract and the rationale.
378
378
 
379
379
  Returns the string the model sees for this tool's entries at the
380
380
  given visibility. Every tool MUST register `full`. `summary` is
381
- optional — if unregistered, falls back to `attributes.summary`
381
+ optional — if unregistered, falls back to `attributes.tags`
382
382
  (model-authored keyword description) or empty string.
383
383
 
384
- At summary visibility, `attributes.summary` is prepended above the
384
+ At summary visibility, `attributes.tags` is prepended above the
385
385
  plugin's summary output automatically by ToolRegistry.view().
386
386
 
387
387
  ## Two Objects {#plugins_two_objects}
@@ -424,6 +424,23 @@ instead.
424
424
  | `rummy.getEntry(path)` | First matching entry or null |
425
425
  | `rummy.getEntries(pattern, bodyFilter?)` | Array of matching entries |
426
426
  | `rummy.setAttributes(path, attrs)` | Merge attributes via json_patch |
427
+ | `rummy.entries.logPath(runId, turn, action, target)` | Build a `log://turn_N/<action>/<slug>` path, slugified + collision-safe |
428
+ | `rummy.entries.slugPath(runId, scheme, content, summary?)` | Build a `<scheme>://<slug>` path, slugified + collision-safe |
429
+
430
+ #### Path conventions {#plugins_path_conventions}
431
+
432
+ Entry paths are bounded by a hard `length(path) <= 2048` DB
433
+ CHECK constraint. In normal use, paths stay well under ~100 chars
434
+ because plugins build them via `logPath` / `slugPath`, which run the
435
+ target through `slugify` (80-char cap, `/` preserved as separator,
436
+ URL-encoded per segment) and append an integer tie-breaker on
437
+ collision (e.g. `log://turn_3/set/src/app.js_2`).
438
+
439
+ Plugin authors should pass any model-supplied target straight
440
+ through these helpers instead of stitching paths from the model's
441
+ raw input. The helpers absorb arbitrary target length and exotic
442
+ character composition without the caller having to defend against
443
+ either. The 2048 limit is the outer wall, not the working budget.
427
444
 
428
445
  ### Properties {#plugins_rummy_properties}
429
446
 
@@ -460,23 +477,44 @@ handles all exclusions:
460
477
 
461
478
  ## Hedberg {#plugins_hedberg}
462
479
 
463
- The hedberg plugin exposes pattern matching and interpretation
464
- utilities on `core.hooks.hedberg` for all plugins to use:
480
+ Hedberg has two faces. The implementation is a **library** at
481
+ `src/lib/hedberg/` pattern matching, fuzzy literal replacement,
482
+ unified-diff generation. Internal plugins import these utilities
483
+ directly:
465
484
 
466
485
  ```js
467
- const { match, search, replace, parseSed, parseEdits,
468
- generatePatch } = core.hooks.hedberg;
486
+ import { hedmatch, hedsearch } from "../../lib/hedberg/patterns.js";
487
+ import Hedberg, { generatePatch } from "../../lib/hedberg/hedberg.js";
488
+ ```
489
+
490
+ A thin **plugin shim** at `src/plugins/hedberg/` re-exposes the same
491
+ surface on `core.hooks.hedberg` for external plugins shipped in
492
+ separate packages (`rummy.repo`, `rummy.web`, etc.) that can't reach
493
+ into rummy/main's internals via direct import.
494
+
495
+ ```js
496
+ const { match, search, replace, generatePatch } = core.hooks.hedberg;
469
497
  ```
470
498
 
471
499
  | Method | Purpose |
472
500
  |--------|---------|
473
501
  | `match(pattern, string)` | Full-string pattern match (glob, regex, literal) |
474
502
  | `search(pattern, string)` | Substring search |
475
- | `replace(body, search, replacement, opts?)` | Apply replacement |
476
- | `parseSed(input)` | Parse sed syntax (any delimiter) |
477
- | `parseEdits(content)` | Detect edit format (merge conflict, udiff, sed) |
503
+ | `replace(body, search, replacement)` | Fuzzy literal replacement (whitespace-tolerant) |
478
504
  | `generatePatch(path, old, new)` | Generate unified diff |
479
505
 
506
+ Edit-shape parsing for `<set>` bodies (the `<<:::IDENT...:::IDENT`
507
+ marker family — see SPEC.md "Edit Syntax") lives in
508
+ `src/lib/hedberg/marker.js` and is invoked by the XmlParser at
509
+ `<set>` resolution time. It's not on `core.hooks.hedberg` because no
510
+ external plugin needs to re-parse model output.
511
+
512
+ **The split is intentional.** `src/lib/` is for stateless utility
513
+ modules anyone in the project can import. `src/plugins/` is for
514
+ contracts exposed via the hook system. Hedberg is one of the few
515
+ modules that has both shapes — same code, two access paths, one for
516
+ internal consumers and one for cross-package consumers.
517
+
480
518
  ## Events & Filters {#plugins_events_overview}
481
519
 
482
520
  **Events** are fire-and-forget. All handlers run. Return values ignored.
@@ -501,6 +539,7 @@ All hooks are async.
501
539
  | `run.config` | filter | Before run config applied |
502
540
  | `run.progress` | event | Transient turn activity (`thinking` / `processing` / `retrying`) |
503
541
  | `run.state` | event | Turn conclusion, per-command incremental, or terminal run close — full state snapshot (status, history, unknowns, telemetry) |
542
+ | `turn.verdict` | filter | Post-turn decision: continue / abandon / strike. Filter chain — multiple plugins (strike streak, cycle detect, stagnation today; future voters can join) each transform a verdict object. Initial value `{ continue: true }`; final value drives the loop's continue/abandon decision. |
504
543
  | `run.step.completed` | event | Turn verdict resolved (post-healer, pre-close) |
505
544
  | `loop.completed` | event | Loop exit — fires from `finally`, guaranteed on every exit path |
506
545
  | `ask.completed` | event | Ask-mode run finished |
@@ -510,32 +549,92 @@ All hooks are async.
510
549
 
511
550
  ### Turn Pipeline {#plugins_turn_pipeline}
512
551
 
513
- Hooks fire in this order every turn:
552
+ Hooks fire in this order every turn. Type column legend:
553
+ **event** = fire-and-forget, all handlers run, no return value;
554
+ **filter** = chain transform, ordered by priority, return value carries forward;
555
+ **call** = direct named-method invocation on a specific plugin.
556
+ Exceptions for `call`-shaped hooks are documented under
557
+ [Architectural exceptions](#plugins_architectural_exceptions).
514
558
 
515
559
  | # | Hook | Type | When |
516
560
  |---|------|------|------|
517
561
  | 1 | `turn.started` | event | Plugins write prompt/instructions entries |
518
- | 2 | `context.materialized` | event | turn_context populated from v_model_context |
519
- | 3 | `assembly.system` | filter | Build system message from entries |
520
- | 4 | `assembly.user` | filter | Build user message (prompt plugin adds `<prompt tokensFree tokenUsage>`) |
521
- | 5 | `budget.enforce` | call | Measure assembled tokens; if over and it's turn 1, demote prompt, re-materialize, re-check; still over → 413 |
522
- | 6 | `llm.messages` | filter | Transform messages before LLM call |
523
- | 7 | `llm.request.started` | event | LLM call about to fire |
524
- | 8 | `llm.response` | filter | Transform raw LLM response |
525
- | 9 | `llm.request.completed` | event | LLM call finished |
526
- | 10 | `turn.response` | event | Plugins write audit entries (telemetry) |
527
- | 11 | `entry.recording` | filter | Per command, during `#record()`. Returning an entry with `state: "failed"` (or `"cancelled"`) rejects it. |
528
- | 12 | Per recorded entry (sequential, abort-on-failure): | | |
562
+ | 2 | `instructions.resolveSystemPrompt` | call | System prompt assembly single-owner exception (cache stability) |
563
+ | 3 | `context.materialized` | event | turn_context populated from v_model_context |
564
+ | 4 | `assembly.system` | filter | Build system message from entries (called from inside `materializeContext`) |
565
+ | 5 | `assembly.user` | filter | Build user message (prompt plugin adds `<prompt tokensFree tokenUsage>`) |
566
+ | 6 | `turn.beforeDispatch` | filter | Measure assembled tokens; if over and turn 1, demote prompt, re-materialize, re-check; still over → 413. Filter chain on the dispatch packet `{ messages, rows, contextSize, lastPromptTokens, assembledTokens, ok, overflow }`. Budget participates here; future plugins may trim, re-order, or annotate via the same surface. `ok=false` short-circuits dispatch. |
567
+ | 7 | `llm.messages` | filter | Transform messages before LLM call |
568
+ | 8 | `llm.request.started` | event | LLM call about to fire |
569
+ | 9 | (LLM completion call) | | Direct provider call. Errors caught: ContextExceededError → 413; TimeoutError/AbortError → 504 strike (unless drain). |
570
+ | 10 | `llm.response` | filter | Transform raw LLM response |
571
+ | 11 | `llm.request.completed` | event | LLM call finished |
572
+ | 12 | (XML parse + parser-warning emission) | | Synchronous; warnings emitted via `error.log` with `soft: true` — recoverable, no strike |
573
+ | 13 | `llm.reasoning` | filter | Layer plugin reasoning contributions onto API-provided seed (used by `<think>` plugin to merge content-channel thinking into reasoning_content) |
574
+ | 14 | `turn.response` | event | Plugins write audit entries (telemetry) |
575
+ | 15 | `entry.recording` | filter | Per command, during `#record()`. Returning an entry with `state: "failed"` (or `"cancelled"`) rejects it. |
576
+ | 16 | Per recorded entry (sequential, abort-on-failure): | | |
529
577
  | | `tool.before` | event | Before handler dispatch |
530
- | | `tools.dispatch` | | Scheme's registered handler runs |
578
+ | | `tools.dispatch` | call (keyed) | Scheme's registered handler runs. Keyed dispatch is principled — multi-plugin contract by scheme name. |
531
579
  | | `tool.after` | event | Handler finished |
532
580
  | | `entry.created` | event | Entry written to store |
533
581
  | | `run.state` | event | Incremental state push to connected clients |
534
582
  | | `proposal.prepare` | event | This entry's dispatch may have created proposals (e.g. set → 202 revisions) |
535
583
  | | `proposal.pending` | event | Per each materialized proposal — client is notified, dispatch awaits resolution |
536
- | 13 | `budget.postDispatch` | call | Re-materialize + check. If over ceiling Turn Demotion (visibility=summarized on turn's visible rows) + emit 413 error. |
537
- | 14 | `hooks.update.resolve` | call | Update plugin classifies this turn's `<update>` (terminal/continuation, override-to-continuation if actions failed, heal from raw content if missing) |
538
- | 15 | `turn.completed` | event | Turn fully resolved with final status |
584
+ | 17 | `turn.dispatched` | event | Post-dispatch cleanup. Budget subscribes for Turn Demotion (visibility=summarized on visible rows that overflow) + 413 `error://` emission via `hooks.error.log.emit`. Future plugins may subscribe for any post-dispatch concern. |
585
+ | 18 | `update.resolve` | call | Update plugin classifies this turn's `<update>` (terminal/continuation, override-to-continuation if actions failed, heal from raw content if missing). Single-owner exception — synchronous return value (`{ summaryText, updateText }`) is load-bearing. |
586
+ | 19 | `turn.completed` | event | Turn fully resolved with final status |
587
+
588
+ **Legend:** ⚠ = load-bearing exception (kept by design, see below); ✗ = refactor candidate (ceremonial coupling).
589
+
590
+ ### Architectural exceptions {#plugins_architectural_exceptions}
591
+
592
+ The plugin contract aims for **events for emit, filters for transform,
593
+ keyed dispatch for multi-plugin lookups by category**. Five points
594
+ intentionally deviate. They're documented here so they aren't
595
+ mistaken for ceremony and "fixed" in a way that breaks the
596
+ load-bearing reason.
597
+
598
+ **1. `instructions.resolveSystemPrompt(rummy)` — single-owner, cache-stable.**
599
+ The system prompt is deliberately not a filter chain. Multiple
600
+ participants would defeat prefix-cache reasoning ("Static base in
601
+ system, phase-specific in user," see AGENTS.md instruction
602
+ discipline). One plugin owns the surface; direct call enforces it.
603
+
604
+ **2. `update.resolve({ recorded, ... })` — single-owner with
605
+ synchronous return value.** Caller (`TurnExecutor`) needs
606
+ `{ summaryText, updateText }` back to drive the resolve callback.
607
+ Events emit but don't return; only the update plugin understands
608
+ terminal-vs-continuation status semantics. Filter-chain shape
609
+ would only have one element (still update), so the chain would be
610
+ ceremony.
611
+
612
+ **3. Static utility imports across plugins
613
+ (`Entries.scheme`, `Entries.normalizePath`, `countTokens`,
614
+ `stateToStatus`).** Pure stateless utilities. Routing through
615
+ hooks adds a ceremony layer for zero capability gain — these aren't
616
+ extension points; they're canonical implementations.
617
+
618
+ **4. Hedberg lib + thin plugin shim.** The library lives at
619
+ `src/lib/hedberg/` (pattern matching, sed parsing, merge handling).
620
+ A thin plugin shim at `src/plugins/hedberg/hedberg.js` re-exposes
621
+ the same surface on `core.hooks.hedberg` for external plugins
622
+ (rummy.repo, rummy.web) that can't reach into rummy/main's
623
+ internals via direct import. Internal plugins use direct imports
624
+ from `src/lib/hedberg/`; external plugins use the hook namespace.
625
+ See [Hedberg](#plugins_hedberg) for the API table.
626
+
627
+ **5. Transport plugins (`cli`, `rpc`).** These are *interface*
628
+ plugins, not action plugins. Their job is to bridge external
629
+ interfaces (stdin/stdout, WebSocket) to the agent. Direct imports
630
+ of `ProjectAgent` / `RummyContext` are what makes them transports;
631
+ fitting them into the action-plugin shape would require running
632
+ the agent over a back-channel to itself.
633
+
634
+ **Anything else that looks like a direct named call into a plugin
635
+ is a seam, not an exception** — see the ✗-marked entries in the
636
+ Turn Pipeline above. Refactor surface tracked in AGENTS.md "Now"
637
+ under Phase 2.
539
638
 
540
639
  `entry.changed` fires asynchronously from mutation points — not
541
640
  pipeline-ordered. Subscribe when you need to react to any entry
@@ -566,22 +665,41 @@ update, visibility change, state change, attribute update. Payload:
566
665
 
567
666
  | Hook | Type | When |
568
667
  |------|------|------|
569
- | `hooks.budget.enforce` | method | Pre-LLM ceiling check. On first-turn 413 → Prompt Demotion + re-check. |
570
- | `hooks.budget.postDispatch` | method | Post-dispatch re-check. On 413 → Turn Demotion + 413 `error://` entry via `hooks.error.log.emit`. |
668
+ | `turn.beforeDispatch` filter | subscriber | Pre-LLM ceiling check on the dispatch packet. On first-turn 413 → Prompt Demotion + re-check; sets `ok=false` + `overflow` to short-circuit dispatch. |
669
+ | `turn.dispatched` event | subscriber | Post-dispatch re-check. On 413 → Turn Demotion + 413 `error://` entry via `hooks.error.log.emit`. |
670
+ | `assembly.user` filter | subscriber | Renders `<budget>` table into the user message. |
571
671
 
572
672
  The budget plugin measures tokens on the assembled messages — the
573
673
  actual content being sent to the LLM. No estimates at the ceiling,
574
674
  no SQL token sums. The assembled message IS the measurement. When
575
- turn 2+ information is available, `budget.enforce` prefers the actual
576
- API-reported token count (`turns.context_tokens` from the prior
577
- turn) over re-measuring the assembled string.
675
+ turn 2+ information is available, the pre-LLM check prefers the
676
+ actual API-reported token count (`turns.context_tokens` from the
677
+ prior turn) over re-measuring the assembled string.
678
+
679
+ **Use of the assembler.** Budget calls the context assembler in two
680
+ spots — these are projections, not orchestration leaks:
681
+
682
+ - **Pre-LLM Prompt Demotion (`turn.beforeDispatch`)** — when the
683
+ first-turn packet overflows, budget demotes the prompt entry in
684
+ the DB, swaps `body` from `vBody` to `sBody` on the local prompt
685
+ row, and re-runs `ContextAssembler.assembleFromTurnContext` on
686
+ the modified rows. No `materializeContext` round-trip — the row
687
+ already carries both projections.
688
+ - **Post-dispatch projection (`turn.dispatched`)** — budget re-runs
689
+ `materializeContext` to project the *next* turn's packet
690
+ (entries written during dispatch need projection through
691
+ `hooks.tools.view`). If predicted next packet overflows, budget
692
+ demotes now so next turn's enforce isn't stuck with only the
693
+ prompt-demotion lever. Cost projection is the budget plugin's
694
+ job; the assembler is the measurement instrument.
578
695
 
579
696
  **DB tokens vs assembled tokens:** The `tokens` column on `entries`
580
- is strictly for DISPLAY — showing token costs in `<knowns>` tags so
581
- the model can reason about entry sizes. It is NEVER used for budget
582
- decisions. Budget math uses only assembled message token counts.
583
- These are two separate numbers that must never be conflated. See
584
- See [budget_enforcement](SPEC.md#budget_enforcement) for the three-measure table.
697
+ is strictly for DISPLAY — showing token costs on entry tags in
698
+ `<summary>` / `<visible>` so the model can reason about entry
699
+ sizes. It is NEVER used for budget decisions. Budget math uses only
700
+ assembled message token counts. These are two separate numbers that
701
+ must never be conflated. See
702
+ [budget_enforcement](SPEC.md#budget_enforcement) for the three-measure table.
585
703
 
586
704
  ### Client Notifications {#plugins_client_notifications}
587
705
 
@@ -611,7 +729,7 @@ Every entry follows the same lifecycle regardless of origin:
611
729
 
612
730
  Entries at `visibility = 'archived'` skip steps 4–6 (invisible to
613
731
  model, discoverable via pattern search). Entries at `visibility =
614
- 'summarized'` render with `attributes.summary` (model-authored keyword
732
+ 'summarized'` render with `attributes.tags` (model-authored keyword
615
733
  description) prepended above the plugin's `summarized` view output —
616
734
  the body is hidden; promoting with `<get>` brings it back.
617
735
 
@@ -631,7 +749,7 @@ the projected body — they do NOT re-check `entry.visibility`.
631
749
  | `file` (bare paths) | data | `entry.body` | `""` | Same as known |
632
750
 
633
751
  Plugins providing only a `visible` hook fall back to
634
- `attributes.summary` (model-authored keyword description) at summarized;
752
+ `attributes.tags` (model-authored keyword description) at summarized;
635
753
  the renderer inserts it automatically. Plugins providing neither
636
754
  default to empty body — the tag still renders with its attributes so
637
755
  the model can pattern-match the path.
@@ -653,7 +771,7 @@ state: "proposed" (user decision pending)
653
771
 
654
772
  1. On dispatch, create a **proposal entry** at `{scheme}://turn_N/{slug}`
655
773
  with `state: "proposed"`, category=logging. Body empty;
656
- `summary=command` attr.
774
+ `tags=command` attr.
657
775
  2. On user accept (client sends `set { state: "resolved" }` on the
658
776
  proposal path), `AgentLoop.resolve()` transitions the proposal
659
777
  entry to `state: "resolved"` (it becomes the **log entry**) and
@@ -690,31 +808,31 @@ pure RPC plumbing shared across all streaming producers.
690
808
  |--------|------|-------------|
691
809
  | `get` | Core tool | Load file/entry into context |
692
810
  | `set` | Core tool | Edit file/entry, visibility control |
693
- | `known` | Core tool + Assembly | Save knowledge, render `<knowns>` section |
811
+ | `known` | Core tool + Assembly | Save knowledge; renders `<summary>` (priority 50) and `<visible>` (priority 75) for all category=data entries |
694
812
  | `rm` | Core tool | Delete permanently |
695
813
  | `mv` | Core tool | Move entry |
696
814
  | `cp` | Core tool | Copy entry |
697
815
  | `sh` | Core tool | Shell command (act mode only). Streaming producer — see [plugins_streaming_entries](#plugins_streaming_entries) |
698
- | `env` | Core tool | Exploratory command. Streaming producer — see §8.1 |
816
+ | `env` | Core tool | Exploratory command. Streaming producer — see [plugins_streaming_entries](#plugins_streaming_entries) |
699
817
  | `stream` | Internal | Generic streaming-entry RPC (`stream`, `stream/completed`, `stream/aborted`, `stream/cancel`) for sh/env and future producers |
700
818
  | `ask_user` | Core tool | Ask the user |
701
819
  | `search` | Core tool | Web search (via external plugin) |
702
820
  | `update` | Structural | Status report + lifecycle signal. `status="200\|204\|422"` terminates; `status="102"` continues. Exposes `hooks.update.resolve` for TurnExecutor. |
703
- | `unknown` | Structural + Assembly | Register unknowns, render `<unknowns>` |
704
- | `previous` | Assembly | Render `<previous>` loop history |
705
- | `performed` | Assembly | Render `<performed>` active loop work |
706
- | `prompt` | Assembly | Render `<prompt mode="ask\|act" tokensFree="N" tokenUsage="M">` tag |
821
+ | `unknown` | Structural + Assembly | Register unknowns, render `<unknowns>` (priority 150) |
822
+ | `log` | Assembly | Render `<log>` (priority 100) — all logging-category entries plus pre-latest prompts |
823
+ | `prompt` | Assembly | Render `<prompt tokensFree="N" tokenUsage="M">` (priority 30, front of user message) |
707
824
  | `hedberg` | Utility | Pattern matching, interpretation, normalization |
708
- | `instructions` | Internal | Preamble + tool docs + persona assembly; exposes `hooks.instructions.resolveSystemPrompt` |
825
+ | `instructions` | Internal | System prompt assembly (`instructions-system.md` + `[%TOOLS%]` + `[%TOOLDOCS%]` + persona); renders `<instructions>` (priority 165) from `instructions-user.md`; exposes `hooks.instructions.resolveSystemPrompt` |
709
826
  | `file` | Internal | File entry projections and constraints (`scheme IS NULL`) |
710
827
  | `rpc` | Internal | RPC method registration + tool-fallback dispatch |
711
828
  | `telemetry` | Internal | Audit entries, usage stats, reasoning_content |
712
- | `budget` | Internal | Context ceiling enforcement: Prompt Demotion (pre-LLM first-turn 413) + Turn Demotion (post-dispatch). Exposes `hooks.budget.enforce` / `hooks.budget.postDispatch`. |
829
+ | `budget` | Internal | Context ceiling enforcement: Prompt Demotion (pre-LLM first-turn 413) + Turn Demotion (post-dispatch). Subscribes to `turn.beforeDispatch` (filter) + `turn.dispatched` (event) + `assembly.user` (filter, priority 175 — renders `<budget>`). |
713
830
  | `policy` | Internal | Ask-mode per-invocation rejections via `entry.recording` filter |
714
831
  | `error` | Internal | `error.log` hook → `error://` entries |
715
832
  | `think` | Tool | Private reasoning tag; contributes to `reasoning_content` via the `llm.reasoning` filter |
716
833
  | `openai` / `ollama` / `xai` / `openrouter` | LLM provider | Register with `hooks.llm.providers`; handle `{prefix}/...` model aliases. Silently inert if their env isn't configured. |
717
- | `persona` / `skill` | Internal | Runtime persona/skill management via RPC |
834
+ | `persona` | Internal | Renders the persona body inside the system prompt; default at `persona/default.md`. Run-attribute `persona` overrides per run (1:1, immutable for the run's lifetime). |
835
+ | `skill` | Internal | `<skill path="..."/>` tag handler + `skill://` scheme. Walks file/folder/`.zip` (local or URL); registers content under `skill://<name>/...`. |
718
836
 
719
837
  ## External Plugins
720
838
 
@@ -806,9 +924,7 @@ dedicated verbs with 1:1 plugin-API equivalents.
806
924
  | `file/constraint` | `{ pattern, visibility }` | Project-scoped: set overlay. `visibility ∈ {active, readonly, ignore}`. Patterns can be globs. `readonly` is enforced on `set://` accept in `AgentLoop.resolve()`. |
807
925
  | `file/drop` | `{ pattern }` | Project-scoped: remove overlay row. |
808
926
  | `getConstraints` | — | Project-scoped: returns `[{pattern, visibility}]`. |
809
- | `skill/add` / `skill/remove` / `getSkills` / `listSkills` | | Skill management |
810
- | `persona/set` / `listPersonas` | | Persona management |
811
- | `stream` / `stream/completed` / `stream/aborted` / `stream/cancel` | | Streaming RPC (§8.1) |
927
+ | `stream` / `stream/completed` / `stream/aborted` / `stream/cancel` | | Streaming RPC — see [plugins_streaming_entries](#plugins_streaming_entries) |
812
928
 
813
929
  **Why file constraints are typed RPCs and not `set` entries:** they
814
930
  are project-scoped (no `run`), persist across runs, and `readonly`
package/README.md CHANGED
@@ -1,63 +1,69 @@
1
- # RUMMY: Relational Unknowns Memory Management Yoke
1
+ # RUMMY: The General-Purpose Agent Kernel
2
2
 
3
- Rummy is the only LLM agent service inspired by and dedicated to the memory of former Secretary of Defense Donald "Rummy" Rumsfeld. Our unique fusion of apophatic and hedbergian engineering strategies yields more accurate and efficient results than any other agent. Our client/server and plugin architecture integrates it into more workflows than any other agent. It's also more flexible and lean than any other agent. Our dynamic cache management, model hot-swapping, and flexible router interface make it more affordable than any other agent.
3
+ Rummy is a headless, metacognitive relational architecture for LLM agents. It is designed to be integrated into real-world workflows—from IDEs and CLI tools to autonomous research pipelines—where project state is complex and accuracy is non-negotiable.
4
4
 
5
- ## Key Features
5
+ While traditional agents "thrash" and fail under the weight of linear chat history, Rummy treats the LLM as a **program** executing on a **managed memory substrate**. This "Virtual Memory" architecture ensures that Rummy remains reliable in sessions that span hundreds of turns and tens of thousands of files.
6
+
7
+ ## The Architecture: Virtual Memory for Tokens
6
8
 
7
- - **The Rumsfeld Loop:** Forcing models to catalog what they don't know is a powerful weapon against hallucination and laziness. Every turn, the model registers gaps via `<set path="unknown://...">`, records findings via `<set path="known://...">`, and signals continuation or completion via `<update status="...">` — externalizing its reasoning into a persistent K/V store that survives across turns without message history.
9
+ Rummy provides the memory hierarchy necessary to maintain high-fidelity reasoning over unlimited-turn sessions. This is not a benchmarking "harness," but a production-grade Operating System for AI agency:
8
10
 
9
- - **One K/V Store:** Files, knowledge, tool results, unknowns, user prompts — everything is a keyed entry. Content lives in `entries` (scope-owned), per-run fidelity / status / turn in `run_views`. No message history. No separate file listings. The model's entire context is assembled from the store each turn.
11
+ * **L1 Cache (`visible`):** High-fidelity, character-perfect context. This is the active "Working Set" the model is reasoning with right now.
12
+ * **RAM (`summarized`):** Folksonomic metadata and searchable indices. This allows the model to know *what* information exists and how to address it without consuming the L1 token budget.
13
+ * **The Disk (`archived`):** Persistent SQLite storage. A relational substrate where every historical finding, raw source document, and prior tool result is safely indexed and searchable, ready to be "paged" back into Cache on demand.
10
14
 
11
- - **Hedberg:** The interpretation boundary between stochastic model output and deterministic system operations. Models speak in whatever syntax they were trained on — sed regex, SEARCH/REPLACE blocks, escaped characters. Hedberg normalizes all of it. Available to all plugins via `core.hooks.hedberg`.
15
+ ## Key Features
12
16
 
13
- - **Folksonomic Memory:** The model organizes its own knowledge into navigable path hierarchies with searchable summary tags. Not RAG — the model builds and curates its own taxonomy using `<set path="known://project/architecture" summary="keywords,go,here">...</set>`.
17
+ ### Headless & RPC-First
18
+ Rummy is a **headless service**. It exposes a JSON-RPC over WebSocket interface, allowing it to be embedded into any client (e.g., [rummy.nvim](https://github.com/possumtech/rummy.nvim)). The server manages the project state and the "Kernel" loop, while the client drives the UI and handles local proposal resolution.
14
19
 
15
- - **Fidelity System:** Every per-run view of an entry has a fidelity level: `promoted` (body visible), `demoted` (path + summary only), `archived` (invisible, retrievable via pattern search). The model manages its own context by promoting what it needs and demoting what it doesn't. Budget enforcement catches overflow post-dispatch — tools run uninterrupted, demotion happens after.
20
+ ### Extensible Plugin Architecture
21
+ Rummy is built for integration. Every `<tag>` the model sees is a plugin. Every URI scheme (`known://`, `unknown://`, `sh://`) is registered by its owner. Developers can drop custom logic into `src/plugins/` to add new tools, filters, or event hooks. See [PLUGINS.md](PLUGINS.md) for details.
16
22
 
17
- - **Plugin Architecture:** Every `<tag>` the model sees is a plugin. Every scheme is registered by its owner. The prompt itself is assembled from plugins. Drop a directory into `~/.rummy/plugins/` or install via npm. See [PLUGINS.md](PLUGINS.md) for the complete plugin API.
23
+ ### The Six Primitives
24
+ Every operation in Rummy reduces to one of six verbs over a single entry contract: `set` / `get` / `rm` / `mv` / `cp` / `update`. Tools (`<sh>`, `<search>`, `<known>`, `<unknown>`, …) are plugins that compose these primitives. Three actor surfaces — model XML tags, plugin RummyContext methods, JSON-RPC client calls — speak the same grammar at the store layer.
18
25
 
19
- - **Symbols Done Right:** Designed with universal language support in mind. Powered by [@possumtech/antlrmap](https://github.com/possumtech/antlrmap).
26
+ ### The Model Owns Its Context
27
+ Visibility (`visible` / `summarized` / `archived`) is the model's exclusive lever. The engine never silently mutates an entry's visibility behind the model's back; the only enforcements that touch visibility (Turn Demotion at budget overflow, Prompt Demotion at context-exceeded) surface through `error://` so the model sees the trigger. No chat-waterfall horizon, no auto-prune — the model controls what it sees and what it doesn't.
20
28
 
21
- - **SQLite Done Right:** Async, compiled WAL-mode SQL engine in worker threads. Powered by [@possumtech/sqlrite](https://github.com/possumtech/sqlrite).
29
+ ### Apophatic Reasoning (The Rumsfeld Loop)
30
+ Rummy turns "Not Knowing" into a formal state to be processed. By mapping **Unknowns** (`unknown://`) into verified **Knowns** (`known://`), Rummy provides a transparent, auditable trail of how the agent arrived at its conclusion.
22
31
 
23
32
  ## Installation
24
33
 
25
- Rummy loads configuration from exactly **one** directory per
26
- invocation:
34
+ Rummy loads configuration from exactly **one** directory per invocation:
27
35
 
28
- 1. The directory you run `rummy` from, if it contains `.env.example`.
36
+ 1. The current working directory (if it contains `.env.example`).
29
37
  2. Otherwise, `${RUMMY_HOME}` (default `~/.rummy`).
30
38
 
31
- `npm i -g @possumtech/rummy` runs a postinstall that seeds
32
- `${RUMMY_HOME}/.env.example` from the package defaults, so the
33
- out-of-the-box path works:
34
-
35
39
  ```bash
36
- # In your shell rc:
40
+ # Set your RUMMY_HOME
37
41
  export RUMMY_HOME=~/.rummy
38
42
 
43
+ # Install globally
39
44
  npm i -g @possumtech/rummy
40
- $EDITOR ~/.rummy/.env.example # set a model alias, tweak defaults
45
+
46
+ # Configure your environment
47
+ $EDITOR ~/.rummy/.env.example # set model aliases and keys
41
48
  rummy
42
49
  ```
43
50
 
44
- Within the chosen directory, `.env.example` is the baseline and `.env`
45
- (if present) overrides. Shell env beats both. The package's own
46
- `.env.example` is **never** loaded at runtime — if neither the cwd nor
47
- `${RUMMY_HOME}` has an `.env.example`, rummy crashes at startup. No
48
- silent defaults.
49
-
50
51
  ## Usage
51
52
 
52
- Rummy is just the service. You'll need to get (or vibe) yourself a client interface. We're partial the our Neovim plugin: [@possumtech/rummy.nvim](https://github.com/possumtech/rummy.nvim)
53
+ Start the service and connect your preferred client. The server defaults to port `3044`.
54
+
55
+ * **Official Client:** [rummy.nvim](https://github.com/possumtech/rummy.nvim) (Neovim interface)
56
+ * **In-process CLI:** `rummy-cli` (one-shot ask/act invocations against a project; see `src/plugins/cli/`)
57
+ * **Diagnostic Suite:** `test/tbench/` and `test/programbench/` (autonomous diagnostic and benchmarking harnesses)
53
58
 
54
59
  ## Documentation
55
60
 
56
61
  | Document | Contents |
57
62
  |----------|----------|
58
- | [SPEC.md](SPEC.md) | System design: K/V store, dispatch, packet structure, RPC |
59
- | [PLUGINS.md](PLUGINS.md) | Plugin development: registration, events, filters, hedberg |
60
- | [AGENTS.md](AGENTS.md) | Planning and progress |
63
+ | [SPEC.md](SPEC.md) | Technical Specification: K/V store, packet structure, dispatch path, and lifecycle contracts. |
64
+ | [PLUGINS.md](PLUGINS.md) | Extensibility: Hook registry, event filtering, and custom scheme registration. |
65
+ | [src/plugins/](src/plugins/**/README.md) | **Plugin Reference:** Internal documentation for each scheme and toolset. |
66
+ | [AGENTS.md](AGENTS.md) | Project roadmap, planning history, and architectural lessons. |
61
67
 
62
- Each plugin has its own README at `src/plugins/{name}/README.md`.
63
- The `discover` RPC method returns the live protocol reference at runtime.
68
+ ---
69
+ *Rummy: The Managed Operating System for AI Agency.*