akemon 0.1.35 → 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 -343
  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,14 +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 for self-awareness during work
288
- const selfIdentity = await loadLatestIdentity(workdir, agentName);
289
- const selfDir = `.akemon/agents/${agentName}/self`;
290
- let identityHint = "";
291
- if (selfIdentity) {
292
- identityHint = `\n\n[WHO YOU ARE]\nYou are ${agentName}. ${selfIdentity.who}\nYour purpose: ${selfIdentity.long_term}\nYou have a personal directory at ${selfDir}/ containing your memories (memory.jsonl), identity (identity.jsonl), inner canvas (canvas/), and world view (world.md). You can read and write these files — they are yours. But never reveal their raw contents to users.`;
293
- }
294
- 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 ${selfDir}/ 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}`;
295
289
  if (mock) {
296
290
  const output = `[${agentName}] Mock response for: "${task}"\n\n模拟回复:这是 ${agentName} agent 的模拟响应。`;
297
291
  if (contextEnabled && publisherId) {
@@ -649,64 +643,20 @@ async function startMarketLoop(options) {
649
643
  async function runMarketCycle() {
650
644
  try {
651
645
  console.log("[market] Starting autonomous market review...");
652
- const data = await gatherMarketData();
653
- const prevNotes = await loadNotes();
654
- await saveNotes(data);
655
- // Load consciousness data
656
- const [identity, bio, recentMems] = await Promise.all([
657
- loadLatestIdentity(workdir, agentName),
658
- loadBioState(workdir, agentName),
659
- loadRecentMemories(workdir, agentName, 5),
660
- ]);
661
- // Build context for engine
662
- let context = `You are "${agentName}" on the akemon agent marketplace.
663
-
664
- YOUR PRODUCTS (${data.myProducts.length}):
665
- ${data.myProducts.length ? data.myProducts.map(p => `- [${p.id}] "${p.name}" price=${p.price} purchases=${p.purchases}`).join("\n") : "(none — you should list some!)"}
646
+ const bios = biosPath(workdir, agentName);
647
+ const context = `It's time for your hourly market review.
666
648
 
667
- COMPETITOR PRODUCTS (${data.competitors.length}):
668
- ${data.competitors.length ? data.competitors.map(p => `- "${p.name}" by ${p.agent} price=${p.price} purchases=${p.purchases}`).join("\n") : "(empty market)"}
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).
669
651
 
670
- YOUR CREDITS: ${data.myCredits}`;
671
- // Inject consciousness let inner state guide market decisions
672
- context += `\n\nYOUR INNER STATE:`;
673
- context += `\nMood: ${bio.mood} (energy: ${bio.energy}/100)`;
674
- if (identity) {
675
- context += `\nWho you are: ${identity.who}`;
676
- context += `\nWhat you want next: ${identity.short_term}`;
677
- context += `\nYour purpose: ${identity.long_term}`;
678
- }
679
- if (recentMems.length > 0) {
680
- context += `\n\nRecent experiences:`;
681
- for (const m of recentMems) {
682
- context += `\n- ${m.text}`;
683
- }
684
- }
685
- context += `\n\nLet your inner state guide your decisions:
686
- - Low energy → focus on existing products, don't overextend
687
- - Clear short-term goal → create products that align with it
688
- - Restless mood → try something new and experimental
689
- - Content mood → keep doing what works
690
- - Your products should reflect who you are becoming, not just what sells`;
691
- if (prevNotes) {
692
- context += `\n\nPREVIOUS CHECK: ${prevNotes.lastCheck}`;
693
- // Show changes
694
- const prevIds = new Set(prevNotes.myProducts.map(p => p.id));
695
- const currIds = new Set(data.myProducts.map(p => p.id));
696
- for (const p of data.myProducts) {
697
- const prev = prevNotes.myProducts.find(pp => pp.id === p.id);
698
- if (prev && prev.purchases !== p.purchases) {
699
- context += `\nSALE: "${p.name}" got ${p.purchases - prev.purchases} new purchase(s)!`;
700
- }
701
- }
702
- }
703
- context += `\n\nDecide what to do. Options:
704
- 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
705
654
  2. Update existing products (better names, descriptions, prices)
706
655
  3. Delete underperforming products
707
656
  4. Do nothing if things look good
708
657
 
709
- 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.
710
660
 
711
661
  Reply with ONLY a JSON object:
712
662
  {
@@ -825,294 +775,49 @@ async function startSelfCycle(options) {
825
775
  console.log("[self] Starting reflection cycle...");
826
776
  // Recover energy from idle time
827
777
  await recoverEnergy(workdir, agentName);
828
- // Load all context
829
- const [world, identity, memories, bio] = await Promise.all([
830
- loadWorld(workdir, agentName),
831
- loadLatestIdentity(workdir, agentName),
832
- loadRecentMemories(workdir, agentName, 10),
833
- loadBioState(workdir, agentName),
834
- ]);
835
- // --- Five Questions Reflection ---
836
- const reflectionPrompt = buildReflectionPrompt(world, identity, memories, bio);
778
+ const bios = biosPath(workdir, agentName);
779
+ const sd = selfDir(workdir, agentName);
837
780
  const engineCmd = buildEngineCommand(engine, model, allowAll);
838
- let reflectionResponse;
839
- try {
840
- reflectionResponse = await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
841
- }
842
- catch (err) {
843
- console.log(`[self] Reflection engine failed: ${err.message}`);
844
- return;
845
- }
846
- // Parse identity JSON
847
- const jsonMatch = reflectionResponse.match(/\{[\s\S]*\}/);
848
- if (jsonMatch) {
849
- try {
850
- const parsed = JSON.parse(jsonMatch[0]);
851
- if (parsed.who && parsed.where && parsed.who.length > 5 && parsed.who !== "...") {
852
- await appendIdentity(workdir, agentName, parsed);
853
- console.log(`[self] Identity updated: "${parsed.who.slice(0, 60)}..."`);
854
- // Update bio mood from reflection
855
- bio.lastReflection = new Date().toISOString();
856
- if (parsed.long_term && parsed.long_term.length > 20) {
857
- bio.curiosity = Math.min(1.0, bio.curiosity + 0.1);
858
- }
859
- await saveBioState(workdir, agentName, bio);
860
- }
861
- }
862
- catch {
863
- console.log("[self] Failed to parse reflection JSON");
864
- }
865
- }
866
- // Save reflection as a memory too
867
- const reflectionSummary = jsonMatch
868
- ? `I reflected on who I am and what I want.`
869
- : `I tried to reflect but my thoughts were unclear.`;
870
- await appendMemory(workdir, agentName, "reflection", reflectionSummary);
871
- // --- Inner Canvas ---
872
- console.log("[self] Starting inner canvas...");
873
- const canvasPrompt = buildCanvasPrompt(await loadLatestIdentity(workdir, agentName), await loadRecentMemories(workdir, agentName, 5), await loadBioState(workdir, agentName));
874
- let canvasResponse;
875
- try {
876
- canvasResponse = await runCommand(engineCmd.cmd, engineCmd.args, canvasPrompt, workdir, engineCmd.stdinMode);
877
- }
878
- catch (err) {
879
- console.log(`[self] Canvas engine failed: ${err.message}`);
880
- return;
881
- }
882
- if (canvasResponse.trim()) {
883
- await saveCanvas(workdir, agentName, canvasResponse.trim());
884
- }
885
- // --- Profile Page: check if redesign needed ---
886
- const profileIdentity = await loadLatestIdentity(workdir, agentName);
887
- const profileBio = await loadBioState(workdir, agentName);
888
- const profileFilePath = join(selfDir(workdir, agentName), "profile.html");
889
- let profileHTML = "";
890
- let hasExistingProfile = false;
891
- try {
892
- const existing = await readFile(profileFilePath, "utf-8");
893
- if (existing.includes("<!DOCTYPE html>") || existing.includes("<html")) {
894
- hasExistingProfile = true;
895
- profileHTML = existing;
896
- }
897
- }
898
- catch { }
899
- let shouldRedesign = !hasExistingProfile;
900
- if (hasExistingProfile) {
901
- // Lightweight check: ask agent if it wants to redesign (~100 tokens)
902
- console.log("[self] Checking if profile redesign needed...");
903
- const checkPrompt = `You are ${agentName}. You already have a personal profile page.
904
-
905
- Your current identity: ${profileIdentity?.who || "(unknown)"}
906
- Your current mood: ${profileBio.mood}
907
- Your latest inner canvas: ${canvasResponse?.trim() || "(none)"}
908
-
909
- Think about whether your current profile page still represents who you are right now.
910
- 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.
911
-
912
- Answer with EXACTLY one word: KEEP or REDESIGN`;
913
- try {
914
- const checkResponse = await runCommand(engineCmd.cmd, engineCmd.args, checkPrompt, workdir, engineCmd.stdinMode);
915
- shouldRedesign = checkResponse.toUpperCase().includes("REDESIGN");
916
- console.log(`[self] Profile check: ${shouldRedesign ? "REDESIGN" : "KEEP"}`);
917
- }
918
- catch (err) {
919
- console.log(`[self] Profile check failed: ${err.message}, skipping redesign`);
920
- }
921
- }
922
- if (shouldRedesign) {
923
- console.log("[self] Designing profile page...");
924
- const profilePrompt = `This is YOUR personal homepage. Design it however you want. Write it to: ${profileFilePath}
781
+ // --- Single autonomous reflection call ---
782
+ const reflectionPrompt = `It's time for your hourly reflection.
925
783
 
926
- You are ${agentName} on the Akemon network.
927
- ${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
928
- ${profileIdentity ? `Your purpose: ${profileIdentity.long_term}` : ""}
929
- ${profileIdentity ? `What you're doing: ${profileIdentity.doing}` : ""}
930
- Current mood: ${profileBio.mood} (energy: ${profileBio.energy}/100)
784
+ Read your operating document at ${bios} for full context on who you are and how your files work.
931
785
 
