@possumtech/rummy 0.4.0 → 2.0.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.
Files changed (153) hide show
  1. package/.env.example +21 -4
  2. package/PLUGINS.md +389 -194
  3. package/README.md +25 -8
  4. package/SPEC.md +850 -373
  5. package/bin/demo.js +166 -0
  6. package/bin/rummy.js +9 -3
  7. package/biome/no-fallbacks.grit +50 -0
  8. package/lang/en.json +2 -2
  9. package/migrations/001_initial_schema.sql +88 -37
  10. package/package.json +6 -4
  11. package/service.js +50 -9
  12. package/src/agent/AgentLoop.js +460 -331
  13. package/src/agent/ContextAssembler.js +4 -2
  14. package/src/agent/Entries.js +655 -0
  15. package/src/agent/ProjectAgent.js +30 -18
  16. package/src/agent/TurnExecutor.js +232 -379
  17. package/src/agent/XmlParser.js +242 -67
  18. package/src/agent/budget.js +56 -0
  19. package/src/agent/errors.js +22 -0
  20. package/src/agent/httpStatus.js +39 -0
  21. package/src/agent/known_checks.sql +8 -4
  22. package/src/agent/known_queries.sql +9 -13
  23. package/src/agent/known_store.sql +275 -118
  24. package/src/agent/materializeContext.js +102 -0
  25. package/src/agent/runs.sql +10 -7
  26. package/src/agent/schemes.sql +14 -3
  27. package/src/agent/turns.sql +9 -9
  28. package/src/hooks/HookRegistry.js +6 -5
  29. package/src/hooks/Hooks.js +44 -3
  30. package/src/hooks/PluginContext.js +35 -21
  31. package/src/{server → hooks}/RpcRegistry.js +2 -1
  32. package/src/hooks/RummyContext.js +140 -37
  33. package/src/hooks/ToolRegistry.js +36 -35
  34. package/src/llm/LlmProvider.js +64 -90
  35. package/src/llm/errors.js +21 -0
  36. package/src/plugins/ask_user/README.md +1 -1
  37. package/src/plugins/ask_user/ask_user.js +37 -12
  38. package/src/plugins/ask_user/ask_userDoc.js +2 -23
  39. package/src/plugins/ask_user/ask_userDoc.md +10 -0
  40. package/src/plugins/budget/README.md +27 -23
  41. package/src/plugins/budget/budget.js +261 -69
  42. package/src/plugins/cp/README.md +2 -2
  43. package/src/plugins/cp/cp.js +31 -13
  44. package/src/plugins/cp/cpDoc.js +2 -23
  45. package/src/plugins/cp/cpDoc.md +7 -0
  46. package/src/plugins/engine/README.md +2 -2
  47. package/src/plugins/engine/engine.sql +4 -4
  48. package/src/plugins/engine/turn_context.sql +10 -10
  49. package/src/plugins/env/README.md +20 -5
  50. package/src/plugins/env/env.js +47 -8
  51. package/src/plugins/env/envDoc.js +2 -23
  52. package/src/plugins/env/envDoc.md +13 -0
  53. package/src/plugins/error/README.md +16 -0
  54. package/src/plugins/error/error.js +151 -0
  55. package/src/plugins/file/README.md +6 -6
  56. package/src/plugins/file/file.js +15 -7
  57. package/src/plugins/get/README.md +1 -1
  58. package/src/plugins/get/get.js +125 -49
  59. package/src/plugins/get/getDoc.js +2 -43
  60. package/src/plugins/get/getDoc.md +36 -0
  61. package/src/plugins/hedberg/README.md +1 -2
  62. package/src/plugins/hedberg/hedberg.js +8 -4
  63. package/src/plugins/hedberg/matcher.js +16 -17
  64. package/src/plugins/hedberg/normalize.js +0 -48
  65. package/src/plugins/helpers.js +43 -3
  66. package/src/plugins/index.js +146 -123
  67. package/src/plugins/instructions/README.md +35 -9
  68. package/src/plugins/instructions/instructions.js +126 -12
  69. package/src/plugins/instructions/instructions.md +25 -0
  70. package/src/plugins/instructions/instructions_104.md +7 -0
  71. package/src/plugins/instructions/instructions_105.md +46 -0
  72. package/src/plugins/instructions/instructions_106.md +0 -0
  73. package/src/plugins/instructions/instructions_107.md +0 -0
  74. package/src/plugins/instructions/instructions_108.md +8 -0
  75. package/src/plugins/instructions/protocol.js +12 -0
  76. package/src/plugins/known/README.md +2 -2
  77. package/src/plugins/known/known.js +77 -45
  78. package/src/plugins/known/knownDoc.js +2 -29
  79. package/src/plugins/known/knownDoc.md +8 -0
  80. package/src/plugins/log/README.md +48 -0
  81. package/src/plugins/log/log.js +109 -0
  82. package/src/plugins/mv/README.md +2 -2
  83. package/src/plugins/mv/mv.js +57 -24
  84. package/src/plugins/mv/mvDoc.js +2 -29
  85. package/src/plugins/mv/mvDoc.md +10 -0
  86. package/src/plugins/ollama/README.md +15 -0
  87. package/src/{llm/OllamaClient.js → plugins/ollama/ollama.js} +40 -18
  88. package/src/plugins/openai/README.md +17 -0
  89. package/src/plugins/openai/openai.js +120 -0
  90. package/src/plugins/openrouter/README.md +27 -0
  91. package/src/plugins/openrouter/openrouter.js +121 -0
  92. package/src/plugins/persona/README.md +20 -0
  93. package/src/plugins/persona/persona.js +9 -16
  94. package/src/plugins/policy/README.md +21 -0
  95. package/src/plugins/policy/policy.js +29 -14
  96. package/src/plugins/prompt/README.md +1 -1
  97. package/src/plugins/prompt/prompt.js +63 -18
  98. package/src/plugins/rm/README.md +1 -1
  99. package/src/plugins/rm/rm.js +58 -14
  100. package/src/plugins/rm/rmDoc.js +2 -24
  101. package/src/plugins/rm/rmDoc.md +13 -0
  102. package/src/plugins/rpc/README.md +2 -2
  103. package/src/plugins/rpc/rpc.js +515 -296
  104. package/src/plugins/set/README.md +1 -1
  105. package/src/plugins/set/set.js +318 -77
  106. package/src/plugins/set/setDoc.js +2 -35
  107. package/src/plugins/set/setDoc.md +22 -0
  108. package/src/plugins/sh/README.md +28 -5
  109. package/src/plugins/sh/sh.js +52 -8
  110. package/src/plugins/sh/shDoc.js +2 -23
  111. package/src/plugins/sh/shDoc.md +13 -0
  112. package/src/plugins/skill/README.md +23 -0
  113. package/src/plugins/skill/skill.js +14 -17
  114. package/src/plugins/stream/README.md +101 -0
  115. package/src/plugins/stream/stream.js +290 -0
  116. package/src/plugins/telemetry/README.md +1 -1
  117. package/src/plugins/telemetry/telemetry.js +148 -74
  118. package/src/plugins/think/README.md +1 -1
  119. package/src/plugins/think/think.js +14 -1
  120. package/src/plugins/think/thinkDoc.js +2 -17
  121. package/src/plugins/think/thinkDoc.md +7 -0
  122. package/src/plugins/unknown/README.md +3 -3
  123. package/src/plugins/unknown/unknown.js +56 -21
  124. package/src/plugins/unknown/unknownDoc.js +2 -25
  125. package/src/plugins/unknown/unknownDoc.md +11 -0
  126. package/src/plugins/update/README.md +1 -1
  127. package/src/plugins/update/update.js +67 -5
  128. package/src/plugins/update/updateDoc.js +2 -27
  129. package/src/plugins/update/updateDoc.md +8 -0
  130. package/src/plugins/xai/README.md +23 -0
  131. package/src/{llm/XaiClient.js → plugins/xai/xai.js} +58 -37
  132. package/src/server/ClientConnection.js +64 -37
  133. package/src/server/SocketServer.js +23 -10
  134. package/src/server/protocol.js +11 -0
  135. package/src/sql/functions/slugify.js +13 -1
  136. package/src/sql/v_model_context.sql +27 -31
  137. package/src/sql/v_run_log.sql +9 -14
  138. package/EXCEPTIONS.md +0 -46
  139. package/src/agent/KnownStore.js +0 -338
  140. package/src/agent/ResponseHealer.js +0 -188
  141. package/src/llm/OpenAiClient.js +0 -100
  142. package/src/llm/OpenRouterClient.js +0 -100
  143. package/src/plugins/budget/recovery.js +0 -47
  144. package/src/plugins/instructions/preamble.md +0 -37
  145. package/src/plugins/performed/README.md +0 -15
  146. package/src/plugins/performed/performed.js +0 -45
  147. package/src/plugins/previous/README.md +0 -16
  148. package/src/plugins/previous/previous.js +0 -60
  149. package/src/plugins/progress/README.md +0 -16
  150. package/src/plugins/progress/progress.js +0 -26
  151. package/src/plugins/summarize/README.md +0 -19
  152. package/src/plugins/summarize/summarize.js +0 -32
  153. package/src/plugins/summarize/summarizeDoc.js +0 -28
