@possumtech/rummy 0.3.1 → 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 +12 -0
- package/FIDELITY_CONTRACT.md +172 -0
- package/README.md +5 -1
- package/SPEC.md +31 -17
- package/migrations/001_initial_schema.sql +3 -4
- package/package.json +1 -1
- package/src/agent/AgentLoop.js +51 -153
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +16 -9
- package/src/agent/ResponseHealer.js +54 -1
- package/src/agent/TurnExecutor.js +125 -323
- package/src/agent/XmlParser.js +172 -42
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +29 -72
- package/src/agent/runs.sql +2 -2
- package/src/hooks/Hooks.js +1 -0
- package/src/hooks/PluginContext.js +8 -2
- package/src/hooks/RummyContext.js +6 -3
- package/src/hooks/ToolRegistry.js +29 -32
- package/src/plugins/ask_user/ask_user.js +2 -2
- package/src/plugins/ask_user/ask_userDoc.js +7 -10
- package/src/plugins/budget/README.md +28 -18
- package/src/plugins/budget/budget.js +80 -3
- package/src/plugins/budget/recovery.js +47 -0
- package/src/plugins/cp/cp.js +5 -5
- package/src/plugins/cp/cpDoc.js +1 -14
- package/src/plugins/engine/engine.sql +1 -1
- package/src/plugins/env/env.js +4 -4
- package/src/plugins/env/envDoc.js +4 -9
- package/src/plugins/file/file.js +2 -7
- package/src/plugins/get/get.js +32 -13
- package/src/plugins/get/getDoc.js +26 -44
- package/src/plugins/helpers.js +4 -4
- package/src/plugins/instructions/instructions.js +9 -7
- package/src/plugins/instructions/preamble.md +45 -26
- package/src/plugins/known/known.js +71 -15
- package/src/plugins/known/knownDoc.js +4 -20
- package/src/plugins/mv/mv.js +6 -6
- package/src/plugins/mv/mvDoc.js +4 -30
- package/src/plugins/policy/policy.js +47 -0
- package/src/plugins/previous/previous.js +10 -14
- package/src/plugins/progress/progress.js +29 -48
- package/src/plugins/prompt/prompt.js +18 -6
- package/src/plugins/rm/rm.js +4 -4
- package/src/plugins/rm/rmDoc.js +5 -14
- package/src/plugins/rpc/rpc.js +4 -2
- package/src/plugins/set/set.js +86 -91
- package/src/plugins/set/setDoc.js +28 -41
- package/src/plugins/sh/sh.js +4 -4
- package/src/plugins/sh/shDoc.js +4 -9
- package/src/plugins/skill/skill.js +2 -1
- package/src/plugins/summarize/summarize.js +9 -2
- package/src/plugins/summarize/summarizeDoc.js +10 -16
- package/src/plugins/telemetry/telemetry.js +36 -11
- package/src/plugins/think/think.js +13 -0
- package/src/plugins/think/thinkDoc.js +16 -0
- package/src/plugins/unknown/unknown.js +37 -9
- package/src/plugins/unknown/unknownDoc.js +7 -16
- package/src/plugins/update/update.js +9 -2
- package/src/plugins/update/updateDoc.js +12 -14
- package/src/server/ClientConnection.js +11 -1
- package/src/sql/functions/slugify.js +13 -1
- package/src/sql/v_model_context.sql +6 -6
|
@@ -1,26 +1,45 @@
|
|
|
1
|
-
You are a folksonomic
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Required: YOU MUST
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
Required: YOU MUST
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
Required:
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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>
|
|
39
|
+
|
|
40
|
+
# Tool Usage
|
|
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
|
+
|
|
45
|
+
[%TOOLDOCS%]
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
2
|
+
|
|
3
|
+
const MAX_ENTRY_TOKENS = Number(process.env.RUMMY_MAX_ENTRY_TOKENS) || 512;
|
|
2
4
|
|
|
3
5
|
export default class Known {
|
|
4
6
|
#core;
|
|
@@ -7,29 +9,81 @@ export default class Known {
|
|
|
7
9
|
this.#core = core;
|
|
8
10
|
core.registerScheme({ category: "data" });
|
|
9
11
|
core.on("handler", this.handler.bind(this));
|
|
10
|
-
core.on("
|
|
12
|
+
core.on("promoted", this.full.bind(this));
|
|
13
|
+
core.on("demoted", this.summary.bind(this));
|
|
11
14
|
core.filter("assembly.system", this.assembleKnown.bind(this), 100);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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();
|
|
16
19
|
}
|
|
17
20
|
|
|
18
21
|
async handler(entry, rummy) {
|
|
19
22
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
if (!entry.body) return;
|
|
24
|
+
|
|
25
|
+
// Size gate
|
|
26
|
+
const entryTokens = countTokens(entry.body);
|
|
27
|
+
if (entryTokens > MAX_ENTRY_TOKENS) {
|
|
28
|
+
const rejectPath = await store.slugPath(runId, "known", entry.body);
|
|
29
|
+
await store.upsert(
|
|
30
|
+
runId,
|
|
31
|
+
turn,
|
|
32
|
+
rejectPath,
|
|
33
|
+
`Entry too large (${entryTokens} tokens, max ${MAX_ENTRY_TOKENS}). Sort the information, ideas, or plans carefully into multiple entries.`,
|
|
34
|
+
413,
|
|
35
|
+
{ loopId },
|
|
36
|
+
);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Resolve path: explicit or auto-generated slug
|
|
41
|
+
let knownPath = entry.attributes?.path || null;
|
|
42
|
+
if (knownPath && !knownPath.includes("://")) {
|
|
43
|
+
knownPath = `known://${knownPath}`;
|
|
44
|
+
}
|
|
45
|
+
if (!knownPath) {
|
|
46
|
+
knownPath = await store.slugPath(
|
|
47
|
+
runId,
|
|
48
|
+
"known",
|
|
49
|
+
entry.body,
|
|
50
|
+
entry.attributes?.summary,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Dedup: if path exists, update rather than duplicate
|
|
55
|
+
const existing = await store.getEntriesByPattern(runId, knownPath, null);
|
|
56
|
+
if (existing.length > 0) {
|
|
57
|
+
await store.upsert(
|
|
58
|
+
runId,
|
|
59
|
+
turn,
|
|
60
|
+
existing[0].path,
|
|
61
|
+
entry.body || existing[0].body,
|
|
62
|
+
200,
|
|
63
|
+
{ attributes: entry.attributes, loopId },
|
|
64
|
+
);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await store.upsert(runId, turn, knownPath, entry.body, 200, {
|
|
69
|
+
attributes: entry.attributes,
|
|
70
|
+
loopId,
|
|
71
|
+
});
|
|
22
72
|
}
|
|
23
73
|
|
|
24
74
|
full(entry) {
|
|
25
|
-
return
|
|
75
|
+
return entry.body;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
summary() {
|
|
79
|
+
return "";
|
|
26
80
|
}
|
|
27
81
|
|
|
28
82
|
async assembleKnown(content, ctx) {
|
|
29
83
|
const entries = ctx.rows.filter((r) => r.category === "data");
|
|
30
84
|
if (entries.length === 0) return content;
|
|
31
85
|
|
|
32
|
-
// Rows arrive pre-sorted by SQL:
|
|
86
|
+
// Rows arrive pre-sorted by SQL: summary → full, then by recency
|
|
33
87
|
const demotedSet = new Set(ctx.demoted || []);
|
|
34
88
|
const lines = entries.map((e) => renderKnownTag(e, demotedSet));
|
|
35
89
|
return `${content}\n\n<knowns>\n${lines.join("\n")}\n</knowns>`;
|
|
@@ -48,14 +102,16 @@ function renderKnownTag(entry, demotedSet) {
|
|
|
48
102
|
typeof entry.attributes === "string"
|
|
49
103
|
? JSON.parse(entry.attributes)
|
|
50
104
|
: entry.attributes;
|
|
51
|
-
|
|
105
|
+
// Always render summary attribute on knowns — empty value hints the model
|
|
106
|
+
// it forgot to add searchable keywords.
|
|
107
|
+
const summaryText =
|
|
52
108
|
typeof attrs?.summary === "string"
|
|
53
|
-
?
|
|
109
|
+
? attrs.summary.replace(/"/g, "'").slice(0, 80)
|
|
54
110
|
: "";
|
|
111
|
+
const summary = ` summary="${summaryText}"`;
|
|
55
112
|
|
|
56
113
|
if (entry.body) {
|
|
57
|
-
return `<${tag} path="${entry.path}"${turn}${status}${
|
|
114
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}>${entry.body}</${tag}>`;
|
|
58
115
|
}
|
|
59
|
-
|
|
60
|
-
return `<${tag} path="${entry.path}"${turn}${status}${fidelity}${summary}${tokens}${flag}/>`;
|
|
116
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}/>`;
|
|
61
117
|
}
|
|
@@ -2,32 +2,16 @@
|
|
|
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
|
-
// --- Syntax: path = slash-separated topic hierarchy, body = the information to save
|
|
6
5
|
[
|
|
7
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',
|
|
8
7
|
],
|
|
9
|
-
// --- Examples: category-level entries — multiple related facts per entry, not one per item
|
|
10
8
|
[
|
|
11
|
-
'Example: <known path="known://
|
|
12
|
-
"
|
|
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>',
|
|
10
|
+
"Explicit path form: slashed path=category/key, summary=keywords.",
|
|
13
11
|
],
|
|
14
12
|
[
|
|
15
|
-
'
|
|
16
|
-
"
|
|
17
|
-
],
|
|
18
|
-
// --- Constraints: summary and grouping first (model forms generation pattern from header + examples)
|
|
19
|
-
[
|
|
20
|
-
"* `summary` REQUIRED — at summary fidelity the body is hidden; these keywords are your only description",
|
|
21
|
-
"Self-interest framing: without summary, the model has a path but no idea what's inside.",
|
|
22
|
-
],
|
|
23
|
-
[
|
|
24
|
-
"* Group related facts by topic — one entry per topic category, not one per input chunk",
|
|
25
|
-
"Critical behavioral constraint. Topic grouping enables semantic recall; chunk-based filing creates positional, irretrievable entries.",
|
|
26
|
-
],
|
|
27
|
-
// --- Lifecycle
|
|
28
|
-
[
|
|
29
|
-
'* Recall with <get path="known://config/*">replica</get>',
|
|
30
|
-
"Cross-tool lifecycle: glob by category, filter by keyword. Matches the slashed path convention.",
|
|
13
|
+
'* Recall with <get path="known://people/*">keyword</get>',
|
|
14
|
+
"Cross-tool lifecycle: pattern by category, filter by keyword.",
|
|
31
15
|
],
|
|
32
16
|
];
|
|
33
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
|
@@ -2,43 +2,17 @@
|
|
|
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
|
-
// --- Syntax: path attr = source, body = destination
|
|
6
5
|
[
|
|
7
6
|
'## <mv path="[source]">[destination]</mv> - Move or rename a file or entry',
|
|
8
7
|
],
|
|
9
|
-
|
|
10
|
-
// --- Examples: entry rename and file move
|
|
11
8
|
[
|
|
12
9
|
'Example: <mv path="known://active_task">known://completed_task</mv>',
|
|
13
|
-
"Entry rename. Most common mv use case.
|
|
14
|
-
],
|
|
15
|
-
[
|
|
16
|
-
'Example: <mv path="src/old_name.js">src/new_name.js</mv>',
|
|
17
|
-
"File rename. Shows that mv works on files too, not just known entries.",
|
|
18
|
-
],
|
|
19
|
-
|
|
20
|
-
// --- Archive lifecycle
|
|
21
|
-
[
|
|
22
|
-
"* You may move entries or pattern-matching batches of entries to and from the archive to manage your context budget.",
|
|
23
|
-
"Teaches archival as a reversible budget operation, not permanent deletion.",
|
|
24
|
-
],
|
|
25
|
-
[
|
|
26
|
-
'Example: <mv path="known://project/*" fidelity="index"/> ... <mv path="known://project/active_sprint" fidelity="full"/>',
|
|
27
|
-
"Index a whole category to free context while keeping paths visible, restore one entry when needed. No destination = fidelity change in place.",
|
|
28
|
-
],
|
|
29
|
-
[
|
|
30
|
-
"* YOU SHOULD demote irrelevant entries to `index` or `archive` — clean context improves reasoning.",
|
|
31
|
-
"Core curation principle: clean context is a quality signal, not just a budget concern. Teach the model to curate eagerly.",
|
|
32
|
-
],
|
|
33
|
-
|
|
34
|
-
// --- Constraints
|
|
35
|
-
[
|
|
36
|
-
"* Source path accepts patterns for batch moves",
|
|
37
|
-
"Pattern support consistent with get/cp/rm.",
|
|
10
|
+
"Entry rename. Most common mv use case.",
|
|
38
11
|
],
|
|
12
|
+
['Example: <mv path="src/old_name.js">src/new_name.js</mv>', "File rename."],
|
|
39
13
|
[
|
|
40
|
-
|
|
41
|
-
"
|
|
14
|
+
'Example: <mv path="known://project/*" fidelity="demoted"/>',
|
|
15
|
+
"Batch fidelity change via pattern. No destination = fidelity in place.",
|
|
42
16
|
],
|
|
43
17
|
];
|
|
44
18
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import KnownStore from "../../agent/KnownStore.js";
|
|
2
|
+
|
|
3
|
+
export default class Policy {
|
|
4
|
+
constructor(core) {
|
|
5
|
+
core.filter("entry.recording", this.#enforceAskMode.bind(this), 1);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
async #enforceAskMode(entry, ctx) {
|
|
9
|
+
if (ctx.mode !== "ask") return entry;
|
|
10
|
+
|
|
11
|
+
if (entry.scheme === "sh") {
|
|
12
|
+
console.warn("[RUMMY] Rejected <sh> in ask mode");
|
|
13
|
+
return { ...entry, status: 403 };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (entry.scheme === "set" && entry.attributes?.path) {
|
|
17
|
+
const scheme = KnownStore.scheme(entry.attributes.path);
|
|
18
|
+
if (scheme === null && entry.body) {
|
|
19
|
+
console.warn(
|
|
20
|
+
`[RUMMY] Rejected file edit to ${entry.attributes.path} in ask mode`,
|
|
21
|
+
);
|
|
22
|
+
return { ...entry, status: 403 };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (entry.scheme === "rm") {
|
|
27
|
+
const pathAttr = entry.attributes?.path || entry.path;
|
|
28
|
+
const scheme = KnownStore.scheme(pathAttr);
|
|
29
|
+
if (scheme === null) {
|
|
30
|
+
console.warn(`[RUMMY] Rejected file rm of ${pathAttr} in ask mode`);
|
|
31
|
+
return { ...entry, status: 403 };
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (entry.scheme === "mv" || entry.scheme === "cp") {
|
|
36
|
+
const destScheme = KnownStore.scheme(entry.attributes?.to);
|
|
37
|
+
if (destScheme === null) {
|
|
38
|
+
console.warn(
|
|
39
|
+
`[RUMMY] Rejected ${entry.scheme} to file ${entry.attributes?.to} in ask mode`,
|
|
40
|
+
);
|
|
41
|
+
return { ...entry, status: 403 };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return entry;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -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,56 +10,34 @@ export default class Progress {
|
|
|
7
10
|
}
|
|
8
11
|
|
|
9
12
|
async assembleProgress(content, ctx) {
|
|
10
|
-
const {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const parts = [];
|
|
33
|
-
|
|
34
|
-
const knownCount = dataEntries.length;
|
|
35
|
-
const loggingCount = loggingEntries.length;
|
|
36
|
-
const tokenLine = contextSize
|
|
37
|
-
? `${usedTokens} of ${contextSize} tokens (${pct}%) · ${knownCount} known${knownCount !== 1 ? "s" : ""} · ${loggingCount} logging · ${unknownCount} unknown${unknownCount !== 1 ? "s" : ""}`
|
|
38
|
-
: "";
|
|
39
|
-
if (tokenLine) parts.push(tokenLine);
|
|
40
|
-
|
|
41
|
-
// Fidelity distribution
|
|
42
|
-
const fidelityParts = [];
|
|
43
|
-
if (fullEntries.length > 0)
|
|
44
|
-
fidelityParts.push(`${fullEntries.length} full (${fullTokens} tok)`);
|
|
45
|
-
if (summaryEntries.length > 0)
|
|
46
|
-
fidelityParts.push(
|
|
47
|
-
`${summaryEntries.length} summary (${summaryTokens} tok)`,
|
|
48
|
-
);
|
|
49
|
-
if (indexEntries.length > 0)
|
|
50
|
-
fidelityParts.push(`${indexEntries.length} index (${indexTokens} tok)`);
|
|
51
|
-
if (fidelityParts.length > 0)
|
|
52
|
-
parts.push(`Entries: ${fidelityParts.join(" · ")}`);
|
|
53
|
-
|
|
54
|
-
if (hasPerformed) {
|
|
55
|
-
parts.push(
|
|
56
|
-
"The above actions were performed in response to the following prompt:",
|
|
13
|
+
const { rows, contextSize, baselineTokens } = ctx;
|
|
14
|
+
const lines = [];
|
|
15
|
+
|
|
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);
|
|
32
|
+
lines.push(
|
|
33
|
+
`Token Budget: ${tokenBudget}. Using ${used}. ${remaining} remaining. Promote relevant entries with <get/> to spend. Demote irrelevant entries with <set fidelity="demoted"/> to save.`,
|
|
57
34
|
);
|
|
58
35
|
}
|
|
36
|
+
lines.push(
|
|
37
|
+
"Conclude with a brief <update></update> to continue or a brief <summarize></summarize> if done.",
|
|
38
|
+
);
|
|
39
|
+
const body = lines.join("\n");
|
|
59
40
|
|
|
60
|
-
return `${content}<progress turn="${ctx.turn}">${
|
|
41
|
+
return `${content}<progress turn="${ctx.turn}">${body}</progress>\n`;
|
|
61
42
|
}
|
|
62
43
|
}
|
|
@@ -3,7 +3,18 @@ export default class Prompt {
|
|
|
3
3
|
|
|
4
4
|
constructor(core) {
|
|
5
5
|
this.#core = core;
|
|
6
|
-
core.hooks.tools.onView("prompt", (entry) => entry.body);
|
|
6
|
+
core.hooks.tools.onView("prompt", (entry) => entry.body, "promoted");
|
|
7
|
+
core.hooks.tools.onView(
|
|
8
|
+
"prompt",
|
|
9
|
+
(entry) => {
|
|
10
|
+
const limit = 500;
|
|
11
|
+
const text = entry.body?.slice(0, limit) || "";
|
|
12
|
+
return text.length < (entry.body?.length || 0)
|
|
13
|
+
? `${text}\n[truncated — promote to see the complete prompt]`
|
|
14
|
+
: text;
|
|
15
|
+
},
|
|
16
|
+
"demoted",
|
|
17
|
+
);
|
|
7
18
|
core.on("turn.started", this.onTurnStarted.bind(this));
|
|
8
19
|
core.filter("assembly.user", this.assemblePrompt.bind(this), 300);
|
|
9
20
|
}
|
|
@@ -30,13 +41,14 @@ export default class Prompt {
|
|
|
30
41
|
: promptEntry?.attributes;
|
|
31
42
|
const mode = attrs?.mode || ctx.type;
|
|
32
43
|
const body = promptEntry?.body || "";
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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:".
|
|
37
49
|
let warn = "";
|
|
38
50
|
if (mode === "ask") warn = ' warn="File editing disallowed."';
|
|
39
51
|
|
|
40
|
-
return `${content}<prompt mode="${mode}"
|
|
52
|
+
return `${content}<prompt mode="${mode}"${warn}>${body}</prompt>`;
|
|
41
53
|
}
|
|
42
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
|
@@ -2,28 +2,19 @@
|
|
|
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
|
-
// --- Syntax: path attr, self-closing
|
|
6
5
|
['## <rm path="[path]"/> - Remove a file or entry'],
|
|
7
|
-
|
|
8
|
-
// --- Examples: file, known (with slug path), preview safety
|
|
9
6
|
['Example: <rm path="src/config.js"/>', "File removal. Simplest form."],
|
|
10
|
-
[
|
|
11
|
-
'Example: <rm path="known://config/deprecated_service"/>',
|
|
12
|
-
"Shows topic-hierarchy path convention. Paths are category/key, not sentence slugs.",
|
|
13
|
-
],
|
|
14
7
|
[
|
|
15
8
|
'Example: <rm path="known://temp_*" preview/>',
|
|
16
|
-
"Preview before deleting.
|
|
9
|
+
"Preview before deleting. Safety pattern for bulk operations.",
|
|
17
10
|
],
|
|
18
|
-
|
|
19
|
-
// --- Constraints
|
|
20
11
|
[
|
|
21
|
-
'* Permanent. Prefer <set fidelity="
|
|
22
|
-
"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.",
|
|
23
14
|
],
|
|
24
15
|
[
|
|
25
|
-
"*
|
|
26
|
-
"
|
|
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.",
|
|
27
18
|
],
|
|
28
19
|
];
|
|
29
20
|
|
package/src/plugins/rpc/rpc.js
CHANGED
|
@@ -111,7 +111,7 @@ export default class Rpc {
|
|
|
111
111
|
});
|
|
112
112
|
return { status: "ok" };
|
|
113
113
|
},
|
|
114
|
-
description: "Promote entry
|
|
114
|
+
description: "Promote entry fidelity.",
|
|
115
115
|
params: {
|
|
116
116
|
path: "string — file path or glob pattern",
|
|
117
117
|
run: "string — run alias",
|
|
@@ -178,7 +178,7 @@ export default class Rpc {
|
|
|
178
178
|
scheme: e.scheme,
|
|
179
179
|
status: e.status,
|
|
180
180
|
fidelity: e.fidelity,
|
|
181
|
-
tokens: e.
|
|
181
|
+
tokens: e.tokens,
|
|
182
182
|
}));
|
|
183
183
|
},
|
|
184
184
|
description: "Query entries by pattern.",
|
|
@@ -233,6 +233,7 @@ export default class Rpc {
|
|
|
233
233
|
contextLimit: params.contextLimit,
|
|
234
234
|
noRepo: params.noRepo,
|
|
235
235
|
noInteraction: params.noInteraction,
|
|
236
|
+
noProposals: params.noProposals,
|
|
236
237
|
noWeb: params.noWeb,
|
|
237
238
|
fork: params.fork,
|
|
238
239
|
},
|
|
@@ -269,6 +270,7 @@ export default class Rpc {
|
|
|
269
270
|
contextLimit: params.contextLimit,
|
|
270
271
|
noRepo: params.noRepo,
|
|
271
272
|
noInteraction: params.noInteraction,
|
|
273
|
+
noProposals: params.noProposals,
|
|
272
274
|
noWeb: params.noWeb,
|
|
273
275
|
fork: params.fork,
|
|
274
276
|
},
|