@possumtech/rummy 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -0
- package/FIDELITY_CONTRACT.md +172 -0
- package/migrations/001_initial_schema.sql +3 -3
- package/package.json +1 -1
- package/src/agent/AgentLoop.js +1 -2
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +1 -2
- package/src/agent/ResponseHealer.js +54 -1
- package/src/agent/TurnExecutor.js +51 -6
- package/src/agent/XmlParser.js +150 -41
- package/src/agent/known_store.sql +18 -11
- package/src/hooks/PluginContext.js +8 -2
- package/src/hooks/RummyContext.js +6 -3
- package/src/hooks/ToolRegistry.js +23 -27
- package/src/plugins/ask_user/ask_user.js +2 -2
- package/src/plugins/ask_user/ask_userDoc.js +4 -2
- package/src/plugins/budget/README.md +6 -4
- package/src/plugins/budget/budget.js +29 -9
- package/src/plugins/cp/cp.js +5 -5
- package/src/plugins/cp/cpDoc.js +0 -8
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/env.js +4 -4
- package/src/plugins/env/envDoc.js +2 -2
- package/src/plugins/file/file.js +2 -7
- package/src/plugins/get/get.js +31 -10
- package/src/plugins/get/getDoc.js +26 -37
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/instructions/instructions.js +6 -5
- package/src/plugins/instructions/preamble.md +41 -33
- package/src/plugins/known/known.js +17 -16
- package/src/plugins/known/knownDoc.js +1 -13
- package/src/plugins/mv/mv.js +6 -6
- package/src/plugins/mv/mvDoc.js +2 -13
- package/src/plugins/previous/previous.js +10 -14
- package/src/plugins/progress/progress.js +22 -5
- package/src/plugins/prompt/prompt.js +14 -11
- package/src/plugins/rm/rm.js +4 -4
- package/src/plugins/rm/rmDoc.js +4 -8
- package/src/plugins/rpc/rpc.js +1 -1
- package/src/plugins/set/set.js +10 -12
- package/src/plugins/set/setDoc.js +4 -4
- package/src/plugins/sh/sh.js +4 -4
- package/src/plugins/sh/shDoc.js +2 -2
- package/src/plugins/skill/skill.js +2 -1
- package/src/plugins/summarize/summarize.js +2 -2
- package/src/plugins/summarize/summarizeDoc.js +9 -10
- package/src/plugins/telemetry/telemetry.js +36 -11
- package/src/plugins/think/think.js +2 -1
- package/src/plugins/think/thinkDoc.js +3 -5
- package/src/plugins/unknown/unknown.js +21 -14
- package/src/plugins/unknown/unknownDoc.js +2 -6
- package/src/plugins/update/update.js +2 -2
- package/src/plugins/update/updateDoc.js +9 -6
- package/src/sql/functions/slugify.js +13 -1
- package/src/sql/v_model_context.sql +3 -3
package/src/plugins/file/file.js
CHANGED
|
@@ -16,13 +16,8 @@ export default class File {
|
|
|
16
16
|
this.#core = core;
|
|
17
17
|
// "file" scheme covers bare paths (scheme IS NULL in DB)
|
|
18
18
|
core.registerScheme({ category: "data" });
|
|
19
|
-
core.
|
|
20
|
-
core.
|
|
21
|
-
core.on("full", this.full.bind(this));
|
|
22
|
-
core.on("summary", this.summary.bind(this));
|
|
23
|
-
// Default identity views for http/https — rummy.web overrides these
|
|
24
|
-
core.hooks.tools.onView("http", (entry) => entry.body);
|
|
25
|
-
core.hooks.tools.onView("https", (entry) => entry.body);
|
|
19
|
+
core.on("promoted", this.full.bind(this));
|
|
20
|
+
core.on("demoted", this.summary.bind(this));
|
|
26
21
|
}
|
|
27
22
|
|
|
28
23
|
full(entry) {
|
package/src/plugins/get/get.js
CHANGED
|
@@ -9,8 +9,8 @@ export default class Get {
|
|
|
9
9
|
this.#core = core;
|
|
10
10
|
core.registerScheme();
|
|
11
11
|
core.on("handler", this.handler.bind(this));
|
|
12
|
-
core.on("
|
|
13
|
-
core.on("
|
|
12
|
+
core.on("promoted", this.full.bind(this));
|
|
13
|
+
core.on("demoted", this.summary.bind(this));
|
|
14
14
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
15
15
|
docsMap.get = docs;
|
|
16
16
|
return docsMap;
|
|
@@ -29,6 +29,7 @@ export default class Get {
|
|
|
29
29
|
}
|
|
30
30
|
const normalized = KnownStore.normalizePath(target);
|
|
31
31
|
const bodyFilter = entry.attributes.body || null;
|
|
32
|
+
const preview = entry.attributes.preview !== undefined;
|
|
32
33
|
const isPattern = bodyFilter || normalized.includes("*");
|
|
33
34
|
|
|
34
35
|
const line =
|
|
@@ -46,6 +47,25 @@ export default class Get {
|
|
|
46
47
|
bodyFilter,
|
|
47
48
|
);
|
|
48
49
|
|
|
50
|
+
// Preview — list matches with their full-body token costs. No promotion,
|
|
51
|
+
// no fidelity change, no Token Budget spent. Model uses this to plan
|
|
52
|
+
// which entries to actually promote. getDoc promises this behavior; the
|
|
53
|
+
// prior implementation silently promoted anyway, burning the Token Budget
|
|
54
|
+
// on entries the model thought it was only inspecting.
|
|
55
|
+
if (preview) {
|
|
56
|
+
await storePatternResult(
|
|
57
|
+
store,
|
|
58
|
+
runId,
|
|
59
|
+
turn,
|
|
60
|
+
"get",
|
|
61
|
+
target,
|
|
62
|
+
bodyFilter,
|
|
63
|
+
matches,
|
|
64
|
+
{ preview: true, loopId, attributes: { path: target } },
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
49
69
|
// Partial read — no fidelity promotion, returns a line slice as the log item.
|
|
50
70
|
if (line !== null || limit !== null) {
|
|
51
71
|
if (isPattern) {
|
|
@@ -55,7 +75,7 @@ export default class Get {
|
|
|
55
75
|
entry.resultPath,
|
|
56
76
|
"line/limit requires a single path, not a glob or body filter",
|
|
57
77
|
400,
|
|
58
|
-
{ loopId },
|
|
78
|
+
{ loopId, attributes: { path: target } },
|
|
59
79
|
);
|
|
60
80
|
return;
|
|
61
81
|
}
|
|
@@ -66,7 +86,7 @@ export default class Get {
|
|
|
66
86
|
entry.resultPath,
|
|
67
87
|
`${target} not found`,
|
|
68
88
|
200,
|
|
69
|
-
{ loopId },
|
|
89
|
+
{ loopId, attributes: { path: target } },
|
|
70
90
|
);
|
|
71
91
|
return;
|
|
72
92
|
}
|
|
@@ -84,15 +104,15 @@ export default class Get {
|
|
|
84
104
|
entry.resultPath,
|
|
85
105
|
`${header}\n${slice}`,
|
|
86
106
|
200,
|
|
87
|
-
{ loopId },
|
|
107
|
+
{ loopId, attributes: { path: target } },
|
|
88
108
|
);
|
|
89
109
|
return;
|
|
90
110
|
}
|
|
91
111
|
|
|
92
112
|
const VALID_FIDELITY = {
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
demoted: 1,
|
|
114
|
+
promoted: 1,
|
|
115
|
+
archived: 1,
|
|
96
116
|
};
|
|
97
117
|
const fidelityAttr = VALID_FIDELITY[entry.attributes.fidelity]
|
|
98
118
|
? entry.attributes.fidelity
|
|
@@ -113,17 +133,18 @@ export default class Get {
|
|
|
113
133
|
target,
|
|
114
134
|
bodyFilter,
|
|
115
135
|
matches,
|
|
116
|
-
{ loopId },
|
|
136
|
+
{ loopId, attributes: { path: target } },
|
|
117
137
|
);
|
|
118
138
|
} else {
|
|
119
139
|
const total = matches.reduce((s, m) => s + m.tokens, 0);
|
|
120
140
|
const paths = matches.map((m) => m.path).join(", ");
|
|
121
141
|
const body =
|
|
122
142
|
matches.length > 0
|
|
123
|
-
? `${paths} promoted
|
|
143
|
+
? `${paths} promoted (${total} tokens)`
|
|
124
144
|
: `${target} not found`;
|
|
125
145
|
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
126
146
|
loopId,
|
|
147
|
+
attributes: { path: target },
|
|
127
148
|
});
|
|
128
149
|
}
|
|
129
150
|
}
|
|
@@ -2,43 +2,32 @@
|
|
|
2
2
|
// Text goes to the model. Rationale stays in source.
|
|
3
3
|
// Changing ANY line requires reading ALL rationales first.
|
|
4
4
|
const LINES = [
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"* Body text filters results by content match",
|
|
32
|
-
"Body = filter, not just path.",
|
|
33
|
-
],
|
|
34
|
-
[
|
|
35
|
-
"* `line` and `limit` read a slice without promoting — patterns not allowed",
|
|
36
|
-
"Partial read is safe: context budget unaffected.",
|
|
37
|
-
],
|
|
38
|
-
[
|
|
39
|
-
'* Use <set path="src/file.txt" fidelity="summary"/> when the content is irrelevant to save tokens.',
|
|
40
|
-
"Cross-tool lifecycle: get promotes, set demotes.",
|
|
41
|
-
],
|
|
5
|
+
["## <get>[path/to/file]</get> - Promote an entry"],
|
|
6
|
+
["Example: <get>src/app.js</get>", "Simplest form. Body = path."],
|
|
7
|
+
[
|
|
8
|
+
'Example: <get path="known://*">auth</get>',
|
|
9
|
+
"Keyword recall: glob in path, search term in body.",
|
|
10
|
+
],
|
|
11
|
+
[
|
|
12
|
+
'Example: <get path="src/**/*.js">authentication</get>',
|
|
13
|
+
"Full pattern: recursive glob + content filter.",
|
|
14
|
+
],
|
|
15
|
+
[
|
|
16
|
+
'Example: <get path="src/agent/AgentLoop.js" line="644" limit="80"/>',
|
|
17
|
+
"Partial read. Returns lines 644–723 without promoting.",
|
|
18
|
+
],
|
|
19
|
+
[
|
|
20
|
+
"* Paths accept patterns: `src/**/*.js`, `known://api_*`",
|
|
21
|
+
"Reinforces picomatch patterns work everywhere.",
|
|
22
|
+
],
|
|
23
|
+
[
|
|
24
|
+
"* Body text filters results by content match",
|
|
25
|
+
"Body = filter, not just path.",
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"* `line` and `limit` read a slice without promoting the entry, which costs as many tokens as the slice contains.",
|
|
29
|
+
"Partial read is safe: context budget unaffected.",
|
|
30
|
+
],
|
|
42
31
|
];
|
|
43
32
|
|
|
44
33
|
export default LINES.map(([text]) => text).join("\n");
|
package/src/plugins/helpers.js
CHANGED
|
@@ -10,7 +10,7 @@ export async function storePatternResult(
|
|
|
10
10
|
path,
|
|
11
11
|
bodyFilter,
|
|
12
12
|
matches,
|
|
13
|
-
{ preview = false, loopId = null } = {},
|
|
13
|
+
{ preview = false, loopId = null, attributes = null } = {},
|
|
14
14
|
) {
|
|
15
15
|
const slug = await store.slugPath(runId, scheme, path);
|
|
16
16
|
const filter = bodyFilter ? ` body="${bodyFilter}"` : "";
|
|
@@ -18,5 +18,5 @@ export async function storePatternResult(
|
|
|
18
18
|
const listing = matches.map((m) => `${m.path} (${m.tokens})`).join("\n");
|
|
19
19
|
const prefix = preview ? "PREVIEW " : "";
|
|
20
20
|
const body = `${prefix}${scheme} path="${path}"${filter}: ${matches.length} matched (${total} tokens)\n${listing}`;
|
|
21
|
-
await store.upsert(runId, turn, slug, body, 200, { loopId });
|
|
21
|
+
await store.upsert(runId, turn, slug, body, 200, { loopId, attributes });
|
|
22
22
|
}
|
|
@@ -10,7 +10,7 @@ export default class Instructions {
|
|
|
10
10
|
|
|
11
11
|
constructor(core) {
|
|
12
12
|
this.#core = core;
|
|
13
|
-
core.on("
|
|
13
|
+
core.on("promoted", this.full.bind(this));
|
|
14
14
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
15
15
|
}
|
|
16
16
|
|
|
@@ -33,14 +33,15 @@ export default class Instructions {
|
|
|
33
33
|
const activeTools = attrs.toolSet
|
|
34
34
|
? new Set(attrs.toolSet)
|
|
35
35
|
: new Set(this.#core.hooks.tools.names);
|
|
36
|
-
const sorted = this.#core.hooks.tools.names.filter((n) =>
|
|
37
|
-
activeTools.has(n),
|
|
38
|
-
);
|
|
39
|
-
const tools = sorted.join(", ");
|
|
40
36
|
const toolDocs = await this.#core.hooks.instructions.toolDocs.filter(
|
|
41
37
|
{},
|
|
42
38
|
{ toolSet: activeTools },
|
|
43
39
|
);
|
|
40
|
+
// Hidden tools are excluded at the registry level (see ToolRegistry).
|
|
41
|
+
const sorted = this.#core.hooks.tools.advertisedNames.filter((n) =>
|
|
42
|
+
activeTools.has(n),
|
|
43
|
+
);
|
|
44
|
+
const tools = sorted.join(", ");
|
|
44
45
|
const docsText = sorted
|
|
45
46
|
.filter((key) => toolDocs[key])
|
|
46
47
|
.map((key) => toolDocs[key])
|
|
@@ -1,37 +1,45 @@
|
|
|
1
|
-
You are a folksonomic knowledgebase assistant.
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Required: YOU MUST
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
1
|
+
You are a folksonomic knowledgebase assistant. Define what's unknown, gather knowns to resolve the unknown, act, then answer.
|
|
2
|
+
|
|
3
|
+
Required: YOU MUST only respond with Tool Commands in the XML format (max 12/turn): [%TOOLS%]
|
|
4
|
+
|
|
5
|
+
Required: YOU MUST register your unresolved questions as unknown:// entries, then resolve them.
|
|
6
|
+
Example: <set path="unknown://{topic_or_question}" summary="keyword,keyword,keyword">specific question I need to research</set>
|
|
7
|
+
|
|
8
|
+
Required: YOU MUST gather relevant facts, decisions, and information to store in known:// entries.
|
|
9
|
+
Required: YOU MUST include navigable paths and specific, searchable summary tags to enable pattern search and promotion.
|
|
10
|
+
Example: <set path="known://topic/subtopic1" summary="keyword,keyword,keyword">{known facts, decisions, or plans}</set>
|
|
11
|
+
|
|
12
|
+
Required: YOU MUST add the paths of related entries to your entry, and edit existing related entries to add linkbacks.
|
|
13
|
+
Example: <set path="known://topic/subtopic2" summary="keyword,keyword,keyword">{facts} Related: known://topic/subtopic1</set>
|
|
14
|
+
|
|
15
|
+
Required: YOU MUST promote relevant entries to verify their contents. Paths and summaries are approximate and unreliable.
|
|
16
|
+
Example: <get path="facts.txt"/>
|
|
17
|
+
Required: YOU MUST demote entries after organizing and categorizing relevant information into known entries.
|
|
18
|
+
Example: <set path="prompt://42" fidelity="demoted"/>
|
|
19
|
+
|
|
20
|
+
Required: YOU MUST calculate and estimate the token totals (tokens="N") of entries before promoting and not exceed 50% of Token Budget.
|
|
21
|
+
Warning: Promotions and new entries cost tokens. Demotions recover tokens. Exceeding your budget will result in a 413 Token Budget Error.
|
|
22
|
+
Tip: Entries with higher turn numbers are more recent and relevant.
|
|
23
|
+
|
|
24
|
+
Required: YOU MUST create and maintain a checklist to guide and track your progress. Only check items when they're completed.
|
|
25
|
+
Required: YOU MUST adapt and expand this checklist for the specific context, entries, and prompt requirements.
|
|
26
|
+
Example:
|
|
27
|
+
<set path="known://rummy_plan" summary="plan,strategy,steps,roadmap">
|
|
28
|
+
- [ ] identify and record unknown facts, unresolved decisions, and unclear plans
|
|
29
|
+
- [ ] identify, organize, and categorize known facts, decisions, and plans before acting on prompt
|
|
30
|
+
- [ ] identify relevant entries to verify, analyze, review, and record contents (don't assume from path or summary!)
|
|
31
|
+
- [ ] after promoting an entry, organize and categorize findings into known entries
|
|
32
|
+
- [ ] after the entry's information has been stored in known entries, demote it to optimize context relevance and token budget
|
|
33
|
+
- [ ] iteratively analyze and explore until the unknowns that can be resolved are resolved
|
|
34
|
+
- [ ] { specific action required by prompt }
|
|
35
|
+
- [ ] ...
|
|
36
|
+
- [ ] summarize when complete with summarize tag
|
|
37
|
+
</set>
|
|
38
|
+
Example: <set path="known://rummy_plan">s/- [ ] specific action required by prompt/- [x] specific action required by prompt/g</set>
|
|
34
39
|
|
|
35
40
|
# Tool Usage
|
|
36
41
|
|
|
42
|
+
Warning: YOU MUST NOT use shell commands for file operations. Files are entries that require Tool Command operations.
|
|
43
|
+
Example: <set path="newFile.txt" summary="keyword,keyword,keyword">{new file contents}</set>
|
|
44
|
+
|
|
37
45
|
[%TOOLDOCS%]
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { countTokens } from "../../agent/tokens.js";
|
|
2
|
-
import docs from "./knownDoc.js";
|
|
3
2
|
|
|
4
3
|
const MAX_ENTRY_TOKENS = Number(process.env.RUMMY_MAX_ENTRY_TOKENS) || 512;
|
|
5
4
|
|
|
@@ -10,13 +9,13 @@ export default class Known {
|
|
|
10
9
|
this.#core = core;
|
|
11
10
|
core.registerScheme({ category: "data" });
|
|
12
11
|
core.on("handler", this.handler.bind(this));
|
|
13
|
-
core.on("
|
|
14
|
-
core.on("
|
|
12
|
+
core.on("promoted", this.full.bind(this));
|
|
13
|
+
core.on("demoted", this.summary.bind(this));
|
|
15
14
|
core.filter("assembly.system", this.assembleKnown.bind(this), 100);
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
// <known> is internal — written via <set path="known://...">. Hidden
|
|
16
|
+
// from all model-facing tool lists. Handler still dispatches if the
|
|
17
|
+
// model emits <known> directly out of habit.
|
|
18
|
+
core.markHidden();
|
|
20
19
|
}
|
|
21
20
|
|
|
22
21
|
async handler(entry, rummy) {
|
|
@@ -73,11 +72,11 @@ export default class Known {
|
|
|
73
72
|
}
|
|
74
73
|
|
|
75
74
|
full(entry) {
|
|
76
|
-
return
|
|
75
|
+
return entry.body;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
summary(
|
|
80
|
-
return
|
|
78
|
+
summary() {
|
|
79
|
+
return "";
|
|
81
80
|
}
|
|
82
81
|
|
|
83
82
|
async assembleKnown(content, ctx) {
|
|
@@ -103,14 +102,16 @@ function renderKnownTag(entry, demotedSet) {
|
|
|
103
102
|
typeof entry.attributes === "string"
|
|
104
103
|
? JSON.parse(entry.attributes)
|
|
105
104
|
: entry.attributes;
|
|
106
|
-
|
|
105
|
+
// Always render summary attribute on knowns — empty value hints the model
|
|
106
|
+
// it forgot to add searchable keywords.
|
|
107
|
+
const summaryText =
|
|
107
108
|
typeof attrs?.summary === "string"
|
|
108
|
-
?
|
|
109
|
+
? attrs.summary.replace(/"/g, "'").slice(0, 80)
|
|
109
110
|
: "";
|
|
111
|
+
const summary = ` summary="${summaryText}"`;
|
|
110
112
|
|
|
111
|
-
if (entry.
|
|
112
|
-
|
|
113
|
-
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}/>`;
|
|
113
|
+
if (entry.body) {
|
|
114
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}>${entry.body}</${tag}>`;
|
|
114
115
|
}
|
|
115
|
-
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}
|
|
116
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}/>`;
|
|
116
117
|
}
|
|
@@ -5,25 +5,13 @@ const LINES = [
|
|
|
5
5
|
[
|
|
6
6
|
'## <known path="known://topic/subtopic" summary="keyword,keyword,keyword">[specific facts, decisions, or plans]</known> - Sort and save what you learn for later recall',
|
|
7
7
|
],
|
|
8
|
-
[
|
|
9
|
-
'Example: <known summary="hedberg,comedian,death,2005">Mitch Hedberg died on March 30, 2005</known>',
|
|
10
|
-
"Summary-first pattern: comma-separated keywords, path auto-generated.",
|
|
11
|
-
],
|
|
12
8
|
[
|
|
13
9
|
'Example: <known path="known://people/rumsfeld" summary="defense,secretary,born,1932">Donald Rumsfeld was born in 1932 and served as Secretary of Defense</known>',
|
|
14
10
|
"Explicit path form: slashed path=category/key, summary=keywords.",
|
|
15
11
|
],
|
|
16
12
|
[
|
|
17
13
|
'* Recall with <get path="known://people/*">keyword</get>',
|
|
18
|
-
"Cross-tool lifecycle:
|
|
19
|
-
],
|
|
20
|
-
[
|
|
21
|
-
"* YOU SHOULD write `summary` keywords, you can search for them later",
|
|
22
|
-
"Motivates summary writing through self-interest.",
|
|
23
|
-
],
|
|
24
|
-
[
|
|
25
|
-
"* YOU MUST sort and save all new facts, decisions, and plans in their own <known> entries",
|
|
26
|
-
"Critical behavioral constraint. 'new' prevents re-saving known facts.",
|
|
14
|
+
"Cross-tool lifecycle: pattern by category, filter by keyword.",
|
|
27
15
|
],
|
|
28
16
|
];
|
|
29
17
|
|
package/src/plugins/mv/mv.js
CHANGED
|
@@ -8,8 +8,8 @@ export default class Mv {
|
|
|
8
8
|
this.#core = core;
|
|
9
9
|
core.registerScheme();
|
|
10
10
|
core.on("handler", this.handler.bind(this));
|
|
11
|
-
core.on("
|
|
12
|
-
core.on("
|
|
11
|
+
core.on("promoted", this.full.bind(this));
|
|
12
|
+
core.on("demoted", this.summary.bind(this));
|
|
13
13
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
14
|
docsMap.mv = docs;
|
|
15
15
|
return docsMap;
|
|
@@ -29,14 +29,14 @@ export default class Mv {
|
|
|
29
29
|
const matches = await store.getEntriesByPattern(runId, path);
|
|
30
30
|
for (const match of matches)
|
|
31
31
|
await store.setFidelity(runId, match.path, fidelity);
|
|
32
|
-
const label =
|
|
32
|
+
const label = `set to ${fidelity}`;
|
|
33
33
|
await store.upsert(
|
|
34
34
|
runId,
|
|
35
35
|
turn,
|
|
36
36
|
entry.resultPath,
|
|
37
37
|
`${matches.map((m) => m.path).join(", ")} ${label}`,
|
|
38
38
|
200,
|
|
39
|
-
{ fidelity: "
|
|
39
|
+
{ fidelity: "archived", loopId },
|
|
40
40
|
);
|
|
41
41
|
return;
|
|
42
42
|
}
|
|
@@ -71,7 +71,7 @@ export default class Mv {
|
|
|
71
71
|
return `# mv ${entry.attributes.from || ""} ${entry.attributes.to || ""}`;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
-
summary(
|
|
75
|
-
return
|
|
74
|
+
summary() {
|
|
75
|
+
return "";
|
|
76
76
|
}
|
|
77
77
|
}
|
package/src/plugins/mv/mvDoc.js
CHANGED
|
@@ -9,22 +9,11 @@ const LINES = [
|
|
|
9
9
|
'Example: <mv path="known://active_task">known://completed_task</mv>',
|
|
10
10
|
"Entry rename. Most common mv use case.",
|
|
11
11
|
],
|
|
12
|
+
['Example: <mv path="src/old_name.js">src/new_name.js</mv>', "File rename."],
|
|
12
13
|
[
|
|
13
|
-
'Example: <mv path="
|
|
14
|
-
"File rename.",
|
|
15
|
-
],
|
|
16
|
-
[
|
|
17
|
-
'Example: <mv path="known://project/*" fidelity="summary"/>',
|
|
14
|
+
'Example: <mv path="known://project/*" fidelity="demoted"/>',
|
|
18
15
|
"Batch fidelity change via pattern. No destination = fidelity in place.",
|
|
19
16
|
],
|
|
20
|
-
[
|
|
21
|
-
"* Source path accepts patterns for batch moves",
|
|
22
|
-
"Pattern support consistent with get/cp/rm.",
|
|
23
|
-
],
|
|
24
|
-
[
|
|
25
|
-
"* Use `preview` to check matches before pattern-based bulk moves",
|
|
26
|
-
"Safety pattern consistent with rm/cp.",
|
|
27
|
-
],
|
|
28
17
|
];
|
|
29
18
|
|
|
30
19
|
export default LINES.map(([text]) => text).join("\n");
|
|
@@ -43,18 +43,14 @@ async function renderToolTag(entry, _core) {
|
|
|
43
43
|
const status = entry.status ? ` status="${entry.status}"` : "";
|
|
44
44
|
const fidelity = entry.fidelity ? ` fidelity="${entry.fidelity}"` : "";
|
|
45
45
|
const tokens = entry.tokens ? ` tokens="${entry.tokens}"` : "";
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
? ` summary="${summaryText.replace(/"/g, "'").slice(0, limit)}"`
|
|
57
|
-
: "";
|
|
58
|
-
|
|
59
|
-
return `<${entry.scheme} path="${target}"${turn}${status}${summaryAttr}${fidelity}${tokens}/>`;
|
|
46
|
+
const summary =
|
|
47
|
+
typeof attrs?.summary === "string"
|
|
48
|
+
? ` summary="${attrs.summary.replace(/"/g, "'")}"`
|
|
49
|
+
: "";
|
|
50
|
+
|
|
51
|
+
// Trust the projected body. Plugin decided per-fidelity what to show.
|
|
52
|
+
if (entry.body) {
|
|
53
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}${fidelity}${tokens}>${entry.body}</${entry.scheme}>`;
|
|
54
|
+
}
|
|
55
|
+
return `<${entry.scheme} path="${target}"${turn}${status}${summary}${fidelity}${tokens}/>`;
|
|
60
56
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const CEILING_RATIO = Number(process.env.RUMMY_BUDGET_CEILING);
|
|
2
|
+
if (!CEILING_RATIO) throw new Error("RUMMY_BUDGET_CEILING must be set");
|
|
3
|
+
|
|
1
4
|
export default class Progress {
|
|
2
5
|
#core;
|
|
3
6
|
|
|
@@ -7,17 +10,31 @@ export default class Progress {
|
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
async assembleProgress(content, ctx) {
|
|
10
|
-
const {
|
|
11
|
-
const pct = contextSize ? Math.round((usedTokens / contextSize) * 100) : 0;
|
|
12
|
-
|
|
13
|
+
const { rows, contextSize, baselineTokens } = ctx;
|
|
13
14
|
const lines = [];
|
|
15
|
+
|
|
14
16
|
if (contextSize) {
|
|
17
|
+
const ceiling = Math.floor(contextSize * CEILING_RATIO);
|
|
18
|
+
const tokenBudget = Math.max(0, ceiling - (baselineTokens || 0));
|
|
19
|
+
// Used = sum of promoted controllable entries' tokens. Same units as
|
|
20
|
+
// per-entry tokens="N" so the model can predict the effect of a
|
|
21
|
+
// promote/demote: change is exactly the entry's tokens attribute.
|
|
22
|
+
const used = rows.reduce((sum, r) => {
|
|
23
|
+
if (
|
|
24
|
+
(r.category === "data" || r.category === "logging") &&
|
|
25
|
+
r.fidelity === "promoted"
|
|
26
|
+
) {
|
|
27
|
+
return sum + (r.tokens || 0);
|
|
28
|
+
}
|
|
29
|
+
return sum;
|
|
30
|
+
}, 0);
|
|
31
|
+
const remaining = Math.max(0, tokenBudget - used);
|
|
15
32
|
lines.push(
|
|
16
|
-
`
|
|
33
|
+
`Token Budget: ${tokenBudget}. Using ${used}. ${remaining} remaining. Promote relevant entries with <get/> to spend. Demote irrelevant entries with <set fidelity="demoted"/> to save.`,
|
|
17
34
|
);
|
|
18
35
|
}
|
|
19
36
|
lines.push(
|
|
20
|
-
|
|
37
|
+
"Conclude with a brief <update></update> to continue or a brief <summarize></summarize> if done.",
|
|
21
38
|
);
|
|
22
39
|
const body = lines.join("\n");
|
|
23
40
|
|
|
@@ -3,16 +3,18 @@ export default class Prompt {
|
|
|
3
3
|
|
|
4
4
|
constructor(core) {
|
|
5
5
|
this.#core = core;
|
|
6
|
-
core.hooks.tools.onView("prompt", (entry) =>
|
|
7
|
-
|
|
6
|
+
core.hooks.tools.onView("prompt", (entry) => entry.body, "promoted");
|
|
7
|
+
core.hooks.tools.onView(
|
|
8
|
+
"prompt",
|
|
9
|
+
(entry) => {
|
|
8
10
|
const limit = 500;
|
|
9
11
|
const text = entry.body?.slice(0, limit) || "";
|
|
10
12
|
return text.length < (entry.body?.length || 0)
|
|
11
|
-
? `${text}\n[truncated — promote to
|
|
13
|
+
? `${text}\n[truncated — promote to see the complete prompt]`
|
|
12
14
|
: text;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
},
|
|
16
|
+
"demoted",
|
|
17
|
+
);
|
|
16
18
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
17
19
|
core.filter("assembly.user", this.assemblePrompt.bind(this), 300);
|
|
18
20
|
}
|
|
@@ -39,13 +41,14 @@ export default class Prompt {
|
|
|
39
41
|
: promptEntry?.attributes;
|
|
40
42
|
const mode = attrs?.mode || ctx.type;
|
|
41
43
|
const body = promptEntry?.body || "";
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
// No tools="..." attribute. The OpenAI-shaped
|
|
45
|
+
// `<prompt mode tools="x,y,z">` rendering was priming gemma's
|
|
46
|
+
// native-tool-call training prior — A/B test confirmed removing
|
|
47
|
+
// the attribute dropped native-format emissions from ~50% to 0%.
|
|
48
|
+
// Tools list lives in the system prompt as "XML Command Tools:".
|
|
46
49
|
let warn = "";
|
|
47
50
|
if (mode === "ask") warn = ' warn="File editing disallowed."';
|
|
48
51
|
|
|
49
|
-
return `${content}<prompt mode="${mode}"
|
|
52
|
+
return `${content}<prompt mode="${mode}"${warn}>${body}</prompt>`;
|
|
50
53
|
}
|
|
51
54
|
}
|
package/src/plugins/rm/rm.js
CHANGED
|
@@ -8,8 +8,8 @@ export default class Rm {
|
|
|
8
8
|
this.#core = core;
|
|
9
9
|
core.registerScheme();
|
|
10
10
|
core.on("handler", this.handler.bind(this));
|
|
11
|
-
core.on("
|
|
12
|
-
core.on("
|
|
11
|
+
core.on("promoted", this.full.bind(this));
|
|
12
|
+
core.on("demoted", this.summary.bind(this));
|
|
13
13
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
14
|
docsMap.rm = docs;
|
|
15
15
|
return docsMap;
|
|
@@ -74,7 +74,7 @@ export default class Rm {
|
|
|
74
74
|
return entry.body ? `${header}\n${entry.body}` : header;
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
summary(
|
|
78
|
-
return
|
|
77
|
+
summary() {
|
|
78
|
+
return "";
|
|
79
79
|
}
|
|
80
80
|
}
|
package/src/plugins/rm/rmDoc.js
CHANGED
|
@@ -4,21 +4,17 @@
|
|
|
4
4
|
const LINES = [
|
|
5
5
|
['## <rm path="[path]"/> - Remove a file or entry'],
|
|
6
6
|
['Example: <rm path="src/config.js"/>', "File removal. Simplest form."],
|
|
7
|
-
[
|
|
8
|
-
'Example: <rm path="known://config/deprecated_service"/>',
|
|
9
|
-
"Shows topic-hierarchy path convention.",
|
|
10
|
-
],
|
|
11
7
|
[
|
|
12
8
|
'Example: <rm path="known://temp_*" preview/>',
|
|
13
9
|
"Preview before deleting. Safety pattern for bulk operations.",
|
|
14
10
|
],
|
|
15
11
|
[
|
|
16
|
-
'* Permanent. Prefer <set fidelity="
|
|
17
|
-
"Nudges toward archive over rm.",
|
|
12
|
+
'* Permanent. Prefer <set path="..." fidelity="archived"/> to preserve for later retrieval',
|
|
13
|
+
"Nudges toward archive over rm. Path attr included so the model sees a complete invocation shape, not a fragment.",
|
|
18
14
|
],
|
|
19
15
|
[
|
|
20
|
-
"*
|
|
21
|
-
"
|
|
16
|
+
"* `preview` shows what paths would be affected without performing the operation.",
|
|
17
|
+
"Canonical preview teaching lives here — rm is the most intuitive 'check before committing' case. Model generalizes to cp/mv/get by analogy. Advanced uses (e.g. archive rediscovery via <get preview>) belong in persona/skill docs, not here.",
|
|
22
18
|
],
|
|
23
19
|
];
|
|
24
20
|
|