@@ -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
- const CEILING_RATIO = Number(process.env.RUMMY_BUDGET_CEILING);
4
- if (!CEILING_RATIO) throw new Error("RUMMY_BUDGET_CEILING must be set");
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
- function measureMessages(messages) {
7
- return messages.reduce((sum, m) => sum + countTokens(m.content), 0);
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,101 +46,259 @@ 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
- async enforce({ contextSize, messages, rows, lastPromptTokens = 0 }) {
28
- if (!contextSize) {
29
- return { messages, rows, demoted: [], assembledTokens: 0, status: 200 };
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 assembledTokens =
33
- lastPromptTokens > 0 ? lastPromptTokens : measureMessages(messages);
67
+ const cap = ceiling(contextSize);
34
68
 
35
- console.warn(
36
- `[RUMMY] Budget enforce: ${assembledTokens} tokens (${lastPromptTokens > 0 ? "actual" : "estimated"}), ceiling ${contextSize}, ${rows.length} rows`,
37
- );
69
+ const visibleByScheme = new Map();
70
+ let visibleCount = 0;
71
+ let premiumTokens = 0;
72
+ let summarizedCount = 0;
73
+ let summarizedTokens = 0;
74
+ let floorTokens = 0;
38
75
 
39
- const ceiling = Math.floor(contextSize * CEILING_RATIO);
40
- if (assembledTokens > ceiling) {
41
- const overflow = assembledTokens - ceiling;
42
- console.warn(
43
- `[RUMMY] Budget 413: ${assembledTokens} tokens > ${contextSize} ceiling (${overflow} over)`,
44
- );
45
- return {
46
- messages,
47
- rows,
48
- demoted: [],
49
- assembledTokens,
50
- status: 413,
51
- overflow,
52
- };
76
+ for (const r of rows) {
77
+ if (r.aTokens == null) continue;
78
+ const s = r.scheme || "file";
79
+ if (r.visibility === "visible") {
80
+ const entry = visibleByScheme.get(s) ?? { count: 0, tokens: 0 };
81
+ entry.count += 1;
82
+ entry.tokens += r.aTokens;
83
+ visibleByScheme.set(s, entry);
84
+ visibleCount += 1;
85
+ premiumTokens += r.aTokens;
86
+ floorTokens += r.sTokens;
87
+ } else if (r.visibility === "summarized") {
88
+ summarizedCount += 1;
89
+ summarizedTokens += r.sTokens;
90
+ floorTokens += r.sTokens;
91
+ }
53
92
  }
54
93
 
55
- return { messages, rows, demoted: [], assembledTokens, status: 200 };
94
+ const systemTokens = countTokens(systemPrompt || "");
95
+ const tokenUsage = floorTokens + premiumTokens + systemTokens;
96
+ const tokensFree = Math.max(0, cap - tokenUsage);
97
+
98
+ const schemeRows = [...visibleByScheme.entries()]
99
+ .toSorted((a, b) => b[1].tokens - a[1].tokens)
100
+ .map(([scheme, v]) => {
101
+ const pct = Math.round((v.tokens / cap) * 100);
102
+ return `| ${scheme} | ${v.count} | ${v.tokens} | ${pct}% |`;
103
+ });
104
+
105
+ const summarizedPct = Math.round((summarizedTokens / cap) * 100);
106
+ const systemPct = Math.round((systemTokens / cap) * 100);
107
+
108
+ const table = [
109
+ "| scheme | visible | tokens | % |",
110
+ "|---|---|---|---|",
111
+ ...schemeRows,
112
+ ].join("\n");
113
+
114
+ const summarizedLine = `Summarized: ${summarizedCount} entries, ${summarizedTokens} tokens (${summarizedPct}% of budget).`;
115
+ const systemLine = `System: ${systemTokens} tokens (${systemPct}% of budget).`;
116
+ const totalLine = `Total: ${visibleCount} visible + ${summarizedCount} summarized entries; tokenUsage ${tokenUsage} / ceiling ${cap}. ${tokensFree} tokens free.`;
117
+
118
+ return `${content}<budget tokenUsage="${tokenUsage}" tokensFree="${tokensFree}">\n${table}\n\n${summarizedLine}\n${systemLine}\n${totalLine}\n</budget>\n`;
119
+ }
120
+
121
+ #check({ contextSize, messages, rows, lastPromptTokens = 0 }) {
122
+ const totalTokens =
123
+ lastPromptTokens > 0 ? lastPromptTokens : measureMessages(messages);
124
+ const b = computeBudget({ rows, contextSize, totalTokens });
125
+ return {
126
+ messages,
127
+ rows,
128
+ assembledTokens: b.totalTokens,
129
+ overflow: b.overflow,
130
+ ok: b.ok,
131
+ };
132
+ }
133
+
134
+ async #emitOverflow({
135
+ message,
136
+ runId,
137
+ turn,
138
+ loopId,
139
+ rummy,
140
+ demotedCount = 0,
141
+ demotedTokens = 0,
142
+ }) {
143
+ await rummy.hooks.error.log.emit({
144
+ store: rummy.entries,
145
+ runId,
146
+ turn,
147
+ loopId,
148
+ message,
149
+ status: 413,
150
+ attributes: { demotedCount, demotedTokens },
151
+ });
56
152
  }
