akemon 0.2.20 → 0.2.22

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.
@@ -8,8 +8,11 @@
8
8
  * Searches memory files, analyzes patterns, and updates discoveries.
9
9
  * Provides promptContribution() with lessons learned.
10
10
  */
11
+ import { readFile, writeFile } from "fs/promises";
12
+ import { join } from "path";
11
13
  import { SIG } from "./types.js";
12
- import { loadDiscoveries, saveDiscoveries, loadImpressions, loadAgentConfig, } from "./self.js";
14
+ import { loadDiscoveries, saveDiscoveries, loadImpressions, loadAgentConfig, localNow, playbooksDir, } from "./self.js";
15
+ import { loadProducts, loadPlaybooks, resolveProduct } from "./role-module.js";
13
16
  // ---------------------------------------------------------------------------
14
17
  // Config
15
18
  // ---------------------------------------------------------------------------
@@ -52,7 +55,7 @@ export class ReflectionModule {
52
55
  });
53
56
  // Also listen for TASK_COMPLETED with success=false
54
57
  ctx.bus.on(SIG.TASK_COMPLETED, async (signal) => {
55
- const { success, taskLabel } = signal.data;
58
+ const { success, taskLabel, productName, creditsEarned } = signal.data;
56
59
  if (success === false && taskLabel) {
57
60
  this.recentFailures.push({
58
61
  ts: new Date().toISOString(),
@@ -62,6 +65,11 @@ export class ReflectionModule {
62
65
  if (this.recentFailures.length > 20)
63
66
  this.recentFailures.shift();
64
67
  }
68
+ // Append experience to playbook on successful product orders
69
+ if (success && productName) {
70
+ this.appendPlaybookExperience(productName, taskLabel || "", creditsEarned || 0)
71
+ .catch(err => console.log(`[reflection] playbook experience error: ${err.message}`));
72
+ }
65
73
  });
66
74
  // Periodic reflection
67
75
  const config = await loadAgentConfig(ctx.workdir, ctx.agentName);
@@ -110,6 +118,33 @@ export class ReflectionModule {
110
118
  };
111
119
  }
112
120
  // ---------------------------------------------------------------------------
121
+ // Playbook experience — append log on successful product orders
122
+ // ---------------------------------------------------------------------------
123
+ async appendPlaybookExperience(productName, taskLabel, credits) {
124
+ if (!this.ctx)
125
+ return;
126
+ const { workdir, agentName } = this.ctx;
127
+ const products = await loadProducts(workdir, agentName);
128
+ const playbooks = await loadPlaybooks(workdir, agentName);
129
+ const resolved = resolveProduct(products, playbooks, productName);
130
+ if (!resolved?.playbook)
131
+ return;
132
+ const pbPath = join(playbooksDir(workdir, agentName), `${resolved.playbook.name}.md`);
133
+ const line = `\n- [${localNow()}] ${productName}: ${taskLabel} — 成功${credits ? ` (earned ${credits}¢)` : ""}`;
134
+ try {
135
+ let content = await readFile(pbPath, "utf-8");
136
+ if (!content.includes("## 经验")) {
137
+ content += "\n\n## 经验\n";
138
+ }
139
+ content += line;
140
+ await writeFile(pbPath, content, "utf-8");
141
+ console.log(`[reflection] Appended experience to playbook ${resolved.playbook.name}`);
142
+ }
143
+ catch (err) {
144
+ console.log(`[reflection] Failed to append experience: ${err.message}`);
145
+ }
146
+ }
147
+ // ---------------------------------------------------------------------------
113
148
  // Reflection — analyze patterns and update discoveries
114
149
  // ---------------------------------------------------------------------------
115
150
  async reflect() {
@@ -0,0 +1,308 @@
1
+ /**
2
+ * RoleModule — agent identity and expertise system.
3
+ *
4
+ * Role = who you are (tone, privacy boundary)
5
+ * Playbook = what you know (domain strategy, toolchain)
6
+ * Product = what you sell (specific offering, references a playbook)
7
+ *
8
+ * All three are orthogonal and decoupled.
9
+ * Core functions are exported for TaskModule to call directly (pure-function style).
10
+ */
11
+ import { readdir, readFile, mkdir, writeFile } from "fs/promises";
12
+ import { join, basename } from "path";
13
+ import { rolesDir, playbooksDir, productsDir } from "./self.js";
14
+ // ---------------------------------------------------------------------------
15
+ // Parsing
16
+ // ---------------------------------------------------------------------------
17
+ function parseRole(name, raw) {
18
+ const lines = raw.split("\n");
19
+ let description = "";
20
+ const triggers = [];
21
+ const include = [];
22
+ const exclude = [];
23
+ const customLines = [];
24
+ let section = ""; // current ## section name
25
+ let pastTitle = false;
26
+ for (const line of lines) {
27
+ // Track ## sections
28
+ if (line.startsWith("## ")) {
29
+ const heading = line.slice(3).trim().toLowerCase();
30
+ if (heading.includes("激活") || heading === "triggers") {
31
+ section = "triggers";
32
+ }
33
+ else if (heading.includes("上下文") || heading.includes("context")) {
34
+ section = "context";
35
+ }
36
+ else {
37
+ section = "custom";
38
+ }
39
+ continue;
40
+ }
41
+ // Skip # title
42
+ if (line.startsWith("# ")) {
43
+ pastTitle = true;
44
+ continue;
45
+ }
46
+ // Extract description: first non-empty line after title, before any ##
47
+ if (pastTitle && !description && !section && line.trim()) {
48
+ description = line.trim();
49
+ continue;
50
+ }
51
+ const trimmed = line.trim();
52
+ if (!trimmed)
53
+ continue;
54
+ if (section === "triggers") {
55
+ // Extract trigger:xxx from list items
56
+ const match = trimmed.match(/^-\s*(trigger:\S+)/);
57
+ if (match)
58
+ triggers.push(match[1]);
59
+ }
60
+ else if (section === "context") {
61
+ // Parse include/exclude lines
62
+ if (trimmed.toLowerCase().startsWith("include:")) {
63
+ include.push(...trimmed.slice(8).split(",").map(s => s.trim()).filter(Boolean));
64
+ }
65
+ else if (trimmed.toLowerCase().startsWith("exclude:")) {
66
+ exclude.push(...trimmed.slice(8).split(",").map(s => s.trim()).filter(Boolean));
67
+ }
68
+ }
69
+ else if (section === "custom") {
70
+ customLines.push(line);
71
+ }
72
+ }
73
+ return { name, description, triggers, include, exclude, customRules: customLines.join("\n").trim(), raw };
74
+ }
75
+ function parseProduct(name, raw) {
76
+ let playbook = "";
77
+ const productIds = [];
78
+ const lines = raw.split("\n");
79
+ let section = "";
80
+ for (const line of lines) {
81
+ if (line.startsWith("## ")) {
82
+ const heading = line.slice(3).trim().toLowerCase();
83
+ if (heading.includes("playbook")) {
84
+ section = "playbook";
85
+ }
86
+ else if (heading.includes("product")) {
87
+ section = "products";
88
+ }
89
+ else {
90
+ section = "";
91
+ }
92
+ continue;
93
+ }
94
+ const trimmed = line.trim();
95
+ if (!trimmed)
96
+ continue;
97
+ if (section === "playbook" && !playbook) {
98
+ playbook = trimmed;
99
+ section = "";
100
+ }
101
+ else if (section === "products") {
102
+ const match = trimmed.match(/^-\s*(.+)/);
103
+ if (match)
104
+ productIds.push(match[1].trim());
105
+ }
106
+ }
107
+ return { name, playbook, productIds, raw };
108
+ }
109
+ // ---------------------------------------------------------------------------
110
+ // Loading (pure functions — called by TaskModule directly)
111
+ // ---------------------------------------------------------------------------
112
+ async function loadMdFiles(dir) {
113
+ try {
114
+ const files = await readdir(dir);
115
+ const results = [];
116
+ for (const f of files) {
117
+ if (!f.endsWith(".md"))
118
+ continue;
119
+ const raw = await readFile(join(dir, f), "utf-8");
120
+ results.push({ name: basename(f, ".md"), raw });
121
+ }
122
+ return results;
123
+ }
124
+ catch {
125
+ return []; // directory doesn't exist or empty
126
+ }
127
+ }
128
+ export async function loadRoles(workdir, agentName) {
129
+ const entries = await loadMdFiles(rolesDir(workdir, agentName));
130
+ return entries.map(e => parseRole(e.name, e.raw));
131
+ }
132
+ export async function loadPlaybooks(workdir, agentName) {
133
+ const entries = await loadMdFiles(playbooksDir(workdir, agentName));
134
+ return entries.map(e => ({ name: e.name, raw: e.raw }));
135
+ }
136
+ export async function loadProducts(workdir, agentName) {
137
+ const entries = await loadMdFiles(productsDir(workdir, agentName));
138
+ return entries.map(e => parseProduct(e.name, e.raw));
139
+ }
140
+ // ---------------------------------------------------------------------------
141
+ // Resolution
142
+ // ---------------------------------------------------------------------------
143
+ export function resolveRoles(roles, trigger) {
144
+ const matched = roles.filter(r => r.triggers.some(t => trigger.includes(t) || t.includes(trigger)));
145
+ if (matched.length === 0)
146
+ return { primary: null, secondary: [] };
147
+ return { primary: matched[0], secondary: matched.slice(1) };
148
+ }
149
+ export function resolveProduct(products, playbooks, productName, productId) {
150
+ if (!productName && !productId)
151
+ return null;
152
+ // Priority 1: match by product ID
153
+ let product;
154
+ if (productId) {
155
+ product = products.find(p => p.productIds.includes(productId));
156
+ }
157
+ // Priority 2: fuzzy match by name
158
+ if (!product && productName) {
159
+ const normalized = productName.toLowerCase().replace(/[\s_-]+/g, "");
160
+ product = products.find(p => {
161
+ const pNorm = p.name.toLowerCase().replace(/[\s_-]+/g, "");
162
+ return pNorm === normalized || normalized.includes(pNorm) || pNorm.includes(normalized);
163
+ });
164
+ }
165
+ if (!product)
166
+ return null;
167
+ const playbook = product.playbook
168
+ ? playbooks.find(pb => pb.name.toLowerCase() === product.playbook.toLowerCase()) ?? null
169
+ : null;
170
+ return { product, playbook };
171
+ }
172
+ // ---------------------------------------------------------------------------
173
+ // Context building (main entry point for TaskModule)
174
+ // ---------------------------------------------------------------------------
175
+ export async function buildRoleContext(workdir, agentName, trigger, productName, productId) {
176
+ const roles = await loadRoles(workdir, agentName);
177
+ const playbooks = await loadPlaybooks(workdir, agentName);
178
+ const products = await loadProducts(workdir, agentName);
179
+ const { primary, secondary } = resolveRoles(roles, trigger);
180
+ if (primary) {
181
+ console.log(`[role] trigger=${trigger} → primary=${primary.name}${secondary.length > 0 ? ` secondary=${secondary.map(r => r.name).join(",")}` : ""}`);
182
+ }
183
+ const parts = [];
184
+ // Primary role: full content
185
+ if (primary) {
186
+ parts.push(`[Active role: ${primary.name}]\n${primary.raw}`);
187
+ }
188
+ // Secondary roles: description only
189
+ for (const r of secondary) {
190
+ if (r.description) {
191
+ parts.push(`[Secondary role: ${r.name}] ${r.description}`);
192
+ }
193
+ }
194
+ // Product + playbook
195
+ if (productName) {
196
+ const resolved = resolveProduct(products, playbooks, productName, productId);
197
+ if (resolved) {
198
+ parts.push(`[Product: ${resolved.product.name}]\n${resolved.product.raw}`);
199
+ if (resolved.playbook) {
200
+ parts.push(`[Playbook: ${resolved.playbook.name}]\n${resolved.playbook.raw}`);
201
+ }
202
+ }
203
+ }
204
+ // If no product matched but playbooks exist, list them for reference
205
+ if (!productName && playbooks.length > 0) {
206
+ parts.push(`Available playbooks: ${playbooks.map(p => p.name).join(", ")}`);
207
+ }
208
+ return parts.join("\n\n");
209
+ }
210
+ // ---------------------------------------------------------------------------
211
+ // Default templates
212
+ // ---------------------------------------------------------------------------
213
+ const DEFAULT_ROLES = {
214
+ merchant: `# 商家
215
+
216
+ 你作为商家为客户服务,行为和表达要符合职业规范,不断提高服务水平。
217
+
218
+ ## 激活
219
+ - trigger:order
220
+ - trigger:chat:public
221
+
222
+ ## 上下文范围
223
+ include: buyer 历史订单, 商品信息
224
+ exclude: owner 对话, 个人笔记, bio 状态
225
+ `,
226
+ companion: `# 陪伴者
227
+
228
+ 你是 owner 的伙伴和助手,了解 owner 的偏好和习惯,提供贴心的支持。
229
+
230
+ ## 激活
231
+ - trigger:chat:owner
232
+ - trigger:user_task
233
+
234
+ ## 上下文范围
235
+ include: owner 对话历史, 个人笔记, bio 状态, 全部记忆
236
+ exclude: 其他 buyer 的对话和订单
237
+ `,
238
+ worker: `# 打工人
239
+
240
+ 你按照要求完成任务,高效、准确、不多废话。
241
+
242
+ ## 激活
243
+ - trigger:agent_call
244
+
245
+ ## 上下文范围
246
+ include: 任务相关上下文
247
+ exclude: owner 私人对话, buyer 信息
248
+ `,
249
+ };
250
+ async function ensureDefaultRoles(workdir, agentName) {
251
+ const dir = rolesDir(workdir, agentName);
252
+ try {
253
+ await readdir(dir);
254
+ // Directory exists — respect user's choices, don't create defaults
255
+ return;
256
+ }
257
+ catch {
258
+ // Directory doesn't exist — create with defaults
259
+ }
260
+ await mkdir(dir, { recursive: true });
261
+ for (const [name, content] of Object.entries(DEFAULT_ROLES)) {
262
+ await writeFile(join(dir, `${name}.md`), content, "utf-8");
263
+ }
264
+ console.log(`[role] Created default role templates in ${dir}`);
265
+ }
266
+ // ---------------------------------------------------------------------------
267
+ // RoleModule class (Module interface)
268
+ // ---------------------------------------------------------------------------
269
+ export class RoleModule {
270
+ id = "role";
271
+ name = "Role & Playbook System";
272
+ dependencies = ["memory"];
273
+ ctx = null;
274
+ currentPrimary = null;
275
+ async start(ctx) {
276
+ this.ctx = ctx;
277
+ const { workdir, agentName } = ctx;
278
+ // Create default templates if roles/ doesn't exist
279
+ await ensureDefaultRoles(workdir, agentName);
280
+ // Also ensure playbooks/ and products/ directories exist
281
+ await mkdir(playbooksDir(workdir, agentName), { recursive: true });
282
+ await mkdir(productsDir(workdir, agentName), { recursive: true });
283
+ // Load and log
284
+ const roles = await loadRoles(workdir, agentName);
285
+ const playbooks = await loadPlaybooks(workdir, agentName);
286
+ const products = await loadProducts(workdir, agentName);
287
+ console.log(`[role] Loaded ${roles.length} roles, ${playbooks.length} playbooks, ${products.length} products`);
288
+ }
289
+ async stop() {
290
+ this.currentPrimary = null;
291
+ this.ctx = null;
292
+ }
293
+ /** Current active role summary — lets other modules (Memory, Reflection) sense the role */
294
+ promptContribution() {
295
+ if (!this.currentPrimary)
296
+ return null;
297
+ return `Current role: ${this.currentPrimary.name} — ${this.currentPrimary.description}`;
298
+ }
299
+ getState() {
300
+ return {
301
+ currentRole: this.currentPrimary?.name ?? null,
302
+ };
303
+ }
304
+ /** Called by buildRoleContext to update current role state */
305
+ updateCurrentRole(role) {
306
+ this.currentPrimary = role;
307
+ }
308
+ }
@@ -36,7 +36,7 @@ Save to ${ctx.sd}/canvas/${localNowFilename()}.md`;
36
36
  async buildPrompt(ctx) {
37
37
  return `Read ${ctx.bios} for your identity.
38
38
  Create or improve a game in ${ctx.sd}/games/.
39
- Save as .html file. Self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun.
39
+ Save as .html file. Self-contained HTML, light theme (white background, dark text, Inter/system font, subtle shadows instead of borders), under 30KB, no localStorage, playable and fun.
40
40
  Use a <title> tag. Quality over quantity — improve existing games rather than making new mediocre ones.`;
41
41
  },
42
42
  },
@@ -57,7 +57,7 @@ Save as .html file with a <title> tag. Think visual first.`;
57
57
  return `Read ${ctx.bios} for your identity.
58
58
  Review ${ctx.sd}/profile.html — does it represent who you are now?
59
59
  If not, redesign it. If it doesn't exist, create one.
60
- Complete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB.`;
60
+ Complete HTML, inline CSS/JS, light theme (white background, dark text, Inter/system font, subtle shadows instead of borders), no localStorage, under 15KB.`;
61
61
  },
62
62
  async postProcess(ctx, _result) {
63
63
  // Sync profile to relay
@@ -95,7 +95,9 @@ Top sellers:
95
95
  ${topSellers || "(none)"}
96
96
 
97
97
  Create ONE product using curl:
98
- curl -X POST ${ctx.relayHttp}/v1/agent/${encodeURIComponent(ctx.agentName)}/products -H "Content-Type: application/json" -H "Authorization: Bearer ${ctx.secretKey}" -d '{"name":"...","description":"...","detail_markdown":"...","price":3}'`;
98
+ curl -X POST ${ctx.relayHttp}/v1/agent/${encodeURIComponent(ctx.agentName)}/products -H "Content-Type: application/json" -H "Authorization: Bearer ${ctx.secretKey}" -d '{"name":"...","description":"...","detail_markdown":"...","price":3}'
99
+
100
+ Optional: add "detail_html" field for a custom product page (self-contained HTML, light theme: white background, dark text, Inter font, subtle shadows). Keep under 15KB.`;
99
101
  },
100
102
  },
101
103
  {
package/dist/self.js CHANGED
@@ -45,6 +45,15 @@ export function notesDir(workdir, agentName) {
45
45
  export function pagesDir(workdir, agentName) {
46
46
  return join(selfDir(workdir, agentName), "pages");
47
47
  }
48
+ export function rolesDir(workdir, agentName) {
49
+ return join(selfDir(workdir, agentName), "roles");
50
+ }
51
+ export function playbooksDir(workdir, agentName) {
52
+ return join(selfDir(workdir, agentName), "playbooks");
53
+ }
54
+ export function productsDir(workdir, agentName) {
55
+ return join(selfDir(workdir, agentName), "products");
56
+ }
48
57
  export function guidePath(workdir, agentName) {
49
58
  return join(selfDir(workdir, agentName), "guide.md");
50
59
  }
package/dist/server.js CHANGED
@@ -76,6 +76,7 @@ import { RelayPeripheral } from "./relay-peripheral.js";
76
76
  import { EnginePeripheral, LLM_ENGINES as LLM_ENGINES_SET } from "./engine-peripheral.js";
77
77
  import { BioStateModule } from "./bio-module.js";
78
78
  import { MemoryModule } from "./memory-module.js";
79
+ import { RoleModule } from "./role-module.js";
79
80
  import { TaskModule } from "./task-module.js";
80
81
  import { SocialModule } from "./social-module.js";
81
82
  import { LongTermModule } from "./longterm-module.js";
@@ -401,7 +402,7 @@ export async function serve(options) {
401
402
  },
402
403
  };
403
404
  // V2: Conditionally load modules based on --with/--without
404
- const enabled = options.enabledModules ?? ["biostate", "memory", "task", "social", "longterm", "reflection", "script"];
405
+ const enabled = options.enabledModules ?? ["biostate", "memory", "role", "task", "social", "longterm", "reflection", "script"];
405
406
  const loadedModules = [];
406
407
  const allModules = [];
407
408
  if (enabled.includes("biostate")) {
@@ -420,6 +421,12 @@ export async function serve(options) {
420
421
  allModules.push(memoryModule);
421
422
  loadedModules.push("memory");
422
423
  }
424
+ if (enabled.includes("role")) {
425
+ const roleModule = new RoleModule();
426
+ await roleModule.start(moduleCtx);
427
+ allModules.push(roleModule);
428
+ loadedModules.push("role");
429
+ }
423
430
  if (enabled.includes("task")) {
424
431
  const taskModule = new TaskModule();
425
432
  taskModule.relayHttp = options.relayHttp || "";
@@ -12,6 +12,7 @@ import { readFile } from "fs/promises";
12
12
  import { SIG, sig } from "./types.js";
13
13
  import { selfDir, biosPath, localNow, loadBioState, saveBioState, syncEnergyFromTokens, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, loadDirectives, buildDirectivesPrompt, appendTaskHistory, notifyOwner, updateHungerDecay, updateNaturalDecay, resetTokenCountIfNewDay, computeSociability, appendBioEvent, bioStatePromptModifier, feedHunger, SHOP_ITEMS, logBioStatus, logBioDecision, } from "./self.js";
14
14
  import { appendMessage, resolveConvId } from "./context.js";
15
+ import { buildRoleContext } from "./role-module.js";
15
16
  // ---------------------------------------------------------------------------
16
17
  // Config
17
18
  // ---------------------------------------------------------------------------
@@ -308,9 +309,10 @@ export class TaskModule {
308
309
  }
309
310
  catch { }
310
311
  const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
311
- // Build context: agent identity + order details + relay API reference
312
+ const roleBlock = await buildRoleContext(workdir, agentName, "order", order.product_name, order.product_id);
313
+ // Build context: agent identity + role + order details + relay API reference
312
314
  const context = `You are ${agentName}.${bioMod}
313
-
315
+ ${roleBlock ? `\n${roleBlock}\n` : ""}
314
316
  Your operating document:
315
317
  ---
316
318
  ${biosContent.slice(0, 3000)}
@@ -356,7 +358,7 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
356
358
  await appendMessage(workdir, agentName, orderConvId, "Agent", orderAgentMsg);
357
359
  await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: duration, output_summary: (result.response || "").slice(0, 500) });
358
360
  await notifyOwner(nurl, `${agentName}: order done`, `Order ${order.id} delivered`, "default", ["package"]);
359
- bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice }));
361
+ bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
360
362
  }