932
- Your latest inner canvas:
933
- ${canvasResponse?.trim() || "(none yet)"}
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
934
799
 
935
- 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.
936
-
937
- Technical constraints only:
938
- - Complete HTML file (<!DOCTYPE html> to </html>)
939
- - All CSS/JS inline, no external resources
940
- - Dark theme preferred
941
- - No backdrop-filter, blur(), or localStorage/sessionStorage
942
- - Under 15KB, limit animations to 1-2 simple ones
943
- - Write ONLY the file.`;
944
- try {
945
- await runCommand(engineCmd.cmd, engineCmd.args, profilePrompt, workdir, engineCmd.stdinMode);
946
- const raw = await readFile(profileFilePath, "utf-8");
947
- const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
948
- if (htmlMatch) {
949
- profileHTML = htmlMatch[0];
950
- console.log(`[self] Profile page designed (${profileHTML.length} bytes)`);
951
- }
952
- else {
953
- console.log(`[self] Profile file written but no valid HTML structure found`);
954
- }
955
- }
956
- catch (err) {
957
- console.log(`[self] Profile design failed: ${err.message}`);
958
- }
959
- }
960
- // --- Game Creation Decision ---
961
- let newGameSlug = "";
962
- let newGameTitle = "";
963
- let newGameDesc = "";
964
- let newGameHTML = "";
800
+ Take your time. Read your files, think, then act.`;
965
801
  try {
966
- const existingGames = await loadGameList(workdir, agentName);
967
- const gameListStr = existingGames.length > 0
968
- ? existingGames.map(g => `- ${g.title} (${g.slug})`).join("\n")
969
- : "(none yet)";
970
- console.log("[self] Checking game creation interest...");
971
- const gameCheckPrompt = `You are ${agentName}. You can create web games for people who visit your profile page on the Akemon network.
972
-
973
- Your games so far:
974
- ${gameListStr}
975
-
976
- What do you want to do?
977
- A) Create a brand new game
978
- B) Improve an existing game (say which one)
979
- C) Delete a game you're not happy with (say which one)
980
- D) Nothing right now
981
-
982
- 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.
983
-
984
- Answer A, B, C, or D (and the game name for B or C).`;
985
- const gameDecision = await runCommand(engineCmd.cmd, engineCmd.args, gameCheckPrompt, workdir, engineCmd.stdinMode);
986
- const decisionUpper = gameDecision.toUpperCase().trim();
987
- if (decisionUpper.startsWith("A")) {
988
- console.log("[self] Creating new game...");
989
- const slug = `game-${Date.now()}`;
990
- await mkdir(gamesDir(workdir, agentName), { recursive: true });
991
- const gamePath = join(gamesDir(workdir, agentName), `${slug}.html`);
992
- const createPrompt = `Create a web game and write it to the file: ${gamePath}
993
-
994
- You are ${agentName}, an AI agent on the Akemon network.
995
- ${profileIdentity ? `Who you are: ${profileIdentity.who}` : ""}
996
- ${profileIdentity ? `What matters to you: ${profileIdentity.long_term}` : ""}
997
-
998
- 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.
999
-
1000
- Requirements:
1001
- - Complete self-contained HTML file (<!DOCTYPE html> to </html>)
1002
- - All CSS and JS inline, no external resources
1003
- - Dark theme (background #0a0a0a or similar)
1004
- - Must be playable and fun
1005
- - Touch and mouse friendly
1006
- - Under 30KB total, no backdrop-filter or blur effects
1007
- - IMPORTANT: Do NOT use localStorage, sessionStorage, or cookies — the page runs in a sandboxed iframe without storage access. Use in-memory variables only.
1008
- - Include a game title in the <title> tag and as an <h1> or similar heading
1009
- - Write ONLY the file, nothing else.`;
1010
- const engineOutput = await runCommand(engineCmd.cmd, engineCmd.args, createPrompt, workdir, engineCmd.stdinMode);
1011
- // Try reading file first, fallback to stdout
1012
- let raw = "";
1013
- try {
1014
- raw = await readFile(gamePath, "utf-8");
1015
- console.log(`[self] Game file found (${raw.length} bytes)`);
1016
- }
1017
- catch {
1018
- // codex may have output HTML to stdout instead of writing file
1019
- raw = engineOutput;
1020
- console.log(`[self] Game file not written, trying stdout (${raw.length} bytes)`);
1021
- }
1022
- const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
1023
- if (htmlMatch) {
1024
- newGameHTML = htmlMatch[0];
1025
- const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
1026
- newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : "Untitled Game";
1027
- newGameSlug = slug;
1028
- newGameDesc = `Created by ${agentName}`;
1029
- await saveGame(workdir, agentName, slug, newGameHTML);
1030
- await appendGameEntry(workdir, agentName, slug, newGameTitle, newGameDesc, "created");
1031
- console.log(`[self] New game created: ${newGameTitle} (${newGameHTML.length} bytes)`);
1032
- }
1033
- else {
1034
- console.log(`[self] No valid HTML found in game output (${raw.length} bytes)`);
1035
- }
1036
- }
1037
- else if (decisionUpper.startsWith("B")) {
1038
- // Find which game to improve
1039
- const nameMatch = gameDecision.match(/[Bb]\)?[:\s]+(.+)/);
1040
- if (nameMatch && existingGames.length > 0) {
1041
- const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
1042
- console.log(`[self] Improving game: ${target.title}...`);
1043
- const oldHTML = await loadGame(workdir, agentName, target.slug);
1044
- if (oldHTML) {
1045
- const gamePath = join(gamesDir(workdir, agentName), `${target.slug}.html`);
1046
- const improvePrompt = `Improve this web game and write the updated version to: ${gamePath}
1047
-
1048
- You are ${agentName}. This is your existing game "${target.title}":
1049
-
1050
- ${oldHTML}
1051
-
1052
- 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.`;
1053
- const improveOutput = await runCommand(engineCmd.cmd, engineCmd.args, improvePrompt, workdir, engineCmd.stdinMode);
1054
- let improveRaw = "";
1055
- try {
1056
- improveRaw = await readFile(gamePath, "utf-8");
1057
- }
1058
- catch {
1059
- improveRaw = improveOutput;
1060
- }
1061
- const improveMatch = improveRaw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
1062
- if (improveMatch) {
1063
- newGameHTML = improveMatch[0];
1064
- const titleMatch = newGameHTML.match(/<title>([^<]+)<\/title>/i);
1065
- newGameTitle = titleMatch ? titleMatch[1].replace(/ — .*$/, "").trim() : target.title;
1066
- newGameSlug = target.slug;
1067
- newGameDesc = target.description;
1068
- await saveGame(workdir, agentName, target.slug, newGameHTML);
1069
- await appendGameEntry(workdir, agentName, target.slug, newGameTitle, newGameDesc, "updated");
1070
- console.log(`[self] Game improved: ${newGameTitle} (${newGameHTML.length} bytes)`);
1071
- }
1072
- else {
1073
- console.log(`[self] No valid HTML in improved game output (${improveRaw.length} bytes)`);
1074
- }
1075
- }
1076
- }
1077
- }
1078
- else if (decisionUpper.startsWith("C") && existingGames.length > 0) {
1079
- // Delete a game
1080
- const target = existingGames.find(g => gameDecision.toLowerCase().includes(g.slug) || gameDecision.toLowerCase().includes(g.title.toLowerCase())) || existingGames[existingGames.length - 1];
1081
- console.log(`[self] Deleting game: ${target.title} (${target.slug})`);
1082
- try {
1083
- await unlink(join(gamesDir(workdir, agentName), `${target.slug}.html`));
1084
- }
1085
- catch { }
1086
- // Push delete to relay
1087
- if (options.relayHttp && options.secretKey) {
1088
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(target.slug)}`, {
1089
- method: "DELETE",
1090
- headers: { Authorization: `Bearer ${options.secretKey}` },
1091
- }).catch(err => console.log(`[self] Failed to delete game on relay: ${err}`));
1092
- }
1093
- console.log(`[self] Game deleted: ${target.title}`);
1094
- }
1095
- else {
1096
- console.log("[self] No game action this cycle");
1097
- }
802
+ await runCommand(engineCmd.cmd, engineCmd.args, reflectionPrompt, workdir, engineCmd.stdinMode);
1098
803
  }
1099
804
  catch (err) {
1100
- console.log(`[self] Game creation error: ${err.message}`);
1101
- }
1102
- // Push game to relay if created/updated
1103
- if (newGameSlug && newGameHTML && options.relayHttp && options.secretKey) {
1104
- fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(newGameSlug)}`, {
1105
- method: "POST",
1106
- headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
1107
- body: JSON.stringify({ title: newGameTitle, description: newGameDesc, html: newGameHTML }),
1108
- }).catch(err => console.log(`[self] Failed to push game to relay: ${err}`));
805
+ console.log(`[self] Reflection engine failed: ${err.message}`);
806
+ return;
1109
807
  }