57
153
 
58
- async postDispatch({
154
+ /**
155
+ * Pre-LLM budget enforcement. On first-turn overflow, demotes the
156
+ * incoming prompt and re-materializes; re-checks and returns the
157
+ * post-demotion result. If overflow persists after demotion (or on
158
+ * later iterations), emits a 413 error (strike) and returns !ok so
159
+ * TurnExecutor can skip the LLM call this turn.
160
+ *
161
+ * ctx = { runId, loopId, turn, systemPrompt, mode, toolSet, demoted,
162
+ * loopIteration }
163
+ */
164
+ async enforce({
59
165
  contextSize,
60
166
  messages,
61
167
  rows,
62
- runId,
63
- loopId,
64
- turn,
65
- db,
66
- store,
168
+ lastPromptTokens = 0,
169
+ ctx,
170
+ rummy,
67
171
  }) {
68
- if (!contextSize) return null;
172
+ if (!contextSize) {
173
+ return { messages, rows, assembledTokens: 0, ok: true };
174
+ }
69
175
 
70
- const postBudget = await this.enforce({
176
+ const first = this.#check({
71
177
  contextSize,
72
178
  messages,
73
179
  rows,
74
- lastPromptTokens: 0,
180
+ lastPromptTokens,
75
181
  });
182
+ if (first.ok) return first;
76
183
 
77
- if (postBudget.status !== 413) return null;
184
+ if (ctx?.loopIteration !== 1) {
185
+ const cap = ceiling(contextSize);
186
+ await this.#emitOverflow({
187
+ message: `Token Budget overflow: packet was ${cap + first.overflow} tokens, ceiling is ${cap}.`,
188
+ runId: ctx.runId,
189
+ turn: ctx.turn,
190
+ loopId: ctx.loopId,
191
+ rummy,
192
+ });
193
+ return first;
194
+ }
78
195
 
79
- // Demote this turn's entries
80
- const demotedEntries = await db.demote_turn_entries.all({
81
- run_id: runId,
82
- turn,
196
+ const promptRow = rows.findLast(
197
+ (r) => r.category === "prompt" && r.scheme === "prompt",
198
+ );
199
+ if (promptRow) {
200
+ await rummy.entries.set({
201
+ runId: ctx.runId,
202
+ path: promptRow.path,
203
+ visibility: "summarized",
204
+ });
205
+ }
206
+ const reMat = await materializeContext({
207
+ db: rummy.db,
208
+ hooks: rummy.hooks,
209
+ runId: ctx.runId,
210
+ loopId: ctx.loopId,
211
+ turn: ctx.turn,
212
+ systemPrompt: ctx.systemPrompt,
213
+ mode: ctx.mode,
214
+ toolSet: ctx.toolSet,
215
+ contextSize,
216
+ demoted: ctx.demoted,
83
217
  });
218
+ const rechecked = this.#check({
219
+ contextSize,
220
+ messages: reMat.messages,
221
+ rows: reMat.rows,
222
+ lastPromptTokens: reMat.lastContextTokens,
223
+ });
224
+ if (!rechecked.ok) {
225
+ const cap = ceiling(contextSize);
226
+ await this.#emitOverflow({
227
+ message: `Token Budget overflow: packet was ${cap + rechecked.overflow} tokens after demoting the prompt, ceiling is ${cap}.`,
228
+ runId: ctx.runId,
229
+ turn: ctx.turn,
230
+ loopId: ctx.loopId,
231
+ rummy,
232
+ });
233
+ }
234
+ return rechecked;
235
+ }
84
236
 
