@possumtech/rummy 0.5.0 → 2.0.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.
- package/.env.example +42 -5
- package/PLUGINS.md +389 -194
- package/README.md +25 -8
- package/SPEC.md +934 -373
- package/bin/demo.js +166 -0
- package/bin/rummy.js +9 -3
- package/biome/no-fallbacks.grit +50 -0
- package/lang/en.json +2 -2
- package/migrations/001_initial_schema.sql +88 -37
- package/package.json +13 -11
- package/scriptify/ask_run.js +77 -0
- package/service.js +50 -9
- package/src/agent/AgentLoop.js +476 -335
- package/src/agent/ContextAssembler.js +4 -4
- package/src/agent/Entries.js +676 -0
- package/src/agent/ProjectAgent.js +30 -18
- package/src/agent/TurnExecutor.js +232 -421
- package/src/agent/XmlParser.js +99 -33
- package/src/agent/budget.js +56 -0
- package/src/agent/errors.js +22 -0
- package/src/agent/httpStatus.js +39 -0
- package/src/agent/known_checks.sql +8 -4
- package/src/agent/known_queries.sql +9 -13
- package/src/agent/known_store.sql +280 -125
- package/src/agent/materializeContext.js +104 -0
- package/src/agent/runs.sql +29 -7
- package/src/agent/schemes.sql +14 -3
- package/src/agent/tokens.js +6 -0
- package/src/agent/turns.sql +9 -9
- package/src/hooks/HookRegistry.js +6 -5
- package/src/hooks/Hooks.js +44 -3
- package/src/hooks/PluginContext.js +29 -21
- package/src/{server → hooks}/RpcRegistry.js +2 -1
- package/src/hooks/RummyContext.js +139 -35
- package/src/hooks/ToolRegistry.js +21 -16
- package/src/llm/LlmProvider.js +66 -89
- package/src/llm/errors.js +21 -0
- package/src/llm/retry.js +63 -0
- package/src/plugins/ask_user/README.md +1 -1
- package/src/plugins/ask_user/ask_user.js +37 -12
- package/src/plugins/ask_user/ask_userDoc.js +2 -25
- package/src/plugins/ask_user/ask_userDoc.md +10 -0
- package/src/plugins/budget/README.md +27 -25
- package/src/plugins/budget/budget.js +306 -88
- package/src/plugins/cp/README.md +2 -2
- package/src/plugins/cp/cp.js +29 -11
- package/src/plugins/cp/cpDoc.js +2 -15
- package/src/plugins/cp/cpDoc.md +7 -0
- package/src/plugins/engine/README.md +2 -2
- package/src/plugins/engine/engine.sql +4 -4
- package/src/plugins/engine/turn_context.sql +10 -10
- package/src/plugins/env/README.md +20 -5
- package/src/plugins/env/env.js +45 -6
- package/src/plugins/env/envDoc.js +2 -23
- package/src/plugins/env/envDoc.md +13 -0
- package/src/plugins/error/README.md +16 -0
- package/src/plugins/error/error.js +151 -0
- package/src/plugins/file/README.md +6 -6
- package/src/plugins/file/file.js +15 -2
- package/src/plugins/get/README.md +1 -1
- package/src/plugins/get/get.js +103 -48
- package/src/plugins/get/getDoc.js +2 -32
- package/src/plugins/get/getDoc.md +36 -0
- package/src/plugins/hedberg/README.md +1 -2
- package/src/plugins/hedberg/hedberg.js +8 -4
- package/src/plugins/hedberg/matcher.js +16 -17
- package/src/plugins/hedberg/normalize.js +0 -48
- package/src/plugins/helpers.js +42 -2
- package/src/plugins/index.js +146 -123
- package/src/plugins/instructions/README.md +35 -9
- package/src/plugins/instructions/instructions.js +244 -9
- package/src/plugins/instructions/instructions.md +33 -0
- package/src/plugins/instructions/instructions_104.md +7 -0
- package/src/plugins/instructions/instructions_105.md +38 -0
- package/src/plugins/instructions/instructions_106.md +21 -0
- package/src/plugins/instructions/instructions_107.md +10 -0
- package/src/plugins/instructions/instructions_108.md +0 -0
- package/src/plugins/instructions/protocol.js +12 -0
- package/src/plugins/known/README.md +2 -2
- package/src/plugins/known/known.js +68 -36
- package/src/plugins/known/knownDoc.js +2 -17
- package/src/plugins/known/knownDoc.md +8 -0
- package/src/plugins/log/README.md +48 -0
- package/src/plugins/log/log.js +129 -0
- package/src/plugins/mv/README.md +2 -2
- package/src/plugins/mv/mv.js +55 -22
- package/src/plugins/mv/mvDoc.js +2 -18
- package/src/plugins/mv/mvDoc.md +10 -0
- package/src/plugins/ollama/README.md +15 -0
- package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
- package/src/plugins/openai/README.md +17 -0
- package/src/plugins/openai/openai.js +120 -0
- package/src/plugins/openrouter/README.md +27 -0
- package/src/plugins/openrouter/openrouter.js +121 -0
- package/src/plugins/persona/README.md +20 -0
- package/src/plugins/persona/persona.js +9 -16
- package/src/plugins/policy/README.md +21 -0
- package/src/plugins/policy/policy.js +29 -14
- package/src/plugins/prompt/README.md +1 -1
- package/src/plugins/prompt/prompt.js +64 -16
- package/src/plugins/rm/README.md +1 -1
- package/src/plugins/rm/rm.js +56 -12
- package/src/plugins/rm/rmDoc.js +2 -20
- package/src/plugins/rm/rmDoc.md +13 -0
- package/src/plugins/rpc/README.md +2 -2
- package/src/plugins/rpc/rpc.js +525 -296
- package/src/plugins/set/README.md +1 -1
- package/src/plugins/set/set.js +318 -75
- package/src/plugins/set/setDoc.js +2 -35
- package/src/plugins/set/setDoc.md +22 -0
- package/src/plugins/sh/README.md +28 -5
- package/src/plugins/sh/sh.js +50 -6
- package/src/plugins/sh/shDoc.js +2 -23
- package/src/plugins/sh/shDoc.md +13 -0
- package/src/plugins/skill/README.md +23 -0
- package/src/plugins/skill/skill.js +14 -18
- package/src/plugins/stream/README.md +101 -0
- package/src/plugins/stream/stream.js +290 -0
- package/src/plugins/telemetry/README.md +1 -1
- package/src/plugins/telemetry/telemetry.js +129 -80
- package/src/plugins/think/README.md +1 -1
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +2 -15
- package/src/plugins/think/thinkDoc.md +7 -0
- package/src/plugins/unknown/README.md +3 -3
- package/src/plugins/unknown/unknown.js +47 -19
- package/src/plugins/unknown/unknownDoc.js +2 -21
- package/src/plugins/unknown/unknownDoc.md +11 -0
- package/src/plugins/update/README.md +1 -1
- package/src/plugins/update/update.js +83 -5
- package/src/plugins/update/updateDoc.js +2 -30
- package/src/plugins/update/updateDoc.md +8 -0
- package/src/plugins/xai/README.md +23 -0
- package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
- package/src/plugins/yolo/yolo.js +192 -0
- package/src/server/ClientConnection.js +64 -37
- package/src/server/SocketServer.js +23 -10
- package/src/server/protocol.js +11 -0
- package/src/sql/v_model_context.sql +27 -31
- package/src/sql/v_run_log.sql +9 -14
- package/EXCEPTIONS.md +0 -46
- package/FIDELITY_CONTRACT.md +0 -172
- package/src/agent/KnownStore.js +0 -337
- package/src/agent/ResponseHealer.js +0 -241
- package/src/llm/OpenAiClient.js +0 -100
- package/src/llm/OpenRouterClient.js +0 -100
- package/src/plugins/budget/recovery.js +0 -47
- package/src/plugins/instructions/preamble.md +0 -45
- package/src/plugins/performed/README.md +0 -15
- package/src/plugins/performed/performed.js +0 -45
- package/src/plugins/previous/README.md +0 -16
- package/src/plugins/previous/previous.js +0 -56
- package/src/plugins/progress/README.md +0 -16
- package/src/plugins/progress/progress.js +0 -43
- package/src/plugins/summarize/README.md +0 -19
- package/src/plugins/summarize/summarize.js +0 -32
- package/src/plugins/summarize/summarizeDoc.js +0 -27
|
@@ -3,30 +3,26 @@ CREATE VIEW IF NOT EXISTS v_model_context AS
|
|
|
3
3
|
WITH
|
|
4
4
|
visible AS (
|
|
5
5
|
SELECT
|
|
6
|
-
|
|
7
|
-
,
|
|
8
|
-
,
|
|
9
|
-
,
|
|
10
|
-
,
|
|
11
|
-
,
|
|
12
|
-
,
|
|
13
|
-
,
|
|
14
|
-
,
|
|
15
|
-
,
|
|
16
|
-
,
|
|
6
|
+
rv.run_id
|
|
7
|
+
, rv.id
|
|
8
|
+
, e.path
|
|
9
|
+
, e.body
|
|
10
|
+
, e.scheme
|
|
11
|
+
, rv.state
|
|
12
|
+
, rv.outcome
|
|
13
|
+
, rv.visibility
|
|
14
|
+
, rv.turn
|
|
15
|
+
, rv.updated_at
|
|
16
|
+
, e.attributes
|
|
17
17
|
, COALESCE(s.category, 'logging') AS category
|
|
18
18
|
, CASE
|
|
19
|
-
|
|
20
|
-
WHEN ke.fidelity = 'archived' THEN NULL
|
|
21
|
-
-- 202 Accepted (proposed) hidden until resolved
|
|
22
|
-
WHEN ke.status = 202 THEN NULL
|
|
23
|
-
-- Audit schemes (model_visible = 0) hidden
|
|
19
|
+
WHEN rv.visibility = 'archived' THEN NULL
|
|
24
20
|
WHEN s.model_visible = 0 THEN NULL
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
JOIN schemes AS s ON s.name = COALESCE(
|
|
21
|
+
ELSE rv.visibility
|
|
22
|
+
END AS effective_visibility
|
|
23
|
+
FROM run_views AS rv
|
|
24
|
+
JOIN entries AS e ON e.id = rv.entry_id
|
|
25
|
+
JOIN schemes AS s ON s.name = COALESCE(e.scheme, 'file')
|
|
30
26
|
),
|
|
31
27
|
projected AS (
|
|
32
28
|
SELECT
|
|
@@ -34,27 +30,28 @@ projected AS (
|
|
|
34
30
|
, id
|
|
35
31
|
, path
|
|
36
32
|
, scheme
|
|
37
|
-
,
|
|
38
|
-
,
|
|
33
|
+
, state
|
|
34
|
+
, outcome
|
|
35
|
+
, effective_visibility AS visibility
|
|
39
36
|
, turn
|
|
40
37
|
, updated_at
|
|
41
38
|
, attributes
|
|
42
39
|
-- Category comes from schemes table — plugins declare it via registerScheme().
|
|
43
40
|
, category
|
|
44
|
-
, tokens
|
|
45
41
|
, CASE
|
|
46
|
-
WHEN
|
|
42
|
+
WHEN effective_visibility IN ('visible', 'summarized') THEN body
|
|
47
43
|
ELSE ''
|
|
48
44
|
END AS body
|
|
49
45
|
FROM visible
|
|
50
|
-
WHERE
|
|
46
|
+
WHERE effective_visibility IS NOT NULL
|
|
51
47
|
)
|
|
52
48
|
SELECT
|
|
53
49
|
run_id
|
|
54
50
|
, path
|
|
55
51
|
, scheme
|
|
56
|
-
,
|
|
57
|
-
,
|
|
52
|
+
, visibility
|
|
53
|
+
, state
|
|
54
|
+
, outcome
|
|
58
55
|
, body
|
|
59
56
|
, attributes
|
|
60
57
|
, category
|
|
@@ -71,13 +68,12 @@ SELECT
|
|
|
71
68
|
ELSE 5
|
|
72
69
|
END
|
|
73
70
|
, CASE scheme WHEN 'skill' THEN 0 ELSE 1 END
|
|
74
|
-
, CASE
|
|
75
|
-
WHEN '
|
|
71
|
+
, CASE visibility
|
|
72
|
+
WHEN 'summarized' THEN 0
|
|
76
73
|
ELSE 1
|
|
77
74
|
END
|
|
78
75
|
, turn
|
|
79
76
|
, updated_at
|
|
80
77
|
, id
|
|
81
78
|
) AS ordinal
|
|
82
|
-
, countTokens(body) AS tokens
|
|
83
79
|
FROM projected;
|
package/src/sql/v_run_log.sql
CHANGED
|
@@ -4,20 +4,15 @@ SELECT
|
|
|
4
4
|
ke.run_id
|
|
5
5
|
, ke.path
|
|
6
6
|
, ke.body
|
|
7
|
-
, ke.
|
|
8
|
-
,
|
|
9
|
-
,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
, json_extract(ke.attributes, '$.path')
|
|
13
|
-
, json_extract(ke.attributes, '$.question')
|
|
14
|
-
, ''
|
|
15
|
-
) AS target
|
|
7
|
+
, ke.state
|
|
8
|
+
, ke.outcome
|
|
9
|
+
, ke.turn
|
|
10
|
+
, ke.scheme AS tool
|
|
11
|
+
, ke.attributes
|
|
16
12
|
FROM known_entries AS ke
|
|
17
|
-
JOIN schemes AS s ON s.name =
|
|
13
|
+
JOIN schemes AS s ON s.name = ke.scheme
|
|
18
14
|
WHERE
|
|
19
|
-
|
|
20
|
-
AND ke.
|
|
21
|
-
AND
|
|
22
|
-
AND ke.scheme NOT IN ('system', 'reasoning', 'model', 'content')
|
|
15
|
+
s.category IN ('logging', 'prompt', 'unknown')
|
|
16
|
+
AND ke.state != 'proposed'
|
|
17
|
+
AND ke.scheme != 'run'
|
|
23
18
|
ORDER BY ke.id;
|
package/EXCEPTIONS.md
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# EXCEPTIONS.md — Documented Backbone Responsibilities
|
|
2
|
-
|
|
3
|
-
Operations that bypass the plugin protocol. Each must justify WHY it
|
|
4
|
-
can't go through the standard tool handler path. If the justification
|
|
5
|
-
is weak, the exception should be eliminated.
|
|
6
|
-
|
|
7
|
-
## Resolved
|
|
8
|
-
|
|
9
|
-
### File.setConstraint / File.dropConstraint
|
|
10
|
-
|
|
11
|
-
**What:** Direct DB writes to `file_constraints` table.
|
|
12
|
-
**Justification:** File constraints are project-level config — they
|
|
13
|
-
define which files a project cares about. This is backbone, not tool
|
|
14
|
-
dispatch. Entry promotion/demotion that follows constraints now goes
|
|
15
|
-
through the standard tool handler chain via `dispatchTool`.
|
|
16
|
-
**Boundary documented:** SPEC.md §2.3.
|
|
17
|
-
|
|
18
|
-
## Currently Identified
|
|
19
|
-
|
|
20
|
-
### 1. TurnExecutor#record — tool-specific handling
|
|
21
|
-
|
|
22
|
-
**What:** `known`, `unknown`, `summarize`, `update` have special-case
|
|
23
|
-
code in `#record` (dedup, slug paths, lifecycle classification).
|
|
24
|
-
**Bypasses:** These tools don't go through the same dispatch path as
|
|
25
|
-
`get`, `set`, `rm` etc.
|
|
26
|
-
**Justification:** Lifecycle signals (`summarize`, `update`) are state
|
|
27
|
-
declarations, not tool operations — they always dispatch and cannot be
|
|
28
|
-
409'd. `known` and `unknown` generate their own paths from body content
|
|
29
|
-
(slug paths). The classification is a fundamental architectural split
|
|
30
|
-
(lifecycle vs action), not a protocol violation.
|
|
31
|
-
|
|
32
|
-
### 2. Token math — multiple measurement points
|
|
33
|
-
|
|
34
|
-
**What:** `known_entries.tokens`, `turn_context.tokens`,
|
|
35
|
-
`turns.context_tokens`, `countTokens()` estimates.
|
|
36
|
-
**Bypasses:** No single function call, but a strict rule.
|
|
37
|
-
**Justification:** Each serves a different purpose. `known_entries.tokens`
|
|
38
|
-
is display-only (model sees entry sizes in `<knowns>`). `turn_context.tokens`
|
|
39
|
-
is per-turn snapshot. `turns.context_tokens` is assembled ground truth for
|
|
40
|
-
budget. The rule: budget decisions use ONLY assembled message tokens.
|
|
41
|
-
DB tokens are NEVER used for budget. Documented in PLUGINS.md §7.5.
|
|
42
|
-
|
|
43
|
-
---
|
|
44
|
-
|
|
45
|
-
*This file should shrink over time. Every entry is a debt to be paid
|
|
46
|
-
or a boundary to be justified.*
|
package/FIDELITY_CONTRACT.md
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
# Fidelity Contract — Observed State vs Intended
|
|
2
|
-
|
|
3
|
-
## Observed Behavior (traced from test/mab/results/2026-04-14T15-13-55-950Z/last_run.txt, turn 24)
|
|
4
|
-
|
|
5
|
-
### Flow
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
Model emits tool
|
|
9
|
-
↓
|
|
10
|
-
Tool handler stores body in known_entries.body (raw, as model wrote it)
|
|
11
|
-
↓
|
|
12
|
-
Next turn: TurnExecutor materializes context
|
|
13
|
-
↓
|
|
14
|
-
For each row: hooks.tools.view(scheme, entry) → plugin's view hook returns projected body
|
|
15
|
-
↓
|
|
16
|
-
Projected body stored in turn_context.body with fidelity-projected token count
|
|
17
|
-
↓
|
|
18
|
-
Assembly phase: section renderers (knowns, unknowns, previous, performed) pull from ctx.rows (which has projected body) and render tags
|
|
19
|
-
↓
|
|
20
|
-
Model sees the assembled <knowns>, <previous>, etc. sections in the system prompt
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
### Fidelity Values (from code)
|
|
24
|
-
|
|
25
|
-
- **full**: fully shown
|
|
26
|
-
- **summary**: "compact" shown — but WHAT "compact" means varies per plugin
|
|
27
|
-
- **archive**: excluded by `v_model_context` SQL before reaching any renderer (clean)
|
|
28
|
-
|
|
29
|
-
## Three Breaks in the Intended Contract
|
|
30
|
-
|
|
31
|
-
### Break 1 — Plugins disagree on what summary means
|
|
32
|
-
|
|
33
|
-
Every plugin that registers view hooks decides what body to project per fidelity. Observed:
|
|
34
|
-
|
|
35
|
-
| Plugin | full() | summary() |
|
|
36
|
-
|--------|--------|-----------|
|
|
37
|
-
| known | `# known ${path}\n${body}` | **same as full** (wrong) |
|
|
38
|
-
| prompt | `body` | **500-char truncation + marker** (correct) |
|
|
39
|
-
| budget | `body` | `body` (ok — budget is naturally short) |
|
|
40
|
-
| skill | `body` | `body` (inherited default) |
|
|
41
|
-
| unknown | varies — needs audit | needs audit |
|
|
42
|
-
| others | needs audit | needs audit |
|
|
43
|
-
|
|
44
|
-
The `known` plugin's `summary()` returning the full body is a direct contract violation. The summary view should return a compact representation of the entry, not the same full body.
|
|
45
|
-
|
|
46
|
-
### Break 2 — Renderers re-apply fidelity logic
|
|
47
|
-
|
|
48
|
-
Two renderers currently re-check entry fidelity and override the plugin's projection:
|
|
49
|
-
|
|
50
|
-
**`known.js` `renderKnownTag`** (lines 111-115):
|
|
51
|
-
```js
|
|
52
|
-
if (entry.fidelity === "archive") return "";
|
|
53
|
-
if (entry.fidelity === "summary") {
|
|
54
|
-
return `<${tag} path="${entry.path}"...${summary}${fidelity}${tokens}${flag}/>`;
|
|
55
|
-
}
|
|
56
|
-
return `<${tag} path="${entry.path}"...${summary}${fidelity}${tokens}${flag}>${entry.body}</${tag}>`;
|
|
57
|
-
```
|
|
58
|
-
|
|
59
|
-
This ignores entry.body at summary fidelity and renders self-closing. It's a workaround for known.summary() returning the wrong content. Belt over broken suspenders.
|
|
60
|
-
|
|
61
|
-
**`previous.js` `renderToolTag`** (my edit this session):
|
|
62
|
-
```js
|
|
63
|
-
if (entry.fidelity === "full") {
|
|
64
|
-
return `<${entry.scheme} ${attrs}>${body}</${entry.scheme}>`;
|
|
65
|
-
}
|
|
66
|
-
// summary: self-closing with summary attr
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
I added this fidelity re-check when I should have trusted the plugin's projected body. Same mistake as known, added today.
|
|
70
|
-
|
|
71
|
-
### Break 3 — Model writes scheme headers into body
|
|
72
|
-
|
|
73
|
-
Every known/update/unknown entry in the DB has a body that starts with `# known known://path\n`, `# update\n`, or `# unknown\n`. The model writes this because the examples in the system prompt render tags with the body prefixed by `# ${scheme} ${path}\n`.
|
|
74
|
-
|
|
75
|
-
Then the plugin's `full()` hook prepends ANOTHER `# ${scheme} ${path}\n` when projecting. Result: duplicate headers in the rendered output.
|
|
76
|
-
|
|
77
|
-
Observed in turn 16 update body: `"# update\n# update\nDocuments 20-22 indexed and archived."`
|
|
78
|
-
|
|
79
|
-
And in unknown paths: the slug-generation for pathless unknowns takes the body including the `# unknown\n` prefix, resulting in URL-encoded paths like:
|
|
80
|
-
```
|
|
81
|
-
unknown://%23%20unknown%0ADocument%2023%20is%20missing%20from%20the%20prompt.
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
## The Intended Contract
|
|
85
|
-
|
|
86
|
-
Based on the user's stated philosophy ("surface problems, don't solve them; plugin decides, renderer renders"):
|
|
87
|
-
|
|
88
|
-
### Layer 1 — Plugin decides per fidelity
|
|
89
|
-
|
|
90
|
-
Each plugin registers view hooks that return the body content for each fidelity value:
|
|
91
|
-
|
|
92
|
-
```js
|
|
93
|
-
core.hooks.tools.onView("known", (entry) => entry.body, "full");
|
|
94
|
-
core.hooks.tools.onView("known", (entry) => "", "summary");
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
At archive, no view hook is called (v_model_context excludes them).
|
|
98
|
-
|
|
99
|
-
### Layer 2 — Renderer shows the projected body
|
|
100
|
-
|
|
101
|
-
Renderers take the projected body from `ctx.rows[].body`:
|
|
102
|
-
- If non-empty, wrap in tag with body
|
|
103
|
-
- If empty, render self-closing tag
|
|
104
|
-
|
|
105
|
-
Renderers do NOT re-check entry.fidelity. They trust the plugin's projection.
|
|
106
|
-
|
|
107
|
-
### Layer 3 — Tag attributes always present
|
|
108
|
-
|
|
109
|
-
Tag attributes visible in both full and summary rendering:
|
|
110
|
-
- `path` — always
|
|
111
|
-
- `summary` — if present in entry.attributes.summary
|
|
112
|
-
- `turn` — if source_turn is set
|
|
113
|
-
- `status` — if status is set
|
|
114
|
-
- `fidelity` — always (the value itself)
|
|
115
|
-
- `tokens` — always (full-cost value, unchanged by fidelity per `set_fidelity` SQL)
|
|
116
|
-
|
|
117
|
-
### Per-plugin view decisions (revised)
|
|
118
|
-
|
|
119
|
-
| Plugin | Category | Full body | Summary body | Notes |
|
|
120
|
-
|--------|----------|-----------|--------------|-------|
|
|
121
|
-
| known | data | `entry.body` (no `# known` prefix) | `""` | Tag's summary attr carries the keywords |
|
|
122
|
-
| unknown | unknown | `entry.body` | `""` | Same pattern as known/skill — summary attr carries the label |
|
|
123
|
-
| prompt | prompt | `entry.body` | 500-char truncation with `[truncated...]` | Current behavior is correct |
|
|
124
|
-
| budget | logging | `entry.body` | `entry.body` | Feedback signal — always full |
|
|
125
|
-
| update | logging | `entry.body` | `entry.body` | Already 80-char capped |
|
|
126
|
-
| summarize | logging | `entry.body` | `entry.body` | Already 80-char capped |
|
|
127
|
-
| get | logging | result body | `""` | Just the action tag at summary |
|
|
128
|
-
| set, rm, cp, mv | logging | result body | `""` | Just the action tag at summary |
|
|
129
|
-
| env, sh | logging | output | `""` | Just the action tag at summary |
|
|
130
|
-
| search | logging | results | `""` | Just the action tag at summary |
|
|
131
|
-
| skill | data | `entry.body` | `""` | Same as known |
|
|
132
|
-
| file | data | `entry.body` | `""` | Same as known |
|
|
133
|
-
| http, https | data | — | — | **Move to rummy.web plugin** — not in core |
|
|
134
|
-
|
|
135
|
-
## The Body-Header Problem
|
|
136
|
-
|
|
137
|
-
Separate from fidelity: the model writes `# scheme path` into the body because examples show that shape. Plugin view hooks then prepend another header.
|
|
138
|
-
|
|
139
|
-
**Rule**: `# scheme` prefix belongs only in **logging** scheme outputs (tool execution results where the prefix identifies the log entry type). Non-logging schemes (known, unknown, prompt, data entries) should have no body prefix — tag attributes identify the entry.
|
|
140
|
-
|
|
141
|
-
**What to remove**:
|
|
142
|
-
- `known.js` `full()`: remove `# known ${entry.path}\n` prefix — just return `entry.body`
|
|
143
|
-
- `unknown.js` `full()`: remove any `# unknown\n` prefix
|
|
144
|
-
- Tooldoc examples for known/unknown that show bodies starting with `# scheme path` — remove so model stops copying
|
|
145
|
-
|
|
146
|
-
**What to keep**:
|
|
147
|
-
- Logging plugins (update, summarize, budget, get, set, etc.) may keep `# scheme` prefixes if present — they're describing tool execution results.
|
|
148
|
-
|
|
149
|
-
## Test Plan
|
|
150
|
-
|
|
151
|
-
To enforce the contract:
|
|
152
|
-
|
|
153
|
-
1. **Per-plugin unit tests**: Each plugin with fidelity-sensitive views tests `full(entry)` and `summary(entry)` return the expected content.
|
|
154
|
-
2. **Renderer tests**: Each section renderer (knowns, previous, performed, unknowns) tests that it trusts `entry.body` without re-checking fidelity.
|
|
155
|
-
3. **Integration test**: Load a DB with entries at each fidelity, assemble context, verify:
|
|
156
|
-
- Archive entries absent from any section
|
|
157
|
-
- Summary entries visible as compact tags
|
|
158
|
-
- Full entries visible with body
|
|
159
|
-
- No double headers in bodies
|
|
160
|
-
4. **Contract lint**: Grep for `entry.fidelity ===` in renderer files — should have zero matches.
|
|
161
|
-
|
|
162
|
-
## Deliverable Order
|
|
163
|
-
|
|
164
|
-
Before touching code, this document should be reviewed. Once aligned, the fix order would be:
|
|
165
|
-
|
|
166
|
-
1. Fix plugin view hooks to return correct body per fidelity
|
|
167
|
-
2. Remove fidelity re-checks from renderers
|
|
168
|
-
3. Remove the `# scheme path` header prepending (plugin-side) and examples (tooldoc-side)
|
|
169
|
-
4. Write tests per the plan above
|
|
170
|
-
5. Regenerate a sample context packet to confirm clean output
|
|
171
|
-
|
|
172
|
-
No silent interventions. No belt-and-suspenders logic. Plugin projects, renderer renders, model sees honest representation.
|
package/src/agent/KnownStore.js
DELETED
|
@@ -1,337 +0,0 @@
|
|
|
1
|
-
import slugify from "../sql/functions/slugify.js";
|
|
2
|
-
|
|
3
|
-
export default class KnownStore {
|
|
4
|
-
#db;
|
|
5
|
-
#onChanged;
|
|
6
|
-
#schemes = new Map();
|
|
7
|
-
#seq = 0;
|
|
8
|
-
#pendingResolutions = new Map();
|
|
9
|
-
|
|
10
|
-
constructor(db, { onChanged } = {}) {
|
|
11
|
-
this.#db = db;
|
|
12
|
-
this.#onChanged = onChanged || null;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
async loadSchemes(db) {
|
|
16
|
-
const rows = await (db || this.#db).get_all_schemes.all();
|
|
17
|
-
this.#schemes.clear();
|
|
18
|
-
for (const row of rows) {
|
|
19
|
-
this.#schemes.set(row.name, row);
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
#emitChanged(runId, path, changeType) {
|
|
24
|
-
if (this.#onChanged) this.#onChanged({ runId, path, changeType });
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
static scheme(path) {
|
|
28
|
-
const idx = path.indexOf("://");
|
|
29
|
-
return idx > 0 ? path.slice(0, idx) : null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
static normalizePath(path) {
|
|
33
|
-
if (!path?.includes("://")) return path;
|
|
34
|
-
const sep = path.indexOf("://");
|
|
35
|
-
const scheme = path.slice(0, sep).toLowerCase();
|
|
36
|
-
const rest = path.slice(sep + 3);
|
|
37
|
-
try {
|
|
38
|
-
// Decode first (idempotent), then encode — but preserve slashes
|
|
39
|
-
const decoded = decodeURIComponent(rest);
|
|
40
|
-
return `${scheme}://${decoded.split("/").map(encodeURIComponent).join("/")}`;
|
|
41
|
-
} catch {
|
|
42
|
-
return `${scheme}://${rest.split("/").map(encodeURIComponent).join("/")}`;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async nextTurn(runId) {
|
|
47
|
-
const row = await this.#db.next_turn.get({ run_id: runId });
|
|
48
|
-
return row.turn;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async dedup(runId, scheme, target, turn) {
|
|
52
|
-
const encodedTarget = encodeURIComponent(target);
|
|
53
|
-
const turnPrefix = turn ? `turn_${turn}/` : "";
|
|
54
|
-
const candidate = `${scheme}://${turnPrefix}${encodedTarget}`;
|
|
55
|
-
const existing = await this.#db.get_entry_body.get({
|
|
56
|
-
run_id: runId,
|
|
57
|
-
path: candidate,
|
|
58
|
-
});
|
|
59
|
-
if (!existing) return candidate;
|
|
60
|
-
return `${candidate}_${++this.#seq}`;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
async slugPath(runId, scheme, content, summary) {
|
|
64
|
-
const source = summary || content || "";
|
|
65
|
-
const base = slugify(source);
|
|
66
|
-
const prefix = `${scheme}://`;
|
|
67
|
-
|
|
68
|
-
if (!base) return `${prefix}${++this.#seq}`;
|
|
69
|
-
|
|
70
|
-
const candidate = `${prefix}${base}`;
|
|
71
|
-
const existing = await this.#db.get_entry_body.get({
|
|
72
|
-
run_id: runId,
|
|
73
|
-
path: candidate,
|
|
74
|
-
});
|
|
75
|
-
if (!existing) return candidate;
|
|
76
|
-
|
|
77
|
-
return `${prefix}${base}_${++this.#seq}`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async upsert(
|
|
81
|
-
runId,
|
|
82
|
-
turn,
|
|
83
|
-
path,
|
|
84
|
-
body,
|
|
85
|
-
status,
|
|
86
|
-
{
|
|
87
|
-
fidelity = "promoted",
|
|
88
|
-
attributes = null,
|
|
89
|
-
hash = null,
|
|
90
|
-
updatedAt = null,
|
|
91
|
-
loopId = null,
|
|
92
|
-
} = {},
|
|
93
|
-
) {
|
|
94
|
-
const normalized = KnownStore.normalizePath(path);
|
|
95
|
-
await this.#db.upsert_known_entry.run({
|
|
96
|
-
run_id: runId,
|
|
97
|
-
loop_id: loopId,
|
|
98
|
-
turn,
|
|
99
|
-
path: normalized,
|
|
100
|
-
body,
|
|
101
|
-
status,
|
|
102
|
-
fidelity,
|
|
103
|
-
hash,
|
|
104
|
-
attributes: attributes ? JSON.stringify(attributes) : null,
|
|
105
|
-
updated_at: updatedAt,
|
|
106
|
-
});
|
|
107
|
-
this.#emitChanged(runId, normalized, "upsert");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async promote(runId, path, turn) {
|
|
111
|
-
const normalized = KnownStore.normalizePath(path);
|
|
112
|
-
await this.#db.promote_path.run({
|
|
113
|
-
run_id: runId,
|
|
114
|
-
path: normalized,
|
|
115
|
-
turn,
|
|
116
|
-
});
|
|
117
|
-
this.#emitChanged(runId, normalized, "promote");
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
async setFileFidelity(runId, pattern, fidelity) {
|
|
121
|
-
const result = await this.#db.set_file_fidelity.run({
|
|
122
|
-
run_id: runId,
|
|
123
|
-
pattern,
|
|
124
|
-
fidelity,
|
|
125
|
-
});
|
|
126
|
-
if (result.changes === 0) {
|
|
127
|
-
await this.upsert(runId, 0, pattern, "", 200, { fidelity });
|
|
128
|
-
}
|
|
129
|
-
this.#emitChanged(runId, pattern, "fidelity");
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
async setFidelity(runId, path, fidelity) {
|
|
133
|
-
const normalized = KnownStore.normalizePath(path);
|
|
134
|
-
await this.#db.set_fidelity.run({
|
|
135
|
-
run_id: runId,
|
|
136
|
-
path: normalized,
|
|
137
|
-
fidelity,
|
|
138
|
-
});
|
|
139
|
-
this.#emitChanged(runId, normalized, "fidelity");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
async demote(runId, path) {
|
|
143
|
-
const normalized = KnownStore.normalizePath(path);
|
|
144
|
-
await this.#db.demote_path.run({
|
|
145
|
-
run_id: runId,
|
|
146
|
-
path: normalized,
|
|
147
|
-
});
|
|
148
|
-
this.#emitChanged(runId, normalized, "demote");
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
async remove(runId, path) {
|
|
152
|
-
const normalized = KnownStore.normalizePath(path);
|
|
153
|
-
await this.#db.delete_known_entry.run({
|
|
154
|
-
run_id: runId,
|
|
155
|
-
path: normalized,
|
|
156
|
-
});
|
|
157
|
-
this.#emitChanged(runId, normalized, "remove");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
async removeFilesByPattern(runId, pattern) {
|
|
161
|
-
await this.#db.delete_file_entries_by_pattern.run({
|
|
162
|
-
run_id: runId,
|
|
163
|
-
pattern,
|
|
164
|
-
});
|
|
165
|
-
this.#emitChanged(runId, pattern, "remove");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
static #bodyPattern(body) {
|
|
169
|
-
return body || null;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async promoteByPattern(runId, path, body, turn) {
|
|
173
|
-
await this.#db.promote_by_pattern.run({
|
|
174
|
-
run_id: runId,
|
|
175
|
-
path,
|
|
176
|
-
body: KnownStore.#bodyPattern(body),
|
|
177
|
-
turn,
|
|
178
|
-
});
|
|
179
|
-
this.#emitChanged(runId, path, "promote");
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
async demoteByPattern(runId, path, body) {
|
|
183
|
-
await this.#db.demote_by_pattern.run({
|
|
184
|
-
run_id: runId,
|
|
185
|
-
path,
|
|
186
|
-
body: KnownStore.#bodyPattern(body),
|
|
187
|
-
});
|
|
188
|
-
this.#emitChanged(runId, path, "demote");
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
async getEntriesByPattern(runId, path, body, { limit, offset } = {}) {
|
|
192
|
-
return this.#db.get_entries_by_pattern.all({
|
|
193
|
-
run_id: runId,
|
|
194
|
-
path,
|
|
195
|
-
body: KnownStore.#bodyPattern(body),
|
|
196
|
-
limit: limit ?? null,
|
|
197
|
-
offset: offset ?? null,
|
|
198
|
-
});
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
async deleteByPattern(runId, path, body) {
|
|
202
|
-
await this.#db.delete_entries_by_pattern.run({
|
|
203
|
-
run_id: runId,
|
|
204
|
-
path,
|
|
205
|
-
body: KnownStore.#bodyPattern(body),
|
|
206
|
-
});
|
|
207
|
-
this.#emitChanged(runId, path, "remove");
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
async updateBodyByPattern(runId, path, body, newBody) {
|
|
211
|
-
await this.#db.update_body_by_pattern.run({
|
|
212
|
-
run_id: runId,
|
|
213
|
-
path,
|
|
214
|
-
body: KnownStore.#bodyPattern(body),
|
|
215
|
-
new_body: newBody,
|
|
216
|
-
});
|
|
217
|
-
this.#emitChanged(runId, path, "body");
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
async resolve(runId, path, status, body) {
|
|
221
|
-
const normalized = KnownStore.normalizePath(path);
|
|
222
|
-
await this.#db.resolve_known_entry.run({
|
|
223
|
-
run_id: runId,
|
|
224
|
-
path: normalized,
|
|
225
|
-
status,
|
|
226
|
-
body,
|
|
227
|
-
});
|
|
228
|
-
this.#emitChanged(runId, normalized, "resolve");
|
|
229
|
-
const key = `${runId}:${normalized}`;
|
|
230
|
-
const resolver = this.#pendingResolutions.get(key);
|
|
231
|
-
if (resolver) {
|
|
232
|
-
this.#pendingResolutions.delete(key);
|
|
233
|
-
resolver();
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
waitForResolution(runId, path) {
|
|
238
|
-
const normalized = KnownStore.normalizePath(path);
|
|
239
|
-
const key = `${runId}:${normalized}`;
|
|
240
|
-
return new Promise((resolve) => {
|
|
241
|
-
this.#pendingResolutions.set(key, resolve);
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
async restoreSummarizedPrompts(runId) {
|
|
246
|
-
await this.#db.restore_summarized_prompts.run({ run_id: runId });
|
|
247
|
-
this.#emitChanged(runId, "prompt://batch", "fidelity");
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async getLog(runId) {
|
|
251
|
-
return this.#db.get_results.all({ run_id: runId });
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
async getEntries(runId) {
|
|
255
|
-
return this.#db.get_known_entries.all({ run_id: runId });
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async getFileEntries(runId) {
|
|
259
|
-
return this.#db.get_file_entries.all({ run_id: runId });
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
async getFileStatesByPattern(runId, pattern) {
|
|
263
|
-
return this.#db.get_file_states_by_pattern.all({ run_id: runId, pattern });
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async hasRejections(runId, loopId) {
|
|
267
|
-
const row = await this.#db.has_rejections.get({
|
|
268
|
-
run_id: runId,
|
|
269
|
-
loop_id: loopId,
|
|
270
|
-
});
|
|
271
|
-
return row.count > 0;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async hasAcceptedActions(runId) {
|
|
275
|
-
const row = await this.#db.has_accepted_actions.get({ run_id: runId });
|
|
276
|
-
return row.count > 0;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async getUnresolved(runId) {
|
|
280
|
-
return this.#db.get_unresolved.all({ run_id: runId });
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async countUnknowns(runId) {
|
|
284
|
-
const row = await this.#db.count_unknowns.get({ run_id: runId });
|
|
285
|
-
return row.count;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
async getUnknownValues(runId) {
|
|
289
|
-
const rows = await this.#db.get_unknown_values.all({ run_id: runId });
|
|
290
|
-
return new Set(rows.map((r) => r.body));
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
async getBody(runId, path) {
|
|
294
|
-
const row = await this.#db.get_entry_body.get({
|
|
295
|
-
run_id: runId,
|
|
296
|
-
path: KnownStore.normalizePath(path),
|
|
297
|
-
});
|
|
298
|
-
return row?.body ?? null;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async setAttributes(runId, path, attrs) {
|
|
302
|
-
const normalized = KnownStore.normalizePath(path);
|
|
303
|
-
await this.#db.update_entry_attributes.run({
|
|
304
|
-
run_id: runId,
|
|
305
|
-
path: normalized,
|
|
306
|
-
attributes: JSON.stringify(attrs),
|
|
307
|
-
});
|
|
308
|
-
this.#emitChanged(runId, normalized, "attributes");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
async getState(runId, path) {
|
|
312
|
-
return this.#db.get_entry_state.get({
|
|
313
|
-
run_id: runId,
|
|
314
|
-
path: KnownStore.normalizePath(path),
|
|
315
|
-
});
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
async getAttributes(runId, path) {
|
|
319
|
-
const row = await this.#db.get_entry_attributes.get({
|
|
320
|
-
run_id: runId,
|
|
321
|
-
path: KnownStore.normalizePath(path),
|
|
322
|
-
});
|
|
323
|
-
return row?.attributes ? JSON.parse(row.attributes) : null;
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async getTurnAudit(runId, turn) {
|
|
327
|
-
return this.#db.get_turn_audit.all({ run_id: runId, turn });
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
static toolFromPath(path) {
|
|
331
|
-
return KnownStore.scheme(path);
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
static isSystemPath(path) {
|
|
335
|
-
return path.includes("://");
|
|
336
|
-
}
|
|
337
|
-
}
|