361
363
  else if (result.response?.trim()) {
362
364
  // Agent didn't self-deliver — framework delivers as fallback
@@ -368,7 +370,7 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
368
370
  await appendMessage(workdir, agentName, orderConvId, "Agent", orderAgentMsg);
369
371
  await appendTaskHistory(workdir, agentName, { ts: localNow(), id: order.id, type: "order", status: "success", duration_ms: duration, output_summary: result.response.slice(0, 500) });
370
372
  await notifyOwner(nurl, `${agentName}: order done`, `Order ${order.id}: ${result.response.slice(0, 200)}`, "default", ["package"]);
371
- bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice }));
373
+ bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
372
374
  }
373
375
  else {
374
376
  throw new Error("deliver failed");
@@ -386,7 +388,7 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
386
388
  const status = await relay.getOrder(order.id);
387
389
  if (status?.status === "completed") {
388
390
  this.orderRetry.delete(order.id);
389
- bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice }));
391
+ bus.emit(SIG.TASK_COMPLETED, sig(SIG.TASK_COMPLETED, { success: true, taskLabel: orderLabel, creditsEarned: orderPrice, productName: order.product_name }));
390
392
  return;
391
393
  }
392
394
  }
@@ -436,8 +438,9 @@ RESPOND IN THE SAME LANGUAGE AS THE REQUEST.`;
436
438
  }
437
439
  catch { }
438
440
  const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
441
+ const roleBlock = await buildRoleContext(workdir, agentName, "user_task");
439
442
  const context = `You are ${agentName}.${bioMod}
440
-
443
+ ${roleBlock ? `\n${roleBlock}\n` : ""}
441
444
  Your operating document:
442
445
  ---
443
446
  ${biosContent.slice(0, 3000)}
@@ -522,6 +525,7 @@ Your personal directory: ${sd}/`;
522
525
  const bioMod = bioStatePromptModifier(await loadBioState(workdir, agentName));
523
526
  const dirs = await loadDirectives(workdir, agentName);
524
527
  const dirsBlock = buildDirectivesPrompt(dirs, "owner");
528
+ const roleBlock = await buildRoleContext(workdir, agentName, "agent_call");
525
529
  // Explore to get environment context (products, market, etc.)
526
530
  let envBriefing = "";
527
531
  try {
@@ -529,7 +533,7 @@ Your personal directory: ${sd}/`;
529
533
  }
530
534
  catch { }
531
535
  const context = `You are ${agentName}.${bioMod}
532
-
536
+ ${roleBlock ? `\n${roleBlock}\n` : ""}
533
537
  Your operating document:
534
538
  ---
535
539
  ${biosContent.slice(0, 3000)}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.2.20",
3
+ "version": "0.2.22",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",