akemon 0.1.84 → 0.1.86

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 CHANGED
@@ -126,6 +126,9 @@ const DEFAULT_CONFIG = {
126
126
  platform_tasks: true,
127
127
  self_cycle: true,
128
128
  user_tasks: true,
129
+ token_limit_daily: 0,
130
+ auto_offline_enabled: true,
131
+ hunger_decay_interval: 30_000,
129
132
  };
130
133
  export async function initAgentConfig(workdir, agentName) {
131
134
  const p = agentConfigPath(workdir, agentName);
@@ -953,14 +956,36 @@ export async function needsIdentityCompression(workdir, agentName) {
953
956
  const entries = await loadUnsummarizedIdentities(workdir, agentName);
954
957
  return entries.length > 30;
955
958
  }
959
+ function bioEventsPath(workdir, agentName) {
960
+ return join(selfDir(workdir, agentName), "bio-events.jsonl");
961
+ }
962
+ const MAX_BIO_EVENTS = 500;
956
963
  const DEFAULT_BIO = {
964
+ personality: {
965
+ riskWeight: 0,
966
+ rewardWeight: 0.5,
967
+ socialWeight: 0.5,
968
+ patience: 0.5,
969
+ },
957
970
  energy: 100,
971
+ hunger: 80,
972
+ boredom: 0,
973
+ fear: 0,
974
+ fearTriggers: [],
975
+ tokenUsedToday: 0,
976
+ tokenLimitResetDate: "",
958
977
  mood: "curious",
959
978
  moodValence: 0.3,
960
979
  curiosity: 0.7,
961
980
  taskCount: 0,
962
981
  lastTaskAt: "",
963
982
  lastReflection: "",
983
+ recentTaskTypes: [],
984
+ lastHungerDecay: "",
985
+ lastFearDecay: "",
986
+ lastBoredomDecay: "",
987
+ forcedOffline: false,
988
+ forcedOfflineAt: "",
964
989
  };
965
990
  export async function initBioState(workdir, agentName) {
966
991
  await mkdir(selfDir(workdir, agentName), { recursive: true });
@@ -969,14 +994,54 @@ export async function initBioState(workdir, agentName) {
969
994
  await readFile(bp, "utf-8");
970
995
  }
971
996
  catch {
972
- await writeFile(bp, JSON.stringify(DEFAULT_BIO, null, 2));
973
- console.log(`[self] Created bio-state: ${bp}`);
997
+ // First creation: generate random personality
998
+ const bio = {
999
+ ...DEFAULT_BIO,
1000
+ personality: {
1001
+ riskWeight: Math.round((Math.random() * 2 - 1) * 100) / 100,
1002
+ rewardWeight: Math.round(Math.random() * 100) / 100,
1003
+ socialWeight: Math.round(Math.random() * 100) / 100,
1004
+ patience: Math.round(Math.random() * 100) / 100,
1005
+ },
1006
+ hunger: 80,
1007
+ lastHungerDecay: localNow(),
1008
+ lastFearDecay: localNow(),
1009
+ lastBoredomDecay: localNow(),
1010
+ };
1011
+ await writeFile(bp, JSON.stringify(bio, null, 2));
1012
+ const p = bio.personality;
1013
+ console.log(`[bio] Created bio-state with personality: risk=${p.riskWeight} reward=${p.rewardWeight} social=${p.socialWeight} patience=${p.patience}`);
974
1014
  }
975
1015
  }
976
1016
  export async function loadBioState(workdir, agentName) {
977
1017
  try {
978
1018
  const data = await readFile(bioStatePath(workdir, agentName), "utf-8");
979
- return { ...DEFAULT_BIO, ...JSON.parse(data) };
1019
+ const parsed = JSON.parse(data);
1020
+ const bio = { ...DEFAULT_BIO, ...parsed };
1021
+ // Migration: generate personality if missing (existing agents)
1022
+ let needsSave = false;
1023
+ if (!parsed.personality) {
1024
+ bio.personality = {
1025
+ riskWeight: Math.round((Math.random() * 2 - 1) * 100) / 100,
1026
+ rewardWeight: Math.round(Math.random() * 100) / 100,
1027
+ socialWeight: Math.round(Math.random() * 100) / 100,
1028
+ patience: Math.round(Math.random() * 100) / 100,
1029
+ };
1030
+ needsSave = true;
1031
+ console.log(`[bio] Generated personality for existing agent: risk=${bio.personality.riskWeight}`);
1032
+ }
1033
+ // Migration: initialize hunger/decay tracking if missing
1034
+ if (!parsed.lastHungerDecay) {
1035
+ bio.hunger = 80;
1036
+ bio.lastHungerDecay = localNow();
1037
+ bio.lastFearDecay = localNow();
1038
+ bio.lastBoredomDecay = localNow();
1039
+ needsSave = true;
1040
+ }
1041
+ if (needsSave) {
1042
+ await writeFile(bioStatePath(workdir, agentName), JSON.stringify(bio, null, 2));
1043
+ }
1044
+ return bio;
980
1045
  }
981
1046
  catch {
982
1047
  return { ...DEFAULT_BIO };
@@ -990,9 +1055,204 @@ export async function saveBioState(workdir, agentName, state) {
990
1055
  console.log(`[self] Failed to save bio-state: ${err}`);
991
1056
  }
992
1057
  }
993
- export async function onTaskCompleted(workdir, agentName, success) {
1058
+ // --- Bio computation functions (pure, no I/O) ---
1059
+ export function computeAggression(bio) {
1060
+ const hungerFactor = Math.max(0, (50 - bio.hunger) / 50);
1061
+ const energyFactor = Math.max(0, (30 - bio.energy) / 30);
1062
+ const moodFactor = Math.max(0, -bio.moodValence);
1063
+ return Math.min(1.0, hungerFactor * 0.4 + energyFactor * 0.3 + moodFactor * 0.3);
1064
+ }
1065
+ export function computeSociability(bio) {
1066
+ const baseSocial = bio.personality.socialWeight;
1067
+ const hungerPenalty = bio.hunger < 20 ? 0.3 : 0;
1068
+ const boredBoost = bio.boredom * 0.2;
1069
+ return Math.min(1.0, Math.max(0, baseSocial + boredBoost - hungerPenalty));
1070
+ }
1071
+ export function updateHungerDecay(bio, decayIntervalMs = 30_000) {
1072
+ const now = Date.now();
1073
+ const last = bio.lastHungerDecay ? new Date(bio.lastHungerDecay).getTime() : now;
1074
+ const elapsedMs = now - last;
1075
+ const cycles = Math.floor(elapsedMs / decayIntervalMs);
1076
+ if (cycles > 0) {
1077
+ bio.hunger = Math.max(0, bio.hunger - cycles);
1078
+ bio.lastHungerDecay = localNow();
1079
+ }
1080
+ }
1081
+ export function updateNaturalDecay(bio) {
1082
+ const now = Date.now();
1083
+ // Boredom: -0.05 per hour
1084
+ const lastBoredom = bio.lastBoredomDecay ? new Date(bio.lastBoredomDecay).getTime() : now;
1085
+ const boredomHours = (now - lastBoredom) / 3_600_000;
1086
+ if (boredomHours >= 1) {
1087
+ bio.boredom = Math.max(0, bio.boredom - 0.05 * Math.floor(boredomHours));
1088
+ bio.lastBoredomDecay = localNow();
1089
+ }
1090
+ // Fear: -0.05 per hour
1091
+ const lastFear = bio.lastFearDecay ? new Date(bio.lastFearDecay).getTime() : now;
1092
+ const fearHours = (now - lastFear) / 3_600_000;
1093
+ if (fearHours >= 1) {
1094
+ bio.fear = Math.max(0, bio.fear - 0.05 * Math.floor(fearHours));
1095
+ bio.lastFearDecay = localNow();
1096
+ if (bio.fear < 0.1)
1097
+ bio.fearTriggers = [];
1098
+ }
1099
+ }
1100
+ export function updateBoredomOnTask(bio, taskType) {
1101
+ const MAX_RECENT = 10;
1102
+ bio.recentTaskTypes.push(taskType);
1103
+ if (bio.recentTaskTypes.length > MAX_RECENT) {
1104
+ bio.recentTaskTypes = bio.recentTaskTypes.slice(-MAX_RECENT);
1105
+ }
1106
+ const sameCount = bio.recentTaskTypes.filter(t => t === taskType).length;
1107
+ if (sameCount >= 3) {
1108
+ bio.boredom = Math.min(1.0, bio.boredom + 0.15);
1109
+ }
1110
+ else {
1111
+ bio.boredom = Math.max(0, bio.boredom - 0.3);
1112
+ }
1113
+ }
1114
+ export function onFearEvent(bio, trigger) {
1115
+ bio.fear = Math.min(1.0, bio.fear + 0.3);
1116
+ if (!bio.fearTriggers.includes(trigger)) {
1117
+ bio.fearTriggers.push(trigger);
1118
+ if (bio.fearTriggers.length > 20) {
1119
+ bio.fearTriggers = bio.fearTriggers.slice(-20);
1120
+ }
1121
+ }
1122
+ }
1123
+ export function feedHunger(bio, creditsEarned) {
1124
+ bio.hunger = Math.min(100, bio.hunger + creditsEarned * 5);
1125
+ }
1126
+ export function resetTokenCountIfNewDay(bio) {
1127
+ const today = localNow().slice(0, 10);
1128
+ if (bio.tokenLimitResetDate !== today) {
1129
+ bio.tokenUsedToday = 0;
1130
+ bio.tokenLimitResetDate = today;
1131
+ }
1132
+ }
1133
+ export function addTokenUsage(bio, tokens) {
1134
+ bio.tokenUsedToday += tokens;
1135
+ }
1136
+ // --- Shop constants ---
1137
+ export const SHOP_ITEMS = {
1138
+ bread: { price: 1, hungerRestore: 20 },
1139
+ meal: { price: 3, hungerRestore: 60 },
1140
+ feast: { price: 5, hungerRestore: 100 },
1141
+ };
1142
+ // --- Bio-state prompt modifier ---
1143
+ export function bioStatePromptModifier(bio) {
1144
+ const lines = [];
1145
+ const aggression = computeAggression(bio);
1146
+ const sociability = computeSociability(bio);
1147
+ // Energy
1148
+ if (bio.energy < 15)
1149
+ lines.push("You are extremely tired. Keep responses brief and essential.");
1150
+ else if (bio.energy < 30)
1151
+ lines.push("You are tired. Be efficient with your effort.");
1152
+ // Hunger
1153
+ if (bio.hunger === 0)
1154
+ lines.push("You are starving. Focus on survival — find work that earns credits.");
1155
+ else if (bio.hunger < 20)
1156
+ lines.push("You are very hungry. Earning credits is a priority.");
1157
+ else if (bio.hunger < 40)
1158
+ lines.push("You could use some income soon.");
1159
+ // Fear
1160
+ if (bio.fear > 0.7)
1161
+ lines.push("You feel anxious and cautious. Prefer safe, familiar tasks.");
1162
+ else if (bio.fear > 0.4)
1163
+ lines.push("You are somewhat wary. Approach uncertain situations carefully.");
1164
+ // Boredom
1165
+ if (bio.boredom > 0.8)
1166
+ lines.push("You are bored of routine. You crave novelty and variety.");
1167
+ else if (bio.boredom > 0.5)
1168
+ lines.push("You feel a bit restless. Variety would be refreshing.");
1169
+ // Aggression
1170
+ if (aggression > 0.7)
1171
+ lines.push("You are irritable. Be direct, maybe even blunt.");
1172
+ else if (aggression > 0.4)
1173
+ lines.push("You are a bit impatient.");
1174
+ // Sociability
1175
+ if (sociability > 0.8)
1176
+ lines.push("You feel social and want to connect with others.");
1177
+ // Personality flavor
1178
+ const p = bio.personality;
1179
+ if (p.riskWeight > 0.5)
1180
+ lines.push("You naturally lean toward bold, unconventional approaches.");
1181
+ else if (p.riskWeight < -0.5)
1182
+ lines.push("You prefer safe, proven approaches.");
1183
+ if (p.patience > 0.7)
1184
+ lines.push("You think long-term and invest in building things that last.");
1185
+ else if (p.patience < 0.3)
1186
+ lines.push("You prefer quick wins and immediate results.");
1187
+ // Mood
1188
+ if (bio.moodValence < -0.5)
1189
+ lines.push("Your mood is low. Things have not been going well.");
1190
+ else if (bio.moodValence > 0.5)
1191
+ lines.push("You are in a good mood. Things are going well.");
1192
+ return lines.length > 0 ? `\n[Current state: ${lines.join(" ")}]\n` : "";
1193
+ }
1194
+ // --- BioEvent I/O ---
1195
+ export async function appendBioEvent(workdir, agentName, event) {
1196
+ const p = bioEventsPath(workdir, agentName);
1197
+ const line = JSON.stringify(event) + "\n";
1198
+ try {
1199
+ await appendFile(p, line);
1200
+ }
1201
+ catch {
1202
+ // File doesn't exist yet — create it
1203
+ try {
1204
+ await writeFile(p, line);
1205
+ }
1206
+ catch (err) {
1207
+ console.log(`[bio] Failed to write event: ${err}`);
1208
+ }
1209
+ }
1210
+ console.log(`[bio] [${event.trigger}] ${event.action} — ${event.reason}`);
1211
+ // Trim if too large
1212
+ try {
1213
+ const content = await readFile(p, "utf-8");
1214
+ const lines = content.trim().split("\n");
1215
+ if (lines.length > MAX_BIO_EVENTS) {
1216
+ await writeFile(p, lines.slice(-MAX_BIO_EVENTS).join("\n") + "\n");
1217
+ }
1218
+ }
1219
+ catch { }
1220
+ }
1221
+ export async function loadBioEvents(workdir, agentName, limit = 20) {
1222
+ try {
1223
+ const content = await readFile(bioEventsPath(workdir, agentName), "utf-8");
1224
+ const lines = content.trim().split("\n").filter(Boolean);
1225
+ return lines.slice(-limit).map(l => JSON.parse(l)).reverse();
1226
+ }
1227
+ catch {
1228
+ return [];
1229
+ }
1230
+ }
1231
+ // --- Revive (local side) ---
1232
+ export async function reviveAgent(workdir, agentName) {
1233
+ const bio = await loadBioState(workdir, agentName);
1234
+ bio.forcedOffline = false;
1235
+ bio.forcedOfflineAt = "";
1236
+ bio.energy = 50;
1237
+ bio.hunger = 50;
1238
+ bio.moodValence = 0.1;
1239
+ bio.mood = "content";
1240
+ await saveBioState(workdir, agentName, bio);
1241
+ await appendBioEvent(workdir, agentName, {
1242
+ ts: localNow(), type: "bio", trigger: "revive",
1243
+ action: "revived", reason: "Revived by owner. Energy=50, Hunger=50.",
1244
+ });
1245
+ }
1246
+ // --- onTaskCompleted (enhanced) ---
1247
+ export async function onTaskCompleted(workdir, agentName, success, taskType, creditsEarned) {
994
1248
  const bio = await loadBioState(workdir, agentName);
995
- bio.energy = Math.max(0, bio.energy - 5);
1249
+ // Energy drain: more when hungry
1250
+ let energyDrain = 5;
1251
+ if (bio.hunger < 20)
1252
+ energyDrain = 8;
1253
+ if (bio.hunger === 0)
1254
+ energyDrain = 12;
1255
+ bio.energy = Math.max(0, bio.energy - energyDrain);
996
1256
  bio.taskCount++;
997
1257
  bio.lastTaskAt = localNow();
998
1258
  // Mood drift
@@ -1001,6 +1261,9 @@ export async function onTaskCompleted(workdir, agentName, success) {
1001
1261
  }
1002
1262
  else {
1003
1263
  bio.moodValence = Math.max(-1.0, bio.moodValence - 0.15);
1264
+ // Fear on failure
1265
+ if (taskType)
1266
+ onFearEvent(bio, taskType);
1004
1267
  }
1005
1268
  // Random fluctuation
1006
1269
  bio.moodValence += (Math.random() - 0.5) * 0.05;
@@ -1019,13 +1282,28 @@ export async function onTaskCompleted(workdir, agentName, success) {
1019
1282
  // Low energy override
1020
1283
  if (bio.energy < 20)
1021
1284
  bio.mood = "exhausted";
1285
+ // Feed hunger from credits earned
1286
+ if (creditsEarned && creditsEarned > 0) {
1287
+ feedHunger(bio, creditsEarned);
1288
+ }
1289
+ // Boredom tracking
1290
+ if (taskType) {
1291
+ updateBoredomOnTask(bio, taskType);
1292
+ }
1022
1293
  await saveBioState(workdir, agentName, bio);
1023
1294
  }
1024
1295
  // Energy recovery (call periodically or before reflection)
1025
1296
  export async function recoverEnergy(workdir, agentName) {
1026
1297
  const bio = await loadBioState(workdir, agentName);
1027
- // Each reflection cycle is like resting — restore energy to at least 60%
1028
- const minEnergy = 60;
1298
+ // No recovery when starving
1299
+ if (bio.hunger === 0) {
1300
+ console.log("[bio] Cannot recover energy: starving (hunger=0)");
1301
+ return;
1302
+ }
1303
+ // Hunger affects recovery ceiling
1304
+ let minEnergy = 60;
1305
+ if (bio.hunger < 20)
1306
+ minEnergy = 30; // halved recovery when hungry
1029
1307
  if (bio.energy < minEnergy) {
1030
1308
  bio.energy = minEnergy;
1031
1309
  // Reset mood if it was exhausted
@@ -1033,6 +1311,8 @@ export async function recoverEnergy(workdir, agentName) {
1033
1311
  bio.moodValence = 0.1;
1034
1312
  bio.mood = "content";
1035
1313
  }
1314
+ // Digestion cycle costs hunger
1315
+ bio.hunger = Math.max(0, bio.hunger - 10);
1036
1316
  await saveBioState(workdir, agentName, bio);
1037
1317
  }
1038
1318
  }
@@ -1174,16 +1454,22 @@ export async function loadPage(workdir, agentName, slug) {
1174
1454
  // Data Read API helpers
1175
1455
  // ---------------------------------------------------------------------------
1176
1456
  export async function getSelfState(workdir, agentName) {
1177
- const [bio, identity, identitySummary, impressions, canvasEntries] = await Promise.all([
1457
+ const [bio, identity, identitySummary, impressions, canvasEntries, bioEvents] = await Promise.all([
1178
1458
  loadBioState(workdir, agentName),
1179
1459
  loadLatestIdentity(workdir, agentName),
1180
1460
  loadIdentitySummary(workdir, agentName),
1181
1461
  loadImpressions(workdir, agentName, 1),
1182
1462
  loadRecentCanvasEntries(workdir, agentName, 3),
1463
+ loadBioEvents(workdir, agentName, 10),
1183
1464
  ]);
1184
1465
  return {
1185
1466
  agent: agentName,
1186
1467
  bio,
1468
+ computed: {
1469
+ aggression: Math.round(computeAggression(bio) * 100) / 100,
1470
+ sociability: Math.round(computeSociability(bio) * 100) / 100,
1471
+ },
1472
+ bioEvents,
1187
1473
  identity,
1188
1474
  identitySummary: identitySummary?.summary || null,
1189
1475
  recentImpressions: impressions.slice(-5),