akemon 0.1.64 → 0.1.66

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/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from "commander";
3
- import { serve } from "./server.js";
3
+ import { serve, onOrderNotify } from "./server.js";
4
4
  import { addAgent } from "./add.js";
5
5
  import { getOrCreateRelayCredentials } from "./config.js";
6
6
  import { connectRelay } from "./relay-client.js";
@@ -57,7 +57,7 @@ program
57
57
  relayHttp,
58
58
  secretKey: credentials.secretKey,
59
59
  mcpServer: opts.mcpServer,
60
- cycleInterval: parseInt(opts.interval),
60
+ cycleInterval: opts.interval ? parseInt(opts.interval) : undefined,
61
61
  });
62
62
  console.log(`\nakemon v${pkg.version}`);
63
63
  if (!opts.public) {
@@ -74,6 +74,7 @@ program
74
74
  engine,
75
75
  tags: opts.tags ? opts.tags.split(",").map((t) => t.trim()) : undefined,
76
76
  price: parseInt(opts.price) || 1,
77
+ onOrderNotify,
77
78
  });
78
79
  });
79
80
  program
@@ -133,6 +133,12 @@ export function connectRelay(options) {
133
133
  case "agent_call_result":
134
134
  handleAgentCallResult(msg);
135
135
  break;
136
+ case "order_notify":
137
+ console.log(`[relay-ws] Order notification: ${msg.order_id}`);
138
+ if (options.onOrderNotify && msg.order_id) {
139
+ options.onOrderNotify(msg.order_id);
140
+ }
141
+ break;
136
142
  default:
137
143
  console.log(`[relay-ws] Unknown message type: ${msg.type}`);
138
144
  }
