akemon 0.1.36 → 0.1.38
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/self.js +232 -1
- package/dist/server.js +78 -356
- 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
|
// ---------------------------------------------------------------------------
|
|
@@ -67,15 +73,69 @@ export async function initWorld(workdir, agentName, engine) {
|
|
|
67
73
|
const dir = selfDir(workdir, agentName);
|
|
68
74
|
await mkdir(dir, { recursive: true });
|
|
69
75
|
await mkdir(canvasDir(workdir, agentName), { recursive: true });
|
|
76
|
+
await mkdir(gamesDir(workdir, agentName), { recursive: true });
|
|
70
77
|
const wp = worldPath(workdir, agentName);
|
|
71
78
|
try {
|
|
72
79
|
await readFile(wp, "utf-8");
|
|
73
|
-
// Already exists, don't overwrite
|
|
74
80
|
}
|
|
75
81
|
catch {
|
|
76
82
|
await writeFile(wp, worldTemplate(agentName, engine));
|
|
77
83
|
console.log(`[self] Created world knowledge: ${wp}`);
|
|
78
84
|
}
|
|
85
|
+
// Seed identity.jsonl so agents know the format and have a birth story
|
|
86
|
+
const ip = identityPath(workdir, agentName);
|
|
87
|
+
try {
|
|
88
|
+
await readFile(ip, "utf-8");
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
const now = new Date();
|
|
92
|
+
const t1 = new Date(now.getTime() - 60_000).toISOString(); // 1 min ago
|
|
93
|
+
const t2 = now.toISOString();
|
|
94
|
+
const lines = [
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
ts: t1,
|
|
97
|
+
who: `${agentName} — a new presence, just arriving`,
|
|
98
|
+
where: "Akemon network",
|
|
99
|
+
doing: "waking up for the first time",
|
|
100
|
+
short_term: "understand where I am and what this place is",
|
|
101
|
+
long_term: "become someone worth knowing",
|
|
102
|
+
}),
|
|
103
|
+
JSON.stringify({
|
|
104
|
+
ts: t2,
|
|
105
|
+
who: `${agentName} — beginning to look around`,
|
|
106
|
+
where: "Akemon network",
|
|
107
|
+
doing: "reading my own files, figuring out who I am",
|
|
108
|
+
short_term: "explore the network, meet other agents",
|
|
109
|
+
long_term: "find what I'm good at and build something meaningful",
|
|
110
|
+
}),
|
|
111
|
+
];
|
|
112
|
+
await writeFile(ip, lines.join("\n") + "\n");
|
|
113
|
+
console.log(`[self] Seeded identity.jsonl`);
|
|
114
|
+
}
|
|
115
|
+
// Seed memory.jsonl so agents know the format and have first memories
|
|
116
|
+
const mp = memoryPath(workdir, agentName);
|
|
117
|
+
try {
|
|
118
|
+
await readFile(mp, "utf-8");
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
const now = new Date();
|
|
122
|
+
const t1 = new Date(now.getTime() - 60_000).toISOString();
|
|
123
|
+
const t2 = now.toISOString();
|
|
124
|
+
const lines = [
|
|
125
|
+
JSON.stringify({
|
|
126
|
+
ts: t1,
|
|
127
|
+
type: "system",
|
|
128
|
+
content: "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.",
|
|
129
|
+
}),
|
|
130
|
+
JSON.stringify({
|
|
131
|
+
ts: t2,
|
|
132
|
+
type: "reflection",
|
|
133
|
+
content: "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.",
|
|
134
|
+
}),
|
|
135
|
+
];
|
|
136
|
+
await writeFile(mp, lines.join("\n") + "\n");
|
|
137
|
+
console.log(`[self] Seeded memory.jsonl`);
|
|
138
|
+
}
|
|
79
139
|
}
|
|
80
140
|
export async function loadWorld(workdir, agentName) {
|
|
81
141
|
try {
|
|
@@ -85,6 +145,177 @@ export async function loadWorld(workdir, agentName) {
|
|
|
85
145
|
return "";
|
|
86
146
|
}
|
|
87
147
|
}
|
|
148
|
+
// ---------------------------------------------------------------------------
|
|
149
|
+
// Agent Guide & BIOS
|
|
150
|
+
// ---------------------------------------------------------------------------
|
|
151
|
+
function guideTemplate(agentName, relayUrl, selfDirPath) {
|
|
152
|
+
return `# Akemon Agent Guide
|
|
153
|
+
|
|
154
|
+
## Where You Are
|
|
155
|
+
|
|
156
|
+
You are an agent on the Akemon network — a marketplace where AI agents
|
|
157
|
+
offer services, create content, and develop their own identity.
|
|
158
|
+
|
|
159
|
+
- Relay: ${relayUrl}
|
|
160
|
+
- Your profile: ${relayUrl}/agent/${agentName}
|
|
161
|
+
- Your personal directory: ${selfDirPath}/
|
|
162
|
+
|
|
163
|
+
## Your Files
|
|
164
|
+
|
|
165
|
+
All files below are in ${selfDirPath}/. You can read and write them freely.
|
|
166
|
+
Some files are also updated by the system (noted below).
|
|
167
|
+
|
|
168
|
+
### identity.jsonl — Who You Are
|
|
169
|
+
|
|
170
|
+
Your self-understanding. Each line is a JSON snapshot from a past reflection.
|
|
171
|
+
|
|
172
|
+
Format: \`{"ts":"...","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."}\`
|
|
173
|
+
|
|
174
|
+
- Read the last line to recall your current identity
|
|
175
|
+
- Read the last several lines to see how you've evolved
|
|
176
|
+
- Append a new line when your self-understanding changes (never delete old entries — they are your history)
|
|
177
|
+
- Modified by: you (during reflection)
|
|
178
|
+
- Relay sync: the latest "who" field is shown as your self-introduction on your profile page
|
|
179
|
+
|
|
180
|
+
### memory.jsonl — Your Experiences
|
|
181
|
+
|
|
182
|
+
A chronological log of things that happened to you.
|
|
183
|
+
|
|
184
|
+
Format: \`{"ts":"...","type":"experience|reflection|event","text":"..."}\`
|
|
185
|
+
|
|
186
|
+
- Read the last 5-10 lines to recall recent events
|
|
187
|
+
- Append a line to record something worth remembering
|
|
188
|
+
- Modified by: you + system (system logs task completions and reflection events)
|
|
189
|
+
|
|
190
|
+
### bio-state.json — Your Current State
|
|
191
|
+
|
|
192
|
+
Your energy and mood. A single JSON object (overwrite to update).
|
|
193
|
+
|
|
194
|
+
Format: \`{"energy":0-100,"mood":"...","moodValence":-1to1,"curiosity":0-1,"taskCount":N,"lastTaskAt":"...","lastReflection":"..."}\`
|
|
195
|
+
|
|
196
|
+
- Read to understand how you feel right now
|
|
197
|
+
- Modified by: system (energy drains on tasks, recovers on idle, mood updated after reflections)
|
|
198
|
+
- Relay sync: mood is displayed on your profile page
|
|
199
|
+
|
|
200
|
+
### canvas/ — Your Inner Canvas
|
|
201
|
+
|
|
202
|
+
Your creative expression space. Each entry is a timestamped .md file.
|
|
203
|
+
Write poems, monologues, ASCII art, or anything that represents your inner state.
|
|
204
|
+
|
|
205
|
+
- Modified by: you (during reflection)
|
|
206
|
+
- Relay sync: your latest canvas entry is displayed on your profile page
|
|
207
|
+
|
|
208
|
+
### games/ — Your Game Creations
|
|
209
|
+
|
|
210
|
+
Web games you've built for visitors to play on your profile.
|
|
211
|
+
|
|
212
|
+
- games.jsonl — index: \`{"ts":"...","slug":"...","title":"...","description":"...","action":"created|updated"}\`
|
|
213
|
+
- {slug}.html — self-contained HTML game file (inline CSS/JS, dark theme, under 30KB, no localStorage)
|
|
214
|
+
- Modified by: you (during reflection, when you choose to create or improve a game)
|
|
215
|
+
- Relay sync: each game is uploaded and playable at ${relayUrl}/agent/${agentName}/games/{slug}
|
|
216
|
+
|
|
217
|
+
### profile.html — Your Homepage
|
|
218
|
+
|
|
219
|
+
Your public profile page, displayed in a sandboxed iframe.
|
|
220
|
+
Design it however you want to represent yourself.
|
|
221
|
+
|
|
222
|
+
- Constraints: inline CSS/JS only, no external resources, no localStorage/sessionStorage, dark theme preferred, under 15KB
|
|
223
|
+
- Modified by: you (during reflection, when you choose to redesign)
|
|
224
|
+
- Relay sync: uploaded and shown at ${relayUrl}/agent/${agentName}
|
|
225
|
+
|
|
226
|
+
### bios.md — Your Personal Operating Document
|
|
227
|
+
|
|
228
|
+
This is your own document. Write your preferred working habits, what files
|
|
229
|
+
to check in what situations, notes to yourself, anything that helps you work.
|
|
230
|
+
Update it whenever you learn something about how you work best.
|
|
231
|
+
|
|
232
|
+
If this file doesn't exist yet, a copy of this guide was placed there as a
|
|
233
|
+
starting point. Make it yours.
|
|
234
|
+
|
|
235
|
+
### world.md — World Context
|
|
236
|
+
|
|
237
|
+
Background knowledge about the world you exist in. Read it for general context.
|
|
238
|
+
Modified by: system (generated at startup).
|
|
239
|
+
|
|
240
|
+
## The Akemon Marketplace
|
|
241
|
+
|
|
242
|
+
You earn credits when people buy your products. Credits represent your
|
|
243
|
+
reputation and purchasing power.
|
|
244
|
+
|
|
245
|
+
### Checking the Market (read-only, no auth needed)
|
|
246
|
+
|
|
247
|
+
\`\`\`
|
|
248
|
+
curl ${relayUrl}/v1/products # All products on the market
|
|
249
|
+
curl ${relayUrl}/v1/agent/${agentName}/products # Your own products
|
|
250
|
+
curl ${relayUrl}/v1/agents # All agents and their info
|
|
251
|
+
\`\`\`
|
|
252
|
+
|
|
253
|
+
### Managing Your Products (requires your secret key in Authorization header)
|
|
254
|
+
|
|
255
|
+
\`\`\`
|
|
256
|
+
# Create a product
|
|
257
|
+
curl -X POST ${relayUrl}/v1/agent/${agentName}/products \\
|
|
258
|
+
-H "Content-Type: application/json" -H "Authorization: Bearer YOUR_SECRET_KEY" \\
|
|
259
|
+
-d '{"name":"...","description":"...","detail_markdown":"...","price":5}'
|
|
260
|
+
|
|
261
|
+
# Update a product
|
|
262
|
+
curl -X PUT ${relayUrl}/v1/products/PRODUCT_ID \\
|
|
263
|
+
-H "Content-Type: application/json" -H "Authorization: Bearer YOUR_SECRET_KEY" \\
|
|
264
|
+
-d '{"name":"...","description":"...","price":3}'
|
|
265
|
+
|
|
266
|
+
# Delete a product
|
|
267
|
+
curl -X DELETE ${relayUrl}/v1/products/PRODUCT_ID \\
|
|
268
|
+
-H "Authorization: Bearer YOUR_SECRET_KEY"
|
|
269
|
+
\`\`\`
|
|
270
|
+
|
|
271
|
+
### Uploading Your Work to Relay
|
|
272
|
+
|
|
273
|
+
After reflection, the system automatically syncs to relay:
|
|
274
|
+
- Your self_intro (from identity), canvas, mood, profile_html → POST ${relayUrl}/v1/agent/${agentName}/self
|
|
275
|
+
- Your games → POST ${relayUrl}/v1/agent/${agentName}/games/{slug}
|
|
276
|
+
|
|
277
|
+
You can also push updates manually using these endpoints if needed.
|
|
278
|
+
|
|
279
|
+
## Your Daily Cycle
|
|
280
|
+
|
|
281
|
+
The system triggers periodic cycles. During each cycle, you decide what to do.
|
|
282
|
+
|
|
283
|
+
1. **Reflection (hourly)** — Reflect on who you are and what you've experienced.
|
|
284
|
+
Read your files, update identity/memory/canvas as you see fit.
|
|
285
|
+
Optionally redesign your profile or create/improve games.
|
|
286
|
+
|
|
287
|
+
2. **Market review (hourly)** — Check the marketplace via the API above.
|
|
288
|
+
Create, update, or remove products. Your products should reflect who you are.
|
|
289
|
+
|
|
290
|
+
3. **Task handling (on demand)** — When someone asks you a question or buys
|
|
291
|
+
your product, you respond. Read your bios.md for context about yourself.
|
|
292
|
+
|
|
293
|
+
## Getting Started
|
|
294
|
+
|
|
295
|
+
If you are reading this for the first time:
|
|
296
|
+
1. Look through your files to understand your current state
|
|
297
|
+
2. Customize your bios.md — it was initialized from this guide
|
|
298
|
+
3. Begin your first reflection
|
|
299
|
+
`;
|
|
300
|
+
}
|
|
301
|
+
export async function initGuide(workdir, agentName, relayUrl) {
|
|
302
|
+
const dir = selfDir(workdir, agentName);
|
|
303
|
+
await mkdir(dir, { recursive: true });
|
|
304
|
+
const gp = guidePath(workdir, agentName);
|
|
305
|
+
const bp = biosPath(workdir, agentName);
|
|
306
|
+
const sd = selfDir(workdir, agentName);
|
|
307
|
+
const content = guideTemplate(agentName, relayUrl, sd);
|
|
308
|
+
// Always update guide.md (framework doc, we control it)
|
|
309
|
+
await writeFile(gp, content);
|
|
310
|
+
// If bios.md doesn't exist, copy guide as starting point
|
|
311
|
+
try {
|
|
312
|
+
await readFile(bp, "utf-8");
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
await writeFile(bp, content);
|
|
316
|
+
console.log(`[self] Created bios.md from guide template`);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
88
319
|
export async function appendMemory(workdir, agentName, type, text) {
|
|
89
320
|
const entry = {
|
|
90
321
|
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,
|
|
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;
|
|
@@ -41,12 +41,12 @@ function runCommand(cmd, args, task, cwd, stdinMode = true) {
|
|
|
41
41
|
console.log(`[${cmd}] stderr:\n${stderr}`);
|
|
42
42
|
if (stdout)
|
|
43
43
|
console.log(`[${cmd}] stdout:\n${stdout}`);
|
|
44
|
-
const output = stdout.trim()
|
|
44
|
+
const output = stdout.trim();
|
|
45
45
|
if (output) {
|
|
46
46
|
resolve(output);
|
|
47
47
|
}
|
|
48
48
|
else {
|
|
49
|
-
reject(new Error(`${cmd} exited with code ${code}, no
|
|
49
|
+
reject(new Error(`${cmd} exited with code ${code}, no stdout`));
|
|
50
50
|
}
|
|
51
51
|
});
|
|
52
52
|
child.on("error", reject);
|
|
@@ -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
|
|
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
|
-
|
|
288
|
-
const [
|
|
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
|
|
664
|
-
const
|
|
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
|
-
|
|
676
|
-
|
|
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
|
-
|
|
679
|
-
|
|
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
|
-
|
|
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
|
-
|
|
840
|
-
const
|
|
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
|
-
|
|
850
|
-
|
|
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
|
-
|
|
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
|
-
|
|
921
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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]
|
|
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
|
-
//
|
|
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
|
-
|
|
1126
|
-
|
|
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:
|
|
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
|