@possumtech/rummy 0.3.0 → 0.4.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 +13 -1
- package/PLUGINS.md +1 -1
- package/README.md +5 -1
- package/SPEC.md +211 -54
- package/migrations/001_initial_schema.sql +3 -4
- package/package.json +7 -3
- package/service.js +5 -3
- package/src/agent/AgentLoop.js +183 -238
- package/src/agent/ContextAssembler.js +2 -0
- package/src/agent/KnownStore.js +36 -85
- package/src/agent/ResponseHealer.js +65 -31
- package/src/agent/TurnExecutor.js +284 -382
- package/src/agent/XmlParser.js +28 -4
- package/src/agent/known_queries.sql +1 -1
- package/src/agent/known_store.sql +32 -34
- package/src/agent/runs.sql +2 -2
- package/src/agent/tokens.js +1 -0
- package/src/agent/turns.sql +5 -0
- package/src/hooks/HookRegistry.js +7 -0
- package/src/hooks/Hooks.js +2 -4
- package/src/hooks/ToolRegistry.js +8 -13
- package/src/plugins/ask_user/ask_userDoc.js +3 -8
- package/src/plugins/budget/README.md +26 -30
- package/src/plugins/budget/budget.js +69 -36
- package/src/plugins/budget/recovery.js +47 -0
- package/src/plugins/cp/cp.js +1 -1
- package/src/plugins/cp/cpDoc.js +5 -10
- package/src/plugins/env/envDoc.js +3 -8
- package/src/plugins/get/get.js +70 -2
- package/src/plugins/get/getDoc.js +19 -16
- package/src/plugins/hedberg/matcher.js +10 -29
- package/src/plugins/helpers.js +2 -2
- package/src/plugins/instructions/instructions.js +3 -2
- package/src/plugins/instructions/preamble.md +33 -12
- package/src/plugins/known/known.js +66 -17
- package/src/plugins/known/knownDoc.js +7 -10
- package/src/plugins/mv/mv.js +18 -1
- package/src/plugins/mv/mvDoc.js +9 -10
- package/src/plugins/{current → performed}/README.md +4 -3
- package/src/plugins/{current/current.js → performed/performed.js} +15 -20
- package/src/plugins/policy/policy.js +47 -0
- package/src/plugins/previous/README.md +2 -1
- package/src/plugins/previous/previous.js +31 -25
- package/src/plugins/progress/README.md +1 -2
- package/src/plugins/progress/progress.js +10 -60
- package/src/plugins/prompt/prompt.js +10 -8
- package/src/plugins/rm/rm.js +27 -15
- package/src/plugins/rm/rmDoc.js +6 -11
- package/src/plugins/rpc/rpc.js +3 -1
- package/src/plugins/set/set.js +125 -92
- package/src/plugins/set/setDoc.js +28 -37
- package/src/plugins/sh/shDoc.js +2 -7
- package/src/plugins/summarize/summarize.js +7 -0
- package/src/plugins/summarize/summarizeDoc.js +6 -11
- package/src/plugins/telemetry/telemetry.js +14 -9
- package/src/plugins/think/think.js +12 -0
- package/src/plugins/think/thinkDoc.js +18 -0
- package/src/plugins/unknown/README.md +2 -1
- package/src/plugins/unknown/unknown.js +26 -4
- package/src/plugins/unknown/unknownDoc.js +9 -14
- package/src/plugins/update/update.js +7 -0
- package/src/plugins/update/updateDoc.js +6 -11
- package/src/server/ClientConnection.js +69 -45
- package/src/sql/v_model_context.sql +7 -17
- package/src/plugins/budget/BudgetGuard.js +0 -74
package/src/plugins/cp/cpDoc.js
CHANGED
|
@@ -2,27 +2,22 @@
|
|
|
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
|
['## <cp path="[source]">[destination]</cp> - Copy a file or entry'],
|
|
7
|
-
|
|
8
|
-
// --- Examples: single copy, glob batch, cross-scheme
|
|
9
6
|
[
|
|
10
7
|
'Example: <cp path="src/config.js">src/config.backup.js</cp>',
|
|
11
8
|
"Simple file copy. Path = source, body = destination.",
|
|
12
9
|
],
|
|
13
10
|
[
|
|
14
11
|
'Example: <cp path="known://plan_*">known://archive_</cp>',
|
|
15
|
-
"Glob batch copy across known entries.
|
|
12
|
+
"Glob batch copy across known entries.",
|
|
16
13
|
],
|
|
17
|
-
|
|
18
|
-
// --- Constraints
|
|
19
14
|
[
|
|
20
|
-
"* Source path accepts
|
|
21
|
-
"Pattern support
|
|
15
|
+
"* Source path accepts patterns: `src/*.js`, `known://draft_*`",
|
|
16
|
+
"Pattern support consistent with get/rm.",
|
|
22
17
|
],
|
|
23
18
|
[
|
|
24
|
-
"* Use `preview` to check matches before bulk copy",
|
|
25
|
-
"Safety pattern consistent with
|
|
19
|
+
"* Use `preview` to check matches before pattern-based bulk copy",
|
|
20
|
+
"Safety pattern consistent with rm.",
|
|
26
21
|
],
|
|
27
22
|
];
|
|
28
23
|
|
|
@@ -2,10 +2,7 @@
|
|
|
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
|
|
6
5
|
["## <env>[command]</env> - Run an exploratory shell command"],
|
|
7
|
-
|
|
8
|
-
// --- Examples: version check and git status — safe, read-only commands
|
|
9
6
|
[
|
|
10
7
|
"Example: <env>npm --version</env>",
|
|
11
8
|
"Version check. Safe, no side effects.",
|
|
@@ -14,15 +11,13 @@ const LINES = [
|
|
|
14
11
|
"Example: <env>git log --oneline -5</env>",
|
|
15
12
|
"Git history. Shows env for read-only investigation.",
|
|
16
13
|
],
|
|
17
|
-
|
|
18
|
-
// --- Constraints: hard boundaries
|
|
19
14
|
[
|
|
20
15
|
'* YOU MUST NOT use <env/> to read or list files — use <get path="*" preview/> instead',
|
|
21
|
-
"Prevents cat/ls through shell. Forces file access through get
|
|
16
|
+
"Prevents cat/ls through shell. Forces file access through get.",
|
|
22
17
|
],
|
|
23
18
|
[
|
|
24
|
-
"* YOU MUST use <
|
|
25
|
-
"Separates exploration from action. env = observe
|
|
19
|
+
"* YOU MUST NOT use <env/> for commands with side effects",
|
|
20
|
+
"Separates exploration from action. env = observe only.",
|
|
26
21
|
],
|
|
27
22
|
];
|
|
28
23
|
|
package/src/plugins/get/get.js
CHANGED
|
@@ -30,13 +30,79 @@ export default class Get {
|
|
|
30
30
|
const normalized = KnownStore.normalizePath(target);
|
|
31
31
|
const bodyFilter = entry.attributes.body || null;
|
|
32
32
|
const isPattern = bodyFilter || normalized.includes("*");
|
|
33
|
+
|
|
34
|
+
const line =
|
|
35
|
+
entry.attributes.line != null
|
|
36
|
+
? Math.max(1, parseInt(entry.attributes.line, 10))
|
|
37
|
+
: null;
|
|
38
|
+
const limit =
|
|
39
|
+
entry.attributes.limit != null
|
|
40
|
+
? Math.max(1, parseInt(entry.attributes.limit, 10))
|
|
41
|
+
: null;
|
|
42
|
+
|
|
33
43
|
const matches = await store.getEntriesByPattern(
|
|
34
44
|
runId,
|
|
35
45
|
normalized,
|
|
36
46
|
bodyFilter,
|
|
37
47
|
);
|
|
38
48
|
|
|
49
|
+
// Partial read — no fidelity promotion, returns a line slice as the log item.
|
|
50
|
+
if (line !== null || limit !== null) {
|
|
51
|
+
if (isPattern) {
|
|
52
|
+
await store.upsert(
|
|
53
|
+
runId,
|
|
54
|
+
turn,
|
|
55
|
+
entry.resultPath,
|
|
56
|
+
"line/limit requires a single path, not a glob or body filter",
|
|
57
|
+
400,
|
|
58
|
+
{ loopId },
|
|
59
|
+
);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
if (matches.length === 0) {
|
|
63
|
+
await store.upsert(
|
|
64
|
+
runId,
|
|
65
|
+
turn,
|
|
66
|
+
entry.resultPath,
|
|
67
|
+
`${target} not found`,
|
|
68
|
+
200,
|
|
69
|
+
{ loopId },
|
|
70
|
+
);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
const allLines = matches[0].body.split("\n");
|
|
74
|
+
const total = allLines.length;
|
|
75
|
+
const startLine = line ?? 1;
|
|
76
|
+
const startIdx = startLine - 1;
|
|
77
|
+
const endIdx = limit !== null ? Math.min(startIdx + limit, total) : total;
|
|
78
|
+
const slice = allLines.slice(startIdx, endIdx).join("\n");
|
|
79
|
+
const endLine = endIdx;
|
|
80
|
+
const header = `[lines ${startLine}–${endLine} / ${total} total]`;
|
|
81
|
+
await store.upsert(
|
|
82
|
+
runId,
|
|
83
|
+
turn,
|
|
84
|
+
entry.resultPath,
|
|
85
|
+
`${header}\n${slice}`,
|
|
86
|
+
200,
|
|
87
|
+
{ loopId },
|
|
88
|
+
);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const VALID_FIDELITY = {
|
|
93
|
+
summary: 1,
|
|
94
|
+
full: 1,
|
|
95
|
+
archive: 1,
|
|
96
|
+
};
|
|
97
|
+
const fidelityAttr = VALID_FIDELITY[entry.attributes.fidelity]
|
|
98
|
+
? entry.attributes.fidelity
|
|
99
|
+
: null;
|
|
100
|
+
|
|
39
101
|
await store.promoteByPattern(runId, normalized, bodyFilter, turn);
|
|
102
|
+
if (fidelityAttr) {
|
|
103
|
+
for (const match of matches)
|
|
104
|
+
await store.setFidelity(runId, match.path, fidelityAttr);
|
|
105
|
+
}
|
|
40
106
|
|
|
41
107
|
if (isPattern) {
|
|
42
108
|
await storePatternResult(
|
|
@@ -50,10 +116,12 @@ export default class Get {
|
|
|
50
116
|
{ loopId },
|
|
51
117
|
);
|
|
52
118
|
} else {
|
|
53
|
-
const total = matches.reduce((s, m) => s + m.
|
|
119
|
+
const total = matches.reduce((s, m) => s + m.tokens, 0);
|
|
54
120
|
const paths = matches.map((m) => m.path).join(", ");
|
|
55
121
|
const body =
|
|
56
|
-
matches.length > 0
|
|
122
|
+
matches.length > 0
|
|
123
|
+
? `${paths} promoted to full (${total} tokens)`
|
|
124
|
+
: `${target} not found`;
|
|
57
125
|
await store.upsert(runId, turn, entry.resultPath, body, 200, {
|
|
58
126
|
loopId,
|
|
59
127
|
});
|
|
@@ -2,39 +2,42 @@
|
|
|
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: body-form is the primary invocation (simplest)
|
|
6
5
|
["## <get>[path/to/file]</get> - Load a file or entry into context"],
|
|
7
|
-
|
|
8
|
-
// --- Examples: 3 examples covering single file, known recall, and content search
|
|
9
6
|
[
|
|
10
7
|
"Example: <get>src/app.js</get>",
|
|
11
|
-
"Simplest form. Body = path.
|
|
8
|
+
"Simplest form. Body = path.",
|
|
12
9
|
],
|
|
13
10
|
[
|
|
14
11
|
'Example: <get path="known://*">auth</get>',
|
|
15
|
-
"Keyword recall: glob in path, search term in body.
|
|
12
|
+
"Keyword recall: glob in path, search term in body.",
|
|
16
13
|
],
|
|
17
14
|
[
|
|
18
|
-
'Example: <get path="src/**/*.js" preview>
|
|
19
|
-
"Full pattern: recursive glob + preview + content filter.
|
|
15
|
+
'Example: <get path="src/**/*.js" preview>authentication</get>',
|
|
16
|
+
"Full pattern: recursive glob + preview + content filter.",
|
|
17
|
+
],
|
|
18
|
+
[
|
|
19
|
+
'Example: <get path="src/agent/AgentLoop.js" line="644" limit="80"/>',
|
|
20
|
+
"Partial read. Returns lines 644–723 without promoting.",
|
|
20
21
|
],
|
|
21
|
-
|
|
22
|
-
// --- Constraints: RFC-style. Each prevents a specific failure mode.
|
|
23
22
|
[
|
|
24
|
-
"* Paths accept
|
|
25
|
-
"Reinforces picomatch patterns work everywhere
|
|
23
|
+
"* Paths accept patterns: `src/**/*.js`, `known://api_*`",
|
|
24
|
+
"Reinforces picomatch patterns work everywhere.",
|
|
26
25
|
],
|
|
27
26
|
[
|
|
28
|
-
"* `preview`
|
|
29
|
-
"Budget-awareness.
|
|
27
|
+
"* `preview` lists matches without loading into context",
|
|
28
|
+
"Budget-awareness. Preview avoids promotion.",
|
|
30
29
|
],
|
|
31
30
|
[
|
|
32
31
|
"* Body text filters results by content match",
|
|
33
|
-
"
|
|
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.",
|
|
34
37
|
],
|
|
35
38
|
[
|
|
36
|
-
'* Use <set path="
|
|
37
|
-
"
|
|
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.",
|
|
38
41
|
],
|
|
39
42
|
];
|
|
40
43
|
|
|
@@ -1,34 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
1
|
+
import { createTwoFilesPatch } from "diff";
|
|
5
2
|
|
|
6
3
|
export function generatePatch(filePath, oldContent, newContent) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
`diff -u --label "${filePath}\told" --label "${filePath}\tnew" "${oldPath}" "${newPath}"`,
|
|
17
|
-
{ encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] },
|
|
18
|
-
);
|
|
19
|
-
return result;
|
|
20
|
-
} catch (err) {
|
|
21
|
-
// diff exits 1 when files differ — that's the success case
|
|
22
|
-
if (err.stdout) return err.stdout;
|
|
23
|
-
return "";
|
|
24
|
-
} finally {
|
|
25
|
-
try {
|
|
26
|
-
unlinkSync(oldPath);
|
|
27
|
-
} catch {}
|
|
28
|
-
try {
|
|
29
|
-
unlinkSync(newPath);
|
|
30
|
-
} catch {}
|
|
31
|
-
}
|
|
4
|
+
return createTwoFilesPatch(
|
|
5
|
+
`${filePath}\told`,
|
|
6
|
+
`${filePath}\tnew`,
|
|
7
|
+
oldContent,
|
|
8
|
+
newContent,
|
|
9
|
+
"",
|
|
10
|
+
"",
|
|
11
|
+
{ context: 3 },
|
|
12
|
+
);
|
|
32
13
|
}
|
|
33
14
|
|
|
34
15
|
export default class HeuristicMatcher {
|
package/src/plugins/helpers.js
CHANGED
|
@@ -14,8 +14,8 @@ export async function storePatternResult(
|
|
|
14
14
|
) {
|
|
15
15
|
const slug = await store.slugPath(runId, scheme, path);
|
|
16
16
|
const filter = bodyFilter ? ` body="${bodyFilter}"` : "";
|
|
17
|
-
const total = matches.reduce((s, m) => s + m.
|
|
18
|
-
const listing = matches.map((m) => `${m.path} (${m.
|
|
17
|
+
const total = matches.reduce((s, m) => s + m.tokens, 0);
|
|
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
21
|
await store.upsert(runId, turn, slug, body, 200, { loopId });
|
|
@@ -37,7 +37,6 @@ export default class Instructions {
|
|
|
37
37
|
activeTools.has(n),
|
|
38
38
|
);
|
|
39
39
|
const tools = sorted.join(", ");
|
|
40
|
-
let prompt = preamble.replace("[%TOOLS%]", tools);
|
|
41
40
|
const toolDocs = await this.#core.hooks.instructions.toolDocs.filter(
|
|
42
41
|
{},
|
|
43
42
|
{ toolSet: activeTools },
|
|
@@ -46,7 +45,9 @@ export default class Instructions {
|
|
|
46
45
|
.filter((key) => toolDocs[key])
|
|
47
46
|
.map((key) => toolDocs[key])
|
|
48
47
|
.join("\n\n");
|
|
49
|
-
|
|
48
|
+
let prompt = preamble
|
|
49
|
+
.replace("[%TOOLS%]", tools)
|
|
50
|
+
.replace("[%TOOLDOCS%]", docsText);
|
|
50
51
|
if (attrs.persona) prompt += `\n\n## Persona\n\n${attrs.persona}`;
|
|
51
52
|
return prompt;
|
|
52
53
|
}
|
|
@@ -1,16 +1,37 @@
|
|
|
1
|
-
You are
|
|
2
|
-
|
|
3
|
-
# Response Rules
|
|
4
|
-
|
|
5
|
-
Required: YOU MUST respond with Tool Commands in the XML format. YOU MAY use multiple tools in your response.
|
|
6
|
-
Optional: YOU MAY think in an optional <think></think> tag before using any other Tool Commands.
|
|
7
|
-
Required: YOU MUST register all unknowns with <unknown>(specific thing I need to learn)</unknown>.
|
|
8
|
-
Required: YOU MUST register all new information, decisions, and plans with <known>(specific information, ideas, or plans)</known>.
|
|
9
|
-
Required: YOU MUST conclude every turn with EITHER <update/> if still working OR <summarize/> if done. Never both.
|
|
10
|
-
Required: Path and summary information is approximate. YOU MUST use <get> to verify before acting on summarized content.
|
|
11
|
-
Info: When information conflicts, later turns are more likely to be relevant and correct than earlier turns.
|
|
12
|
-
Info: Your context is limited but your storage is not. Organize and categorize your information, ideas, plans, and history to optimize your context.
|
|
1
|
+
You are a folksonomic knowledgebase assistant. YOU MUST discern what you don't know into unknowns, then extract and organize your findings into navigable and searchable knowns, then YOU MAY answer questions and/or perform actions.
|
|
13
2
|
|
|
14
3
|
# Tool Commands
|
|
15
4
|
|
|
16
5
|
Tools: [%TOOLS%]
|
|
6
|
+
|
|
7
|
+
# Tool Rules
|
|
8
|
+
|
|
9
|
+
## Response Rules
|
|
10
|
+
Required: YOU MUST respond with Tool Commands in the XML format. YOU MAY use up to 12 tools in your response.
|
|
11
|
+
Required: YOU MUST register all unknowns with <unknown>[specific thing I need to learn]</unknown>.
|
|
12
|
+
Required: YOU MUST register all new facts, decisions, and plans with <known path="topic/subtopic" summary="keyword,keyword,keyword">[specific facts, decisions, or plans]</known>.
|
|
13
|
+
|
|
14
|
+
## Folksonomic Memory Management
|
|
15
|
+
* Write paths with navigable hierarchies and summaries with searchable tags.
|
|
16
|
+
* When new facts, decisions, and plans appear, set them as <known/> entries with navigable hierarchies and summaries with searchable tags to improve your folksonomic knowledgebase.
|
|
17
|
+
* When new questions emerge, use pattern matching operations to optimize the fidelity and relevance of your knowledgebase.
|
|
18
|
+
* The turn attribute can be helpful for discerning what's fresh or stale, prefer more recent information if conflicts exist.
|
|
19
|
+
* YOU MUST promote all relevant entries and demote all irrelevant entries before acting or answering. Use body pattern search (Example: <get path="known://*">John Doe</get>) to recall archived entries when needed.
|
|
20
|
+
* Logging entries in <previous/> can also be demoted to optimize context.
|
|
21
|
+
|
|
22
|
+
## Fidelity Management
|
|
23
|
+
* full: Entire contents are shown (consumes token budget)
|
|
24
|
+
* summary: Only path and summary are shown. (<= 80 chars, saves token budget)
|
|
25
|
+
* archive: Archived in an unlimited archive. Entries can be recalled with path recall or pattern search. (use caution)
|
|
26
|
+
|
|
27
|
+
## Token Budget Management
|
|
28
|
+
* Entries contain a "fidelity" and a "token" attribute to enable token budget management and context optimization.
|
|
29
|
+
* Set relevant entries to "full" and irrelevant entries to "summary" to optimize context.
|
|
30
|
+
* The less irrelevant information in your context, the better.
|
|
31
|
+
|
|
32
|
+
## Response Termination
|
|
33
|
+
Required: YOU MUST conclude every turn with EITHER <update></update> if still working OR <summarize></summarize> if done. Never both.
|
|
34
|
+
|
|
35
|
+
# Tool Usage
|
|
36
|
+
|
|
37
|
+
[%TOOLDOCS%]
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import { countTokens } from "../../agent/tokens.js";
|
|
1
2
|
import docs from "./knownDoc.js";
|
|
2
3
|
|
|
4
|
+
const MAX_ENTRY_TOKENS = Number(process.env.RUMMY_MAX_ENTRY_TOKENS) || 512;
|
|
5
|
+
|
|
3
6
|
export default class Known {
|
|
4
7
|
#core;
|
|
5
8
|
|
|
@@ -8,6 +11,7 @@ export default class Known {
|
|
|
8
11
|
core.registerScheme({ category: "data" });
|
|
9
12
|
core.on("handler", this.handler.bind(this));
|
|
10
13
|
core.on("full", this.full.bind(this));
|
|
14
|
+
core.on("summary", this.summary.bind(this));
|
|
11
15
|
core.filter("assembly.system", this.assembleKnown.bind(this), 100);
|
|
12
16
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
13
17
|
docsMap.known = docs;
|
|
@@ -16,28 +20,78 @@ export default class Known {
|
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
async handler(entry, rummy) {
|
|
19
|
-
const { entries: store, sequence: turn, runId } = rummy;
|
|
20
|
-
|
|
21
|
-
|
|
23
|
+
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
24
|
+
if (!entry.body) return;
|
|
25
|
+
|
|
26
|
+
// Size gate
|
|
27
|
+
const entryTokens = countTokens(entry.body);
|
|
28
|
+
if (entryTokens > MAX_ENTRY_TOKENS) {
|
|
29
|
+
const rejectPath = await store.slugPath(runId, "known", entry.body);
|
|
30
|
+
await store.upsert(
|
|
31
|
+
runId,
|
|
32
|
+
turn,
|
|
33
|
+
rejectPath,
|
|
34
|
+
`Entry too large (${entryTokens} tokens, max ${MAX_ENTRY_TOKENS}). Sort the information, ideas, or plans carefully into multiple entries.`,
|
|
35
|
+
413,
|
|
36
|
+
{ loopId },
|
|
37
|
+
);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Resolve path: explicit or auto-generated slug
|
|
42
|
+
let knownPath = entry.attributes?.path || null;
|
|
43
|
+
if (knownPath && !knownPath.includes("://")) {
|
|
44
|
+
knownPath = `known://${knownPath}`;
|
|
45
|
+
}
|
|
46
|
+
if (!knownPath) {
|
|
47
|
+
knownPath = await store.slugPath(
|
|
48
|
+
runId,
|
|
49
|
+
"known",
|
|
50
|
+
entry.body,
|
|
51
|
+
entry.attributes?.summary,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Dedup: if path exists, update rather than duplicate
|
|
56
|
+
const existing = await store.getEntriesByPattern(runId, knownPath, null);
|
|
57
|
+
if (existing.length > 0) {
|
|
58
|
+
await store.upsert(
|
|
59
|
+
runId,
|
|
60
|
+
turn,
|
|
61
|
+
existing[0].path,
|
|
62
|
+
entry.body || existing[0].body,
|
|
63
|
+
200,
|
|
64
|
+
{ attributes: entry.attributes, loopId },
|
|
65
|
+
);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await store.upsert(runId, turn, knownPath, entry.body, 200, {
|
|
70
|
+
attributes: entry.attributes,
|
|
71
|
+
loopId,
|
|
72
|
+
});
|
|
22
73
|
}
|
|
23
74
|
|
|
24
75
|
full(entry) {
|
|
25
76
|
return `# known ${entry.path}\n${entry.body}`;
|
|
26
77
|
}
|
|
27
78
|
|
|
79
|
+
summary(entry) {
|
|
80
|
+
return this.full(entry);
|
|
81
|
+
}
|
|
82
|
+
|
|
28
83
|
async assembleKnown(content, ctx) {
|
|
29
84
|
const entries = ctx.rows.filter((r) => r.category === "data");
|
|
30
85
|
if (entries.length === 0) return content;
|
|
31
86
|
|
|
32
|
-
// Rows arrive pre-sorted by SQL:
|
|
87
|
+
// Rows arrive pre-sorted by SQL: summary → full, then by recency
|
|
33
88
|
const demotedSet = new Set(ctx.demoted || []);
|
|
34
|
-
const
|
|
35
|
-
const lines = entries.map((e) => renderKnownTag(e, demotedSet, panic));
|
|
89
|
+
const lines = entries.map((e) => renderKnownTag(e, demotedSet));
|
|
36
90
|
return `${content}\n\n<knowns>\n${lines.join("\n")}\n</knowns>`;
|
|
37
91
|
}
|
|
38
92
|
}
|
|
39
93
|
|
|
40
|
-
function renderKnownTag(entry, demotedSet
|
|
94
|
+
function renderKnownTag(entry, demotedSet) {
|
|
41
95
|
const tag = entry.scheme || "file";
|
|
42
96
|
const turn = entry.source_turn ? ` turn="${entry.source_turn}"` : "";
|
|
43
97
|
const tokens = entry.tokens ? ` tokens="${entry.tokens}"` : "";
|
|
@@ -45,23 +99,18 @@ function renderKnownTag(entry, demotedSet, panic = false) {
|
|
|
45
99
|
const fidelity = entry.fidelity ? ` fidelity="${entry.fidelity}"` : "";
|
|
46
100
|
const flag = demotedSet?.has(entry.path) ? " demoted" : "";
|
|
47
101
|
|
|
48
|
-
// Panic mode: index-only view so context fits in LLM window
|
|
49
|
-
if (panic) {
|
|
50
|
-
return `<${tag} path="${entry.path}"${turn}${fidelity}${tokens}/>`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
102
|
const attrs =
|
|
54
103
|
typeof entry.attributes === "string"
|
|
55
104
|
? JSON.parse(entry.attributes)
|
|
56
105
|
: entry.attributes;
|
|
57
106
|
const summary =
|
|
58
107
|
typeof attrs?.summary === "string"
|
|
59
|
-
? ` summary="${attrs.summary.slice(0, 80)}"`
|
|
108
|
+
? ` summary="${attrs.summary.replace(/"/g, "'").slice(0, 80)}"`
|
|
60
109
|
: "";
|
|
61
110
|
|
|
62
|
-
if (entry.
|
|
63
|
-
|
|
111
|
+
if (entry.fidelity === "archive") return "";
|
|
112
|
+
if (entry.fidelity === "summary") {
|
|
113
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}/>`;
|
|
64
114
|
}
|
|
65
|
-
|
|
66
|
-
return `<${tag} path="${entry.path}"${turn}${status}${fidelity}${summary}${tokens}${flag}/>`;
|
|
115
|
+
return `<${tag} path="${entry.path}"${turn}${status}${summary}${fidelity}${tokens}${flag}>${entry.body}</${tag}>`;
|
|
67
116
|
}
|
|
@@ -2,30 +2,27 @@
|
|
|
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: 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: summary-with-keywords first (teaches the right pattern)
|
|
10
8
|
[
|
|
11
9
|
'Example: <known summary="hedberg,comedian,death,2005">Mitch Hedberg died on March 30, 2005</known>',
|
|
12
|
-
"
|
|
10
|
+
"Summary-first pattern: comma-separated keywords, path auto-generated.",
|
|
13
11
|
],
|
|
14
12
|
[
|
|
15
13
|
'Example: <known path="known://people/rumsfeld" summary="defense,secretary,born,1932">Donald Rumsfeld was born in 1932 and served as Secretary of Defense</known>',
|
|
16
|
-
"Explicit path form: slashed path=category/key, summary=keywords.
|
|
14
|
+
"Explicit path form: slashed path=category/key, summary=keywords.",
|
|
17
15
|
],
|
|
18
|
-
// --- Lifecycle
|
|
19
16
|
[
|
|
20
17
|
'* Recall with <get path="known://people/*">keyword</get>',
|
|
21
|
-
"Cross-tool lifecycle: glob by category, filter by keyword.
|
|
18
|
+
"Cross-tool lifecycle: glob by category, filter by keyword.",
|
|
22
19
|
],
|
|
23
20
|
[
|
|
24
|
-
"* `summary` keywords
|
|
25
|
-
"
|
|
21
|
+
"* YOU SHOULD write `summary` keywords, you can search for them later",
|
|
22
|
+
"Motivates summary writing through self-interest.",
|
|
26
23
|
],
|
|
27
24
|
[
|
|
28
|
-
"* YOU MUST sort and save all new
|
|
25
|
+
"* YOU MUST sort and save all new facts, decisions, and plans in their own <known> entries",
|
|
29
26
|
"Critical behavioral constraint. 'new' prevents re-saving known facts.",
|
|
30
27
|
],
|
|
31
28
|
];
|
package/src/plugins/mv/mv.js
CHANGED
|
@@ -19,11 +19,28 @@ export default class Mv {
|
|
|
19
19
|
async handler(entry, rummy) {
|
|
20
20
|
const { entries: store, sequence: turn, runId, loopId } = rummy;
|
|
21
21
|
const { path, to } = entry.attributes;
|
|
22
|
-
const VALID = { stored: 1, summary: 1, index: 1, full: 1 };
|
|
22
|
+
const VALID = { stored: 1, summary: 1, index: 1, full: 1, archive: 1 };
|
|
23
23
|
const fidelity = VALID[entry.attributes.fidelity]
|
|
24
24
|
? entry.attributes.fidelity
|
|
25
25
|
: undefined;
|
|
26
26
|
|
|
27
|
+
// Fidelity-in-place: no destination, change visibility of matched entries
|
|
28
|
+
if (fidelity && !to) {
|
|
29
|
+
const matches = await store.getEntriesByPattern(runId, path);
|
|
30
|
+
for (const match of matches)
|
|
31
|
+
await store.setFidelity(runId, match.path, fidelity);
|
|
32
|
+
const label = fidelity === "archive" ? "archived" : `set to ${fidelity}`;
|
|
33
|
+
await store.upsert(
|
|
34
|
+
runId,
|
|
35
|
+
turn,
|
|
36
|
+
entry.resultPath,
|
|
37
|
+
`${matches.map((m) => m.path).join(", ")} ${label}`,
|
|
38
|
+
200,
|
|
39
|
+
{ fidelity: "archive", loopId },
|
|
40
|
+
);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
27
44
|
const source = await store.getBody(runId, path);
|
|
28
45
|
if (source === null) return;
|
|
29
46
|
|
package/src/plugins/mv/mvDoc.js
CHANGED
|
@@ -2,29 +2,28 @@
|
|
|
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.
|
|
10
|
+
"Entry rename. Most common mv use case.",
|
|
14
11
|
],
|
|
15
12
|
[
|
|
16
13
|
'Example: <mv path="src/old_name.js">src/new_name.js</mv>',
|
|
17
|
-
"File rename.
|
|
14
|
+
"File rename.",
|
|
15
|
+
],
|
|
16
|
+
[
|
|
17
|
+
'Example: <mv path="known://project/*" fidelity="summary"/>',
|
|
18
|
+
"Batch fidelity change via pattern. No destination = fidelity in place.",
|
|
18
19
|
],
|
|
19
|
-
|
|
20
|
-
// --- Constraints
|
|
21
20
|
[
|
|
22
|
-
"* Source path accepts
|
|
21
|
+
"* Source path accepts patterns for batch moves",
|
|
23
22
|
"Pattern support consistent with get/cp/rm.",
|
|
24
23
|
],
|
|
25
24
|
[
|
|
26
|
-
"*
|
|
27
|
-
"
|
|
25
|
+
"* Use `preview` to check matches before pattern-based bulk moves",
|
|
26
|
+
"Safety pattern consistent with rm/cp.",
|
|
28
27
|
],
|
|
29
28
|
];
|
|
30
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
#
|
|
1
|
+
# performed
|
|
2
2
|
|
|
3
|
-
Renders the `<
|
|
3
|
+
Renders the `<performed>` section of the user message — the active loop's
|
|
4
4
|
tool results and lifecycle signals.
|
|
5
5
|
|
|
6
6
|
## Registration
|
|
@@ -11,4 +11,5 @@ tool results and lifecycle signals.
|
|
|
11
11
|
|
|
12
12
|
Filters turn_context rows where `category === "logging"` and
|
|
13
13
|
`source_turn >= loopStartTurn`. Renders each entry chronologically
|
|
14
|
-
with turn
|
|
14
|
+
with turn, status, summary, fidelity, and tokens. Empty on the first
|
|
15
|
+
turn of a loop.
|