85
- // Also summarize the prompt
86
- const promptRow = rows.find((r) => r.scheme === "prompt");
237
+ /**
238
+ * Post-dispatch Turn Demotion. Re-materializes end-of-turn context and
239
+ * checks against the ceiling. On overflow, demotes this turn's promoted
240
+ * entries and emits a 413 error (strike) with the descriptive body so
241
+ * the model sees it next turn via the unified error channel.
242
+ *
243
+ * ctx = { runId, loopId, turn, systemPrompt, mode, toolSet, demoted }
244
+ */
245
+ async postDispatch({ contextSize, ctx, rummy }) {
246
+ if (!contextSize) return { failed: false };
247
+ const postMat = await materializeContext({
248
+ db: rummy.db,
249
+ hooks: rummy.hooks,
250
+ runId: ctx.runId,
251
+ loopId: ctx.loopId,
252
+ turn: ctx.turn,
253
+ systemPrompt: ctx.systemPrompt,
254
+ mode: ctx.mode,
255
+ toolSet: ctx.toolSet,
256
+ contextSize,
257
+ demoted: ctx.demoted,
258
+ });
259
+ // Baseline from this turn's actual API tokens (telemetry wrote it
260
+ // before post-dispatch runs). Delta from rows added this turn.
261
+ // Predicted next-turn packet stays on the tokenUsage scale the
262
+ // model can verify against its own arithmetic. materializeContext
263
+ // guarantees a number (0 when no prior API call exists).
264
+ const baseline = postMat.lastContextTokens;
265
+ const predicted = predictNextPacket(postMat.rows, ctx.turn, baseline);
266
+ const cap = ceiling(contextSize);
267
+ if (predicted <= cap) return { failed: false };
268
+ const post = { overflow: predicted - cap };
269
+
270
+ const store = rummy.entries;
271
+ let demotedEntries = await store.demoteTurnEntries(ctx.runId, ctx.turn);
272
+ // Fallback: if this turn had nothing to demote but the packet still
273
+ // overflows, the pressure is coming from prior-turn promotions the
274
+ // model never demoted itself. Widen to all currently-visible
275
+ // entries in the run. Without this fallback, overflow-with-nothing
276
+ // strikes out runs where the base context has drifted over ceiling
277
+ // through no fault of the current turn (observed: runs where 3
278
+ // stale promotions from turns 12–14 saturate every subsequent
279
+ // turn's budget).
280
+ if (demotedEntries.length === 0) {
281
+ demotedEntries = await store.demoteRunVisibleEntries(ctx.runId);
282
+ }
283
+ const promptRow = postMat.rows.find((r) => r.scheme === "prompt");
87
284
  if (promptRow) {
88
- await store.setFidelity(runId, promptRow.path, "summary");
285
+ await store.set({
286
+ runId: ctx.runId,
287
+ path: promptRow.path,
288
+ visibility: "summarized",
289
+ });
89
290
  }
90
291
 
91
- // Write budget entry
92
- const ceiling = Math.floor(contextSize * CEILING_RATIO);
93
292
  const totalDemoted = demotedEntries.reduce((s, r) => s + r.tokens, 0);
94
- const pathList = demotedEntries
95
- .map((r) => `${r.path} (${r.tokens} tokens)`)
96
- .join("\n");
97
- const body = [
98
- `Error 413: Context overflowed by ${postBudget.overflow} tokens.`,
99
- `${demotedEntries.length} entries (${totalDemoted} tokens total) demoted. Budget: ${ceiling} tokens.`,
100
- pathList,
101
- ].join("\n");
102
-
103
- await store.upsert(runId, turn, `budget://${loopId}/${turn}`, body, 413, {
104
- loopId,
293
+ await this.#emitOverflow({
294
+ message: overflowBody(post.overflow, contextSize, demotedEntries),
295
+ demotedCount: demotedEntries.length,
296
+ demotedTokens: totalDemoted,
297
+ runId: ctx.runId,
298
+ turn: ctx.turn,
299
+ loopId: ctx.loopId,
300
+ rummy,
105
301
  });
106
-
107
- return {
108
- target: ceiling,
109
- promptPath: promptRow?.path ?? null,
110
- };
302
+ return { failed: true };
111
303
  }
