@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
|
@@ -1,10 +1,44 @@
|
|
|
1
|
+
import { ceiling, computeBudget, measureMessages } from "../../agent/budget.js";
|
|
2
|
+
import materializeContext from "../../agent/materializeContext.js";
|
|
1
3
|
import { countTokens } from "../../agent/tokens.js";
|
|
2
4
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Delta-from-actual baseline. The pre-call <prompt tokenUsage> reports
|
|
7
|
+
* the prior turn's actual API prompt_tokens; post-dispatch predicts
|
|
8
|
+
* next turn's packet = this turn's actual tokens + tokens of new rows
|
|
9
|
+
* written this turn. Keeps the 413 body on the same scale as the
|
|
10
|
+
* model's <prompt> arithmetic — a 60% divergence between pre-call
|
|
11
|
+
* (actual) and post-check (conservative estimator) makes the model
|
|
12
|
+
* dismiss the system as janky and stop following rules.
|
|
13
|
+
*/
|
|
14
|
+
function predictNextPacket(rows, currentTurn, baseline) {
|
|
15
|
+
let delta = 0;
|
|
16
|
+
for (const r of rows) {
|
|
17
|
+
if (r.source_turn === currentTurn) delta += countTokens(r.body);
|
|
18
|
+
}
|
|
19
|
+
return baseline + delta;
|
|
20
|
+
}
|
|
5
21
|
|
|
6
|
-
|
|
7
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Format the 413 error body. Names each demoted path with its turn
|
|
24
|
+
* and token count so the model can avoid re-promoting them next turn.
|
|
25
|
+
* Exported (not private) so unit tests can assert the exact wire
|
|
26
|
+
* format — the model reads this string, so its shape is part of the
|
|
27
|
+
* contract.
|
|
28
|
+
*/
|
|
29
|
+
export function overflowBody(overflow, contextSize, demoted) {
|
|
30
|
+
const cap = ceiling(contextSize);
|
|
31
|
+
const size = cap + overflow;
|
|
32
|
+
const count = demoted.length;
|
|
33
|
+
const totalTokens = demoted.reduce((s, r) => s + r.tokens, 0);
|
|
34
|
+
const head = `Token Budget overflow: packet was ${size} tokens, ceiling is ${cap}. ${count} promotion${count === 1 ? "" : "s"} (${totalTokens} tokens) demoted to fit.`;
|
|
35
|
+
if (count === 0) return head;
|
|
36
|
+
const lines = demoted.map((d) =>
|
|
37
|
+
d.turn
|
|
38
|
+
? `- ${d.path} (turn ${d.turn}, ${d.tokens} tokens)`
|
|
39
|
+
: `- ${d.path} (${d.tokens} tokens)`,
|
|
40
|
+
);
|
|
41
|
+
return `${head}\nDemoted:\n${lines.join("\n")}`;
|
|
8
42
|
}
|
|
9
43
|
|
|
10
44
|
export default class Budget {
|
|
@@ -12,121 +46,305 @@ export default class Budget {
|
|
|
12
46
|
|
|
13
47
|
constructor(core) {
|
|
14
48
|
this.#core = core;
|
|
15
|
-
core.registerScheme({
|
|
16
|
-
name: "budget",
|
|
17
|
-
modelVisible: 1,
|
|
18
|
-
category: "logging",
|
|
19
|
-
});
|
|
20
|
-
core.hooks.tools.onView("budget", (entry) => entry.body);
|
|
21
49
|
core.hooks.budget = {
|
|
22
50
|
enforce: this.enforce.bind(this),
|
|
23
51
|
postDispatch: this.postDispatch.bind(this),
|
|
24
52
|
};
|
|
53
|
+
core.filter("assembly.user", this.assembleBudget.bind(this), 275);
|
|
25
54
|
}
|
|
26
55
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
56
|
+
/**
|
|
57
|
+
* Render the <budget> table between <instructions> and <prompt>.
|
|
58
|
+
* See SPEC @token_accounting for the contract: per-row tokens are
|
|
59
|
+
* aTokens (the promotion premium = vTokens − sTokens), summarized
|
|
60
|
+
* entries collapse into a single aggregate line, system overhead
|
|
61
|
+
* (system prompt + tool defs) gets its own line.
|
|
62
|
+
*/
|
|
63
|
+
assembleBudget(content, ctx) {
|
|
64
|
+
const { rows, contextSize, systemPrompt } = ctx;
|
|
65
|
+
if (!contextSize) return content;
|
|
31
66
|
|
|
32
|
-
const
|
|
33
|
-
lastPromptTokens > 0 ? lastPromptTokens : measureMessages(messages);
|
|
67
|
+
const cap = ceiling(contextSize);
|
|
34
68
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
69
|
+
// Per-scheme aggregation: counts and costs at each visibility tier
|
|
70
|
+
// plus the savings (premium) the model would unlock by demoting
|
|
71
|
+
// visible → summarized. All math derives from per-row vTokens
|
|
72
|
+
// (cost as visible) / sTokens (cost as summarized) / aTokens
|
|
73
|
+
// (= vTokens − sTokens, the promotion premium).
|
|
74
|
+
const byScheme = new Map();
|
|
75
|
+
let visibleCount = 0;
|
|
76
|
+
let premiumTokens = 0;
|
|
77
|
+
let summarizedCount = 0;
|
|
78
|
+
let _summarizedTokens = 0;
|
|
79
|
+
let floorTokens = 0;
|
|
80
|
+
let knownVTokens = 0;
|
|
81
|
+
let sourceVTokens = 0;
|
|
82
|
+
|
|
83
|
+
const schemeEntry = (s) => {
|
|
84
|
+
let e = byScheme.get(s);
|
|
85
|
+
if (!e) {
|
|
86
|
+
e = {
|
|
87
|
+
vis: 0,
|
|
88
|
+
sum: 0,
|
|
89
|
+
visTokens: 0, // current cost of visible entries
|
|
90
|
+
visIfSumTokens: 0, // sTokens of visible (what they'd cost demoted)
|
|
91
|
+
sumTokens: 0, // current cost of summarized entries
|
|
92
|
+
premium: 0, // savings from demoting visible → summarized
|
|
93
|
+
};
|
|
94
|
+
byScheme.set(s, e);
|
|
95
|
+
}
|
|
96
|
+
return e;
|
|
97
|
+
};
|
|
38
98
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
99
|
+
for (const r of rows) {
|
|
100
|
+
if (r.aTokens == null) continue;
|
|
101
|
+
const s = r.scheme || "file";
|
|
102
|
+
const entry = schemeEntry(s);
|
|
103
|
+
if (r.visibility === "visible") {
|
|
104
|
+
entry.vis += 1;
|
|
105
|
+
entry.visTokens += r.vTokens || 0;
|
|
106
|
+
entry.visIfSumTokens += r.sTokens || 0;
|
|
107
|
+
entry.premium += r.aTokens || 0;
|
|
108
|
+
visibleCount += 1;
|
|
109
|
+
premiumTokens += r.aTokens;
|
|
110
|
+
floorTokens += r.sTokens;
|
|
111
|
+
const v = r.vTokens || 0;
|
|
112
|
+
if (s === "known") knownVTokens += v;
|
|
113
|
+
else if (s === "prompt") sourceVTokens += v;
|
|
114
|
+
else if (r.category === "data") sourceVTokens += v;
|
|
115
|
+
} else if (r.visibility === "summarized") {
|
|
116
|
+
entry.sum += 1;
|
|
117
|
+
entry.sumTokens += r.sTokens || 0;
|
|
118
|
+
summarizedCount += 1;
|
|
119
|
+
_summarizedTokens += r.sTokens;
|
|
120
|
+
floorTokens += r.sTokens;
|
|
121
|
+
}
|
|
53
122
|
}
|
|
54
123
|
|
|
55
|
-
|
|
124
|
+
const fcrmDenom = knownVTokens + sourceVTokens;
|
|
125
|
+
const fcrmScore =
|
|
126
|
+
fcrmDenom > 0 ? (knownVTokens / fcrmDenom).toFixed(2) : "1.00";
|
|
127
|
+
|
|
128
|
+
const systemTokens = countTokens(systemPrompt || "");
|
|
129
|
+
const tokenUsage = floorTokens + premiumTokens + systemTokens;
|
|
130
|
+
const tokensFree = Math.max(0, cap - tokenUsage);
|
|
131
|
+
|
|
132
|
+
// Sort schemes by current cost descending — biggest-impact rows
|
|
133
|
+
// land at the top, so "what should I demote first?" reads
|
|
134
|
+
// straight off the table.
|
|
135
|
+
const schemeRows = [...byScheme.entries()]
|
|
136
|
+
.toSorted(
|
|
137
|
+
([, a], [, b]) =>
|
|
138
|
+
b.visTokens + b.sumTokens - (a.visTokens + a.sumTokens),
|
|
139
|
+
)
|
|
140
|
+
.map(([scheme, e]) => {
|
|
141
|
+
const cost = e.visTokens + e.sumTokens;
|
|
142
|
+
const ifAllSum = e.visIfSumTokens + e.sumTokens;
|
|
143
|
+
return `| ${scheme} | ${e.vis} | ${e.sum} | ${cost} | ${ifAllSum} | ${e.premium} |`;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const systemPct =
|
|
147
|
+
tokenUsage > 0 ? Math.round((systemTokens / tokenUsage) * 100) : 0;
|
|
148
|
+
|
|
149
|
+
const table = [
|
|
150
|
+
"| scheme | vis | sum | cost | if-all-sum | premium |",
|
|
151
|
+
"|---|---|---|---|---|---|",
|
|
152
|
+
...schemeRows,
|
|
153
|
+
].join("\n");
|
|
154
|
+
|
|
155
|
+
const systemLine = `System: ${systemTokens} tokens (${systemPct}% of budget).`;
|
|
156
|
+
const totalLine = `Total: ${visibleCount} visible + ${summarizedCount} summarized entries; tokenUsage ${tokenUsage} / ceiling ${cap}. ${tokensFree} tokens free.`;
|
|
157
|
+
const legend = [
|
|
158
|
+
"Columns:",
|
|
159
|
+
"- cost: current cost of this scheme (vTokens for visible + sTokens for summarized)",
|
|
160
|
+
"- if-all-sum: cost if every entry of this scheme were demoted to summarized",
|
|
161
|
+
"- premium: savings from demoting visible → summarized (cost − if-all-sum)",
|
|
162
|
+
].join("\n");
|
|
163
|
+
|
|
164
|
+
return `${content}<budget tokenUsage="${tokenUsage}" tokensFree="${tokensFree}" fcrmScore="${fcrmScore}">\n${table}\n\n${legend}\n${systemLine}\n${totalLine}\n</budget>\n`;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
#check({ contextSize, messages, rows, lastPromptTokens = 0 }) {
|
|
168
|
+
const totalTokens =
|
|
169
|
+
lastPromptTokens > 0 ? lastPromptTokens : measureMessages(messages);
|
|
170
|
+
const b = computeBudget({ rows, contextSize, totalTokens });
|
|
171
|
+
return {
|
|
172
|
+
messages,
|
|
173
|
+
rows,
|
|
174
|
+
assembledTokens: b.totalTokens,
|
|
175
|
+
overflow: b.overflow,
|
|
176
|
+
ok: b.ok,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async #emitOverflow({
|
|
181
|
+
message,
|
|
182
|
+
runId,
|
|
183
|
+
turn,
|
|
184
|
+
loopId,
|
|
185
|
+
rummy,
|
|
186
|
+
demotedCount = 0,
|
|
187
|
+
demotedTokens = 0,
|
|
188
|
+
}) {
|
|
189
|
+
await rummy.hooks.error.log.emit({
|
|
190
|
+
store: rummy.entries,
|
|
191
|
+
runId,
|
|
192
|
+
turn,
|
|
193
|
+
loopId,
|
|
194
|
+
message,
|
|
195
|
+
status: 413,
|
|
196
|
+
attributes: { demotedCount, demotedTokens },
|
|
197
|
+
});
|
|
56
198
|
}
|
|
57
199
|
|
|
58
|
-
|
|
200
|
+
/**
|
|
201
|
+
* Pre-LLM budget enforcement. On first-turn overflow, demotes the
|
|
202
|
+
* incoming prompt and re-materializes; re-checks and returns the
|
|
203
|
+
* post-demotion result. If overflow persists after demotion (or on
|
|
204
|
+
* later iterations), emits a 413 error (strike) and returns !ok so
|
|
205
|
+
* TurnExecutor can skip the LLM call this turn.
|
|
206
|
+
*
|
|
207
|
+
* ctx = { runId, loopId, turn, systemPrompt, mode, toolSet, demoted,
|
|
208
|
+
* loopIteration }
|
|
209
|
+
*/
|
|
210
|
+
async enforce({
|
|
59
211
|
contextSize,
|
|
60
212
|
messages,
|
|
61
213
|
rows,
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
db,
|
|
66
|
-
store,
|
|
214
|
+
lastPromptTokens = 0,
|
|
215
|
+
ctx,
|
|
216
|
+
rummy,
|
|
67
217
|
}) {
|
|
68
|
-
if (!contextSize)
|
|
218
|
+
if (!contextSize) {
|
|
219
|
+
return { messages, rows, assembledTokens: 0, ok: true };
|
|
220
|
+
}
|
|
69
221
|
|
|
70
|
-
const
|
|
222
|
+
const first = this.#check({
|
|
71
223
|
contextSize,
|
|
72
224
|
messages,
|
|
73
225
|
rows,
|
|
74
|
-
lastPromptTokens
|
|
226
|
+
lastPromptTokens,
|
|
75
227
|
});
|
|
228
|
+
if (first.ok) return first;
|
|
76
229
|
|
|
77
|
-
if (
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
230
|
+
if (ctx?.loopIteration !== 1) {
|
|
231
|
+
const cap = ceiling(contextSize);
|
|
232
|
+
await this.#emitOverflow({
|
|
233
|
+
message: `Token Budget overflow: packet was ${cap + first.overflow} tokens, ceiling is ${cap}.`,
|
|
234
|
+
runId: ctx.runId,
|
|
235
|
+
turn: ctx.turn,
|
|
236
|
+
loopId: ctx.loopId,
|
|
237
|
+
rummy,
|
|
238
|
+
});
|
|
239
|
+
return first;
|
|
240
|
+
}
|
|
84
241
|
|
|
85
|
-
|
|
86
|
-
|
|
242
|
+
const promptRow = rows.findLast(
|
|
243
|
+
(r) => r.category === "prompt" && r.scheme === "prompt",
|
|
244
|
+
);
|
|
87
245
|
if (promptRow) {
|
|
88
|
-
await
|
|
246
|
+
await rummy.entries.set({
|
|
247
|
+
runId: ctx.runId,
|
|
248
|
+
path: promptRow.path,
|
|
249
|
+
visibility: "summarized",
|
|
250
|
+
});
|
|
251
|
+
}
|
|
252
|
+
const reMat = await materializeContext({
|
|
253
|
+
db: rummy.db,
|
|
254
|
+
hooks: rummy.hooks,
|
|
255
|
+
runId: ctx.runId,
|
|
256
|
+
loopId: ctx.loopId,
|
|
257
|
+
turn: ctx.turn,
|
|
258
|
+
systemPrompt: ctx.systemPrompt,
|
|
259
|
+
mode: ctx.mode,
|
|
260
|
+
toolSet: ctx.toolSet,
|
|
261
|
+
contextSize,
|
|
262
|
+
demoted: ctx.demoted,
|
|
263
|
+
});
|
|
264
|
+
const rechecked = this.#check({
|
|
265
|
+
contextSize,
|
|
266
|
+
messages: reMat.messages,
|
|
267
|
+
rows: reMat.rows,
|
|
268
|
+
lastPromptTokens: reMat.lastContextTokens,
|
|
269
|
+
});
|
|
270
|
+
if (!rechecked.ok) {
|
|
271
|
+
const cap = ceiling(contextSize);
|
|
272
|
+
await this.#emitOverflow({
|
|
273
|
+
message: `Token Budget overflow: packet was ${cap + rechecked.overflow} tokens after demoting the prompt, ceiling is ${cap}.`,
|
|
274
|
+
runId: ctx.runId,
|
|
275
|
+
turn: ctx.turn,
|
|
276
|
+
loopId: ctx.loopId,
|
|
277
|
+
rummy,
|
|
278
|
+
});
|
|
89
279
|
}
|
|
280
|
+
return rechecked;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Post-dispatch Turn Demotion. Re-materializes end-of-turn context and
|
|
285
|
+
* checks against the ceiling. On overflow, demotes this turn's promoted
|
|
286
|
+
* entries and emits a 413 error (strike) with the descriptive body so
|
|
287
|
+
* the model sees it next turn via the unified error channel.
|
|
288
|
+
*
|
|
289
|
+
* ctx = { runId, loopId, turn, systemPrompt, mode, toolSet, demoted }
|
|
290
|
+
*/
|
|
291
|
+
async postDispatch({ contextSize, ctx, rummy }) {
|
|
292
|
+
if (!contextSize) return { failed: false };
|
|
293
|
+
const postMat = await materializeContext({
|
|
294
|
+
db: rummy.db,
|
|
295
|
+
hooks: rummy.hooks,
|
|
296
|
+
runId: ctx.runId,
|
|
297
|
+
loopId: ctx.loopId,
|
|
298
|
+
turn: ctx.turn,
|
|
299
|
+
systemPrompt: ctx.systemPrompt,
|
|
300
|
+
mode: ctx.mode,
|
|
301
|
+
toolSet: ctx.toolSet,
|
|
302
|
+
contextSize,
|
|
303
|
+
demoted: ctx.demoted,
|
|
304
|
+
});
|
|
305
|
+
// Baseline from this turn's actual API tokens (telemetry wrote it
|
|
306
|
+
// before post-dispatch runs). Delta from rows added this turn.
|
|
307
|
+
// Predicted next-turn packet stays on the tokenUsage scale the
|
|
308
|
+
// model can verify against its own arithmetic. materializeContext
|
|
309
|
+
// guarantees a number (0 when no prior API call exists).
|
|
310
|
+
const baseline = postMat.lastContextTokens;
|
|
311
|
+
const predicted = predictNextPacket(postMat.rows, ctx.turn, baseline);
|
|
312
|
+
const cap = ceiling(contextSize);
|
|
313
|
+
if (predicted <= cap) return { failed: false };
|
|
314
|
+
const post = { overflow: predicted - cap };
|
|
90
315
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
//
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
316
|
+
const store = rummy.entries;
|
|
317
|
+
let demotedEntries = await store.demoteTurnEntries(ctx.runId, ctx.turn);
|
|
318
|
+
// Fallback: if this turn had nothing to demote but the packet still
|
|
319
|
+
// overflows, the pressure is coming from prior-turn promotions the
|
|
320
|
+
// model never demoted itself. Widen to all currently-visible
|
|
321
|
+
// entries in the run. Without this fallback, overflow-with-nothing
|
|
322
|
+
// strikes out runs where the base context has drifted over ceiling
|
|
323
|
+
// through no fault of the current turn (observed: runs where 3
|
|
324
|
+
// stale promotions from turns 12–14 saturate every subsequent
|
|
325
|
+
// turn's budget).
|
|
326
|
+
if (demotedEntries.length === 0) {
|
|
327
|
+
demotedEntries = await store.demoteRunVisibleEntries(ctx.runId);
|
|
328
|
+
}
|
|
329
|
+
const promptRow = postMat.rows.find((r) => r.scheme === "prompt");
|
|
330
|
+
if (promptRow) {
|
|
331
|
+
await store.set({
|
|
332
|
+
runId: ctx.runId,
|
|
333
|
+
path: promptRow.path,
|
|
334
|
+
visibility: "summarized",
|
|
101
335
|
});
|
|
102
336
|
}
|
|
103
337
|
|
|
104
|
-
// Write budget entry — terse, actionable. Path list dropped since
|
|
105
|
-
// demoted entries already render at fidelity="demoted" in <knowns>/<files>.
|
|
106
|
-
// "tokens remaining" dropped too — the number was over-optimistic (it
|
|
107
|
-
// treated re-demoted files as freeing their full-body tokens when their
|
|
108
|
-
// demoted-view renderings return to baseline). Model reads the truthful
|
|
109
|
-
// remaining in next turn's progress line.
|
|
110
|
-
//
|
|
111
|
-
// The 50% rule is the key directive: it forces the model to sum
|
|
112
|
-
// promotion costs (which is the behavior we want), and the threshold
|
|
113
|
-
// gives a concrete ceiling for the next try. Twofer — abiding by the
|
|
114
|
-
// rule requires budget awareness as a side effect.
|
|
115
|
-
const ceiling = Math.floor(contextSize * CEILING_RATIO);
|
|
116
338
|
const totalDemoted = demotedEntries.reduce((s, r) => s + r.tokens, 0);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
339
|
+
await this.#emitOverflow({
|
|
340
|
+
message: overflowBody(post.overflow, contextSize, demotedEntries),
|
|
341
|
+
demotedCount: demotedEntries.length,
|
|
342
|
+
demotedTokens: totalDemoted,
|
|
343
|
+
runId: ctx.runId,
|
|
344
|
+
turn: ctx.turn,
|
|
345
|
+
loopId: ctx.loopId,
|
|
346
|
+
rummy,
|
|
125
347
|
});
|
|
126
|
-
|
|
127
|
-
return {
|
|
128
|
-
target: ceiling,
|
|
129
|
-
promptPath: promptRow?.path ?? null,
|
|
130
|
-
};
|
|
348
|
+
return { failed: true };
|
|
131
349
|
}
|
|
132
350
|
}
|
package/src/plugins/cp/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# cp
|
|
1
|
+
# cp {#cp_plugin}
|
|
2
2
|
|
|
3
3
|
Copies an entry from one path to another within the K/V store.
|
|
4
4
|
|
|
@@ -15,5 +15,5 @@ Shows `cp {from} {to}`.
|
|
|
15
15
|
## Behavior
|
|
16
16
|
|
|
17
17
|
Warns if the destination already exists and will be overwritten. Uses
|
|
18
|
-
`
|
|
18
|
+
`Entries.scheme()` to determine whether the destination is a scheme
|
|
19
19
|
path or a file path.
|
package/src/plugins/cp/cp.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import Entries from "../../agent/Entries.js";
|
|
2
2
|
import docs from "./cpDoc.js";
|
|
3
3
|
|
|
4
4
|
export default class Cp {
|
|
@@ -8,8 +8,8 @@ export default class Cp {
|
|
|
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("visible", this.full.bind(this));
|
|
12
|
+
core.on("summarized", this.summary.bind(this));
|
|
13
13
|
core.filter("instructions.toolDocs", async (docsMap) => {
|
|
14
14
|
docsMap.cp = docs;
|
|
15
15
|
return docsMap;
|
|
@@ -19,15 +19,15 @@ export default class Cp {
|
|
|
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 = {
|
|
23
|
-
const
|
|
24
|
-
? entry.attributes.
|
|
22
|
+
const VALID = { visible: 1, summarized: 1, archived: 1 };
|
|
23
|
+
const visibility = VALID[entry.attributes.visibility]
|
|
24
|
+
? entry.attributes.visibility
|
|
25
25
|
: undefined;
|
|
26
26
|
|
|
27
27
|
const source = await store.getBody(runId, path);
|
|
28
28
|
if (source === null) return;
|
|
29
29
|
|
|
30
|
-
const destScheme =
|
|
30
|
+
const destScheme = Entries.scheme(to);
|
|
31
31
|
const existing = await store.getBody(runId, to);
|
|
32
32
|
const warning =
|
|
33
33
|
existing !== null && destScheme !== null
|
|
@@ -36,13 +36,31 @@ export default class Cp {
|
|
|
36
36
|
|
|
37
37
|
const body = `${path} ${to}`;
|
|
38
38
|
if (destScheme === null) {
|
|
39
|
-
await store.
|
|
39
|
+
await store.set({
|
|
40
|
+
runId,
|
|
41
|
+
turn,
|
|
42
|
+
path: entry.resultPath,
|
|
43
|
+
body,
|
|
44
|
+
state: "proposed",
|
|
40
45
|
attributes: { from: path, to, isMove: false, warning },
|
|
41
46
|
loopId,
|
|
42
47
|
});
|
|
43
48
|
} else {
|
|
44
|
-
await store.
|
|
45
|
-
|
|
49
|
+
await store.set({
|
|
50
|
+
runId,
|
|
51
|
+
turn,
|
|
52
|
+
path: to,
|
|
53
|
+
body: source,
|
|
54
|
+
state: "resolved",
|
|
55
|
+
visibility,
|
|
56
|
+
loopId,
|
|
57
|
+
});
|
|
58
|
+
await store.set({
|
|
59
|
+
runId,
|
|
60
|
+
turn,
|
|
61
|
+
path: entry.resultPath,
|
|
62
|
+
body,
|
|
63
|
+
state: "resolved",
|
|
46
64
|
attributes: { from: path, to, isMove: false, warning },
|
|
47
65
|
loopId,
|
|
48
66
|
});
|
|
@@ -50,7 +68,7 @@ export default class Cp {
|
|
|
50
68
|
}
|
|
51
69
|
|
|
52
70
|
full(entry) {
|
|
53
|
-
return `# cp ${entry.attributes.from
|
|
71
|
+
return `# cp ${entry.attributes.from} ${entry.attributes.to}`;
|
|
54
72
|
}
|
|
55
73
|
|
|
56
74
|
summary() {
|
package/src/plugins/cp/cpDoc.js
CHANGED
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
// Text goes to the model. Rationale stays in source.
|
|
3
|
-
// Changing ANY line requires reading ALL rationales first.
|
|
4
|
-
const LINES = [
|
|
5
|
-
['## <cp path="[source]">[destination]</cp> - Copy a file or entry'],
|
|
6
|
-
[
|
|
7
|
-
'Example: <cp path="src/config.js">src/config.backup.js</cp>',
|
|
8
|
-
"Simple file copy. Path = source, body = destination.",
|
|
9
|
-
],
|
|
10
|
-
[
|
|
11
|
-
'Example: <cp path="known://plan_*">known://archive_</cp>',
|
|
12
|
-
"Glob batch copy across known entries.",
|
|
13
|
-
],
|
|
14
|
-
];
|
|
1
|
+
import { loadDoc } from "../helpers.js";
|
|
15
2
|
|
|
16
|
-
export default
|
|
3
|
+
export default loadDoc(import.meta.url, "cpDoc.md");
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
## <cp path="[source]">[destination]</cp> - Copy a file or entry
|
|
2
|
+
|
|
3
|
+
Example: <cp path="src/config.js">src/config.backup.js</cp>
|
|
4
|
+
<!-- Simple file copy. Path = source, body = destination. -->
|
|
5
|
+
|
|
6
|
+
Example: <cp path="known://plan_*">known://archive_</cp>
|
|
7
|
+
<!-- Glob batch copy across known entries. -->
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
# engine
|
|
1
|
+
# engine {#engine_plugin}
|
|
2
2
|
|
|
3
3
|
SQL infrastructure for context assembly and turn management. No JS plugin.
|
|
4
4
|
|
|
5
5
|
## Files
|
|
6
6
|
|
|
7
|
-
- **engine.sql** — Queries for retrieving
|
|
7
|
+
- **engine.sql** — Queries for retrieving visible entries by scheme tier, model visibility, and state.
|
|
8
8
|
- **turn_context.sql** — Queries for clearing and reading the `turn_context` / `v_model_context` view, which produces the ordered context sent to the model.
|
|
9
9
|
|
|
10
10
|
## Behavior
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
-- PREP: get_promoted_entries
|
|
2
2
|
SELECT
|
|
3
|
-
ke.path, ke.scheme, ke.
|
|
4
|
-
, ke.tokens, ke.refs
|
|
3
|
+
ke.path, ke.scheme, ke.state, ke.outcome, ke.visibility, ke.turn
|
|
4
|
+
, countTokens(ke.body) AS tokens, ke.refs
|
|
5
5
|
FROM known_entries AS ke
|
|
6
6
|
JOIN schemes AS s ON s.name = COALESCE(ke.scheme, 'file')
|
|
7
7
|
WHERE
|
|
8
8
|
ke.run_id = :run_id
|
|
9
|
-
AND ke.
|
|
9
|
+
AND ke.visibility IN ('visible', 'summarized')
|
|
10
10
|
AND s.model_visible = 1
|
|
11
|
-
ORDER BY ke.turn, ke.refs, ke.
|
|
11
|
+
ORDER BY ke.turn, ke.refs, countTokens(ke.body) DESC;
|
|
12
12
|
|
|
13
13
|
|
|
@@ -4,33 +4,33 @@ WHERE run_id = :run_id AND turn = :turn;
|
|
|
4
4
|
|
|
5
5
|
-- PREP: get_model_context
|
|
6
6
|
SELECT
|
|
7
|
-
ordinal, path, scheme,
|
|
8
|
-
,
|
|
7
|
+
ordinal, path, scheme, visibility, state, outcome, body
|
|
8
|
+
, attributes, category, turn
|
|
9
9
|
FROM v_model_context
|
|
10
10
|
WHERE run_id = :run_id
|
|
11
11
|
ORDER BY ordinal;
|
|
12
12
|
|
|
13
13
|
-- PREP: insert_turn_context
|
|
14
14
|
INSERT INTO turn_context (
|
|
15
|
-
run_id, loop_id, turn, ordinal, path,
|
|
16
|
-
, body,
|
|
15
|
+
run_id, loop_id, turn, ordinal, path, visibility, state, outcome
|
|
16
|
+
, body, attributes, category, source_turn
|
|
17
17
|
)
|
|
18
18
|
VALUES (
|
|
19
|
-
:run_id, :loop_id, :turn, :ordinal, :path, :
|
|
20
|
-
, :
|
|
19
|
+
:run_id, :loop_id, :turn, :ordinal, :path, :visibility
|
|
20
|
+
, :state, :outcome, :body
|
|
21
21
|
, COALESCE(:attributes, '{}'), :category, :source_turn
|
|
22
22
|
);
|
|
23
23
|
|
|
24
24
|
-- PREP: get_turn_context
|
|
25
25
|
SELECT
|
|
26
|
-
ordinal, path, scheme,
|
|
27
|
-
,
|
|
26
|
+
ordinal, path, scheme, visibility, state, outcome, body
|
|
27
|
+
, attributes, category, source_turn
|
|
28
28
|
FROM turn_context
|
|
29
29
|
WHERE run_id = :run_id AND turn = :turn
|
|
30
30
|
ORDER BY ordinal;
|
|
31
31
|
|
|
32
32
|
-- PREP: get_turn_budget
|
|
33
|
-
SELECT COALESCE(SUM(
|
|
33
|
+
SELECT COALESCE(SUM(countTokens(body)), 0) AS total
|
|
34
34
|
FROM turn_context
|
|
35
35
|
WHERE run_id = :run_id AND turn = :turn;
|
|
36
36
|
|
|
@@ -43,7 +43,7 @@ SELECT
|
|
|
43
43
|
WHEN 'prompt' THEN 'prompt'
|
|
44
44
|
ELSE 'system'
|
|
45
45
|
END AS bucket,
|
|
46
|
-
COALESCE(SUM(
|
|
46
|
+
COALESCE(SUM(countTokens(body)), 0) AS tokens,
|
|
47
47
|
COUNT(*) AS entries
|
|
48
48
|
FROM turn_context
|
|
49
49
|
WHERE run_id = :run_id AND turn = :turn
|