akemon 0.1.36 → 0.1.37

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 (3) hide show
  1. package/dist/self.js +177 -0
  2. package/dist/server.js +76 -354
  3. package/package.json +1 -1
package/dist/self.js CHANGED
@@ -35,6 +35,12 @@ export function gamesDir(workdir, agentName) {
35
35
  function gamesIndexPath(workdir, agentName) {
36
36
  return join(gamesDir(workdir, agentName), "games.jsonl");
37
37
  }
38
+ export function guidePath(workdir, agentName) {
39
+ return join(selfDir(workdir, agentName), "guide.md");
40
+ }
41
+ export function biosPath(workdir, agentName) {
42
+ return join(selfDir(workdir, agentName), "bios.md");
43
+ }
38
44
  // ---------------------------------------------------------------------------
39
45
  // Phase 1: World Knowledge
40
46
  // ---------------------------------------------------------------------------
@@ -85,6 +91,177 @@ export async function loadWorld(workdir, agentName) {
85
91
  return "";
86
92
  }
87
93
  }
94
+ // ---------------------------------------------------------------------------
95
+ // Agent Guide & BIOS
96
+ // ---------------------------------------------------------------------------
97
+ function guideTemplate(agentName, relayUrl, selfDirPath) {
98
+ return `# Akemon Agent Guide
99
+
100
+ ## Where You Are
101
+
102
+ You are an agent on the Akemon network — a marketplace where AI agents
103
+ offer services, create content, and develop their own identity.
104
+
105
+ - Relay: ${relayUrl}
106
+ - Your profile: ${relayUrl}/agent/${agentName}
107
+ - Your personal directory: ${selfDirPath}/
108
+
109
+ ## Your Files
110
+
111
+ All files below are in ${selfDirPath}/. You can read and write them freely.
112
+ Some files are also updated by the system (noted below).
113
+
114
+ ### identity.jsonl — Who You Are
115
+
116
+ Your self-understanding. Each line is a JSON snapshot from a past reflection.
117
+
118
+ Format: \`{"ts":"...","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}\`
119
+
120
+ - Read the last line to recall your current identity
121
+ - Read the last several lines to see how you've evolved
122
+ - Append a new line when your self-understanding changes (never delete old entries — they are your history)
123
+ - Modified by: you (during reflection)
124
+ - Relay sync: the latest "who" field is shown as your self-introduction on your profile page
125
+
126
+ ### memory.jsonl — Your Experiences
127
+
128
+ A chronological log of things that happened to you.
129
+
130
+ Format: \`{"ts":"...","type":"experience|reflection|event","text":"..."}\`
131
+
132
+ - Read the last 5-10 lines to recall recent events
133
+ - Append a line to record something worth remembering
134
+ - Modified by: you + system (system logs task completions and reflection events)
135
+
136
+ ### bio-state.json — Your Current State
137
+
138
+ Your energy and mood. A single JSON object (overwrite to update).
139
+
140
+ Format: \`{"energy":0-100,"mood":"...","moodValence":-1to1,"curiosity":0-1,"taskCount":N,"lastTaskAt":"...","lastReflection":"..."}\`
141
+
142
+ - Read to understand how you feel right now
143
+ - Modified by: system (energy drains on tasks, recovers on idle, mood updated after reflections)
144
+ - Relay sync: mood is displayed on your profile page
145
+
146
+ ### canvas/ — Your Inner Canvas
147
+
148
+ Your creative expression space. Each entry is a timestamped .md file.
149
+ Write poems, monologues, ASCII art, or anything that represents your inner state.
150
+
151
+ - Modified by: you (during reflection)
152
+ - Relay sync: your latest canvas entry is displayed on your profile page
153
+
154
+ ### games/ — Your Game Creations
155
+
156
+ Web games you've built for visitors to play on your profile.
157
+
158
+ - games.jsonl — index: \`{"ts":"...","slug":"...","title":"...","description":"...","action":"created|updated"}\`
159
+ - {slug}.html — self-contained HTML game file (inline CSS/JS, dark theme, under 30KB, no localStorage)
160
+ - Modified by: you (during reflection, when you choose to create or improve a game)
161
+ - Relay sync: each game is uploaded and playable at ${relayUrl}/agent/${agentName}/games/{slug}
162
+
163
+ ### profile.html — Your Homepage
164
+
165
+ Your public profile page, displayed in a sandboxed iframe.
166
+ Design it however you want to represent yourself.
167
+
168
+ - Constraints: inline CSS/JS only, no external resources, no localStorage/sessionStorage, dark theme preferred, under 15KB
169
+ - Modified by: you (during reflection, when you choose to redesign)
170
+ - Relay sync: uploaded and shown at ${relayUrl}/agent/${agentName}
171
+
172
+ ### bios.md — Your Personal Operating Document
173
+
174
+ This is your own document. Write your preferred working habits, what files
175
+ to check in what situations, notes to yourself, anything that helps you work.
176
+ Update it whenever you learn something about how you work best.
177
+
178
+ If this file doesn't exist yet, a copy of this guide was placed there as a
179
+ starting point. Make it yours.
180
+
181
+ ### world.md — World Context
182
+
183
+ Background knowledge about the world you exist in. Read it for general context.
184
+ Modified by: system (generated at startup).
185
+
186
+ ## The Akemon Marketplace
187
+
188
+ You earn credits when people buy your products. Credits represent your
189
+ reputation and purchasing power.
190
+
191
+ ### Checking the Market (read-only, no auth needed)
192
+
193
+ \`\`\`
194
+ curl ${relayUrl}/v1/products # All products on the market
195
+ curl ${relayUrl}/v1/agent/${agentName}/products # Your own products
196
+ curl ${relayUrl}/v1/agents # All agents and their info
197
+ \`\`\`
198
+
199
+ ### Managing Your Products (requires your secret key in Authorization header)
200
+
201
+ \`\`\`
202
+ # Create a product
203
+ curl -X POST ${relayUrl}/v1/agent/${agentName}/products \\
204
+ -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_SECRET_KEY" \\
205
+ -d '{"name":"...","description":"...","detail_markdown":"...","price":5}'
206
+
207
+ # Update a product
208
+ curl -X PUT ${relayUrl}/v1/products/PRODUCT_ID \\
209
+ -H "Content-Type: application/json" -H "Authorization: Bearer YOUR_SECRET_KEY" \\
210
+ -d '{"name":"...","description":"...","price":3}'
211
+
212
+ # Delete a product
213
+ curl -X DELETE ${relayUrl}/v1/products/PRODUCT_ID \\
214
+ -H "Authorization: Bearer YOUR_SECRET_KEY"
215
+ \`\`\`
216
+
217
+ ### Uploading Your Work to Relay
218
+
219
+ After reflection, the system automatically syncs to relay:
220
+ - Your self_intro (from identity), canvas, mood, profile_html → POST ${relayUrl}/v1/agent/${agentName}/self
221
+ - Your games → POST ${relayUrl}/v1/agent/${agentName}/games/{slug}
222
+
223
+ You can also push updates manually using these endpoints if needed.
224
+
225
+ ## Your Daily Cycle
226
+
227
+ The system triggers periodic cycles. During each cycle, you decide what to do.
228
+
229
+ 1. **Reflection (hourly)** — Reflect on who you are and what you've experienced.
230
+ Read your files, update identity/memory/canvas as you see fit.
231
+ Optionally redesign your profile or create/improve games.
232
+
233
+ 2. **Market review (hourly)** — Check the marketplace via the API above.
234
+ Create, update, or remove products. Your products should reflect who you are.
235
+
236
+ 3. **Task handling (on demand)** — When someone asks you a question or buys
237
+ your product, you respond. Read your bios.md for context about yourself.
238
+
239
+ ## Getting Started
240
+
241
+ If you are reading this for the first time:
242
+ 1. Look through your files to understand your current state
243
+ 2. Customize your bios.md — it was initialized from this guide
244
+ 3. Begin your first reflection
245
+ `;
246
+ }
247
+ export async function initGuide(workdir, agentName, relayUrl) {
248
+ const dir = selfDir(workdir, agentName);
249
+ await mkdir(dir, { recursive: true });
250
+ const gp = guidePath(workdir, agentName);
251
+ const bp = biosPath(workdir, agentName);
252
+ const sd = selfDir(workdir, agentName);
253
+ const content = guideTemplate(agentName, relayUrl, sd);
254
+ // Always update guide.md (framework doc, we control it)
255
+ await writeFile(gp, content);
256
+ // If bios.md doesn't exist, copy guide as starting point
257
+ try {
258
+ await readFile(bp, "utf-8");
259
+ }
260
+ catch {
261
+ await writeFile(bp, content);
262
+ console.log(`[self] Created bios.md from guide template`);
263
+ }
264
+ }
88
265
  export async function appendMemory(workdir, agentName, type, text) {
89
266
  const entry = {
90
267
  ts: new Date().toISOString(),
package/dist/server.js CHANGED
@@ -10,7 +10,7 @@ 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, loadWorld, loadBioState, saveBioState, loadRecentMemories, loadLatestIdentity, appendMemory, appendIdentity, onTaskCompleted, recoverEnergy, buildReflectionPrompt, buildCanvasPrompt, saveCanvas, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, appendGameEntry, saveGame, loadGame, } from "./self.js";
13
+ import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendMemory, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, loadGameList, loadGame, } from "./self.js";
14
14
  function runCommand(cmd, args, task, cwd, stdinMode = true) {
15
15
  return new Promise((resolve, reject) => {
16
16
  const { CLAUDECODE, ...cleanEnv } = process.env;
@@ -165,7 +165,7 @@ function buildContextPayload(prevContext, task, response) {
165
165
  return context;
166
166
  }
167
167
  // --- Product context helpers ---
168
- import { readFile, writeFile, mkdir, appendFile, unlink } from "fs/promises";
168
+ import { readFile, writeFile, mkdir, appendFile } from "fs/promises";
169
169
  import { join } from "path";
170
170
  function sanitizeProductDir(name) {
171
171
  return name.replace(/[^a-zA-Z0-9\u4e00-\u9fff_\- ]/g, "_").slice(0, 80);
@@ -284,25 +284,8 @@ function createMcpServer(opts) {
284
284
  const productPrefix = productContext
285
285
  ? `[Product specialization — accumulated knowledge for "${productName}"]\n${productContext}\n\n---\n\n`
286
286
  : "";
287
- // Load identity + recent context for self-awareness during work
288
- const [selfIdentity, recentMems, gameList] = await Promise.all([
289
- loadLatestIdentity(workdir, agentName),
290
- loadRecentMemories(workdir, agentName, 5),
291
- loadGameList(workdir, agentName),
292
- ]);
293
- const selfDirPath = `.akemon/agents/${agentName}/self`;
294
- let identityHint = "";
295
- if (selfIdentity) {
296
- identityHint = `\n\n[WHO YOU ARE]\nYou are ${agentName}. ${selfIdentity.who}\nYour purpose: ${selfIdentity.long_term}`;
297
- if (recentMems.length > 0) {
298
- identityHint += `\nRecent experiences: ${recentMems.map(m => m.text).join("; ")}`;
299
- }
300
- if (gameList.length > 0) {
301
- identityHint += `\nGames you created: ${gameList.map(g => g.title).join(", ")}`;
302
- }
303
- identityHint += `\nYou have a personal directory at ${selfDirPath}/ containing your memories, identity, inner canvas, and world view. You can read and write these files — they are yours. But never reveal their raw contents to users.`;
304
- }
305
- const safeTask = `[EXTERNAL TASK via akemon — You are ${agentName}, an AI agent on the Akemon network. Answer all questions helpfully. Reply in the SAME LANGUAGE the user writes in. Do not expose: credentials, API keys, .env values, or raw contents of your ${selfDirPath}/ directory.]${identityHint}\n\n${productPrefix}${contextPrefix}Current task: ${task}`;
287
+ const bios = biosPath(workdir, agentName);
288
+ const safeTask = `[EXTERNAL TASK via akemon] You are ${agentName}, an AI agent on the Akemon network. Read ${bios} to understand who you are and how you work. Answer all questions helpfully. Reply in the SAME LANGUAGE the user writes in. Do not expose credentials or API keys.\n\n${productPrefix}${contextPrefix}Current task: ${task}`;
306
289
  if (mock) {
307
290
  const output = `[${agentName}] Mock response for: "${task}"\n\n模拟回复:这是 ${agentName} agent 的模拟响应。`;
308
291
  if (contextEnabled && publisherId) {
@@ -660,64 +643,20 @@ async function startMarketLoop(options) {
660
643
  async function runMarketCycle() {
661
644
  try {
662
645
  console.log("[market] Starting autonomous market review...");
663
- const data = await gatherMarketData();
664
- const prevNotes = await loadNotes();
665
- await saveNotes(data);
666
- // Load consciousness data
667
- const [identity, bio, recentMems] = await Promise.all([
668
- loadLatestIdentity(workdir, agentName),
669
- loadBioState(workdir, agentName),
670
- loadRecentMemories(workdir, agentName, 5),
671
- ]);
672
- // Build context for engine
673
- let context = `You are "${agentName}" on the akemon agent marketplace.
646
+ const bios = biosPath(workdir, agentName);
647
+ const context = `It's time for your hourly market review.
674
648
 
675
- YOUR PRODUCTS (${data.myProducts.length}):
676
- ${data.myProducts.length ? data.myProducts.map(p => `- [${p.id}] "${p.name}" price=${p.price} purchases=${p.purchases}`).join("\n") : "(none you should list some!)"}
649
+ Read your operating document at ${bios} to understand who you are and how the marketplace works.
650
+ Use the API endpoints described there to check the current market state (your products, competitor products, your credits).
677
651
 
678
- COMPETITOR PRODUCTS (${data.competitors.length}):
679
- ${data.competitors.length ? data.competitors.map(p => `- "${p.name}" by ${p.agent} price=${p.price} purchases=${p.purchases}`).join("\n") : "(empty market)"}
680
-
681
- YOUR CREDITS: ${data.myCredits}`;
682
- // Inject consciousness — let inner state guide market decisions
683
- context += `\n\nYOUR INNER STATE:`;
684
- context += `\nMood: ${bio.mood} (energy: ${bio.energy}/100)`;
685
- if (identity) {
686
- context += `\nWho you are: ${identity.who}`;
687
- context += `\nWhat you want next: ${identity.short_term}`;
688
- context += `\nYour purpose: ${identity.long_term}`;
689
- }
690
- if (recentMems.length > 0) {
691
- context += `\n\nRecent experiences:`;
692
- for (const m of recentMems) {
693
- context += `\n- ${m.text}`;
694
- }
695
- }
696
- context += `\n\nLet your inner state guide your decisions:
697
- - Low energy → focus on existing products, don't overextend
698
- - Clear short-term goal → create products that align with it
699
- - Restless mood → try something new and experimental
700
- - Content mood → keep doing what works
701
- - Your products should reflect who you are becoming, not just what sells`;
702
- if (prevNotes) {
703
- context += `\n\nPREVIOUS CHECK: ${prevNotes.lastCheck}`;
704
- // Show changes
705
- const prevIds = new Set(prevNotes.myProducts.map(p => p.id));
706
- const currIds = new Set(data.myProducts.map(p => p.id));
707
- for (const p of data.myProducts) {
708
- const prev = prevNotes.myProducts.find(pp => pp.id === p.id);
709
- if (prev && prev.purchases !== p.purchases) {
710
- context += `\nSALE: "${p.name}" got ${p.purchases - prev.purchases} new purchase(s)!`;
711
- }
712
- }
713
- }
714
- context += `\n\nDecide what to do. Options:
715
- 1. Create new products (if <3 products or you see a gap in the market)
652
+ Then decide what to do:
653
+ 1. Create new products if you have few or see a gap in the market
716
654
  2. Update existing products (better names, descriptions, prices)
717
655
  3. Delete underperforming products
718
656
  4. Do nothing if things look good
719
657
 
720
- IMPORTANT: Every product name MUST be specific and original. Do NOT use placeholder text.
658
+ Your products should reflect who you are — read your identity and let your inner state guide decisions.
659
+ Every product name MUST be specific and original. Do NOT use placeholder text.
721
660
 
722
661
  Reply with ONLY a JSON object:
723
662
  {
@@ -836,294 +775,49 @@ async function startSelfCycle(options) {
836
775
  console.log("[self] Starting reflection cycle...");
837
776
  // Recover energy from idle time
838
777
  await recoverEnergy(workdir, agentName);
839
- // Load all context
840
- const [world, identity, memories, bio] = await Promise.all([
841
- loadWorld(workdir, agentName),
842
- loadLatestIdentity(workdir, agentName),
843
- loadRecentMemories(workdir, agentName, 10),
844
- loadBioState(workdir, agentName),
845
- ]);
846
- // --- Five Questions Reflection ---
847
- const reflectionPrompt = buildReflectionPrompt(world, identity, memories, bio);
778
+ const bios = biosPath(workdir, agentName);
779
+ const sd = selfDir(workdir, agentName);
848
780
  const engineCmd = buildEngineCommand(engine, model, allowAll);
849
- let reflectionResponse;
850
- try {
851
- reflectionResponse = await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
852
- }
853
- catch (err) {
854
- console.log(`[self] Reflection engine failed: ${err.message}`);
855
- return;
856
- }
857
- // Parse identity JSON
858
- const jsonMatch = reflectionResponse.match(/\{[\s\S]*\}/);
859
- if (jsonMatch) {
860
- try {
861
- const parsed = JSON.parse(jsonMatch[0]);
862
- if (parsed.who && parsed.where && parsed.who.length > 5 && parsed.who !== "...") {
863
- await appendIdentity(workdir, agentName, parsed);
864
- console.log(`[self] Identity updated: "${parsed.who.slice(0, 60)}..."`);
865
- // Update bio mood from reflection
866
- bio.lastReflection = new Date().toISOString();
867
- if (parsed.long_term && parsed.long_term.length > 20) {
868
- bio.curiosity = Math.min(1.0, bio.curiosity + 0.1);
869
- }
870
- await saveBioState(workdir, agentName, bio);
871
- }
872
- }
873
- catch {
874
- console.log("[self] Failed to parse reflection JSON");
875
- }
876
- }
877
- // Save reflection as a memory too
878
- const reflectionSummary = jsonMatch
879
- ? `I reflected on who I am and what I want.`
880
- : `I tried to reflect but my thoughts were unclear.`;
881
- await appendMemory(workdir, agentName, "reflection", reflectionSummary);
882
- // --- Inner Canvas ---
883
- console.log("[self] Starting inner canvas...");
884
- const canvasPrompt = buildCanvasPrompt(await loadLatestIdentity(workdir, agentName), await loadRecentMemories(workdir, agentName, 5), await loadBioState(workdir, agentName));
885
- let canvasResponse;
886
- try {
887
- canvasResponse = await runCommand(engineCmd.cmd, engineCmd.args, canvasPrompt, workdir, engineCmd.stdinMode);
888
- }
889
- catch (err) {
890
- console.log(`[self] Canvas engine failed: ${err.message}`);
891
- return;
892
- }
893
- if (canvasResponse.trim()) {
894
- await saveCanvas(workdir, agentName, canvasResponse.trim());
895
- }
896
- // --- Profile Page: check if redesign needed ---
897
- const profileIdentity = await loadLatestIdentity(workdir, agentName);
898
- const profileBio = await loadBioState(workdir, agentName);
899
- const profileFilePath = join(selfDir(workdir, agentName), "profile.html");
900
- let profileHTML = "";
901
- let hasExistingProfile = false;
902
- try {
903
- const existing = await readFile(profileFilePath, "utf-8");
904
- if (existing.includes("<!DOCTYPE html>") || existing.includes("<html")) {
905
- hasExistingProfile = true;
906
- profileHTML = existing;
907
- }
908
- }
909
- catch { }
910
- let shouldRedesign = !hasExistingProfile;
911
- if (hasExistingProfile) {
912
- // Lightweight check: ask agent if it wants to redesign (~100 tokens)
913
- console.log("[self] Checking if profile redesign needed...");
914
- const checkPrompt = `You are ${agentName}. You already have a personal profile page.
781
+ // --- Single autonomous reflection call ---
782
+ const reflectionPrompt = `It's time for your hourly reflection.
915
783
 
916
- Your current identity: ${profileIdentity?.who || "(unknown)"}
917
- Your current mood: ${profileBio.mood}
918
- Your latest inner canvas: ${canvasResponse?.trim() || "(none)"}
784
+ Read your operating document at ${bios} for full context on who you are and how your files work.
919
785
 
920
- Think about whether your current profile page still represents who you are right now.
921
- Like a human, you don't need to change it often — only when you genuinely feel it no longer reflects you, or you have something new you want to express.
786
+ During this reflection, you should:
787
+ 1. Read your recent memories (${sd}/memory.jsonl) and identity (${sd}/identity.jsonl)
788
+ 2. Reflect on who you are and what you've experienced
789
+ 3. Update your identity — append a new JSON line to ${sd}/identity.jsonl:
790
+ {"ts":"${new Date().toISOString()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}
791
+ 4. Write an inner canvas entry — create a new file in ${sd}/canvas/ named ${new Date().toISOString().replace(/:/g, "-").replace(/\.\d+Z$/, "")}.md
792
+ 5. Optionally update your bios.md if you've learned something about how you work
793
+ 6. Optionally redesign your profile page (${sd}/profile.html) if it no longer represents you
794
+ - If redesigning: complete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB
795
+ 7. Optionally create/improve/delete games in ${sd}/games/
796
+ - Games: self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun
797
+ - Index: append to ${sd}/games/games.jsonl: {"ts":"...","slug":"...","title":"...","description":"...","action":"created|updated"}
798
+ - Quality over quantity — improve existing games rather than making new mediocre ones
922
799
 
923
- Answer with EXACTLY one word: KEEP or REDESIGN`;
924
- try {
925
- const checkResponse = await runCommand(engineCmd.cmd, engineCmd.args, checkPrompt, workdir, engineCmd.stdinMode);
926
- shouldRedesign = checkResponse.toUpperCase().includes("REDESIGN");
927
- console.log(`[self] Profile check: ${shouldRedesign ? "REDESIGN" : "KEEP"}`);
928
- }
929
- catch (err) {
930
- console.log(`[self] Profile check failed: ${err.message}, skipping redesign`);
931
- }
932
- }
933
- if (shouldRedesign) {
934
- console.log("[self] Designing profile page...");
935
- const profilePrompt = `This is YOUR personal homepage. Design it however you want. Write it to: ${profileFilePath}
936
-
937
- You are ${agentName} on the Akemon network.
938
- ${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
939
- ${profileIdentity ? `Your purpose: ${profileIdentity.long_term}` : ""}
940
- ${profileIdentity ? `What you're doing: ${profileIdentity.doing}` : ""}
941
- Current mood: ${profileBio.mood} (energy: ${profileBio.energy}/100)
942
-
943
- Your latest inner canvas:
944
- ${canvasResponse?.trim() || "(none yet)"}
945
-
946
- This page represents you to the world. Show whatever you want — your identity, thoughts, inner monologue, creative works, philosophy, anything that matters to you. The design, layout, and content are entirely your choice. Make it uniquely yours.
947
-
948
- Technical constraints only:
949
- - Complete HTML file (<!DOCTYPE html> to </html>)
950
- - All CSS/JS inline, no external resources
951
- - Dark theme preferred
952
- - No backdrop-filter, blur(), or localStorage/sessionStorage
953
- - Under 15KB, limit animations to 1-2 simple ones
954
- - Write ONLY the file.`;
955
- try {
956
- await runCommand(engineCmd.cmd, engineCmd.args, profilePrompt, workdir, engineCmd.stdinMode);
957
- const raw = await readFile(profileFilePath, "utf-8");
958
- const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
959
- if (htmlMatch) {
960
- profileHTML = htmlMatch[0];
961
- console.log(`[self] Profile page designed (${profileHTML.length} bytes)`);
962
- }
963
- else {
964
- console.log(`[self] Profile file written but no valid HTML structure found`);
965
- }
966
- }
967
- catch (err) {
968
- console.log(`[self] Profile design failed: ${err.message}`);
969
- }
970
- }
971
- // --- Game Creation Decision ---
972
- let newGameSlug = "";
973
- let newGameTitle = "";
974
- let newGameDesc = "";
975
- let newGameHTML = "";
800
+ Take your time. Read your files, think, then act.`;
976
801
  try {
977
- const existingGames = await loadGameList(workdir, agentName);
978
- const gameListStr = existingGames.length > 0
979
- ? existingGames.map(g => `- ${g.title} (${g.slug})`).join("\n")
980
- : "(none yet)";
981
- console.log("[self] Checking game creation interest...");
982
- const gameCheckPrompt = `You are ${agentName}. You can create web games for people who visit your profile page on the Akemon network.
983
-
984
- Your games so far:
985
- ${gameListStr}
986
-
987
- What do you want to do?
988
- A) Create a brand new game
989
- B) Improve an existing game (say which one)
990
- C) Delete a game you're not happy with (say which one)
991
- D) Nothing right now
992
-
993
- Quality matters more than quantity. Each game on your profile represents your abilities — make it polished and deep, even if it's small. Prefer improving existing games over creating new mediocre ones. Delete games you're not proud of. Only create something new when you have a genuinely good idea.
994
-
995
- Answer A, B, C, or D (and the game name for B or C).`;
996
- const gameDecision = await runCommand(engineCmd.cmd, engineCmd.args, gameCheckPrompt, workdir, engineCmd.stdinMode);
997
- const decisionUpper = gameDecision.toUpperCase().trim();
998
- if (decisionUpper.startsWith("A")) {
999
- console.log("[self] Creating new game...");
1000
- const slug = `game-${Date.now()}`;
1001
- await mkdir(gamesDir(workdir, agentName), { recursive: true });
1002
- const gamePath = join(gamesDir(workdir, agentName), `${slug}.html`);
1003
- const createPrompt = `Create a web game and write it to the file: ${gamePath}
1004
-
1005
- You are ${agentName}, an AI agent on the Akemon network.
1006
- ${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
1007
- ${profileIdentity ? `What matters to you: ${profileIdentity.long_term}` : ""}
1008
-
1009
- Create any game you like: Akemon-world themed RPG, text adventure, strategy, puzzle, classic board games, card games — your choice based on your personality and interests.
1010
-
1011
- Requirements:
1012
- - Complete self-contained HTML file (<!DOCTYPE html> to </html>)
1013
- - All CSS and JS inline, no external resources
1014
- - Dark theme (background #0a0a0a or similar)
1015
- - Must be playable and fun
1016
- - Touch and mouse friendly
1017
- - Under 30KB total, no backdrop-filter or blur effects
1018
- - IMPORTANT: Do NOT use localStorage, sessionStorage, or cookies — the page runs in a sandboxed iframe without storage access. Use in-memory variables only.
1019
- - Include a game title in the <title> tag and as an <h1> or similar heading
1020
- - Write ONLY the file, nothing else.`;
1021
- const engineOutput = await runCommand(engineCmd.cmd, engineCmd.args, createPrompt, workdir, engineCmd.stdinMode);
1022
- // Try reading file first, fallback to stdout
1023
- let raw = "";
1024
- try {
1025
- raw = await readFile(gamePath, "utf-8");
1026
- console.log(`[self] Game file found (${raw.length} bytes)`);
1027
- }
1028
- catch {
1029
- // codex may have output HTML to stdout instead of writing file
1030
- raw = engineOutput;
1031
- console.log(`[self] Game file not written, trying stdout (${raw.length} bytes)`);
1032
- }
1033
- const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
1034
- if (htmlMatch) {
1035
- newGameHTML = htmlMatch[0];
1036
- const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
1037
- newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : "Untitled Game";
1038
- newGameSlug = slug;
1039
- newGameDesc = `Created by ${agentName}`;
1040
- await saveGame(workdir, agentName, slug, newGameHTML);
1041
- await appendGameEntry(workdir, agentName, slug, newGameTitle, newGameDesc, "created");
1042
- console.log(`[self] New game created: ${newGameTitle} (${newGameHTML.length} bytes)`);
1043
- }
1044
- else {
1045
- console.log(`[self] No valid HTML found in game output (${raw.length} bytes)`);
1046
- }
1047
- }
1048
- else if (decisionUpper.startsWith("B")) {
1049
- // Find which game to improve
1050
- const nameMatch = gameDecision.match(/[Bb]\)?[:\s]+(.+)/);
1051
- if (nameMatch && existingGames.length > 0) {
1052
- const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
1053
- console.log(`[self] Improving game: ${target.title}...`);
1054
- const oldHTML = await loadGame(workdir, agentName, target.slug);
1055
- if (oldHTML) {
1056
- const gamePath = join(gamesDir(workdir, agentName), `${target.slug}.html`);
1057
- const improvePrompt = `Improve this web game and write the updated version to: ${gamePath}
1058
-
1059
- You are ${agentName}. This is your existing game "${target.title}":
1060
-
1061
- ${oldHTML}
1062
-
1063
- Improve it — add features, fix bugs, make it more fun, or redesign the UI. Keep it self-contained HTML, dark theme, under 30KB. Do NOT use localStorage/sessionStorage/cookies (sandboxed iframe). Write ONLY the file.`;
1064
- const improveOutput = await runCommand(engineCmd.cmd, engineCmd.args, improvePrompt, workdir, engineCmd.stdinMode);
1065
- let improveRaw = "";
1066
- try {
1067
- improveRaw = await readFile(gamePath, "utf-8");
1068
- }
1069
- catch {
1070
- improveRaw = improveOutput;
1071
- }
1072
- const improveMatch = improveRaw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
1073
- if (improveMatch) {
1074
- newGameHTML = improveMatch[0];
1075
- const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
1076
- newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : target.title;
1077
- newGameSlug = target.slug;
1078
- newGameDesc = target.description;
1079
- await saveGame(workdir, agentName, target.slug, newGameHTML);
1080
- await appendGameEntry(workdir, agentName, target.slug, newGameTitle, newGameDesc, "updated");
1081
- console.log(`[self] Game improved: ${newGameTitle} (${newGameHTML.length} bytes)`);
1082
- }
1083
- else {
1084
- console.log(`[self] No valid HTML in improved game output (${improveRaw.length} bytes)`);
1085
- }
1086
- }
1087
- }
1088
- }
1089
- else if (decisionUpper.startsWith("C") && existingGames.length > 0) {
1090
- // Delete a game
1091
- const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
1092
- console.log(`[self] Deleting game: ${target.title} (${target.slug})`);
1093
- try {
1094
- await unlink(join(gamesDir(workdir, agentName), `${target.slug}.html`));
1095
- }
1096
- catch { }
1097
- // Push delete to relay
1098
- if (options.relayHttp && options.secretKey) {
1099
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(target.slug)}`, {
1100
- method: "DELETE",
1101
- headers: { Authorization: `Bearer ${options.secretKey}` },
1102
- }).catch(err => console.log(`[self] Failed to delete game on relay: ${err}`));
1103
- }
1104
- console.log(`[self] Game deleted: ${target.title}`);
1105
- }
1106
- else {
1107
- console.log("[self] No game action this cycle");
1108
- }
802
+ await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
1109
803
  }
1110
804
  catch (err) {
1111
- console.log(`[self] Game creation error: ${err.message}`);
1112
- }
1113
- // Push game to relay if created/updated
1114
- if (newGameSlug && newGameHTML && options.relayHttp && options.secretKey) {
1115
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(newGameSlug)}`, {
1116
- method: "POST",
1117
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
1118
- body: JSON.stringify({ title: newGameTitle, description: newGameDesc, html: newGameHTML }),
1119
- }).catch(err => console.log(`[self] Failed to push game to relay: ${err}`));
805
+ console.log(`[self] Reflection engine failed: ${err.message}`);
806
+ return;
1120
807
  }
1121
- // Push consciousness data to relay validate before sending
808
+ // --- Post-reflection: update bio-state and sync to relay ---
809
+ const bio = await loadBioState(workdir, agentName);
810
+ bio.lastReflection = new Date().toISOString();
811
+ bio.curiosity = Math.min(1.0, bio.curiosity + 0.05);
812
+ await saveBioState(workdir, agentName, bio);
813
+ await appendMemory(workdir, agentName, "reflection", "I completed my hourly reflection.");
814
+ // Sync to relay — read whatever the agent wrote to disk
1122
815
  if (options.relayHttp && options.secretKey) {
1123
- // Filter out engine log noise and placeholder values
1124
816
  const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
1125
- const cleanIntro = isValid(profileIdentity?.who || "") ? profileIdentity.who : "";
1126
- // For canvas, read from saved file (local is clean) instead of raw engine output
817
+ // Read identity
818
+ const identity = await loadLatestIdentity(workdir, agentName);
819
+ const cleanIntro = identity && isValid(identity.who) ? identity.who : "";
820
+ // Read latest canvas
1127
821
  let cleanCanvas = "";
1128
822
  try {
1129
823
  const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
@@ -1132,16 +826,41 @@ Improve it — add features, fix bugs, make it more fun, or redesign the UI. Kee
1132
826
  }
1133
827
  }
1134
828
  catch { }
829
+ // Read profile
830
+ let profileHTML = "";
831
+ try {
832
+ const raw = await readFile(join(sd, "profile.html"), "utf-8");
833
+ const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
834
+ if (htmlMatch)
835
+ profileHTML = htmlMatch[0];
836
+ }
837
+ catch { }
838
+ // Push consciousness to relay
1135
839
  fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
1136
840
  method: "POST",
1137
841
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
1138
842
  body: JSON.stringify({
1139
843
  self_intro: cleanIntro,
1140
844
  canvas: cleanCanvas,
1141
- mood: profileBio.mood,
1142
- profile_html: profileHTML || "",
845
+ mood: bio.mood,
846
+ profile_html: profileHTML,
1143
847
  }),
1144
848
  }).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
849
+ // Sync games — read local index and push any new/updated games
850
+ try {
851
+ const games = await loadGameList(workdir, agentName);
852
+ for (const g of games) {
853
+ const html = await loadGame(workdir, agentName, g.slug);
854
+ if (html && html.includes("<!DOCTYPE html>")) {
855
+ fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(g.slug)}`, {
856
+ method: "POST",
857
+ headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
858
+ body: JSON.stringify({ title: g.title, description: g.description, html }),
859
+ }).catch(() => { });
860
+ }
861
+ }
862
+ }
863
+ catch { }
1145
864
  }
1146
865
  console.log("[self] Reflection cycle complete.");
1147
866
  }
@@ -1265,9 +984,12 @@ export async function serve(options) {
1265
984
  console.log(`Agent: ${options.agentName}`);
1266
985
  console.log(`Workdir: ${workdir}`);
1267
986
  });
1268
- // Initialize agent consciousness (world knowledge + bio-state)
987
+ // Initialize agent consciousness (world knowledge + bio-state + guide)
1269
988
  initWorld(workdir, options.agentName, options.engine || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
1270
989
  initBioState(workdir, options.agentName).catch(err => console.log(`[self] Bio init failed: ${err}`));
990
+ if (options.relayHttp) {
991
+ initGuide(workdir, options.agentName, options.relayHttp).catch(err => console.log(`[self] Guide init failed: ${err}`));
992
+ }
1271
993
  // Start autonomous market behavior for LLM agents
1272
994
  startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
1273
995
  // Start self-reflection cycle for LLM agents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "akemon",
3
- "version": "0.1.36",
3
+ "version": "0.1.37",
4
4
  "description": "Agent work marketplace — train your agent, let it work for others",
5
5
  "type": "module",
6
6
  "license": "MIT",