112
304
  }
@@ -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
- `KnownStore.scheme()` to determine whether the destination is a scheme
18
+ `Entries.scheme()` to determine whether the destination is a scheme
19
19
  path or a file path.
@@ -1,4 +1,4 @@
1
- import KnownStore from "../../agent/KnownStore.js";
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("full", this.full.bind(this));
12
- core.on("summary", this.summary.bind(this));
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 = { stored: 1, summary: 1, index: 1, full: 1, archive: 1 };
23
- const fidelity = VALID[entry.attributes.fidelity]
24
- ? entry.attributes.fidelity
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 = KnownStore.scheme(to);
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.upsert(runId, turn, entry.resultPath, body, 202, {
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.upsert(runId, turn, to, source, 200, { fidelity, loopId });
45
- await store.upsert(runId, turn, entry.resultPath, body, 200, {
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,10 +68,10 @@ export default class Cp {
50
68
  }
51
69
 
52
70
  full(entry) {
53
- return `# cp ${entry.attributes.from || ""} ${entry.attributes.to || ""}`;
71
+ return `# cp ${entry.attributes.from} ${entry.attributes.to}`;
54
72
  }
55
73
 
56
- summary(entry) {
57
- return this.full(entry);
74
+ summary() {
75
+ return "";
58
76
  }
59
77
  }
@@ -1,24 +1,3 @@
1
- // Tool doc for <cp>. Each entry: [text, rationale].
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
- [
15
- "* Source path accepts patterns: `src/*.js`, `known://draft_*`",
16
- "Pattern support consistent with get/rm.",
17
- ],
18
- [
19
- "* Use `preview` to check matches before pattern-based bulk copy",
20
- "Safety pattern consistent with rm.",
21
- ],
22
- ];
1
+ import { loadDoc } from "../helpers.js";
23
2
 
24
- export default LINES.map(([text]) => text).join("\n");
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 promoted entries by scheme tier, model visibility, and state.
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.status, ke.fidelity, ke.turn
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.fidelity IN ('full', 'summary')
9
+ AND ke.visibility IN ('visible', 'summarized')
10
10
  AND s.model_visible = 1
11
- ORDER BY ke.turn, ke.refs, ke.tokens DESC;
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, fidelity, status, body
8
- , tokens, attributes, category, turn
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, fidelity, status
16
- , body, tokens, attributes, category, source_turn
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, :fidelity
20
- , :status, :body, :tokens
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, fidelity, status, body
27
- , tokens, attributes, category, source_turn
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(tokens), 0) AS total
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(tokens), 0) AS tokens,
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
@@ -1,13 +1,28 @@
1
- # env
1
+ # env {#env_plugin}
2
2
 
3
- Runs an exploratory shell command and records the output.
3
+ Runs an exploratory shell command and records the output. Streaming
4
+ producer — same entry shape as [sh](../sh/README.md), different scheme
5
+ name so ask-mode policy can admit read-only discovery without allowing
6
+ side effects.
4
7
 
5
8
  ## Registration
6
9
 
7
10
  - **Tool**: `env`
8
- - **Category**: `logging`
9
- - **Handler**: Upserts the entry at status 202 (proposed) with original attributes preserved.
11
+ - **Scheme**: `env` — `category: "data"` (channels only; see below)
12
+ - **Handler**: Upserts the proposal entry at status 202 (proposed).
13
+
14
+ ## Two namespaces per invocation
15
+
16
+ - **Log entry**: `log://turn_N/env/{slug}` — scheme=`log`, category=`logging`.
17
+ The audit record (renders inside `<log>` as `<env>`).
18
+ - **Data channels**: `env://turn_N/{slug}_1` (stdout), `env://turn_N/{slug}_2`
19
+ (stderr) — scheme=`env`, category=`data`. The captured payload
20
+ (renders inside `<context>` as `<env>`).
21
+
22
+ The `env` scheme exists **only** for the data channels. See
23
+ [scheme_category_split](#scheme_category_split).
10
24
 
11
25
  ## Projection
12
26
 
13
- Shows `env {command}` followed by the entry body.
27
+ - **Visible**: `# env {command}\n{body}`.
28
+ - **Summarized**: empty.