@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/SPEC.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # RUMMY: Architecture Specification
2
2
 
3
3
  The authoritative reference for Rummy's design. The instructions
4
- plugin (`instructions.md` + phase-specific `instructions_10N.md` +
5
- tool docs) defines model-facing behavior. This document defines
6
- everything else.
4
+ plugin (`instructions-system.md` + `instructions-user.md` + tool
5
+ docs) and the persona plugin (`persona/default.md`) define model-
6
+ facing behavior. This document defines everything else.
7
7
 
8
8
  ---
9
9
 
@@ -18,10 +18,9 @@ uses one of these words, it should mean exactly what's written here.
18
18
  | **loop** | One `ask` or `act` invocation and all its continuation turns until terminal `<update>`, abandonment, or abort. A run can contain multiple loops if a fresh prompt arrives on an existing run. |
19
19
  | **turn** | One round-trip with the LLM: one assembled prompt sent, one response parsed. A loop is a sequence of turns. |
20
20
  | **mode** | `ask` (read-only — no proposals, no `<sh>`, no edits) or `act` (full tool surface). Per loop, set at the entry point. |
21
- | **phase** | (Primary, FCRM sense.) One of five FCRM states selected by `<update status="1XY">`: 104=Definition, 105=Discovery, 106=Demotion, 107=Deployment, 108=Verification. Maps to `instructions_10N.md` rendered in `<instructions>`. **The model-facing instructions call these "stages"** same concept, dual vocabulary kept for the model's surface stability. Two non-FCRM uses of "phase" coexist in the codebase and AGENTS.md: (1) "two-phase turn execution" refers to RECORD→DISPATCH within a single turn; (2) AGENTS.md "Phase 1 / Phase 2 / ..." entries refer to project-development milestones (Schema, Primitives, etc.) neither is the FCRM phase. Context disambiguates; if it doesn't, it's a doc bug. |
22
- | **stage** | Model-facing synonym for **phase**. Lives in `instructions_*.md` and tooldocs. |
21
+ | **phase** | The RECORD→DISPATCH split within a single turn (see [dispatch_path](#dispatch_path)). AGENTS.md "Phase 1 / Phase 2 / ..." entries refer to project-development milestones; that's a separate use of the word. The model-facing workflow lives in `persona/default.md` as the 7D ladder Draft Decompose Discover Distill Define Determine Deliver a persona convention, not a status-keyed engine state. |
23
22
  | **proposal** | A tool-call entry at status 202 awaiting client resolution (accept/reject). Side-effecting actions (`<sh>`, `<env>`, file `<set>`, file `<rm>`/`<mv>`/`<cp>`, `<ask_user>`) emit proposals. YOLO mode auto-accepts. |
24
- | **verdict** | The end-of-turn ruling from `hooks.error.verdict` (owned by the error plugin). Returns `{continue, status, reason}`. Decides whether the loop continues to another turn or terminates. |
23
+ | **verdict** | The end-of-turn ruling from `hooks.turn.verdict.filter` a generic filter chain. Returns `{continue, status, reason}`. The error plugin is the canonical subscriber today; future plugins (cycle-detection, budget-overflow termination) can join the chain to vote without touching error.js or AgentLoop. Decides whether the loop continues to another turn or terminates. |
25
24
  | **strike** | A turn whose verdict counts toward `MAX_STRIKES`. A strike fires when `turnErrors > 0` (any `error.log` entry that turn) or when cycle detection trips silently. The streak counter resets on a clean turn (no errors, no cycle); reaches `MAX_STRIKES` → loop abandons at 499. |
26
25
  | **resolution** | Client's accept/reject of a proposal via `run/resolve` RPC. |
27
26
  | **dispatch** | The DISPATCH phase of a turn — actually executing recorded action entries. |
@@ -124,11 +123,12 @@ hooks-and-filters system. Plugins subscribe to events (fire-and-forget
124
123
  side effects) and filters (transformation chains that thread a value
125
124
  through subscribers in priority order).
126
125
 
127
- **Every `<tag>` the model sees is a plugin.** `<knowns>` → known
128
- plugin. `<unknowns>` → unknown plugin. `<performed>` → performed
129
- plugin. `<previous>` → previous plugin. `<prompt>` → prompt plugin.
130
- No monolithic assembler decides what goes where. Each plugin filters
131
- for its own data from the shared row set, renders its section, returns.
126
+ **Every `<tag>` the model sees is a plugin.** `<summary>` /
127
+ `<visible>` → known plugin. `<unknowns>` → unknown plugin. `<log>`
128
+ → log plugin. `<instructions>` → instructions plugin. `<prompt>` →
129
+ prompt plugin. `<budget>` budget plugin. No monolithic assembler
130
+ decides what goes where. Each plugin filters for its own data from
131
+ the shared row set, renders its section, returns.
132
132
 
133
133
  **Plugins compose, they don't coordinate.** A plugin subscribes to a
134
134
  filter at a priority, receives the accumulator value, appends its
@@ -252,7 +252,7 @@ Every entry plays one of four roles:
252
252
 
253
253
  | Role | Category | Section | Description |
254
254
  |------|----------|---------|-------------|
255
- | **Data** | `data` | `<summarized>` + `<visible>` | Entries the model works with — persistent state and captured payload. Summary line in `<summarized>` for visible+summarized tiers; full body in `<visible>` only when promoted. |
255
+ | **Data** | `data` | `<summary>` + `<visible>` | Entries the model works with — persistent state and captured payload. Summary line in `<summary>` for visible+summarized tiers; full body in `<visible>` only when promoted. |
256
256
  | **Logging** | `logging` | `<log>` | Records of what happened — tool results, lifecycle signals |
257
257
  | **Unknowns** | `unknown` | `<unknowns>` | Open questions the model is tracking |
258
258
  | **Prompt** | `prompt` | `<prompt>` | The task driving the loop |
@@ -287,7 +287,7 @@ across two namespaces as a direct consequence:
287
287
  scheme=`log`, category=`logging`. Renders in `<log>`.
288
288
  - **Payload channels** live in `{action}://turn_N/{slug}_N` —
289
289
  scheme=`{action}` (registered as `category: "data"`). Render in
290
- `<summarized>` (always, while tracked) and `<visible>` (when
290
+ `<summary>` (always, while tracked) and `<visible>` (when
291
291
  promoted).
292
292
 
293
293
  This keeps `<log>` a terse audit trail (what happened, exit code,
@@ -390,11 +390,18 @@ the current loop; pending loops survive. Projects > runs > loops > turns.
390
390
 
391
391
  The `file_constraints` table is project-level configuration — it
392
392
  defines which files a project cares about. This is backbone, not tool
393
- dispatch. Constraints have three visibilities:
394
-
395
- - `active` matching files are promoted into the run's context
396
- - `readonly` promoted but not editable by the model
397
- - `ignore` demoted (excluded from context)
393
+ dispatch. Constraint type governs **membership** and **write
394
+ permission**, not in-context visibility. In-context visibility
395
+ (`visible` / `summarized` / `archived`) is per-entry and model-
396
+ controlledfiles default to `archived` on ingestion; the model
397
+ promotes via `<get>` / `<set visibility=…>`.
398
+
399
+ - `add` — file is part of the project; ingested as an entry; model
400
+ may write. Default for `setConstraint`.
401
+ - `readonly` — same ingestion; `<set>` is vetoed at the proposal-
402
+ accept gate.
403
+ - `ignore` — excluded from scans entirely. The file remains on disk
404
+ for `<sh>` / `<env>` invocation but is not present as an entry.
398
405
 
399
406
  **Boundary:** Setting a constraint (`File.setConstraint`) is a
400
407
  project-config write. Promoting/demoting the matching entries is tool
@@ -503,9 +510,9 @@ to a continuation (the model's claim of doneness is false); the update
503
510
  plugin resolves the update entry to 409 and surfaces it to the next
504
511
  turn as a continuation. Multiple `<update>` tags → last signal wins.
505
512
 
506
- **Post-dispatch budget check:** After all tools dispatch, the budget
507
- plugin re-materializes context and checks the ceiling
508
- (`hooks.budget.postDispatch`). If context exceeds the ceiling, Turn
513
+ **Post-dispatch budget check:** After all tools dispatch, TurnExecutor
514
+ emits `turn.dispatched`; the budget plugin subscribes, re-materializes
515
+ context, and checks the ceiling. If context exceeds the ceiling, Turn
509
516
  Demotion fires — all `visible` `run_views` rows for the current turn
510
517
  have their `visibility` flipped to `summarized`, and an `error://` entry at status 413 is
511
518
  written. Status is NOT touched (see [schemes_status_visibility](#schemes_status_visibility)). The tools already ran;
@@ -685,44 +692,43 @@ plumbing for the attribute and the rummy-context payload enrichment
685
692
  on `proposal.pending`. Feature logic stays in
686
693
  `src/plugins/yolo/yolo.js`.
687
694
 
688
- ### Repo Overview {#repo_overview}
695
+ ### Project Manifest {#project_manifest}
689
696
 
690
- The `rummy.repo` plugin maintains a single `repo://overview` entry per
691
- run, regenerated on every scan, that gives the model a navigable map
692
- of the project. It is the entry-point for code-aware runs files
693
- themselves default to `archived` so a 5000-file repo doesn't dump
694
- hundreds of thousands of tokens into context before any work happens.
697
+ The `rummy.repo` plugin writes a single `log://turn_0/repo/manifest` entry
698
+ once per run a flat snapshot of every project file with its token
699
+ cost. It gives the model orientation at run start without burning
700
+ prefix-cache on a turn-keyed regeneration. Files themselves default
701
+ to `archived` so a 5000-file repo doesn't dump hundreds of thousands
702
+ of tokens into context before any work happens.
695
703
 
696
704
  **Entry contract.**
697
705
 
698
- - Path: `repo://overview` (scheme `repo`, category `data`,
699
- `model_visible: 1`)
700
- - Visibility: `visible` (the navigation map is always in context)
701
- - Body: a markdown structure containing the project root, file count,
702
- root-level files, top-level directories with file counts,
703
- active/readonly constraints, and a navigation legend showing the
704
- promote/demote idioms.
705
- - Visible projection: full body.
706
- - Summarized projection: first ~12 lines + a truncation marker, so a
707
- model can demote it once it has the layout memorized.
706
+ - Path: `log://turn_0/repo/manifest` (log scheme; turn-0 marks "before
707
+ any model turn"). One entry per run, written once.
708
+ - Visibility: `visible` at write; demotable like any log entry.
709
+ - Body: a flat list of `* <relative-path> - <N> tokens` lines, one
710
+ per file, sorted by path. No headers, no directory aggregation, no
711
+ constraints, no navigation legend those are the model's business
712
+ to derive from the list itself or from tooldocs.
713
+
714
+ **Stale by design.** The manifest is a turn-0 snapshot; it does not
715
+ update mid-run. Authoritative current state lives in the per-file
716
+ entries (mtime/hash-driven, change-only writes). The model can
717
+ `<get path="**" preview/>` for a fresh listing if it suspects
718
+ staleness.
708
719
 
709
720
  **File default visibility flip.**
710
721
 
711
722
  `FileScanner` registers each tracked file at `archived` by default
712
723
  (was `summarized`). Files with `constraint=active` still register at
713
- `visible`. The model uses `repo://overview` to discover paths, then
724
+ `visible`. The model uses the manifest to discover paths, then
714
725
  promotes individual files via `<get path=...>` (visible, full body)
715
726
  or whole subtrees via `<set path=".../**" visibility="summarized"/>`
716
727
  (skim mode, symbols only).
717
728
 
718
- **Bounded cost.** The overview body is constant-ish in size regardless
719
- of repo size: root files capped, directory counts aggregated, no per-
720
- file symbol enumeration. The token cost in context stays roughly
721
- flat from a 30-file project to a 50,000-file monorepo.
722
-
723
729
  **Disabled when noRepo.** Setting `noRepo: true` on a run skips the
724
- scan entirely; no `repo://overview` is created and no file entries
725
- are registered. Behaviour identical to pre-plugin runs.
730
+ scan entirely; no manifest is created and no file entries are
731
+ registered. Behaviour identical to pre-plugin runs.
726
732
 
727
733
  ### Streaming Entries {#streaming_entries}
728
734
 
@@ -749,13 +755,13 @@ log://turn_N/{action}/{slug} scheme=log category=logging status=202
749
755
 
750
756
  {action}://turn_N/{slug}_1 scheme={action} category=data status=102 → 200/500
751
757
  body: primary stream (stdout for shell)
752
- summary="{command}" visibility=summarized
753
- (line in <summarized>; full body in
758
+ tags="{command}" visibility=summarized
759
+ (line in <summary>; full body in
754
760
  <visible> when promoted)
755
761
 
756
762
  {action}://turn_N/{slug}_2 scheme={action} category=data status=102 → 200/500
757
763
  body: alt stream (stderr for shell)
758
- (line in <summarized>; full body in
764
+ (line in <summary>; full body in
759
765
  <visible> when promoted, often empty)
760
766
  ```
761
767
 
@@ -809,21 +815,32 @@ Two messages per turn. System = stable truth. User = active task.
809
815
 
810
816
  ```
811
817
  [system message]
812
- instructions text
813
- (instructions.md base template + tool docs injected via
814
- instructions.toolDocs filter; optional persona appended)
815
- [user message]
816
- <summarized>
818
+ instructions-system.md text (with [%TOOLS%] / [%TOOLDOCS%]
819
+ expansions) + persona body. Resolved by the instructions
820
+ plugin's hooks.instructions.resolveSystemPrompt single-owner,
821
+ cache-stable across all turns within a run. The assembly.system
822
+ filter chain exists but currently has no subscribers; the
823
+ system message is the resolved system prompt verbatim.
824
+ [user message] (sandwich ordering — see below)
825
+ <prompt tokenUsage="N" tokensFree="M">user prompt</prompt>
826
+ (prompt.js, assembly.user priority 30 — front, cacheable
827
+ across the run within a loop)
828
+ <summary>
817
829
  one entry per category=data entry whose visibility is visible
818
- or summarized; plus the named carve-out (archived prompts pass
819
- through with visibility="archived" so the model can <get> the
820
- active prompt back). Each entry renders under its scheme tag
821
- with its summarized projection as the tag body — this is the
822
- compact-but-informative view produced by the plugin's summary()
823
- hook (e.g. truncated knowns, code symbols for files, page
824
- abstracts for URLs). Identity-keyed, slow-mutating: only grows
825
- when a new entry lands. (known.js, assembly.user priority 50)
826
- </summarized>
830
+ or summarized. Each entry renders under its scheme tag with
831
+ its summarized projection as the tag body the compact-but-
832
+ informative view produced by the plugin's summarized() hook
833
+ (truncated knowns, code symbols for files, page abstracts
834
+ for URLs). Identity-keyed, slow-mutating: only grows when a
835
+ new entry lands. Archived entries including prompts
836
+ are filtered out uniformly. There is no instruction-side
837
+ guard against archiving the active prompt if the model
838
+ archives it, the next turn renders without a <prompt> tag
839
+ and visibly fails (paradigm purity over silent rescue;
840
+ action-gate is the principled future fix per
841
+ src/plugins/prompt/README.md).
842
+ (known.js, assembly.user priority 50)
843
+ </summary>
827
844
  <visible>
828
845
  each category=data entry whose visibility is visible, rendered
829
846
  under its scheme tag with its visible projection as the tag
@@ -833,63 +850,86 @@ Two messages per turn. System = stable truth. User = active task.
833
850
  (known.js, assembly.user priority 75)
834
851
  </visible>
835
852
  <log>
836
- action history — log:// entries + pre-latest prompts
853
+ action history — all logging-category entries (log:// audit
854
+ records, error://, update://) plus pre-latest prompt://
855
+ entries (the active prompt is extracted to <prompt>).
837
856
  (log.js, assembly.user priority 100)
838
857
  </log>
839
858
  <unknowns>
840
- (open questions at category=unknown, unknown.js priority 200)
859
+ open questions at category=unknown, rendered under <unknown>
860
+ children with their bodies as questions. (unknown.js,
861
+ assembly.user priority 150)
841
862
  </unknowns>
842
863
  <instructions>
843
- current phase directive one of instructions_104.md …
844
- instructions_108.md, selected by the latest <update status="1XY">
845
- emission (instructions.js, assembly.user priority 250)
864
+ instructions-user.md text. Per-turn imperative reminders.
865
+ Same bytes every turn no phase keying, no status-driven
866
+ selection. (instructions.js, assembly.user priority 165)
846
867
  </instructions>
847
- <prompt mode="ask|act" tokenUsage="N" tokensFree="M">user prompt</prompt>
868
+ <budget tokenUsage="N" tokensFree="M">…breakdown table…</budget>
869
+ (budget.js, assembly.user priority 175 — last, recency for
870
+ the live accounting at the action site)
848
871
  ```
849
872
 
850
873
  **System** = stable world state the model operates within (identity,
851
- tools, tool docs). Stable across turns within a run, which keeps
852
- prompt caching intact. **User** = active work (what the model is
853
- doing right now): the project's data surface, history, open questions,
854
- current phase, and current prompt. Both phase-specific
855
- `<instructions>` and the codebase blocks (`<summarized>` / `<visible>`)
856
- live in the user message because they change turn-to-turn — putting
857
- mutable state in system would invalidate the cache on every promote
858
- or phase transition.
874
+ tools, tool docs, persona). Stable across turns within a run, which
875
+ keeps prompt caching intact. **User** = active work: the project's
876
+ data surface, history, open questions, current task, and live
877
+ accounting. The user message changes turn-to-turn so it sits outside
878
+ the prefix-cacheable region; both `<instructions>` and the codebase
879
+ blocks (`<summary>` / `<visible>`) live here because they mutate at
880
+ turn cadence — putting mutable state in system would invalidate the
881
+ cache on every promote.
882
+
883
+ **Sandwich ordering.** User-message blocks are arranged
884
+ `<prompt>` (30, front) → `<summary>` (50) → `<visible>` (75) →
885
+ `<log>` (100) → `<unknowns>` (150) → `<instructions>` (165) →
886
+ `<budget>` (175, last). The prompt sits at the front (cacheable
887
+ across turns of a loop, since it doesn't change within a loop); the
888
+ instructions and budget sit at the tail so the rules and live
889
+ accounting have recency at the action site. An earlier front-loaded
890
+ ordering (instructions first for max cache) regressed terminal-
891
+ `<update>` discipline in e2e — the model lost the rule when it sat
892
+ 3K tokens upstream of the action. Recency at the action site beats
893
+ cache savings when the action depends on remembering a rule.
859
894
 
860
895
  **Why two blocks instead of one `<context>`.** Promote/demote is the
861
- dominant intra-phase operation. Today's single-block render
862
- invalidates the entire data surface every time. With the split,
863
- `<summarized>` mutates only when a new entry lands (slow); `<visible>`
896
+ dominant intra-loop operation. A single-block render would
897
+ invalidate the entire data surface on every promote. With the split,
898
+ `<summary>` mutates only when a new entry lands (slow); `<visible>`
864
899
  mutates on every promote/demote (fast). Ordering slow-above-fast
865
- preserves the prefix cache for `<summarized>` across the common case.
866
- Cognitively: `<summarized>` is "what I know exists" (identity);
900
+ preserves the prefix cache for `<summary>` across the common case.
901
+ Cognitively: `<summary>` is "what I know exists" (identity);
867
902
  `<visible>` is "what I'm reading right now" (working memory).
868
903
 
869
904
  The `<prompt>` tag is present on every turn — first turn and
870
- continuations alike. The model always sees its task. The active prompt
871
- is extracted from its chronological position and placed last for maximum
872
- recency. The `<prompt>` element carries `tokenUsage` / `tokensFree`
873
- attributes so the model can do budget arithmetic in-line with the cause.
905
+ continuations alike. The model always sees its task. The
906
+ `tokenUsage` / `tokensFree` attributes also appear on `<budget>` so
907
+ the model can do budget arithmetic at both ends of the user message.
874
908
 
875
- ### Loops, Previous, and Performed {#loops_previous_performed}
909
+ ### Loops and Cross-Loop Continuity {#loops_previous_performed}
876
910
 
877
911
  A **loop** is one `ask` or `act` invocation and all its continuation
878
- turns until `<update status="200">`, fail, or abort.
879
-
880
- **Previous** = all completed loops on this run. The user prompt, model
881
- responses, tool results, agent warnings the full chronicle in order.
882
- Lives in the system message as established history. Omitted on the
883
- first turn of the first loop.
884
-
885
- **Performed** = the active loop's work so far. Model responses, tool
886
- results, agent warnings — in order. Does NOT include the user prompt
887
- (one per loop, extracted to `<prompt>`). Lives in the user
888
- message as immediate context. Empty on the first turn of a loop.
912
+ turns until `<update status="200">`, fail, or abort. A run may
913
+ contain many loops; pending loops queue FIFO via the loops table.
914
+
915
+ Cross-loop continuity is carried by the entry store itself:
916
+
917
+ - **Knowns, files, unknowns** persist across loop boundaries with
918
+ whatever visibility the model left them at. They render in
919
+ `<summary>` / `<visible>` per visibility, regardless of which
920
+ loop wrote them.
921
+ - **Log entries** (action audit, errors, updates) accumulate at
922
+ `log://turn_N/...` for every turn of every loop; `log.js`
923
+ renders all logging-category entries plus pre-latest prompts in
924
+ `<log>` in chronological order.
925
+ - **The active prompt** is extracted from its chronological
926
+ position and rendered as `<prompt>` at priority 30 (front);
927
+ prior prompts render in `<log>` like any other logging entry.
889
928
 
890
929
  When a new prompt arrives on an existing run, the prior loop's
891
- `<performed>` content plus its prompt move to `<previous>`. When a loop
892
- continues (next turn), new results append to `<performed>`.
930
+ `prompt://N` entry stays in the store; on the next assembly it
931
+ falls out of `<prompt>` (replaced by the new prompt) and into
932
+ `<log>` — visibility-driven re-rendering of the same entry rows.
893
933
 
894
934
  ### Key Entries {#key_entries}
895
935
 
@@ -911,23 +951,33 @@ Each turn:
911
951
 
912
952
  1. Write `instructions://system` (empty body, attributes = { persona, toolSet })
913
953
  2. Emit `turn.started` — plugins write prompt/instructions entries
914
- 3. Resolve the instructions system prompt (`hooks.instructions.resolveSystemPrompt`)
954
+ 3. Resolve the instructions system prompt
955
+ (`hooks.instructions.resolveSystemPrompt` — single-owner; see
956
+ AGENTS.md "Architectural exceptions"). Returns
957
+ `instructions-system.md` with `[%TOOLS%]` / `[%TOOLDOCS%]`
958
+ expanded, persona body appended.
915
959
  4. Query `v_model_context` VIEW → visible entries (joined from
916
960
  `run_views` + `entries` + `schemes`)
917
961
  5. Project each entry through its scheme's `visible`/`summarized` projection
918
962
  6. Insert projected rows into `turn_context`
919
- 7. Invoke `assembly.system` filter chain (instructions text as base):
920
- - Known plugin (priority 100) `<knowns>` section
921
- - Previous plugin (priority 200) → `<previous>` section
963
+ 7. Invoke `assembly.system` filter chain currently no
964
+ subscribers, so the system message is the resolved system
965
+ prompt verbatim.
922
966
  8. Invoke `assembly.user` filter chain (empty string as base):
923
- - Performed plugin (priority 100) → `<performed>` section
924
- - Unknown plugin (priority 200) → `<unknowns>` section
925
- - Prompt plugin (priority 300) → `<prompt>` element (carries
926
- `tokenUsage` / `tokensFree` attrs when `contextSize` is set)
967
+ - Prompt plugin (priority 30) → `<prompt>` element (carries
968
+ `tokenUsage` / `tokensFree` attrs)
969
+ - Known plugin (priority 50) → `<summary>` section
970
+ - Known plugin (priority 75) `<visible>` section
971
+ - Log plugin (priority 100) → `<log>` section
972
+ - Unknown plugin (priority 150) → `<unknowns>` section
973
+ - Instructions plugin (priority 165) → `<instructions>` section
974
+ (renders `instructions-user.md`)
975
+ - Budget plugin (priority 175) → `<budget>` element (carries
976
+ `tokenUsage` / `tokensFree` and per-scheme breakdown)
927
977
  9. Store as `system://N` and `user://N` audit entries (telemetry plugin)
928
978
 
929
979
  The VIEW determines visibility from `visibility` and `status`:
930
- - `visibility = 'visible'` → full body visible in `<knowns>` / `<performed>`.
980
+ - `visibility = 'visible'` → full body visible in `<visible>` (data) or `<log>` (logging).
931
981
  - `visibility = 'summarized'` → summarized projection visible (typically path +
932
982
  summary attr). Promote with `<get>` to expand.
933
983
  - `visibility = 'archived'` → invisible. Discoverable via pattern search
@@ -947,6 +997,29 @@ Model controls visibility via `<set>` attributes:
947
997
  attaches a description (≤ 80 chars) that persists across visibility
948
998
  changes.
949
999
 
1000
+ ### Filesystem Freshness {#filesystem_freshness}
1001
+
1002
+ After any mutation of a file or scheme entry, the next turn's
1003
+ assembled context reflects the post-mutation body AND visibility,
1004
+ without the model needing a fresh `<get>` to recover its own
1005
+ changes. The model's view of the entry store is always a faithful
1006
+ projection of current state — there is no read-after-write skew.
1007
+
1008
+ The invariant has two parts:
1009
+
1010
+ 1. **Body freshness** — a write that changes the entry body shows
1011
+ the new body on the next assembly's `<visible>` (when visible)
1012
+ or under `<get>` (when summarized/archived).
1013
+ 2. **Visibility freshness** — a write that explicitly sets
1014
+ `visibility=...` honors the requested level on the next
1015
+ assembly. Edit-path side effects (e.g., a SEARCH/REPLACE accept
1016
+ silently downgrading visibility) violate the invariant; the
1017
+ model would answer the next turn from memory of pre-edit state
1018
+ while the new body sits invisible.
1019
+
1020
+ Enforcement: `test/integration/file_freshness.test.js` exercises
1021
+ write-through for both file and scheme entries.
1022
+
950
1023
  ### Token Accounting {#token_accounting}
951
1024
 
952
1025
  Tokens are a property of the materialized packet, not of stored entries.
@@ -1021,49 +1094,55 @@ an entry is being written.
1021
1094
  ### Budget Enforcement {#budget_enforcement}
1022
1095
 
1023
1096
  The model owns its context. The system enforces a hard ceiling and
1024
- surfaces the numbers it does not automatically manage entries.
1097
+ surfaces the numbers. Auto-demotion is reserved for the 413 budget
1098
+ grinder, which only fires in response to actual overflow — never
1099
+ helpfully or speculatively.
1025
1100
 
1026
1101
  **Ceiling.** `ceiling = floor(contextSize × RUMMY_BUDGET_CEILING)`
1027
1102
  (default `RUMMY_BUDGET_CEILING = 0.9`, i.e. 10% headroom). All budget
1028
1103
  decisions compare `assembledTokens` against `ceiling`, never against
1029
1104
  `contextSize` directly.
1030
1105
 
1031
- **Pre-LLM enforce** (`hooks.budget.enforce`, in TurnExecutor before
1032
- the LLM call). Measures the assembled messages (using
1033
- `turns.context_tokens` from the prior turn when available,
1034
- `countTokens(messages)` as a first-turn estimate).
1035
-
1036
- - `assembledTokens ≤ ceiling` → return 200, proceed to LLM.
1037
- - `assembledTokens > ceiling` on the first turn of a loop → **Prompt
1038
- Demotion**: demote the incoming `prompt://N` entry to `visibility =
1039
- demoted`, re-materialize, re-check. If the retry fits, proceed.
1040
- - `assembledTokens > ceiling` on a non-first turn, or still over after
1041
- Prompt Demotion → return 413. AgentLoop exits the loop with 413.
1042
-
1043
- **Post-dispatch Turn Demotion** (`hooks.budget.postDispatch`, after
1044
- all tool dispatches complete). Re-materializes end-of-turn context
1045
- and re-checks. If still over the ceiling, flips every `run_views` row
1046
- for this turn from `visibility = visible` to `visibility = summarized`
1047
- (status preservedsee [schemes_status_visibility](#schemes_status_visibility))
1048
- and emits a 413 error via `hooks.error.log.emit` with the descriptive
1049
- body (what was demoted, the 50% rule for the next turn). The model
1050
- sees the `error://` entry next turn and adjusts.
1051
-
1052
- **Delta-from-actual prediction.** Post-dispatch uses
1053
- `predictNextPacket = lastContextTokens + Σ countTokens(body) for rows added this turn`,
1054
- not the conservative measureMessages estimator. Reason: a 60%+
1055
- divergence between the pre-call `<prompt tokenUsage>` (real API
1056
- prompt_tokens) and the post-check estimator made the model dismiss
1057
- the budget as janky and stop following demote rules. The two numbers
1058
- must live on the same scale.
1059
-
1060
- **Prior-turn-pressure fallback.** If post-dispatch finds nothing to
1061
- demote in the current turn but the packet still overflows, the
1062
- pressure is coming from prior-turn promotions the model never demoted
1063
- itself. Demotion widens to all currently-visible entries in the run
1064
- and the prompt is also demoted. Without this fallback, observed
1065
- behavior was strikes accumulating on runs whose base context had
1066
- drifted over ceiling through no fault of the current turn.
1106
+ **Pre-LLM grinder** (`hooks.turn.beforeDispatch.filter`, in
1107
+ TurnExecutor before the LLM call; budget is the canonical
1108
+ subscriber). A four-step ladder. Each step demotes a strictly smaller
1109
+ scope and rechecks. The first step that fits the ceiling proceeds to
1110
+ the LLM; if step 4 fires, AgentLoop exits the loop with 413.
1111
+
1112
+ 1. **Check budget.** Measure `assembledTokens` (using
1113
+ `turns.context_tokens` from the prior turn when available, the
1114
+ materialized packet estimate as a first-turn fallback). If
1115
+ `assembledTokens ceiling`, proceed to the LLM.
1116
+ 2. **Soft 413 previous-turn demotion.** Flip every `run_views`
1117
+ row where `turn = current_turn - 1 AND visibility = visible` to
1118
+ `summarized` (status preserved see
1119
+ [schemes_status_visibility](#schemes_status_visibility)). All
1120
+ schemes participate; no exemption for knowns / unknowns /
1121
+ files. Re-materialize, re-check.
1122
+ 3. **Soft 413 current-prompt demotion.** Flip the incoming
1123
+ `prompt://N` entry to `summarized`. Re-materialize, re-check.
1124
+ Step 3 exists because the prompt is stamped at `current_turn`,
1125
+ not the previous turn step 2's filter never sees it. Without
1126
+ step 3, an oversized first-turn prompt has no path to fit.
1127
+ 4. **Hard 413.** Emit a 413 `error://` entry via
1128
+ `hooks.error.log.emit` with the descriptive body (what was
1129
+ demoted across steps 2-3, the ceiling, the residual overflow).
1130
+ AgentLoop exits the loop with 413.
1131
+
1132
+ Steps 2 and 3 also emit 413 `error://` entries when they fire
1133
+ (distinct from step 4 in that the run keeps going). The model reads
1134
+ those next turn and learns what got auto-demoted. Status of the
1135
+ turn that proceeded after a soft 413 is unaffected.
1136
+
1137
+ **Trunks and forks are treated identically.** A forked run inherits
1138
+ the parent's `run_views` rows verbatim each entry keeps its
1139
+ original `turn`. There is no fork-event restamping. The grinder's
1140
+ `current_turn - 1` rule applies the same way in both cases. For
1141
+ the rule to point at meaningful inherited content on a fork's first
1142
+ dispatch, the child run inherits the parent's `next_turn` so turn
1143
+ numbering is absolute across the lineage; sibling forks share the
1144
+ same prior history at lower turn numbers and only diverge at
1145
+ fork-time.
1067
1146
 
1068
1147
  **LLM-reported context exceeded.** If the LLM rejects the request
1069
1148
  with a "context too long" error (detected via the regex in
@@ -1147,9 +1226,9 @@ on `get` also sets a project-level file constraint (operator privilege).
1147
1226
 
1148
1227
  | Method | Params |
1149
1228
  |--------|--------|
1150
- | `startRun` | `{ model, temperature?, persona?, contextLimit? }` |
1151
- | `ask` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noRepo?, noInteraction?, noWeb?, fork? }` |
1152
- | `act` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noRepo?, noInteraction?, noWeb?, fork? }` |
1229
+ | `startRun` | `{ model, temperature?, persona?, contextLimit?, yolo? }` |
1230
+ | `ask` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noRepo?, noInteraction?, noWeb?, noProposals?, yolo?, fork? }` |
1231
+ | `act` | `{ prompt, model, run?, temperature?, persona?, contextLimit?, noRepo?, noInteraction?, noWeb?, noProposals?, yolo?, fork? }` |
1153
1232
  | `run/resolve` | `{ run, resolution: { path, action, output? } }` |
1154
1233
  | `run/abort` | `{ run }` |
1155
1234
  | `run/rename` | `{ run, name }` |
@@ -1161,6 +1240,10 @@ on `get` also sets a project-level file constraint (operator privilege).
1161
1240
  be added explicitly by the client).
1162
1241
  `noInteraction` removes `ask_user` from the tool list.
1163
1242
  `noWeb` removes `search` from the tool list.
1243
+ `noProposals` removes `ask_user` / `env` / `sh` from the tool list
1244
+ (no proposals at all).
1245
+ `yolo` opts the run into server-side proposal auto-accept and
1246
+ in-process sh/env execution — see [yolo_mode](#yolo_mode).
1164
1247
 
1165
1248
  #### Streaming (see [streaming_entries](#streaming_entries))
1166
1249
 
@@ -1190,17 +1273,20 @@ connected clients). `stream/cancel` also handles stale 102 cleanup.
1190
1273
 
1191
1274
  #### Skills & Personas
1192
1275
 
1193
- | Method | Params |
1194
- |--------|--------|
1195
- | `skill/add` | `{ run, name }` |
1196
- | `skill/remove` | `{ run, name }` |
1197
- | `getSkills` | `{ run }` |
1198
- | `listSkills` | — |
1199
- | `persona/set` | `{ run, name?, text? }` |
1200
- | `listPersonas` | |
1201
-
1202
- Skills loaded from `RUMMY_HOME/skills/{name}.md`. Personas from
1203
- `RUMMY_HOME/personas/{name}.md`.
1276
+ Both attach to a run via the entry grammar.
1277
+
1278
+ - **Skills** model emits `<skill path="[path-or-url]"/>`.
1279
+ Handler walks local file/folder/`.zip` (via `yauzl-promise`) or
1280
+ fetches a URL. Single `.md` registers as `skill://<name>`
1281
+ (summarized); folder/zip registers root `index.md` summarized,
1282
+ rest archived; `foo/index.md` collapses to `skill://<name>/foo`.
1283
+ Re-emit overwrites. Authors link with absolute `skill://...` URIs.
1284
+ - **Personas** — `ask` / `act` / `startRun` accept `persona` as a
1285
+ run attribute. The persona plugin renders the persona body inside
1286
+ the system prompt (below tooldocs) on first turn; if no `persona`
1287
+ is passed, `AgentLoop.ensureRun` defaults to
1288
+ `src/plugins/persona/default.md`. 1:1 run:persona, immutable for
1289
+ the run's lifetime.
1204
1290
 
1205
1291
  ### Notifications {#notifications}
1206
1292
 
@@ -1378,18 +1464,116 @@ are universal — not a feature of any single tool.
1378
1464
 
1379
1465
  ---
1380
1466
 
1381
- ## Hedberg Editing Syntax {#hedberg}
1467
+ ## Edit Syntax
1468
+
1469
+ The model expresses entry writes through `<set path="..."><body></set>`.
1470
+ The body shape determines the operation. All shaped operations use a
1471
+ bash-heredoc-flavored marker family.
1472
+
1473
+ ### Marker Grammar
1474
+
1475
+ <<IDENT
1476
+ body content
1477
+ IDENT
1478
+
1479
+ Where `IDENT` matches `[A-Z][A-Za-z0-9_]*`. The leading keyword of
1480
+ `IDENT` selects the operation; any trailing alphanumeric suffix is
1481
+ opaque to operation routing and exists to disambiguate nested markers
1482
+ or avoid collisions when the body literally contains the bare keyword
1483
+ (same convention as bash heredoc `<<EOF1` vs `<<EOF`).
1484
+
1485
+ The opener `<<IDENT` must be preceded by start-of-body, whitespace,
1486
+ or `>` (so `vec<<SEARCH` mid-token does not false-trigger). The
1487
+ closer is bare `IDENT` with whitespace boundaries on both sides.
1488
+
1489
+ Newline-tolerant: the multi-line shape above and the single-line
1490
+ `<<IDENT body IDENT` form parse identically.
1491
+
1492
+ ### Distinct from Packet Rendering
1493
+
1494
+ The engine renders entry bodies in context using a different marker
1495
+ shape: `<<:::path...:::path` (see `plugins/helpers.js`). Edit syntax
1496
+ is the bare `<<IDENT` form; packet rendering keeps the `:::` sentinel.
1497
+ The two grammars are visibly distinct so model emissions and engine
1498
+ renderings can never be confused. A `<set>` body echoing the packet
1499
+ shape is NOT treated as edit syntax — it falls through to plain-body
1500
+ REPLACE with the markers preserved as literal content.
1501
+
1502
+ ### Operations
1503
+
1504
+ | IDENT prefix | Effect |
1505
+ |---|---|
1506
+ | `NEW` | Create the entry. Behaves identically to `REPLACE` on existing entries — named separately to align with model intent. |
1507
+ | `PREPEND` | Prepend body content to the existing entry. Creates the entry if it doesn't exist. |
1508
+ | `APPEND` | Append body content to the existing entry. Creates the entry if it doesn't exist. |
1509
+ | `REPLACE` | Replace the entire entry body with the marker content. Standalone (not preceded by `SEARCH`). |
1510
+ | `DELETE` | Remove a literal-matching region from the existing entry body. The marker content is the region to remove. |
1511
+ | `SEARCH` | Match a literal region in the existing entry body. Must be immediately followed by a `REPLACE` block; the pair is an in-place edit. |
1512
+
1513
+ ### SEARCH / REPLACE Pairs
1514
+
1515
+ Surgical in-place edits. `SEARCH` must be immediately followed by
1516
+ `REPLACE` (no intervening operation):
1517
+
1518
+ <set path="src/main.go"><<SEARCH
1519
+ old line
1520
+ SEARCH
1521
+ <<REPLACE
1522
+ new line
1523
+ REPLACE</set>
1524
+
1525
+ Multiple pairs in one `<set>` body apply in order against the
1526
+ progressively-edited body.
1527
+
1528
+ ### Suffix for Body Collisions
1529
+
1530
+ When the body content literally contains a marker keyword (`SEARCH`
1531
+ in prose, `<<` in code), the model appends a digit or alphanumeric
1532
+ suffix to the IDENT so the inner literal does not prematurely close
1533
+ the outer marker:
1534
+
1535
+ <set path="docs/grammar.md"><<DOC1
1536
+ The opener is <<SEARCH and the closer is bare SEARCH alone on
1537
+ a line. Use <<SEARCH1 ... SEARCH1 if your body contains literal
1538
+ SEARCH or <<SEARCH tokens.
1539
+ DOC1</set>
1540
+
1541
+ ### Errors
1542
+
1543
+ | Condition | Outcome |
1544
+ |---|---|
1545
+ | `SEARCH` content not found in current body | conflict (soft) |
1546
+ | `DELETE` content not found in current body | conflict (soft) |
1547
+ | Lone `SEARCH` (no following `REPLACE`) | parse error |
1548
+ | Unclosed marker (opener with no matching `IDENT` closer) | parse error |
1549
+ | Non-keyword `IDENT` (e.g. `<<EOF`, `<<DOC`) | routes to REPLACE — inner content becomes the new body |
1550
+ | `<set>` body with no `<<IDENT` markers at all | full-body REPLACE (tolerated; not demonstrated to models) |
1551
+
1552
+ ### Pattern Matching
1553
+
1554
+ The literal-match semantics used by `SEARCH` and `DELETE` are
1555
+ delegated to the Hedberg pattern library — see [hedberg](#hedberg).
1556
+ Matching is fuzzy on whitespace and indentation; an exact-byte match
1557
+ is not required.
1558
+
1559
+ ---
1560
+
1561
+ ## Hedberg Pattern Library {#hedberg}
1562
+
1563
+ The pattern library exposed to every plugin through `core.hooks.hedberg`.
1564
+ Used internally by the Edit Syntax (above) for `SEARCH` / `DELETE`
1565
+ matching and by `<get>` / `<rm>` for path globs.
1382
1566
 
1383
- The model picks its preferred edit format. The parser understands all of them:
1567
+ | Function | Purpose |
1568
+ |---|---|
1569
+ | `match(pattern, string)` | Full-string match — paths, equality. |
1570
+ | `search(pattern, string)` | Substring search — content filtering. |
1571
+ | `replace(text, search, replace, options)` | Patch application; fuzzy on whitespace and indentation. |
1572
+ | `generatePatch(path, oldBody, newBody)` | Unified-diff rendering for telemetry. |
1384
1573
 
1385
- 1. Git merge conflict: `<<<<<<< SEARCH ... ======= ... >>>>>>> REPLACE`
1386
- 2. Replace-only: `======= ... >>>>>>> REPLACE`
1387
- 3. Unified diff: `@@ -1,3 +1,3 @@` with `-`/`+` lines
1388
- 4. Sed syntax: `s/old/new/flags`
1389
- 5. Claude XML: `<old_text>old</old_text><new_text>new</new_text>`
1390
- 6. JSON body: `{"search": "old", "replace": "new"}` or `{search="old", replace="new"}`
1391
- 7. XML attributes: `<set search="old" replace="new"/>`
1392
- 8. Full replacement: anything else becomes the new content
1574
+ Pattern types: glob (picomatch-backed, with `**` cross-slash and
1575
+ `!()` negation), regex (`/pattern/flags`), xpath, jsonpath. Detection
1576
+ is by syntactic shape see `src/lib/hedberg/patterns.js`.
1393
1577
 
1394
1578
  ---
1395
1579
 
@@ -1477,10 +1661,9 @@ htmlparser2 dropped. Close current, open new, emit recovery warning.
1477
1661
  attributes on the open tag *and* body text inside the tag. If the
1478
1662
  canonical attribute is missing, the body silently fills it. The
1479
1663
  shape per tool:
1480
- - `set` — structured edit detection (merge-conflict markers, udiff,
1481
- Claude `<old_text>` XML, JSON `{search,replace}`, sed `s/.../.../`,
1482
- attribute-mode `search=`/`replace=`, body-as-search-when-`body=`
1483
- attr-set, plain write).
1664
+ - `set` — body parsed via `parseMarkerBody` (see "Edit Syntax"
1665
+ above): `<<:::IDENT...:::IDENT` markers route to `operations`
1666
+ list; bodies without markers are plain-body REPLACE.
1484
1667
  - `update` — body fills `body`, status defaults to 102 if absent.
1485
1668
  - `get` / `rm` — attr `path` or body fills target. Spread `a` so
1486
1669
  `line` / `limit` / `visibility` / future attrs reach the handler.
@@ -1642,7 +1825,7 @@ Full reference is `.env.example` — these are the load-bearing vars.
1642
1825
  | Var | Default | Purpose |
1643
1826
  |-----|---------|---------|
1644
1827
  | `PORT` | 3044 | WebSocket port |
1645
- | `RUMMY_HOME` | `~/.rummy` | Skills, personas, local config |
1828
+ | `RUMMY_HOME` | `~/.rummy` | Local config root. Used by telemetry; available for future per-user state. |
1646
1829
  | `RUMMY_DB_PATH` | `rummy.db` | SQLite path |
1647
1830
  | `RUMMY_MMAP_MB` | 0 | SQLite mmap hint (MB; 0 disables) |
1648
1831
  | `RUMMY_DEBUG` | false | Verbose logging |
@@ -1665,10 +1848,12 @@ Full reference is `.env.example` — these are the load-bearing vars.
1665
1848
  | `RUMMY_MIN_CYCLES` | 3 | Consecutive repetitions to trigger cycle detection |
1666
1849
  | `RUMMY_MAX_CYCLE_PERIOD` | 4 | Max cycle period checked by healer |
1667
1850
  | `RUMMY_RETENTION_DAYS` | 31 | Days of completed/aborted runs kept |
1668
- | `RUMMY_THINK` | 1 | Enable `<think>` tag reasoning |
1669
- | `RUMMY_TEMPERATURE` | 0.5 | Default LLM temperature |
1851
+ | `RUMMY_THINK` | 0 | Reasoning request flag forwarded to LLM provider |
1852
+ | `RUMMY_TEMPERATURE` | 0.1 | Default LLM temperature |
1670
1853
  | `RUMMY_RPC_TIMEOUT` | 30000 | RPC timeout (ms) |
1671
1854
  | `RUMMY_FETCH_TIMEOUT` | 300000 | LLM HTTP timeout (ms) |
1855
+ | `RUMMY_LLM_DEADLINE` | 600000 | LLM transient-retry deadline (ms). Used as the budget for `warmup` and `rate_limit` categories in `src/llm/retry.js#retryClassified`; gateway/server categories have shorter hardcoded deadlines (30s / 60s). |
1856
+ | `RUMMY_LLM_MAX_BACKOFF` | 30000 | Max single backoff between retry attempts (ms) for warmup/rate_limit categories. |
1672
1857
 
1673
1858
  **LLM providers** (plugin-scoped; a provider with no config is inert):
1674
1859
 
@@ -1699,9 +1884,11 @@ local `node_modules` then global).
1699
1884
 
1700
1885
  | Var | Purpose |
1701
1886
  |-----|---------|
1702
- | `RUMMY_SEARCH` | `brave` \| `searxng` |
1703
- | `BRAVE_API_KEY` | Brave Search API key |
1704
- | `RUMMY_SEARXNG_URL` | SearXNG instance URL |
1887
+ | `RUMMY_WEB_SEARXNG_URL` | SearXNG instance URL (SearXNG federates Brave / DuckDuckGo / Wikipedia / etc. upstream and normalizes the responses) |
1888
+ | `RUMMY_WEB_FETCH_TIMEOUT` | Playwright `page.goto` timeout (ms) |
1889
+ | `RUMMY_WEB_PLAYWRIGHT_WS` | Optional CDP endpoint for shared chromium |
1890
+ | `RUMMY_WEB_NO_SANDBOX` | `1` to drop chromium's user-namespace sandbox |
1891
+ | `RUMMY_WEB_CHROMIUM_HEAP_MB` | Cap chromium's V8 heap (MB) |
1705
1892
 
1706
1893
  **Testing:**
1707
1894