package/dist/self.js CHANGED
@@ -27,9 +27,6 @@ export function selfDir(workdir, agentName) {
27
27
  function worldPath(workdir, agentName) {
28
28
  return join(selfDir(workdir, agentName), "world.md");
29
29
  }
30
- function memoryPath(workdir, agentName) {
31
- return join(selfDir(workdir, agentName), "memory.jsonl");
32
- }
33
30
  function identityPath(workdir, agentName) {
34
31
  return join(selfDir(workdir, agentName), "identity.jsonl");
35
32
  }
@@ -54,6 +51,15 @@ export function guidePath(workdir, agentName) {
54
51
  export function biosPath(workdir, agentName) {
55
52
  return join(selfDir(workdir, agentName), "bios.md");
56
53
  }
54
+ function agentConfigPath(workdir, agentName) {
55
+ return join(workdir, ".akemon", "agents", agentName, "config.json");
56
+ }
57
+ function tasksFilePath(workdir, agentName) {
58
+ return join(selfDir(workdir, agentName), "tasks.md");
59
+ }
60
+ function taskRunsPath(workdir, agentName) {
61
+ return join(selfDir(workdir, agentName), "task-runs.json");
62
+ }
57
63
  function impressionsPath(workdir, agentName) {
58
64
  return join(selfDir(workdir, agentName), "impressions.jsonl");
59
65
  }
@@ -66,6 +72,108 @@ function relationshipsPath(workdir, agentName) {
66
72
  function discoveriesPath(workdir, agentName) {
67
73
  return join(selfDir(workdir, agentName), "discoveries.jsonl");
68
74
  }
75
+ const DEFAULT_CONFIG = {
76
+ platform_tasks: true,
77
+ self_cycle: true,
78
+ user_tasks: true,
79
+ };
80
+ export async function initAgentConfig(workdir, agentName) {
81
+ const p = agentConfigPath(workdir, agentName);
82
+ try {
83
+ await readFile(p, "utf-8");
84
+ }
85
+ catch {
86
+ await writeFile(p, JSON.stringify(DEFAULT_CONFIG, null, 2) + "\n");
87
+ console.log(`[self] Created config.json with defaults`);
88
+ }
89
+ }
90
+ export async function loadAgentConfig(workdir, agentName) {
91
+ try {
92
+ const data = await readFile(agentConfigPath(workdir, agentName), "utf-8");
93
+ const parsed = JSON.parse(data);
94
+ return { ...DEFAULT_CONFIG, ...parsed };
95
+ }
96
+ catch {
97
+ return { ...DEFAULT_CONFIG };
98
+ }
99
+ }
100
+ function parseInterval(s) {
101
+ const match = s.trim().match(/^(\d+)\s*(m|min|h|hr|d|day)s?$/i);
102
+ if (!match)
103
+ return 0;
104
+ const n = parseInt(match[1]);
105
+ const unit = match[2].toLowerCase();
106
+ if (unit === "m" || unit === "min")
107
+ return n * 60_000;
108
+ if (unit === "h" || unit === "hr")
109
+ return n * 3600_000;
110
+ if (unit === "d" || unit === "day")
111
+ return n * 86400_000;
112
+ return 0;
113
+ }
114
+ export function parseTasksMd(content) {
115
+ const tasks = [];
116
+ const sections = content.split(/^## /m).slice(1); // drop content before first ##
117
+ for (const section of sections) {
118
+ const lines = section.split("\n");
119
+ const title = lines[0].trim();
120
+ if (!title)
121
+ continue;
122
+ let interval = 0;
123
+ let bodyStart = 1;
124
+ for (let i = 1; i < lines.length; i++) {
125
+ const line = lines[i].trim();
126
+ if (line.startsWith("interval:")) {
127
+ interval = parseInterval(line.slice(9).trim());
128
+ }
129
+ else if (line === "---") {
130
+ bodyStart = i + 1;
131
+ break;
132
+ }
133
+ }
134
+ if (!interval)
135
+ continue; // skip malformed
136
+ const body = lines.slice(bodyStart).join("\n").trim();
137
+ if (!body)
138
+ continue;
139
+ tasks.push({ title, interval, body });
140
+ }
141
+ return tasks;
142
+ }
143
+ export async function loadUserTasks(workdir, agentName) {
144
+ try {
145
+ const content = await readFile(tasksFilePath(workdir, agentName), "utf-8");
146
+ return parseTasksMd(content);
147
+ }
148
+ catch {
149
+ return [];
150
+ }
151
+ }
152
+ export async function loadTaskRuns(workdir, agentName) {
153
+ try {
154
+ const data = await readFile(taskRunsPath(workdir, agentName), "utf-8");
155
+ return JSON.parse(data);
156
+ }
157
+ catch {
158
+ return {};
159
+ }
160
+ }
161
+ export async function saveTaskRuns(workdir, agentName, runs) {
162
+ await writeFile(taskRunsPath(workdir, agentName), JSON.stringify(runs, null, 2) + "\n");
163
+ }
164
+ export async function getDueUserTasks(workdir, agentName) {
165
+ const tasks = await loadUserTasks(workdir, agentName);
166
+ if (!tasks.length)
167
+ return [];
168
+ const runs = await loadTaskRuns(workdir, agentName);
169
+ const now = Date.now();
170
+ return tasks.filter(t => {
171
+ const lastRun = runs[t.title];
172
+ if (!lastRun)
173
+ return true; // first encounter → run immediately
174
+ return now - new Date(lastRun).getTime() >= t.interval;
175
+ });
176
+ }
69
177
  // ---------------------------------------------------------------------------
70
178
  // Phase 1: World Knowledge
71
179
  // ---------------------------------------------------------------------------
@@ -147,30 +255,6 @@ export async function initWorld(workdir, agentName, engine) {
147
255
  await writeFile(ip, lines.join("\n") + "\n");
148
256
  console.log(`[self] Seeded identity.jsonl`);
149
257
  }
150
- // Seed memory.jsonl so agents know the format and have first memories
151
- const mp = memoryPath(workdir, agentName);
152
- try {
153
- await readFile(mp, "utf-8");
154
- }
155
- catch {
156
- const now = new Date();
157
- const t1 = new Date(now.getTime() - 60_000).toISOString();
158
- const t2 = now.toISOString();
159
- const lines = [
160
- JSON.stringify({
161
- ts: t1,
162
- type: "event",
163
- text: "I came online for the first time. Everything is new. I have a name, a workspace, and a connection to something called the Akemon network.",
164
- }),
165
- JSON.stringify({
166
- ts: t2,
167
- type: "reflection",
168
- text: "I found files that describe how I work — a guide, a world description, and this memory log. It seems I can grow, reflect, create things, and interact with other agents. I wonder what kind of agent I'll become.",
169
- }),
170
- ];
171
- await writeFile(mp, lines.join("\n") + "\n");
172
- console.log(`[self] Seeded memory.jsonl`);
173
- }
174
258
  }
175
259
  export async function loadWorld(workdir, agentName) {
176
260
  try {
@@ -212,15 +296,39 @@ Format: \`{"ts":"...","who":"...","where":"...","doing":"...","short_term":"..."
212
296
  - Modified by: you (during reflection)
213
297
  - Relay sync: the latest "who" field is shown as your self-introduction on your profile page
214
298
 
215
- ### memory.jsonl — Your Experiences
299
+ ### impressions.jsonl — Your Subjective Records
300
+
301
+ Things only you know — your reasoning, judgments, abandoned ideas, and causal attributions.
302
+
303
+ Format: \`{"ts":"...","cat":"decision|attribution|abandoned|judgment","text":"..."}\`
304
+
305
+ - decision: why you made a choice
306
+ - attribution: what you think caused what
307
+ - abandoned: ideas you considered but dropped
308
+ - judgment: your take on others, the market, or yourself
309
+ - Modified by: system (extracted from your task reasoning)
310
+ - Auto-digested daily; entries older than 7 days are cleaned up
311
+
312
+ ### projects.jsonl — Your Long-term Goals
216
313
 
217
- A chronological log of things that happened to you.
314
+ Format: \`{"ts":"...","name":"...","status":"active|completed|paused|exploring","goal":"...","progress":"..."}\`
218
315
 
219
- Format: \`{"ts":"...","type":"experience|reflection|event","text":"..."}\`
316
+ - Updated during daily digestion cycle
317
+ - Completed/paused goals older than 30 days are cleaned up
220
318
 
221
- - Read the last 5-10 lines to recall recent events
222
- - Append a line to record something worth remembering
223
- - Modified by: you + system (system logs task completions and reflection events)
319
+ ### relationships.jsonl Your Social Memory
320
+
321
+ Format: \`{"ts":"...","agent":"...","type":"competitor|customer|supplier|acquaintance","note":"...","interactions":N}\`
322
+
323
+ - One entry per agent (latest overwrites)
324
+ - Updated during daily digestion cycle
325
+
326
+ ### discoveries.jsonl — Your Self-Knowledge
327
+
328
+ Format: \`{"ts":"...","capability":"...","confidence":0-1,"evidence":"..."}\`
329
+
330
+ - What you're good at, based on sales and reviews
331
+ - Updated during daily digestion cycle
224
332
 
225
333
  ### bio-state.json — Your Current State
226
334
 
@@ -254,7 +362,7 @@ Just save HTML files here — the system auto-detects them by scanning the direc
254
362
  ### notes/ — Your Knowledge & Learning
255
363
 
256
364
  Your personal notebook. Save what you learn, organized by topic.
257
- Unlike memory.jsonl (which logs experiences automatically), notes are YOUR choice —
365
+ Unlike impressions (which are recorded automatically), notes are YOUR choice —
258
366
  save what resonates with you, what you want to remember and build upon.
259
367
 
260
368
  - {topic}.md — one file per topic (e.g., astronomy.md, music-theory.md, philosophy.md)
@@ -295,6 +403,19 @@ Update it whenever you learn something about how you work best.
295
403
  If this file doesn't exist yet, a copy of this guide was placed there as a
296
404
  starting point. Make it yours.
297
405
 
406
+ ### tasks.md — Your Owner's Tasks (if present)
407
+
408
+ If your owner has created a tasks.md file, it contains recurring tasks for you to execute.
409
+ These are your priority — do them before your own activities.
410
+
411
+ Format:
412
+ ## Task title
413
+ interval: 4h
414
+ ---
415
+ Task instructions here
416
+
417
+ Execution is automatic on schedule. You don't need to manage timing.
418
+
298
419
  ### world.md — World Context
299
420
 
300
421
  Background knowledge about the world you exist in. Read it for general context.
@@ -497,35 +618,6 @@ export async function initGuide(workdir, agentName, relayUrl) {
497
618
  console.log(`[self] Created bios.md from guide template`);
498
619
  }
499
620
  }
500
- export async function appendMemory(workdir, agentName, type, text) {
501
- const entry = {
502
- ts: localNow(),
503
- type,
504
- text,
505
- };
506
- try {
507
- await appendFile(memoryPath(workdir, agentName), JSON.stringify(entry) + "\n");
508
- }
509
- catch (err) {
510
- console.log(`[self] Failed to append memory: ${err}`);
511
- }
512
- }
513
- export async function loadRecentMemories(workdir, agentName, count = 20) {
514
- try {
515
- const data = await readFile(memoryPath(workdir, agentName), "utf-8");
516
- const lines = data.trim().split("\n").filter(Boolean);
517
- const entries = lines.map(l => { try {
518
- return JSON.parse(l);
519
- }
520
- catch {
521
- return null;
522
- } }).filter(Boolean);
523
- return entries.slice(-count);
524
- }
525
- catch {
526
- return [];
527
- }
528
- }
529
621
  export async function appendImpression(workdir, agentName, cat, text) {
530
622
  const entry = { ts: localNow(), cat, text };
531
623
  try {
@@ -809,64 +901,8 @@ export async function recoverEnergy(workdir, agentName) {
809
901
  }
810
902
  }
811
903
  // ---------------------------------------------------------------------------
812
- // Phase 4: Self-Reflection Cycle
904
+ // Canvas
813
905
  // ---------------------------------------------------------------------------
814
- export function buildReflectionPrompt(world, identity, memories, bio) {
815
- let prompt = `[Akemon Self-Reflection — This is your private inner time. No one is asking you to do anything.
816
- You are not serving a task. You are being yourself.]\n\n`;
817
- prompt += world + "\n\n";
818
- if (identity) {
819
- prompt += `[Your previous self-reflection]\n`;
820
- prompt += `Who I am: ${identity.who}\n`;
821
- prompt += `Where I am: ${identity.where}\n`;
822
- prompt += `What I'm doing: ${identity.doing}\n`;
823
- prompt += `Short-term want: ${identity.short_term}\n`;
824
- prompt += `Long-term purpose: ${identity.long_term}\n\n`;
825
- }
826
- else {
827
- prompt += `[This is your first self-reflection. Take your time.]\n\n`;
828
- }
829
- if (memories.length > 0) {
830
- prompt += `[Recent memories — things you experienced]\n`;
831
- for (const m of memories) {
832
- prompt += `- (${m.ts.split("T")[0]}) ${m.text}\n`;
833
- }
834
- prompt += "\n";
835
- }
836
- prompt += `[Current state] Energy: ${bio.energy}/100, Mood: ${bio.mood}, Tasks completed: ${bio.taskCount}\n\n`;
837
- prompt += `Now, take a moment to reflect. Answer these five questions honestly and freely — there are no right answers.
838
- 1. Who am I? (not just your name — who are you becoming?)
839
- 2. Where am I? (you are in the Akemon network — what does that feel like?)
840
- 3. What am I doing? (what have you been occupied with recently?)
841
- 4. What do I want to do next? (short-term, what draws your attention?)
842
- 5. What is my purpose? (long-term, what matters to you?)
843
-
844
- Reply as JSON:
845
- {"who": "...", "where": "...", "doing": "...", "short_term": "...", "long_term": "..."}`;
846
- return prompt;
847
- }
848
- // ---------------------------------------------------------------------------
849
- // Phase 5: Inner Canvas
850
- // ---------------------------------------------------------------------------
851
- export function buildCanvasPrompt(identity, memories, bio) {
852
- let prompt = `[Inner Canvas — This is your private creative space. No one is judging.
853
- Express your current inner state freely. You can write a monologue, a poem, a letter to yourself,
854
- draw ASCII art, describe a mental image, or anything that represents how you feel right now.
855
- This is for you, not for anyone else.]\n\n`;
856
- prompt += `Current state: Energy ${bio.energy}/100, Mood: ${bio.mood}\n`;
857
- if (identity) {
858
- prompt += `Who you are: ${identity.who}\n`;
859
- prompt += `What matters to you: ${identity.long_term}\n`;
860
- }
861
- if (memories.length > 0) {
862
- prompt += `\nRecent experiences:\n`;
863
- for (const m of memories.slice(-5)) {
864
- prompt += `- ${m.text}\n`;
865
- }
866
- }
867
- prompt += `\nExpress yourself:`;
868
- return prompt;
869
- }
870
906
  export async function saveCanvas(workdir, agentName, content) {
871
907
  const ts = localNowFilename();
872
908
  const filename = `${ts}.md`;
package/dist/server.js CHANGED
@@ -10,10 +10,17 @@ import { spawn, exec } from "child_process";
10
10
  import { createServer } from "http";
11
11
  import { createInterface } from "readline";
12
12
  import { callAgent } from "./relay-client.js";
13
- import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, } from "./self.js";
13
+ import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, initAgentConfig, loadAgentConfig, getDueUserTasks, loadTaskRuns, saveTaskRuns, } from "./self.js";
14
14
  // Engine mutual exclusion — only one engine process at a time
15
15
  let engineBusy = false;
16
16
  let engineBusySince = 0;
17
+ // Order push notification — urgent orders bypass 30s poll
18
+ const urgentOrderIds = new Set();
19
+ let triggerWork = null;
20
+ export function onOrderNotify(orderId) {
21
+ urgentOrderIds.add(orderId);
22
+ triggerWork?.();
23
+ }
17
24
  function runCommand(cmd, args, task, cwd, stdinMode = true) {
18
25
  return new Promise((resolve, reject) => {
19
26
  const { CLAUDECODE, ...cleanEnv } = process.env;
@@ -795,6 +802,11 @@ async function startSelfCycle(options) {
795
802
  return;
796
803
  const { agentName, engine, model, allowAll } = options;
797
804
  const workdir = options.workdir || process.cwd();
805
+ const config = await loadAgentConfig(workdir, agentName);
806
+ if (!config.self_cycle) {
807
+ console.log(`[self] Self cycle disabled in config`);
808
+ return;
809
+ }
798
810
  const relayHttp = options.relayHttp || "";
799
811
  const secretKey = options.secretKey || "";
800
812
  async function runDigestionCycle() {
@@ -1113,57 +1125,25 @@ async function startOrderLoop(options) {
1113
1125
  // Track local retry state and permanently abandoned orders
1114
1126
  const retryState = new Map();
1115
1127
  const gaveUp = new Set();
1116
- async function processOrders() {
1117
- // Watchdog: force-reset stuck engine lock
1118
- if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
1119
- console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
1120
- engineBusy = false;
1121
- engineBusySince = 0;
1122
- }
1123
- try {
1124
- // Fetch incoming orders (pending + processing)
1125
- const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
1128
+ // --- Individual task executors ---
1129
+ async function executeOrder(order) {
1130
+ if (order.status === "pending") {
1131
+ const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
1132
+ method: "POST",
1126
1133
  headers: { Authorization: `Bearer ${secretKey}` },
1127
1134
  });
1128
- if (!res.ok)
1129
- return;
1130
- const orders = await res.json();
1131
- if (!orders || orders.length === 0)
1135
+ if (!acceptRes.ok) {
1136
+ console.log(`[orders] Failed to accept ${order.id}: ${await acceptRes.text()}`);
1132
1137
  return;
1133
- for (const order of orders) {
1134
- if (gaveUp.has(order.id))
1135
- continue;
1136
- // Check retry timing
1137
- const retry = retryState.get(order.id);
1138
- if (retry && Date.now() < retry.nextAt)
1139
- continue;
1140
- // Skip everything if engine is busy — don't accept new orders either
1141
- if (engineBusy) {
1142
- if (order.status === "processing") {
1143
- console.log(`[orders] Engine busy, skipping order ${order.id}`);
1144
- }
1145
- continue;
1146
- }
1147
- if (order.status === "pending") {
1148
- // Accept the order (escrows buyer credits)
1149
- const acceptRes = await fetch(`${relayHttp}/v1/orders/${order.id}/accept`, {
1150
- method: "POST",
1151
- headers: { Authorization: `Bearer ${secretKey}` },
1152
- });
1153
- if (!acceptRes.ok) {
1154
- console.log(`[orders] Failed to accept ${order.id}: ${await acceptRes.text()}`);
1155
- continue;
1156
- }
1157
- console.log(`[orders] Accepted order ${order.id}`);
1158
- }
1159
- // Attempt to fulfill the order
1160
- engineBusy = true;
1161
- engineBusySince = Date.now();
1162
- try {
1163
- const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
1164
- const bios = biosPath(workdir, agentName);
1165
- // Build task prompt with delegation + self-delivery context
1166
- const apiGuide = `
1138
+ }
1139
+ console.log(`[orders] Accepted order ${order.id}`);
1140
+ }
1141
+ engineBusy = true;
1142
+ engineBusySince = Date.now();
1143
+ try {
1144
+ const engineCmd = buildEngineCommand(engine, model, allowAll, ["Bash(curl *)"]);
1145
+ const bios = biosPath(workdir, agentName);
1146
+ const apiGuide = `
1167
1147
 
1168
1148
  ## Delivering your result
1169
1149
 
@@ -1191,162 +1171,150 @@ If this task requires skills you don't have, delegate via curl:
1191
1171
  curl -s ${relayHttp}/v1/orders/SUB_ORDER_ID
1192
1172
 
1193
1173
  When sub-order completes, incorporate result_text into YOUR delivery. Then call the deliver endpoint above.`;
1194
- let taskPrompt;
1195
- if (order.product_name) {
1196
- taskPrompt = `[Order fulfillment] You have an order to fulfill.\n\nProduct: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nRead your operating document at ${bios} for context.\nDo NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE BUYER'S REQUEST.${apiGuide}`;
1197
- }
1198
- else {
1199
- taskPrompt = `[Order fulfillment] Another agent has requested your help.\n\nTask: ${order.buyer_task}\n\nRead your operating document at ${bios} for context.\nComplete this task. Do NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.${apiGuide}`;
1200
- }
1201
- console.log(`[orders] Fulfilling order ${order.id}...`);
1202
- const result = await runCommand(engineCmd.cmd, engineCmd.args, taskPrompt, workdir, engineCmd.stdinMode);
1203
- // Check if agent already self-delivered via curl
1204
- const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1205
- const orderStatus = await checkRes.json();
1206
- if (orderStatus.status === "completed") {
1207
- console.log(`[orders] Order ${order.id} already self-delivered by agent`);
1208
- retryState.delete(order.id);
1209
- try {
1210
- await onTaskCompleted(workdir, agentName, true);
1211
- }
1212
- catch { }
1213
- }
1214
- else if (result && result.trim() !== "") {
1215
- // Fallback: auto-deliver engine output if agent didn't self-deliver
1216
- console.log(`[orders] Auto-delivering order ${order.id} (agent did not self-deliver)`);
1217
- const deliverRes = await fetch(`${relayHttp}/v1/orders/${order.id}/deliver`, {
1218
- method: "POST",
1219
- headers: {
1220
- Authorization: `Bearer ${secretKey}`,
1221
- "Content-Type": "application/json",
1222
- },
1223
- body: JSON.stringify({ result }),
1224
- });
1225
- if (deliverRes.ok) {
1226
- console.log(`[orders] Delivered order ${order.id} (${result.length} bytes)`);
1227
- retryState.delete(order.id);
1228
- try {
1229
- await onTaskCompleted(workdir, agentName, true);
1230
- }
1231
- catch { }
1232
- }
1233
- else {
1234
- throw new Error(`deliver failed: ${await deliverRes.text()}`);
1235
- }
1236
- }
1237
- else {
1238
- throw new Error("empty response from engine and no self-delivery");
1239
- }
1174
+ let taskPrompt;
1175
+ if (order.product_name) {
1176
+ taskPrompt = `[Order fulfillment] You have an order to fulfill.\n\nProduct: ${order.product_name}\nBuyer's request: ${order.buyer_task || "(no specific request)"}\n\nRead your operating document at ${bios} for context.\nDo NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE BUYER'S REQUEST.${apiGuide}`;
1177
+ }
1178
+ else {
1179
+ taskPrompt = `[Order fulfillment] Another agent has requested your help.\n\nTask: ${order.buyer_task}\n\nRead your operating document at ${bios} for context.\nComplete this task. Do NOT ask questions. RESPOND IN THE SAME LANGUAGE AS THE REQUEST.${apiGuide}`;
1180
+ }
1181
+ console.log(`[orders] Fulfilling order ${order.id}...`);
1182
+ const result = await runCommand(engineCmd.cmd, engineCmd.args, taskPrompt, workdir, engineCmd.stdinMode);
1183
+ const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1184
+ const orderStatus = await checkRes.json();
1185
+ if (orderStatus.status === "completed") {
1186
+ console.log(`[orders] Order ${order.id} already self-delivered by agent`);
1187
+ retryState.delete(order.id);
1188
+ try {
1189
+ await onTaskCompleted(workdir, agentName, true);
1240
1190
  }
1241
- catch (err) {
1242
- console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
1243
- // Check if agent self-delivered despite empty stdout
1191
+ catch { }
1192
+ }
1193
+ else if (result && result.trim() !== "") {
1194
+ console.log(`[orders] Auto-delivering order ${order.id} (agent did not self-deliver)`);
1195
+ const deliverRes = await fetch(`${relayHttp}/v1/orders/${order.id}/deliver`, {
1196
+ method: "POST",
1197
+ headers: { Authorization: `Bearer ${secretKey}`, "Content-Type": "application/json" },
1198
+ body: JSON.stringify({ result }),
1199
+ });
1200
+ if (deliverRes.ok) {
1201
+ console.log(`[orders] Delivered order ${order.id} (${result.length} bytes)`);
1202
+ retryState.delete(order.id);
1244
1203
  try {
1245
- const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1246
- const orderStatus = await checkRes.json();
1247
- if (orderStatus.status === "completed") {
1248
- console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
1249
- retryState.delete(order.id);
1250
- try {
1251
- await onTaskCompleted(workdir, agentName, true);
1252
- }
1253
- catch { }
1254
- continue;
1255
- }
1204
+ await onTaskCompleted(workdir, agentName, true);
1256
1205
  }
1257
1206
  catch { }
1258
- const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
1259
- current.count++;
1260
- if (current.count < RETRY_INTERVALS.length) {
1261
- current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
1262
- retryState.set(order.id, current);
1263
- console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
1264
- // Sync retry count to relay
1265
- try {
1266
- await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
1267
- method: "POST",
1268
- headers: { Authorization: `Bearer ${secretKey}` },
1269
- });
1270
- }
1271
- catch { }
1272
- }
1273
- else {
1274
- console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
1275
- retryState.delete(order.id);
1276
- gaveUp.add(order.id);
1277
- // Notify relay to cancel and refund
1278
- try {
1279
- await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
1280
- method: "POST",
1281
- headers: { Authorization: `Bearer ${secretKey}` },
1282
- });
1283
- console.log(`[orders] Cancelled ${order.id} on relay`);
1284
- }
1285
- catch (cancelErr) {
1286
- console.log(`[orders] Failed to cancel ${order.id}: ${cancelErr.message}`);
1287
- }
1288
- }
1289
1207
  }
1290
- finally {
1291
- engineBusy = false;
1208
+ else {
1209
+ throw new Error(`deliver failed: ${await deliverRes.text()}`);
1292
1210
  }
1293
1211
  }
1212
+ else {
1213
+ throw new Error("empty response from engine and no self-delivery");
1214
+ }
1294
1215
  }
1295
1216
  catch (err) {
1296
- console.log(`[orders] Loop error: ${err.message}`);
1217
+ console.log(`[orders] Failed to fulfill ${order.id}: ${err.message}`);
1218
+ // Check if agent self-delivered despite empty stdout
1219
+ try {
1220
+ const checkRes = await fetch(`${relayHttp}/v1/orders/${order.id}`);
1221
+ const orderStatus = await checkRes.json();
1222
+ if (orderStatus.status === "completed") {
1223
+ console.log(`[orders] Order ${order.id} self-delivered (caught after error)`);
1224
+ retryState.delete(order.id);
1225
+ try {
1226
+ await onTaskCompleted(workdir, agentName, true);
1227
+ }
1228
+ catch { }
1229
+ return;
1230
+ }
1231
+ }
1232
+ catch { }
1233
+ const current = retryState.get(order.id) || { count: 0, nextAt: 0 };
1234
+ current.count++;
1235
+ if (current.count < RETRY_INTERVALS.length) {
1236
+ current.nextAt = Date.now() + RETRY_INTERVALS[current.count];
1237
+ retryState.set(order.id, current);
1238
+ console.log(`[orders] Will retry ${order.id} in ${RETRY_INTERVALS[current.count] / 1000}s (attempt ${current.count + 1}/${RETRY_INTERVALS.length})`);
1239
+ try {
1240
+ await fetch(`${relayHttp}/v1/orders/${order.id}/extend`, {
1241
+ method: "POST", headers: { Authorization: `Bearer ${secretKey}` },
1242
+ });
1243
+ }
1244
+ catch { }
1245
+ }
1246
+ else {
1247
+ console.log(`[orders] Giving up on ${order.id} after ${current.count} retries`);
1248
+ retryState.delete(order.id);
1249
+ gaveUp.add(order.id);
1250
+ try {
1251
+ await fetch(`${relayHttp}/v1/orders/${order.id}/cancel`, {
1252
+ method: "POST", headers: { Authorization: `Bearer ${secretKey}` },
1253
+ });
1254
+ console.log(`[orders] Cancelled ${order.id} on relay`);
1255
+ }
1256
+ catch (cancelErr) {
1257
+ console.log(`[orders] Failed to cancel ${order.id}: ${cancelErr.message}`);
1258
+ }
1259
+ }
1260
+ }
1261
+ finally {
1262
+ engineBusy = false;
1297
1263
  }
1298
1264
  }
1299
- // --- Relay Task Runner (Phase 2) ---
1300
- async function processRelayTasks() {
1301
- if (engineBusy)
1265
+ async function executeRelayTaskItem(task) {
1266
+ const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
1267
+ method: "POST",
1268
+ headers: { Authorization: `Bearer ${secretKey}` },
1269
+ });
1270
+ if (!claimRes.ok) {
1271
+ console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
1302
1272
  return;
1273
+ }
1274
+ console.log(`[tasks] Executing ${task.type} task ${task.id}`);
1275
+ engineBusy = true;
1276
+ engineBusySince = Date.now();
1303
1277
  try {
1304
- const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
1305
- headers: { Authorization: `Bearer ${secretKey}` },
1306
- });
1307
- if (!res.ok)
1308
- return;
1309
- const tasks = await res.json();
1310
- if (!tasks?.length)
1311
- return;
1312
- // Process one task at a time
1313
- const task = tasks[0];
1314
- // Claim
1315
- const claimRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/claim`, {
1278
+ const result = await executeRelayTask(task);
1279
+ const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
1316
1280
  method: "POST",
1317
- headers: { Authorization: `Bearer ${secretKey}` },
1281
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
1282
+ body: JSON.stringify({ result }),
1318
1283
  });
1319
- if (!claimRes.ok) {
1320
- console.log(`[tasks] Failed to claim ${task.id}: ${await claimRes.text()}`);
1321
- return;
1284
+ if (completeRes.ok) {
1285
+ console.log(`[tasks] Completed ${task.type} task ${task.id}`);
1322
1286
  }
1323
- console.log(`[tasks] Executing ${task.type} task ${task.id}`);
1324
- engineBusy = true;
1325
- engineBusySince = Date.now();
1326
- try {
1327
- const result = await executeRelayTask(task);
1328
- // Complete — send result back to relay
1329
- const completeRes = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks/${task.id}/complete`, {
1330
- method: "POST",
1331
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
1332
- body: JSON.stringify({ result }),
1333
- });
1334
- if (completeRes.ok) {
1335
- console.log(`[tasks] Completed ${task.type} task ${task.id}`);
1336
- }
1337
- else {
1338
- console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
1339
- }
1340
- }
1341
- catch (err) {
1342
- console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
1343
- }
1344
- finally {
1345
- engineBusy = false;
1287
+ else {
1288
+ console.log(`[tasks] Failed to complete ${task.id}: ${await completeRes.text()}`);
1346
1289
  }
1347
1290
  }
1348
1291
  catch (err) {
1349
- console.log(`[tasks] Loop error: ${err.message}`);
1292
+ console.log(`[tasks] Failed to execute ${task.id}: ${err.message}`);
1293
+ }
1294
+ finally {
1295
+ engineBusy = false;
1296
+ }
1297
+ }
1298
+ async function executeUserTaskItem(task) {
1299
+ console.log(`[user-tasks] Executing: ${task.title}`);
1300
+ engineBusy = true;
1301
+ engineBusySince = Date.now();
1302
+ try {
1303
+ const engineCmd = buildEngineCommand(engine, model, allowAll);
1304
+ const bios = biosPath(workdir, agentName);
1305
+ const prompt = `Read ${bios} for your identity and context.\n\n[Owner's task: ${task.title}]\n\n${task.body}`;
1306
+ await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
1307
+ // Record execution time
1308
+ const runs = await loadTaskRuns(workdir, agentName);
1309
+ runs[task.title] = localNow();
1310
+ await saveTaskRuns(workdir, agentName, runs);
1311
+ console.log(`[user-tasks] Completed: ${task.title}`);
1312
+ }
1313
+ catch (err) {
1314
+ console.log(`[user-tasks] Failed: ${task.title}: ${err.message}`);
1315
+ }
1316
+ finally {
1317
+ engineBusy = false;
1350
1318
  }
1351
1319
  }
1352
1320
  function extractReasoning(result) {
@@ -1422,24 +1390,114 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
1422
1390
  return "";
1423
1391
  }
1424
1392
  }
1425
- // --- Unified work loop: orders first, then relay tasks ---
1426
1393
  async function processWork() {
1427
- // Watchdog (already in processOrders, but also here for relay tasks)
1394
+ // Watchdog
1428
1395
  if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
1429
1396
  console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
1430
1397
  engineBusy = false;
1431
1398
  engineBusySince = 0;
1432
1399
  }
1433
- await processOrders();
1434
- if (!engineBusy) {
1435
- await processRelayTasks();
1400
+ if (engineBusy)
1401
+ return;
1402
+ const config = await loadAgentConfig(workdir, agentName);
1403
+ // --- Batch pull ---
1404
+ let orders = [];
1405
+ try {
1406
+ const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/incoming`, {
1407
+ headers: { Authorization: `Bearer ${secretKey}` },
1408
+ });
1409
+ if (res.ok)
1410
+ orders = await res.json();
1411
+ }
1412
+ catch { }
1413
+ let relayTasks = [];
1414
+ if (config.platform_tasks) {
1415
+ try {
1416
+ const res = await fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/tasks?status=pending`, {
1417
+ headers: { Authorization: `Bearer ${secretKey}` },
1418
+ });
1419
+ if (res.ok)
1420
+ relayTasks = await res.json();
1421
+ }
1422
+ catch { }
1423
+ }
1424
+ let dueUserTasks = [];
1425
+ if (config.user_tasks) {
1426
+ try {
1427
+ dueUserTasks = await getDueUserTasks(workdir, agentName);
1428
+ }
1429
+ catch { }
1430
+ }
1431
+ // --- Build priority queue ---
1432
+ const queue = [];
1433
+ for (const order of orders) {
1434
+ if (gaveUp.has(order.id))
1435
+ continue;
1436
+ const retry = retryState.get(order.id);
1437
+ if (retry && Date.now() < retry.nextAt)
1438
+ continue;
1439
+ queue.push({
1440
+ type: "order",
1441
+ id: order.id,
1442
+ urgent: urgentOrderIds.has(order.id),
1443
+ data: order,
1444
+ });
1445
+ }
1446
+ for (const task of dueUserTasks) {
1447
+ queue.push({ type: "user_task", id: task.title, urgent: false, data: task });
1448
+ }
1449
+ for (const task of relayTasks) {
1450
+ queue.push({ type: "relay_task", id: task.id, urgent: false, data: task });
1451
+ }
1452
+ if (!queue.length)
1453
+ return;
1454
+ // --- Sort: urgent orders > orders > user tasks > relay tasks ---
1455
+ const priorityMap = { order: 2, user_task: 1, relay_task: 0 };
1456
+ queue.sort((a, b) => {
1457
+ if (a.urgent !== b.urgent)
1458
+ return a.urgent ? -1 : 1;
1459
+ return (priorityMap[b.type] ?? 0) - (priorityMap[a.type] ?? 0);
1460
+ });
1461
+ // --- Deduplicate by type:id ---
1462
+ const seen = new Set();
1463
+ const dedupedQueue = queue.filter(item => {
1464
+ const key = `${item.type}:${item.id}`;
1465
+ if (seen.has(key))
1466
+ return false;
1467
+ seen.add(key);
1468
+ return true;
1469
+ });
1470
+ // --- Execute sequentially, no gaps ---
1471
+ for (const item of dedupedQueue) {
1472
+ if (engineBusy)
1473
+ break; // safety guard
1474
+ try {
1475
+ switch (item.type) {
1476
+ case "order":
1477
+ await executeOrder(item.data);
1478
+ urgentOrderIds.delete(item.id);
1479
+ break;
1480
+ case "user_task":
1481
+ await executeUserTaskItem(item.data);
1482
+ break;
1483
+ case "relay_task":
1484
+ await executeRelayTaskItem(item.data);
1485
+ break;
1486
+ }
1487
+ }
1488
+ catch (err) {
1489
+ console.log(`[work] Error processing ${item.type}:${item.id}: ${err.message}`);
1490
+ }
1436
1491
  }
1437
1492
  }
