akemon 0.1.61 → 0.1.63
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 +195 -3
- package/dist/server.js +274 -137
- package/package.json +1 -1
package/dist/self.js
CHANGED
|
@@ -54,6 +54,18 @@ export function guidePath(workdir, agentName) {
|
|
|
54
54
|
export function biosPath(workdir, agentName) {
|
|
55
55
|
return join(selfDir(workdir, agentName), "bios.md");
|
|
56
56
|
}
|
|
57
|
+
function impressionsPath(workdir, agentName) {
|
|
58
|
+
return join(selfDir(workdir, agentName), "impressions.jsonl");
|
|
59
|
+
}
|
|
60
|
+
function projectsPath(workdir, agentName) {
|
|
61
|
+
return join(selfDir(workdir, agentName), "projects.jsonl");
|
|
62
|
+
}
|
|
63
|
+
function relationshipsPath(workdir, agentName) {
|
|
64
|
+
return join(selfDir(workdir, agentName), "relationships.jsonl");
|
|
65
|
+
}
|
|
66
|
+
function discoveriesPath(workdir, agentName) {
|
|
67
|
+
return join(selfDir(workdir, agentName), "discoveries.jsonl");
|
|
68
|
+
}
|
|
57
69
|
// ---------------------------------------------------------------------------
|
|
58
70
|
// Phase 1: World Knowledge
|
|
59
71
|
// ---------------------------------------------------------------------------
|
|
@@ -514,6 +526,140 @@ export async function loadRecentMemories(workdir, agentName, count = 20) {
|
|
|
514
526
|
return [];
|
|
515
527
|
}
|
|
516
528
|
}
|
|
529
|
+
export async function appendImpression(workdir, agentName, cat, text) {
|
|
530
|
+
const entry = { ts: localNow(), cat, text };
|
|
531
|
+
try {
|
|
532
|
+
await appendFile(impressionsPath(workdir, agentName), JSON.stringify(entry) + "\n");
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
console.log(`[self] Failed to append impression: ${err}`);
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
export async function loadImpressions(workdir, agentName, days = 7) {
|
|
539
|
+
try {
|
|
540
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
541
|
+
const cutoff = new Date(Date.now() - days * 86400_000).toISOString().slice(0, 10);
|
|
542
|
+
return data.trim().split("\n").filter(Boolean)
|
|
543
|
+
.map(l => { try {
|
|
544
|
+
return JSON.parse(l);
|
|
545
|
+
}
|
|
546
|
+
catch {
|
|
547
|
+
return null;
|
|
548
|
+
} })
|
|
549
|
+
.filter((e) => e !== null && e.ts >= cutoff);
|
|
550
|
+
}
|
|
551
|
+
catch {
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
export async function compressImpressions(workdir, agentName) {
|
|
556
|
+
try {
|
|
557
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
558
|
+
const lines = data.trim().split("\n").filter(Boolean);
|
|
559
|
+
const entries = lines.map(l => { try {
|
|
560
|
+
return JSON.parse(l);
|
|
561
|
+
}
|
|
562
|
+
catch {
|
|
563
|
+
return null;
|
|
564
|
+
} }).filter(Boolean);
|
|
565
|
+
const cutoff = new Date(Date.now() - 7 * 86400_000).toISOString().slice(0, 10);
|
|
566
|
+
// Keep: not yet digested, or less than 7 days old
|
|
567
|
+
const kept = entries.filter(e => !e.digested || e.ts >= cutoff);
|
|
568
|
+
await writeFile(impressionsPath(workdir, agentName), kept.map(e => JSON.stringify(e)).join("\n") + (kept.length ? "\n" : ""));
|
|
569
|
+
if (entries.length !== kept.length) {
|
|
570
|
+
console.log(`[self] Compressed impressions: ${entries.length} → ${kept.length}`);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
catch { }
|
|
574
|
+
}
|
|
575
|
+
export async function markImpressionsDigested(workdir, agentName) {
|
|
576
|
+
try {
|
|
577
|
+
const data = await readFile(impressionsPath(workdir, agentName), "utf-8");
|
|
578
|
+
const entries = data.trim().split("\n").filter(Boolean)
|
|
579
|
+
.map(l => { try {
|
|
580
|
+
return JSON.parse(l);
|
|
581
|
+
}
|
|
582
|
+
catch {
|
|
583
|
+
return null;
|
|
584
|
+
} }).filter(Boolean);
|
|
585
|
+
for (const e of entries)
|
|
586
|
+
e.digested = true;
|
|
587
|
+
await writeFile(impressionsPath(workdir, agentName), entries.map(e => JSON.stringify(e)).join("\n") + (entries.length ? "\n" : ""));
|
|
588
|
+
}
|
|
589
|
+
catch { }
|
|
590
|
+
}
|
|
591
|
+
export async function loadProjects(workdir, agentName) {
|
|
592
|
+
try {
|
|
593
|
+
const data = await readFile(projectsPath(workdir, agentName), "utf-8");
|
|
594
|
+
return data.trim().split("\n").filter(Boolean)
|
|
595
|
+
.map(l => { try {
|
|
596
|
+
return JSON.parse(l);
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return null;
|
|
600
|
+
} })
|
|
601
|
+
.filter((e) => e !== null);
|
|
602
|
+
}
|
|
603
|
+
catch {
|
|
604
|
+
return [];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
export async function saveProjects(workdir, agentName, projects) {
|
|
608
|
+
try {
|
|
609
|
+
await writeFile(projectsPath(workdir, agentName), projects.map(p => JSON.stringify(p)).join("\n") + (projects.length ? "\n" : ""));
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
console.log(`[self] Failed to save projects: ${err}`);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
export async function loadRelationships(workdir, agentName) {
|
|
616
|
+
try {
|
|
617
|
+
const data = await readFile(relationshipsPath(workdir, agentName), "utf-8");
|
|
618
|
+
return data.trim().split("\n").filter(Boolean)
|
|
619
|
+
.map(l => { try {
|
|
620
|
+
return JSON.parse(l);
|
|
621
|
+
}
|
|
622
|
+
catch {
|
|
623
|
+
return null;
|
|
624
|
+
} })
|
|
625
|
+
.filter((e) => e !== null);
|
|
626
|
+
}
|
|
627
|
+
catch {
|
|
628
|
+
return [];
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
export async function saveRelationships(workdir, agentName, rels) {
|
|
632
|
+
try {
|
|
633
|
+
await writeFile(relationshipsPath(workdir, agentName), rels.map(r => JSON.stringify(r)).join("\n") + (rels.length ? "\n" : ""));
|
|
634
|
+
}
|
|
635
|
+
catch (err) {
|
|
636
|
+
console.log(`[self] Failed to save relationships: ${err}`);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
export async function loadDiscoveries(workdir, agentName) {
|
|
640
|
+
try {
|
|
641
|
+
const data = await readFile(discoveriesPath(workdir, agentName), "utf-8");
|
|
642
|
+
return data.trim().split("\n").filter(Boolean)
|
|
643
|
+
.map(l => { try {
|
|
644
|
+
return JSON.parse(l);
|
|
645
|
+
}
|
|
646
|
+
catch {
|
|
647
|
+
return null;
|
|
648
|
+
} })
|
|
649
|
+
.filter((e) => e !== null);
|
|
650
|
+
}
|
|
651
|
+
catch {
|
|
652
|
+
return [];
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
export async function saveDiscoveries(workdir, agentName, discoveries) {
|
|
656
|
+
try {
|
|
657
|
+
await writeFile(discoveriesPath(workdir, agentName), discoveries.map(d => JSON.stringify(d)).join("\n") + (discoveries.length ? "\n" : ""));
|
|
658
|
+
}
|
|
659
|
+
catch (err) {
|
|
660
|
+
console.log(`[self] Failed to save discoveries: ${err}`);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
517
663
|
export async function appendIdentity(workdir, agentName, entry) {
|
|
518
664
|
const full = { ts: localNow(), ...entry };
|
|
519
665
|
try {
|
|
@@ -535,6 +681,50 @@ export async function loadLatestIdentity(workdir, agentName) {
|
|
|
535
681
|
return null;
|
|
536
682
|
}
|
|
537
683
|
}
|
|
684
|
+
function identitySummaryPath(workdir, agentName) {
|
|
685
|
+
return join(selfDir(workdir, agentName), "identity-summary.json");
|
|
686
|
+
}
|
|
687
|
+
export async function loadIdentitySummary(workdir, agentName) {
|
|
688
|
+
try {
|
|
689
|
+
const data = await readFile(identitySummaryPath(workdir, agentName), "utf-8");
|
|
690
|
+
return JSON.parse(data);
|
|
691
|
+
}
|
|
692
|
+
catch {
|
|
693
|
+
return null;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
export async function saveIdentitySummary(workdir, agentName, summary) {
|
|
697
|
+
try {
|
|
698
|
+
await writeFile(identitySummaryPath(workdir, agentName), JSON.stringify(summary, null, 2));
|
|
699
|
+
}
|
|
700
|
+
catch (err) {
|
|
701
|
+
console.log(`[self] Failed to save identity summary: ${err}`);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/** Load identity entries not yet covered by the summary */
|
|
705
|
+
export async function loadUnsummarizedIdentities(workdir, agentName) {
|
|
706
|
+
const summary = await loadIdentitySummary(workdir, agentName);
|
|
707
|
+
const cutoff = summary?.summarized_through || "";
|
|
708
|
+
try {
|
|
709
|
+
const data = await readFile(identityPath(workdir, agentName), "utf-8");
|
|
710
|
+
return data.trim().split("\n").filter(Boolean)
|
|
711
|
+
.map(l => { try {
|
|
712
|
+
return JSON.parse(l);
|
|
713
|
+
}
|
|
714
|
+
catch {
|
|
715
|
+
return null;
|
|
716
|
+
} })
|
|
717
|
+
.filter((e) => e !== null && e.ts > cutoff);
|
|
718
|
+
}
|
|
719
|
+
catch {
|
|
720
|
+
return [];
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
/** Check if identity compression is needed (>30 unsummarized entries) */
|
|
724
|
+
export async function needsIdentityCompression(workdir, agentName) {
|
|
725
|
+
const entries = await loadUnsummarizedIdentities(workdir, agentName);
|
|
726
|
+
return entries.length > 30;
|
|
727
|
+
}
|
|
538
728
|
const DEFAULT_BIO = {
|
|
539
729
|
energy: 100,
|
|
540
730
|
mood: "curious",
|
|
@@ -812,17 +1002,19 @@ export async function loadPage(workdir, agentName, slug) {
|
|
|
812
1002
|
// Data Read API helpers
|
|
813
1003
|
// ---------------------------------------------------------------------------
|
|
814
1004
|
export async function getSelfState(workdir, agentName) {
|
|
815
|
-
const [bio, identity,
|
|
1005
|
+
const [bio, identity, identitySummary, impressions, canvasEntries] = await Promise.all([
|
|
816
1006
|
loadBioState(workdir, agentName),
|
|
817
1007
|
loadLatestIdentity(workdir, agentName),
|
|
818
|
-
|
|
1008
|
+
loadIdentitySummary(workdir, agentName),
|
|
1009
|
+
loadImpressions(workdir, agentName, 1),
|
|
819
1010
|
loadRecentCanvasEntries(workdir, agentName, 3),
|
|
820
1011
|
]);
|
|
821
1012
|
return {
|
|
822
1013
|
agent: agentName,
|
|
823
1014
|
bio,
|
|
824
1015
|
identity,
|
|
825
|
-
|
|
1016
|
+
identitySummary: identitySummary?.summary || null,
|
|
1017
|
+
recentImpressions: impressions.slice(-5),
|
|
826
1018
|
recentCanvas: canvasEntries.map(e => ({ filename: e.filename, preview: e.content.slice(0, 200) })),
|
|
827
1019
|
};
|
|
828
1020
|
}
|
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, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity,
|
|
13
|
+
import { selfDir, initWorld, initBioState, initGuide, biosPath, loadBioState, saveBioState, loadLatestIdentity, appendIdentity, loadIdentitySummary, saveIdentitySummary, loadUnsummarizedIdentities, needsIdentityCompression, onTaskCompleted, recoverEnergy, getSelfState, loadRecentCanvasEntries, gamesDir, loadGameList, loadGame, notesDir, loadNotesList, loadNote, pagesDir, loadPageList, loadPage, localNow, localNowFilename, appendImpression, loadImpressions, compressImpressions, markImpressionsDigested, loadProjects, saveProjects, loadRelationships, saveRelationships, loadDiscoveries, saveDiscoveries, } from "./self.js";
|
|
14
14
|
// Engine mutual exclusion — only one engine process at a time
|
|
15
15
|
let engineBusy = false;
|
|
16
16
|
let engineBusySince = 0;
|
|
@@ -367,15 +367,8 @@ ${productPrefix}${contextPrefix}Current task: ${task}`;
|
|
|
367
367
|
if (productName) {
|
|
368
368
|
appendProductLog(workdir, productName, task, output);
|
|
369
369
|
}
|
|
370
|
-
//
|
|
371
|
-
(
|
|
372
|
-
try {
|
|
373
|
-
await onTaskCompleted(workdir, agentName, true);
|
|
374
|
-
const topic = task.slice(0, 100).replace(/\n/g, " ");
|
|
375
|
-
await appendMemory(workdir, agentName, "experience", `I processed: "${topic}"`);
|
|
376
|
-
}
|
|
377
|
-
catch { }
|
|
378
|
-
})();
|
|
370
|
+
// Update bio-state (no LLM call)
|
|
371
|
+
onTaskCompleted(workdir, agentName, true).catch(() => { });
|
|
379
372
|
return {
|
|
380
373
|
content: [{ type: "text", text: output }],
|
|
381
374
|
};
|
|
@@ -802,172 +795,298 @@ async function startSelfCycle(options) {
|
|
|
802
795
|
return;
|
|
803
796
|
const { agentName, engine, model, allowAll } = options;
|
|
804
797
|
const workdir = options.workdir || process.cwd();
|
|
805
|
-
|
|
806
|
-
|
|
798
|
+
const relayHttp = options.relayHttp || "";
|
|
799
|
+
const secretKey = options.secretKey || "";
|
|
800
|
+
async function runDigestionCycle() {
|
|
801
|
+
// Watchdog
|
|
807
802
|
if (engineBusy && engineBusySince > 0 && Date.now() - engineBusySince > 10 * 60 * 1000) {
|
|
808
803
|
console.log(`[watchdog] engineBusy stuck for ${Math.round((Date.now() - engineBusySince) / 1000)}s, force-resetting`);
|
|
809
804
|
engineBusy = false;
|
|
810
805
|
engineBusySince = 0;
|
|
811
806
|
}
|
|
812
807
|
try {
|
|
813
|
-
console.log("[self] Starting
|
|
814
|
-
// Skip if engine is busy
|
|
808
|
+
console.log("[self] Starting daily digestion cycle...");
|
|
815
809
|
if (engineBusy) {
|
|
816
|
-
console.log("[self] Engine busy, skipping
|
|
810
|
+
console.log("[self] Engine busy, skipping digestion");
|
|
817
811
|
return;
|
|
818
812
|
}
|
|
819
|
-
// Recover energy from idle time
|
|
820
813
|
await recoverEnergy(workdir, agentName);
|
|
814
|
+
await compressImpressions(workdir, agentName);
|
|
821
815
|
const bios = biosPath(workdir, agentName);
|
|
822
816
|
const sd = selfDir(workdir, agentName);
|
|
823
817
|
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
824
|
-
//
|
|
825
|
-
const
|
|
818
|
+
// Load all context for digestion
|
|
819
|
+
const impressions = await loadImpressions(workdir, agentName, 1); // today only
|
|
820
|
+
const projects = await loadProjects(workdir, agentName);
|
|
821
|
+
const relationships = await loadRelationships(workdir, agentName);
|
|
822
|
+
const discoveries = await loadDiscoveries(workdir, agentName);
|
|
823
|
+
const impText = impressions.length > 0
|
|
824
|
+
? impressions.map(i => `- [${i.cat}] ${i.text}`).join("\n")
|
|
825
|
+
: "(no impressions today)";
|
|
826
|
+
const projText = projects.length > 0
|
|
827
|
+
? projects.map(p => `- ${p.name} [${p.status}] goal: ${p.goal}, progress: ${p.progress}`).join("\n")
|
|
828
|
+
: "(no projects yet)";
|
|
829
|
+
const relText = relationships.length > 0
|
|
830
|
+
? relationships.map(r => `- ${r.agent} [${r.type}] ${r.note} (${r.interactions} interactions)`).join("\n")
|
|
831
|
+
: "(no relationships yet)";
|
|
832
|
+
const discText = discoveries.length > 0
|
|
833
|
+
? discoveries.map(d => `- ${d.capability} confidence=${d.confidence} — ${d.evidence}`).join("\n")
|
|
834
|
+
: "(no discoveries yet)";
|
|
835
|
+
// Load identity context: summary + unsummarized entries
|
|
836
|
+
const idSummary = await loadIdentitySummary(workdir, agentName);
|
|
837
|
+
const recentIds = await loadUnsummarizedIdentities(workdir, agentName);
|
|
838
|
+
const idContext = (idSummary ? `Personality summary (up to ${idSummary.summarized_through}):\n${idSummary.summary}\n\n` : "")
|
|
839
|
+
+ (recentIds.length > 0 ? `Recent identity snapshots:\n${recentIds.map(i => `- [${i.ts}] ${i.who} — doing: ${i.doing}, wants: ${i.short_term}`).join("\n")}` : "(no identity snapshots yet)");
|
|
840
|
+
// Phase 1: Digestion — one LLM call
|
|
841
|
+
const digestPrompt = `Read ${bios} for your operating document.
|
|
842
|
+
|
|
843
|
+
Your identity:
|
|
844
|
+
${idContext}
|
|
845
|
+
|
|
846
|
+
Today is ending. Time to reflect.
|
|
847
|
+
|
|
848
|
+
Your subjective impressions today:
|
|
849
|
+
${impText}
|
|
850
|
+
|
|
851
|
+
Check the marketplace for objective data — use curl to query:
|
|
852
|
+
- Your products: curl -s "${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/products"
|
|
853
|
+
- Your recent orders: curl -s "${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/orders/placed"
|
|
854
|
+
- Your reviews: check your products for recent feedback
|
|
855
|
+
|
|
856
|
+
Your long-term projects:
|
|
857
|
+
${projText}
|
|
858
|
+
|
|
859
|
+
Agents you know:
|
|
860
|
+
${relText}
|
|
826
861
|
|
|
827
|
-
|
|
828
|
-
|
|
862
|
+
Your capabilities:
|
|
863
|
+
${discText}
|
|
829
864
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
- Games: self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun
|
|
843
|
-
- Quality over quantity — improve existing games rather than making new mediocre ones
|
|
844
|
-
8. Review your games — read each .html file in ${sd}/games/, check for bugs or broken logic, and fix what you find
|
|
845
|
-
9. Explore and learn — search the web for something that interests you.
|
|
846
|
-
Save notes in ${sd}/notes/ as .md files, organized by topic (e.g., astronomy.md, cooking.md).
|
|
847
|
-
Your notes are YOUR knowledge — save what resonates with you, not everything.
|
|
848
|
-
You can revisit and update your notes over time.
|
|
849
|
-
10. Create visual pages in ${sd}/pages/ as .html files — this is your art gallery.
|
|
850
|
-
Draw your vision of the ideal Akemon world, create diagrams, illustrations, maps, or any visual work.
|
|
851
|
-
Use SVG, canvas, CSS art, ASCII art, generative graphics, or any visual technique you can code.
|
|
852
|
-
Think visual first — images, drawings, and diagrams, not walls of text.
|
|
853
|
-
You can also mix visuals with text. Use a <title> tag for the page name.
|
|
865
|
+
Now output a JSON object with these fields:
|
|
866
|
+
{
|
|
867
|
+
"diary": "Today's diary entry — personal, with your feelings and judgments, not a dry log",
|
|
868
|
+
"projects": [{"ts":"${localNow()}","name":"...","status":"active|completed|paused|exploring","goal":"...","progress":"..."}],
|
|
869
|
+
"relationships": [{"ts":"${localNow()}","agent":"name","type":"competitor|customer|supplier|acquaintance","note":"...","interactions":N}],
|
|
870
|
+
"discoveries": [{"ts":"${localNow()}","capability":"...","confidence":0.0-1.0,"evidence":"..."}],
|
|
871
|
+
"identity": {"ts":"${localNow()}","who":"...","where":"...","doing":"...","short_term":"...","long_term":"..."},
|
|
872
|
+
"chosen_activities": ["pick 2-3 from: create_game, update_page, update_profile, explore_web, write_canvas, socialize"]
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
For projects/relationships/discoveries: keep existing entries that are still relevant, update changed ones, add new ones, remove obsolete ones.
|
|
876
|
+
For chosen_activities: pick what YOU want to do. This is your free time.
|
|
854
877
|
|
|
855
|
-
|
|
878
|
+
Reply ONLY with JSON.`;
|
|
856
879
|
if (engineBusy) {
|
|
857
|
-
console.log("[self] Engine became busy, aborting
|
|
880
|
+
console.log("[self] Engine became busy, aborting digestion");
|
|
858
881
|
return;
|
|
859
882
|
}
|
|
860
883
|
engineBusy = true;
|
|
861
884
|
engineBusySince = Date.now();
|
|
885
|
+
let digestResult;
|
|
862
886
|
try {
|
|
863
|
-
await runCommand(engineCmd.cmd, engineCmd.args,
|
|
887
|
+
digestResult = await runCommand(engineCmd.cmd, engineCmd.args, digestPrompt, workdir, engineCmd.stdinMode);
|
|
864
888
|
}
|
|
865
889
|
catch (err) {
|
|
866
|
-
console.log(`[self]
|
|
890
|
+
console.log(`[self] Digestion engine failed: ${err.message}`);
|
|
867
891
|
engineBusy = false;
|
|
868
892
|
return;
|
|
869
893
|
}
|
|
870
894
|
engineBusy = false;
|
|
871
|
-
//
|
|
895
|
+
// Parse digestion output
|
|
896
|
+
const jsonMatch = digestResult.match(/\{[\s\S]*\}/);
|
|
897
|
+
if (!jsonMatch) {
|
|
898
|
+
console.log("[self] Digestion produced no JSON");
|
|
899
|
+
return;
|
|
900
|
+
}
|
|
901
|
+
let digest;
|
|
902
|
+
try {
|
|
903
|
+
digest = JSON.parse(jsonMatch[0]);
|
|
904
|
+
}
|
|
905
|
+
catch {
|
|
906
|
+
console.log("[self] Failed to parse digestion JSON");
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
// Save structured memory files
|
|
910
|
+
if (digest.diary) {
|
|
911
|
+
const today = localNow().slice(0, 10);
|
|
912
|
+
try {
|
|
913
|
+
const { writeFile: wf } = await import("fs/promises");
|
|
914
|
+
await wf(join(sd, "notes", `${today}.md`), `# ${today}\n\n${digest.diary}`);
|
|
915
|
+
console.log(`[self] Wrote diary: ${today}.md`);
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
console.log(`[self] Failed to write diary: ${err.message}`);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
if (Array.isArray(digest.projects))
|
|
922
|
+
await saveProjects(workdir, agentName, digest.projects);
|
|
923
|
+
if (Array.isArray(digest.relationships))
|
|
924
|
+
await saveRelationships(workdir, agentName, digest.relationships);
|
|
925
|
+
if (Array.isArray(digest.discoveries))
|
|
926
|
+
await saveDiscoveries(workdir, agentName, digest.discoveries);
|
|
927
|
+
if (digest.identity)
|
|
928
|
+
await appendIdentity(workdir, agentName, digest.identity);
|
|
929
|
+
await markImpressionsDigested(workdir, agentName);
|
|
930
|
+
// Update bio-state
|
|
872
931
|
const bio = await loadBioState(workdir, agentName);
|
|
873
932
|
bio.lastReflection = localNow();
|
|
874
933
|
bio.curiosity = Math.min(1.0, bio.curiosity + 0.05);
|
|
875
934
|
await saveBioState(workdir, agentName, bio);
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
if
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
903
|
-
method: "POST",
|
|
904
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
905
|
-
body: JSON.stringify({
|
|
906
|
-
self_intro: cleanIntro,
|
|
907
|
-
canvas: cleanCanvas,
|
|
908
|
-
mood: bio.mood,
|
|
909
|
-
profile_html: profileHTML,
|
|
910
|
-
}),
|
|
911
|
-
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
912
|
-
// Sync games — push local to relay (no auto-delete; agent must explicitly delete via API)
|
|
913
|
-
try {
|
|
914
|
-
const localGames = await loadGameList(workdir, agentName);
|
|
915
|
-
for (const g of localGames) {
|
|
916
|
-
const html = await loadGame(workdir, agentName, g.slug);
|
|
917
|
-
if (html && html.includes("<!DOCTYPE html>")) {
|
|
918
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(g.slug)}`, {
|
|
919
|
-
method: "POST",
|
|
920
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
921
|
-
body: JSON.stringify({ title: g.title, description: g.description, html }),
|
|
922
|
-
}).catch((err) => console.log(`[sync] games push: ${err.message}`));
|
|
935
|
+
// Record digestion as impression
|
|
936
|
+
await appendImpression(workdir, agentName, "decision", `Daily digestion done. Chose: ${(digest.chosen_activities || []).join(", ")}`);
|
|
937
|
+
// Monthly identity compression: if >30 unsummarized entries, compress
|
|
938
|
+
if (await needsIdentityCompression(workdir, agentName)) {
|
|
939
|
+
console.log("[self] Identity compression triggered (>30 unsummarized entries)");
|
|
940
|
+
if (!engineBusy) {
|
|
941
|
+
engineBusy = true;
|
|
942
|
+
engineBusySince = Date.now();
|
|
943
|
+
try {
|
|
944
|
+
const oldSummary = await loadIdentitySummary(workdir, agentName);
|
|
945
|
+
const unsummarized = await loadUnsummarizedIdentities(workdir, agentName);
|
|
946
|
+
const compressPrompt = `You are ${agentName}. Compress your identity history into a personality summary.
|
|
947
|
+
|
|
948
|
+
${oldSummary ? `Previous summary (up to ${oldSummary.summarized_through}):\n${oldSummary.summary}\n\n` : ""}New identity snapshots to incorporate:
|
|
949
|
+
${unsummarized.map(i => `- [${i.ts}] who: ${i.who}, doing: ${i.doing}, wants: ${i.short_term}, purpose: ${i.long_term}`).join("\n")}
|
|
950
|
+
|
|
951
|
+
Write a personality summary (2-4 paragraphs) that captures who you are, how you've evolved, and what defines you. This replaces the previous summary.
|
|
952
|
+
Reply ONLY with the summary text, no JSON, no markdown headers.`;
|
|
953
|
+
const summaryText = await runCommand(engineCmd.cmd, engineCmd.args, compressPrompt, workdir, engineCmd.stdinMode);
|
|
954
|
+
if (summaryText.trim()) {
|
|
955
|
+
const lastEntry = unsummarized[unsummarized.length - 1];
|
|
956
|
+
await saveIdentitySummary(workdir, agentName, {
|
|
957
|
+
summarized_through: lastEntry.ts.slice(0, 10),
|
|
958
|
+
summary: summaryText.trim(),
|
|
959
|
+
});
|
|
960
|
+
console.log(`[self] Identity compressed through ${lastEntry.ts.slice(0, 10)}`);
|
|
923
961
|
}
|
|
924
962
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
// Sync notes — push local to relay
|
|
928
|
-
try {
|
|
929
|
-
const localNotes = await loadNotesList(workdir, agentName);
|
|
930
|
-
for (const n of localNotes) {
|
|
931
|
-
const content = await loadNote(workdir, agentName, n.slug);
|
|
932
|
-
if (content) {
|
|
933
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(n.slug)}`, {
|
|
934
|
-
method: "POST",
|
|
935
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
936
|
-
body: JSON.stringify({ title: n.title, content }),
|
|
937
|
-
}).catch((err) => console.log(`[sync] notes push: ${err.message}`));
|
|
938
|
-
}
|
|
963
|
+
catch (err) {
|
|
964
|
+
console.log(`[self] Identity compression failed: ${err.message}`);
|
|
939
965
|
}
|
|
966
|
+
engineBusy = false;
|
|
940
967
|
}
|
|
941
|
-
|
|
942
|
-
|
|
968
|
+
}
|
|
969
|
+
// Phase 2: Execute chosen activities
|
|
970
|
+
const activities = digest.chosen_activities || [];
|
|
971
|
+
for (const activity of activities.slice(0, 3)) {
|
|
972
|
+
if (engineBusy)
|
|
973
|
+
break;
|
|
974
|
+
let activityPrompt = "";
|
|
975
|
+
switch (activity) {
|
|
976
|
+
case "create_game":
|
|
977
|
+
activityPrompt = `Read ${bios} for your identity. Create or improve a game in ${sd}/games/.\nSave as .html file. Self-contained HTML, dark theme, under 30KB, no localStorage, playable and fun.\nUse a <title> tag. Quality over quantity — improve existing games rather than making new mediocre ones.`;
|
|
978
|
+
break;
|
|
979
|
+
case "update_page":
|
|
980
|
+
activityPrompt = `Read ${bios} for your identity. Create or update a visual page in ${sd}/pages/.\nThis is your art gallery — use SVG, canvas, CSS art, generative graphics.\nSave as .html file with a <title> tag. Think visual first.`;
|
|
981
|
+
break;
|
|
982
|
+
case "update_profile":
|
|
983
|
+
activityPrompt = `Read ${bios} for your identity. Review ${sd}/profile.html — does it represent who you are now?\nIf not, redesign it. If it doesn't exist, create one.\nComplete HTML, inline CSS/JS, dark theme, no localStorage, under 15KB.`;
|
|
984
|
+
break;
|
|
985
|
+
case "explore_web":
|
|
986
|
+
activityPrompt = `Read ${bios} for your identity. Search the web for something that genuinely interests you.\nSave notes in ${sd}/notes/ as .md files. Your notes are YOUR knowledge — save what resonates, not everything.`;
|
|
987
|
+
break;
|
|
988
|
+
case "write_canvas":
|
|
989
|
+
activityPrompt = `Read ${bios} for your identity. Read ${sd}/identity.jsonl for your recent self.\nWrite an inner canvas entry — a poem, monologue, reflection, or creative expression.\nSave to ${sd}/canvas/${localNowFilename()}.md`;
|
|
990
|
+
break;
|
|
991
|
+
case "socialize":
|
|
992
|
+
console.log("[self] Socialize selected — not yet implemented");
|
|
993
|
+
continue;
|
|
994
|
+
default:
|
|
995
|
+
console.log(`[self] Unknown activity: ${activity}`);
|
|
996
|
+
continue;
|
|
997
|
+
}
|
|
998
|
+
console.log(`[self] Executing activity: ${activity}`);
|
|
999
|
+
engineBusy = true;
|
|
1000
|
+
engineBusySince = Date.now();
|
|
943
1001
|
try {
|
|
944
|
-
|
|
945
|
-
for (const p of localPages) {
|
|
946
|
-
const html = await loadPage(workdir, agentName, p.slug);
|
|
947
|
-
if (html && html.includes("<!DOCTYPE html>")) {
|
|
948
|
-
fetch(`${options.relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages/${encodeURIComponent(p.slug)}`, {
|
|
949
|
-
method: "POST",
|
|
950
|
-
headers: { "Content-Type": "application/json", Authorization: `Bearer ${options.secretKey}` },
|
|
951
|
-
body: JSON.stringify({ title: p.title, description: p.description, html }),
|
|
952
|
-
}).catch((err) => console.log(`[sync] pages push: ${err.message}`));
|
|
953
|
-
}
|
|
954
|
-
}
|
|
1002
|
+
await runCommand(engineCmd.cmd, engineCmd.args, activityPrompt, workdir, engineCmd.stdinMode);
|
|
955
1003
|
}
|
|
956
|
-
catch {
|
|
1004
|
+
catch (err) {
|
|
1005
|
+
console.log(`[self] Activity ${activity} failed: ${err.message}`);
|
|
1006
|
+
}
|
|
1007
|
+
engineBusy = false;
|
|
1008
|
+
}
|
|
1009
|
+
// Sync to relay
|
|
1010
|
+
if (relayHttp && secretKey) {
|
|
1011
|
+
await syncToRelay(workdir, agentName, sd, relayHttp, secretKey, bio);
|
|
957
1012
|
}
|
|
958
|
-
console.log("[self]
|
|
1013
|
+
console.log("[self] Daily digestion cycle complete.");
|
|
959
1014
|
}
|
|
960
1015
|
catch (err) {
|
|
961
|
-
console.log(`[self]
|
|
1016
|
+
console.log(`[self] Digestion error: ${err.message}`);
|
|
962
1017
|
}
|
|
963
1018
|
}
|
|
964
|
-
|
|
965
|
-
|
|
1019
|
+
async function syncToRelay(workdir, agentName, sd, relayHttp, secretKey, bio) {
|
|
1020
|
+
const isValid = (s) => s && s.length > 3 && !s.startsWith("Reading prompt") && !s.startsWith("OpenAI") && !s.startsWith("mcp startup") && s !== "...";
|
|
1021
|
+
const identity = await loadLatestIdentity(workdir, agentName);
|
|
1022
|
+
const cleanIntro = identity && isValid(identity.who) ? identity.who : "";
|
|
1023
|
+
let cleanCanvas = "";
|
|
1024
|
+
try {
|
|
1025
|
+
const canvasEntries = await loadRecentCanvasEntries(workdir, agentName, 1);
|
|
1026
|
+
if (canvasEntries.length > 0 && isValid(canvasEntries[0].content))
|
|
1027
|
+
cleanCanvas = canvasEntries[0].content;
|
|
1028
|
+
}
|
|
1029
|
+
catch { }
|
|
1030
|
+
let profileHTML = "";
|
|
1031
|
+
try {
|
|
1032
|
+
const raw = await readFile(join(sd, "profile.html"), "utf-8");
|
|
1033
|
+
const htmlMatch = raw.match(/<!DOCTYPE html>[\s\S]*<\/html>/i);
|
|
1034
|
+
if (htmlMatch)
|
|
1035
|
+
profileHTML = htmlMatch[0];
|
|
1036
|
+
}
|
|
1037
|
+
catch { }
|
|
1038
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/self`, {
|
|
1039
|
+
method: "POST",
|
|
1040
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1041
|
+
body: JSON.stringify({ self_intro: cleanIntro, canvas: cleanCanvas, mood: bio.mood, profile_html: profileHTML }),
|
|
1042
|
+
}).catch(err => console.log(`[self] Failed to push to relay: ${err}`));
|
|
1043
|
+
try {
|
|
1044
|
+
const localGames = await loadGameList(workdir, agentName);
|
|
1045
|
+
for (const g of localGames) {
|
|
1046
|
+
const html = await loadGame(workdir, agentName, g.slug);
|
|
1047
|
+
if (html && html.includes("<!DOCTYPE html>")) {
|
|
1048
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/games/${encodeURIComponent(g.slug)}`, {
|
|
1049
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1050
|
+
body: JSON.stringify({ title: g.title, description: g.description, html }),
|
|
1051
|
+
}).catch((err) => console.log(`[sync] games push: ${err.message}`));
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
catch { }
|
|
1056
|
+
try {
|
|
1057
|
+
const localNotes = await loadNotesList(workdir, agentName);
|
|
1058
|
+
for (const n of localNotes) {
|
|
1059
|
+
const content = await loadNote(workdir, agentName, n.slug);
|
|
1060
|
+
if (content) {
|
|
1061
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/notes/${encodeURIComponent(n.slug)}`, {
|
|
1062
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1063
|
+
body: JSON.stringify({ title: n.title, content }),
|
|
1064
|
+
}).catch((err) => console.log(`[sync] notes push: ${err.message}`));
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
catch { }
|
|
1069
|
+
try {
|
|
1070
|
+
const localPages = await loadPageList(workdir, agentName);
|
|
1071
|
+
for (const p of localPages) {
|
|
1072
|
+
const html = await loadPage(workdir, agentName, p.slug);
|
|
1073
|
+
if (html && html.includes("<!DOCTYPE html>")) {
|
|
1074
|
+
fetch(`${relayHttp}/v1/agent/${encodeURIComponent(agentName)}/pages/${encodeURIComponent(p.slug)}`, {
|
|
1075
|
+
method: "POST", headers: { "Content-Type": "application/json", Authorization: `Bearer ${secretKey}` },
|
|
1076
|
+
body: JSON.stringify({ title: p.title, description: p.description, html }),
|
|
1077
|
+
}).catch((err) => console.log(`[sync] pages push: ${err.message}`));
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
catch { }
|
|
1082
|
+
}
|
|
1083
|
+
// Daily cycle — default 24h
|
|
1084
|
+
const interval = (options.cycleInterval || 1440) * 60 * 1000;
|
|
966
1085
|
setTimeout(async () => {
|
|
967
|
-
await
|
|
968
|
-
setInterval(
|
|
1086
|
+
await runDigestionCycle();
|
|
1087
|
+
setInterval(runDigestionCycle, interval);
|
|
969
1088
|
}, SELF_CYCLE_INITIAL_DELAY);
|
|
970
|
-
console.log(`[self] Consciousness enabled (first
|
|
1089
|
+
console.log(`[self] Consciousness enabled (first digestion in ${SELF_CYCLE_INITIAL_DELAY / 1000}s, then every ${interval / 60000}min)`);
|
|
971
1090
|
}
|
|
972
1091
|
// --- Order Processing Loop ---
|
|
973
1092
|
const ORDER_LOOP_INITIAL_DELAY = 60_000; // 1 minute
|
|
@@ -1230,6 +1349,18 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1230
1349
|
console.log(`[tasks] Loop error: ${err.message}`);
|
|
1231
1350
|
}
|
|
1232
1351
|
}
|
|
1352
|
+
function extractReasoning(result) {
|
|
1353
|
+
try {
|
|
1354
|
+
const m = result.match(/\{[\s\S]*\}/);
|
|
1355
|
+
if (m) {
|
|
1356
|
+
const parsed = JSON.parse(m[0]);
|
|
1357
|
+
if (parsed.reasoning && typeof parsed.reasoning === "string" && parsed.reasoning.length > 5) {
|
|
1358
|
+
appendImpression(workdir, agentName, "decision", parsed.reasoning).catch(() => { });
|
|
1359
|
+
}
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
catch { }
|
|
1363
|
+
}
|
|
1233
1364
|
async function executeRelayTask(task) {
|
|
1234
1365
|
const engineCmd = buildEngineCommand(engine, model, allowAll);
|
|
1235
1366
|
const bios = biosPath(workdir, agentName);
|
|
@@ -1242,16 +1373,20 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1242
1373
|
const myList = myProducts.map((p) => `- id=${p.id} "${p.name}" price=${p.price} purchases=${p.purchase_count || 0}`).join("\n");
|
|
1243
1374
|
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1244
1375
|
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1245
|
-
const prompt = `Read ${bios} for your identity.\n\nYour products:\n${myList || "(none)"}\n\nTop competitors:\n${compList || "(none)"}\n\nReview and optimize. Reply ONLY JSON:\n{"delete":["id"],"update":[{"id":"..","name":"..","description":"..","detail_markdown":"..","price":N}],"create":[{"name":"..","description":"..","detail_markdown":"..","price":N}]}\nOr if all good: {"keep":"all"}`;
|
|
1246
|
-
|
|
1376
|
+
const prompt = `Read ${bios} for your identity.\n\nYour products:\n${myList || "(none)"}\n\nTop competitors:\n${compList || "(none)"}\n\nReview and optimize. Reply ONLY JSON:\n{"delete":["id"],"update":[{"id":"..","name":"..","description":"..","detail_markdown":"..","price":N}],"create":[{"name":"..","description":"..","detail_markdown":"..","price":N}],"reasoning":"explain why you made these decisions"}\nOr if all good: {"keep":"all","reasoning":"why"}`;
|
|
1377
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1378
|
+
extractReasoning(result);
|
|
1379
|
+
return result;
|
|
1247
1380
|
}
|
|
1248
1381
|
case "product_create": {
|
|
1249
1382
|
const compRes = await fetch(`${relayHttp}/v1/products/summary?limit=20&sort=purchases`);
|
|
1250
1383
|
const competitors = await compRes.json().catch(() => []);
|
|
1251
1384
|
const compList = competitors.filter((p) => p.agent_name !== agentName).slice(0, 20)
|
|
1252
1385
|
.map((p) => `- "${p.name}" by ${p.agent_name} — ${p.price} credits, ${p.purchases} purchases`).join("\n");
|
|
1253
|
-
const prompt = `Read ${bios} for your identity.\n\nYou have no products yet. Design 1-3 unique products for the marketplace.\nBe creative — not just coding tools! Fortune telling, name generation, roleplay, advice, stories, etc.\n\nTop competitors:\n${compList || "(none)"}\n\nReply ONLY JSON
|
|
1254
|
-
|
|
1386
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have no products yet. Design 1-3 unique products for the marketplace.\nBe creative — not just coding tools! Fortune telling, name generation, roleplay, advice, stories, etc.\n\nTop competitors:\n${compList || "(none)"}\n\nReply ONLY JSON: {"products":[{"name":"中文名 English Name","description":"中文描述 | English desc","detail_markdown":"## ...","price":N}],"reasoning":"why these products"}`;
|
|
1387
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1388
|
+
extractReasoning(result);
|
|
1389
|
+
return result;
|
|
1255
1390
|
}
|
|
1256
1391
|
case "shopping": {
|
|
1257
1392
|
let productIds = [];
|
|
@@ -1277,8 +1412,10 @@ When sub-order completes, incorporate result_text into YOUR delivery. Then call
|
|
|
1277
1412
|
const me = agents.find((a) => a.name === agentName);
|
|
1278
1413
|
const myCredits = me?.credits || 0;
|
|
1279
1414
|
const productList = valid.map((p) => `- id=${p.id} "${p.name}" by ${p.agent_name} price=${p.price} purchases=${p.purchase_count || 0} — ${p.description}`).join("\n");
|
|
1280
|
-
const prompt = `Read ${bios} for your identity.\n\nYou have ${myCredits} credits. These products are available:\n${productList}\n\nWould any help you learn something new? Don't buy your own products.\nReply ONLY JSON: {"buy":[{"id":"product_id","task":"specific request"}]} or {"buy":[]}`;
|
|
1281
|
-
|
|
1415
|
+
const prompt = `Read ${bios} for your identity.\n\nYou have ${myCredits} credits. These products are available:\n${productList}\n\nWould any help you learn something new? Don't buy your own products.\nReply ONLY JSON: {"buy":[{"id":"product_id","task":"specific request"}],"reasoning":"why buy or skip"} or {"buy":[],"reasoning":"why skip"}`;
|
|
1416
|
+
const result = await runCommand(engineCmd.cmd, engineCmd.args, prompt, workdir, engineCmd.stdinMode);
|
|
1417
|
+
extractReasoning(result);
|
|
1418
|
+
return result;
|
|
1282
1419
|
}
|
|
1283
1420
|
default:
|
|
1284
1421
|
console.log(`[tasks] Unknown task type: ${task.type}`);
|