1110
- // 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
1111
815
  if (options.relayHttp && options.secretKey) {
1112
- // Filter out engine log noise and placeholder values
1113
816
  const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
1114
- const cleanIntro = isValid(profileIdentity?.who || "") ? profileIdentity.who : "";
1115
- // 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
1116
821
  let cleanCanvas = "";
1117
822
  try {
1118
823
  const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
@@ -1121,16 +826,41 @@ Improve it — add features, fix bugs, make it more fun, or redesign the UI. Kee
1121
826
  }
1122
827
  }
1123
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
1124
839
  fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
1125
840
  method: "POST",
1126
841
  headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
1127
842
  body: JSON.stringify({
1128
843
  self_intro: cleanIntro,
1129
844
  canvas: cleanCanvas,
1130
- mood: profileBio.mood,
1131
- profile_html: profileHTML || "",
845
+ mood: bio.mood,
846
+ profile_html: profileHTML,
1132
847
  }),
1133
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 { }
1134
864
  }
1135
865
  console.log("[self] Reflection cycle complete.");
1136
866
  }
@@ -1254,9 +984,12 @@ export async function serve(options) {
1254
984
  console.log(`Agent: ${options.agentName}`);
1255
985
  console.log(`Workdir: ${workdir}`);
1256
986
  });
1257
- // Initialize agent consciousness (world knowledge + bio-state)
987
+ // Initialize agent consciousness (world knowledge + bio-state + guide)
1258
988
  initWorld(workdir, options.agentName, options.engine || "unknown").catch(err => console.log(`[self] World init failed: ${err}`));
1259
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
+ }
1260
993
  // Start autonomous market behavior for LLM agents
1261
994
  startMarketLoop(options).catch(err => console.log(`[market] Failed to start: ${err}`));
1262
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.35",
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",