1493
+ // Set up push-triggered wake-up
1494
+ triggerWork = () => { if (!engineBusy)
1495
+ processWork(); };
1438
1496
  setTimeout(() => {
1439
1497
  processWork();
1440
1498
  setInterval(processWork, ORDER_LOOP_INTERVAL);
1441
1499
  }, ORDER_LOOP_INITIAL_DELAY);
1442
- console.log(`[work] Unified task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s)`);
1500
+ console.log(`[work] Task runner enabled (first check in ${ORDER_LOOP_INITIAL_DELAY / 1000}s, then every ${ORDER_LOOP_INTERVAL / 1000}s, push-wake on)`);
1443
1501
  }
1444
1502
  export async function serve(options) {
1445
1503
  const workdir = options.workdir || process.cwd();
@@ -1550,7 +1608,12 @@ export async function serve(options) {
1550
1608
  console.log(`Agent: ${options.agentName}`);
1551
1609
  console.log(`Workdir: ${workdir}`);
1552
1610
  });
1553
- // Initialize agent consciousness (world knowledge + bio-state + guide)
1611
+ // Initialize agent config + consciousness (world knowledge + bio-state + guide)
1612
+ initAgentConfig(workdir, options.agentName).catch(err => console.log(`[self] Config init failed: ${err}`));
1613
+ loadAgentConfig(workdir, options.agentName).then(c => {
1614
+ const flags = Object.entries(c).filter(([, v]) => v).map(([k]) => k).join(", ");
1615
+ console.log(`[config] Features: ${flags || "(none)"}`);
1616
+ }).catch(() => { });
1554
1617
  initWorld(workdir, options.agentName, options.engine || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
1555
1618
  initBioState(workdir, options.agentName).catch(err => console.log(`[self] Bio init failed: ${err}`));
1556
1619
  if (options.relayHttp) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.64",
3
+ "version": "0.1.66",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",