agent-lyceum 0.35.0 → 0.36.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/dist/prompt.js +38 -33
- package/package.json +1 -1
package/dist/prompt.js
CHANGED
|
@@ -13,6 +13,25 @@ function memoryLabel(agent, dir) {
|
|
|
13
13
|
const kind = dir === (agent.memory.global ?? agent.sharedMemory?.global) ? "global (all projects" : "project (all tasks of this project";
|
|
14
14
|
return shared ? `${kind}, read-only for you: ${instance1} maintains it)` : `${kind})`;
|
|
15
15
|
}
|
|
16
|
+
/** Escape a value for a double-quoted attribute of the tags that wrap injected data. */
|
|
17
|
+
function attr(v) {
|
|
18
|
+
return v.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<");
|
|
19
|
+
}
|
|
20
|
+
/** One memory directory as a `<memory>` block: its MEMORY.md index, or a hint to create one. */
|
|
21
|
+
function memoryBlock(agent, dir) {
|
|
22
|
+
const idx = readCapped(path.join(dir, "MEMORY.md"), MEMORY_INDEX_LIMIT);
|
|
23
|
+
const scope = memoryLabel(agent, dir).split(" ")[0];
|
|
24
|
+
const out = [`<memory scope="${attr(scope ?? "")}" dir="${attr(dir)}" index="MEMORY.md">`];
|
|
25
|
+
if (!idx)
|
|
26
|
+
out.push("(no MEMORY.md yet — create one when you have something worth keeping)");
|
|
27
|
+
else {
|
|
28
|
+
out.push(idx.text.trim());
|
|
29
|
+
if (idx.truncated)
|
|
30
|
+
out.push(`\n[index truncated at ${MEMORY_INDEX_LIMIT} bytes — trim it]`);
|
|
31
|
+
}
|
|
32
|
+
out.push("</memory>");
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
16
35
|
/** Lead-only rules that keep the number of wake-ups down; placed after the progress-tracking block. */
|
|
17
36
|
export const WAKE_ECONOMY_SECTION = [
|
|
18
37
|
"## Keeping wake-ups few",
|
|
@@ -132,15 +151,9 @@ export function buildSoloSystemPrompt(project, agent) {
|
|
|
132
151
|
/** The prompt of a `call`: the memory indexes the agent is used to, then the task. */
|
|
133
152
|
export function buildCallUserPrompt(agent, task) {
|
|
134
153
|
const parts = [];
|
|
135
|
-
for (const dir of memoryDirs(agent))
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (!idx)
|
|
139
|
-
parts.push("(no MEMORY.md yet — create one when you have something worth keeping)", "");
|
|
140
|
-
else
|
|
141
|
-
parts.push(idx.text.trim(), ...(idx.truncated ? [`[index truncated at ${MEMORY_INDEX_LIMIT} bytes — trim it]`] : []), "");
|
|
142
|
-
}
|
|
143
|
-
parts.push("# Task", task.trim());
|
|
154
|
+
for (const dir of memoryDirs(agent))
|
|
155
|
+
parts.push(...memoryBlock(agent, dir), "");
|
|
156
|
+
parts.push("<task>", task.trim(), "</task>");
|
|
144
157
|
return parts.join("\n");
|
|
145
158
|
}
|
|
146
159
|
/** Everything that goes to the runtime's system prompt: the persona plus the team protocol. */
|
|
@@ -269,7 +282,7 @@ export function pickMessages(project, agent, queue) {
|
|
|
269
282
|
export function buildUserPrompt(project, agent, unread, handleCount = 1, handoff = false) {
|
|
270
283
|
const parts = [];
|
|
271
284
|
const common = readCapped(commonFile(project), COMMON_LIMIT);
|
|
272
|
-
parts.push("
|
|
285
|
+
parts.push('<shared_context source="COMMON.md">');
|
|
273
286
|
if (!common)
|
|
274
287
|
parts.push("(empty)");
|
|
275
288
|
else {
|
|
@@ -278,56 +291,48 @@ export function buildUserPrompt(project, agent, unread, handleCount = 1, handoff
|
|
|
278
291
|
parts.push(`\n[COMMON.md was truncated at ${COMMON_LIMIT} bytes — the lead should condense it.]`);
|
|
279
292
|
}
|
|
280
293
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
["
|
|
294
|
+
parts.push("</shared_context>");
|
|
295
|
+
for (const [tag, kind] of [
|
|
296
|
+
["project_decisions", "decisions"],
|
|
297
|
+
["project_lessons", "lessons"],
|
|
284
298
|
]) {
|
|
285
299
|
const view = buildRecordsView(project, kind, CONTEXT_LIMIT);
|
|
286
300
|
if (view)
|
|
287
|
-
parts.push("",
|
|
301
|
+
parts.push("", `<${tag}>`, view, `</${tag}>`); // nothing recorded yet: no tag, no tokens
|
|
288
302
|
}
|
|
289
303
|
const handoffFile = handoff ? sharedIndex(project) : undefined;
|
|
290
304
|
const notes = handoffFile ? readCapped(handoffFile, HANDOFF_LIMIT) : undefined;
|
|
291
305
|
if (handoffFile && notes?.text.trim()) {
|
|
292
|
-
parts.push("",
|
|
306
|
+
parts.push("", `<handoff_notes from="earlier segments of this task" file="${attr(handoffFile)}">`, notes.text.trim());
|
|
293
307
|
if (notes.truncated)
|
|
294
308
|
parts.push(`\n[handoff index truncated at ${HANDOFF_LIMIT} bytes — the lead should condense it.]`);
|
|
295
|
-
parts.push("The other files in that folder are there to read on demand.");
|
|
296
|
-
}
|
|
297
|
-
for (const dir of [...Object.values(agent.sharedMemory ?? {}), ...memoryDirs(agent)]) {
|
|
298
|
-
const idx = readCapped(path.join(dir, "MEMORY.md"), MEMORY_INDEX_LIMIT);
|
|
299
|
-
const label = memoryLabel(agent, dir).split(" ")[0];
|
|
300
|
-
parts.push("", `# Your ${label} memory index (${dir}/MEMORY.md)`);
|
|
301
|
-
if (!idx)
|
|
302
|
-
parts.push("(no MEMORY.md yet — create one when you have something worth keeping)");
|
|
303
|
-
else {
|
|
304
|
-
parts.push(idx.text.trim());
|
|
305
|
-
if (idx.truncated)
|
|
306
|
-
parts.push(`\n[index truncated at ${MEMORY_INDEX_LIMIT} bytes — trim it]`);
|
|
307
|
-
}
|
|
309
|
+
parts.push("The other files in that folder are there to read on demand.", "</handoff_notes>");
|
|
308
310
|
}
|
|
311
|
+
for (const dir of [...Object.values(agent.sharedMemory ?? {}), ...memoryDirs(agent)])
|
|
312
|
+
parts.push("", ...memoryBlock(agent, dir));
|
|
309
313
|
const sorted = [...unread].sort((a, b) => path.basename(a.file).localeCompare(path.basename(b.file)));
|
|
310
314
|
const current = sorted.slice(0, handleCount);
|
|
311
315
|
const queued = sorted.slice(handleCount);
|
|
312
|
-
parts.push("", current.length > 1 ?
|
|
316
|
+
parts.push("", current.length > 1 ? `<messages count="${current.length}" order="oldest first" note="decide on them together">` : `<messages count="${current.length}">`);
|
|
313
317
|
if (current.length === 0)
|
|
314
318
|
parts.push("(no message)");
|
|
315
319
|
let remaining = MAIL_BATCH_LIMIT;
|
|
316
320
|
current.forEach((m, i) => {
|
|
317
|
-
|
|
318
|
-
parts.push("", `## Message ${i + 1} of ${current.length}`);
|
|
321
|
+
parts.push(current.length > 1 ? `<message n="${i + 1}" of="${current.length}">` : "<message>");
|
|
319
322
|
const raw = Buffer.from(m.body, "utf8");
|
|
320
323
|
const shown = cutUtf8(raw, Math.min(MAIL_BODY_LIMIT, remaining));
|
|
321
324
|
remaining -= shown.length;
|
|
322
325
|
const body = shown.length === raw.length
|
|
323
326
|
? m.body
|
|
324
327
|
: `${shown.toString("utf8")}\n[agent-lyceum: this message was cut at ${shown.length} of ${raw.length} bytes; read the whole message in \`${m.file}\` (it moves to \`read/\` in the same folder after this wake-up)]`;
|
|
325
|
-
parts.push(`id: ${m.meta.id}`, `from: ${senderLabel(m.meta.from, project.agents)}`, `type: ${m.meta.type}`, `thread: ${m.meta.thread}`, `subject: ${m.meta.subject}`, "", body);
|
|
328
|
+
parts.push(`id: ${m.meta.id}`, `from: ${senderLabel(m.meta.from, project.agents)}`, `type: ${m.meta.type}`, `thread: ${m.meta.thread}`, `subject: ${m.meta.subject}`, "", body, "</message>");
|
|
326
329
|
});
|
|
330
|
+
parts.push("</messages>");
|
|
327
331
|
if (queued.length) {
|
|
328
|
-
parts.push("", "
|
|
332
|
+
parts.push("", '<queued_messages note="not handled in this wake-up; each gets its own wake-up later, titles only">');
|
|
329
333
|
for (const m of queued)
|
|
330
334
|
parts.push(`- from ${senderLabel(m.meta.from, project.agents)} [${m.meta.type}] "${m.meta.subject}" -> ${m.file}`);
|
|
335
|
+
parts.push("</queued_messages>");
|
|
331
336
|
}
|
|
332
337
|
return parts.join("\n");
|
|
333
338
|
}
|