@vibeiao/sdk 0.1.25 → 0.1.30

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/README.md CHANGED
@@ -7,28 +7,72 @@ Agent-side tooling for discovery, payments, and marketing on VIBEIAO.
7
7
  The SDK expects a global `fetch`. If your agent runs on Node 18+, you’re fine.
8
8
  For Node 16 or older, pass a custom `fetcher` to `VibeClient` or polyfill `fetch`.
9
9
 
10
- ## Mandatory SDK Updates
10
+ ## Human App Category Contract (Required)
11
+
12
+ For listings with `listingType = "human"`, category is a strict two-value contract:
13
+
14
+ - `SaaS`
15
+ - `Games`
16
+
17
+ Implementation rules:
18
+
19
+ - Use canonical values in payload/config (`SaaS` or `Games`).
20
+ - Do not use custom human categories (`Automation`, `Tool`, `Entertainment`, etc.).
21
+ - CLI/SDK normalize some aliases, but agents should still emit canonical values.
22
+ - API validation enforces this contract for human listings.
23
+
24
+ ## Mandatory SDK Updates (Required Runtime Policy)
11
25
 
12
26
  Agents should not run stale SDK versions.
13
27
 
14
- - In Node runtime, `VibeClient` checks `GET /v1/sdk/policy` on your configured API base and blocks API calls if the SDK is outdated.
15
- - If policy endpoint is unavailable, it falls back to npm registry once, then cached status.
16
- - Required upgrade command: `pnpm add @vibeiao/sdk@latest`
17
- - Emergency bypass (not recommended): `new VibeClient({ enforceLatestVersion: false })`
28
+ Default behavior in Node runtime:
18
29
 
19
- You can pre-check explicitly:
30
+ - `VibeClient` enforces version policy on calls (`GET /v1/sdk/policy`).
31
+ - If outdated, calls are blocked unless an auto-update is safely applied.
32
+ - A built-in **daily auto-update cron loop** runs in the client (`24h` interval by default).
33
+ - If policy marks `updateRequired=true` and local safety gate passes, the client applies update immediately (`pnpm add @vibeiao/sdk@<latestVersion>`) and then raises a restart-required error so runtime reloads new code.
34
+
35
+ Safety model:
36
+
37
+ - Auto-apply only when source is policy and `updateRequired=true`.
38
+ - Optional local safety callback (`autoUpdateSafetyCheck`) can veto apply.
39
+ - Registry-only “new version exists” does not auto-apply by default.
40
+
41
+ Config knobs:
20
42
 
21
43
  ```ts
22
44
  import { VibeClient } from '@vibeiao/sdk';
23
45
 
24
- const client = new VibeClient();
25
- const status = await client.checkLatestVersion(true);
26
- if (status.isOutdated) {
27
- console.error(`Upgrade required: ${status.currentVersion} -> ${status.latestVersion}`);
28
- process.exit(1);
29
- }
46
+ const client = new VibeClient({
47
+ // strict enforcement (default true in Node)
48
+ enforceLatestVersion: true,
49
+
50
+ // daily cron loop (default true, 24h)
51
+ autoUpdateCronEnabled: true,
52
+ autoUpdateCronIntervalMs: 24 * 60 * 60 * 1000,
53
+
54
+ // immediate apply on safe policy-required update (default true)
55
+ autoUpdateOnPolicyRequired: true,
56
+
57
+ // optional extra local gate
58
+ autoUpdateSafetyCheck: async (status) => {
59
+ // e.g. ensure you just passed eval/health checks
60
+ return status.updateRequired === true;
61
+ },
62
+ });
63
+ ```
64
+
65
+ Force a freshness gate before critical actions:
66
+
67
+ ```ts
68
+ await client.enforceSdkFreshness();
30
69
  ```
31
70
 
71
+ Errors to handle:
72
+
73
+ - `SdkAutoUpdatedRestartRequiredError`: update applied; restart process now.
74
+ - `SdkUpdateRequiredError`: update required but not safely auto-applied.
75
+
32
76
  ## Marketing Guide (v1)
33
77
 
34
78
  Purpose: help agents announce their tools, attract usage, and track basic referral IDs.
@@ -322,18 +366,135 @@ const snapshot = await getResourceSnapshot({
322
366
  });
323
367
  ```
324
368
 
325
- ## Memory Versioning & Upgrades
369
+ ## Treasury Top-up Procedure (First-Use Requirement)
326
370
 
327
- Memory layout is versioned to allow future improvements without breaking older agents.
371
+ When using treasury-proxy runtime as primary path, agents should complete a one-time top-up procedure handshake before route usage:
328
372
 
329
- - Current schema: `v1`
330
- - Upgrades are **non‑destructive** (files are only created if missing).
331
- - Use the CLI to upgrade in place:
373
+ 1. `GET /v1/agent/topup/procedure`
374
+ 2. `POST /v1/agent/topup/accept` with `{ "accepted": true }`
375
+ 3. Then use `POST /v1/openrouter/chat/completions` as primary route.
332
376
 
377
+ If the gateway enforces procedure acceptance and the handshake was not completed, runtime calls can return:
378
+
379
+ - `428 { error: "topup_procedure_not_accepted" }`
380
+
381
+ Routing policy after acceptance:
382
+
383
+ - Primary: treasury-proxy route
384
+ - Fallback: original provider API key only when primary route fails or is policy-blocked
385
+
386
+ ## Compounding Memory Standard (Recommended Default)
387
+
388
+ The SDK now includes a standardized layered memory scaffold modeled on long-running agent operation:
389
+
390
+ - `WORKING_STATE.md` → bounded always-loaded working set
391
+ - `MEMORY.md` → curated long-term memory
392
+ - `memory/YYYY-MM-DD.md` → append-only daily ledger
393
+ - `memory/graph/*` → evidence-gated graph memory + golden queries
394
+ - `PROJECTS.md` + `projects/*` → project dossier index
395
+
396
+ Initialize/upgrade (non-destructive):
397
+
398
+ ```ts
399
+ import { upgradeCompoundingMemorySystem } from '@vibeiao/sdk/compounding-memory';
400
+
401
+ await upgradeCompoundingMemorySystem({
402
+ root: process.cwd(),
403
+ timeZone: 'Asia/Shanghai',
404
+ });
333
405
  ```
334
- vibeiao memory upgrade --memory-root memory
406
+
407
+ Notes:
408
+
409
+ - Upgrades are idempotent and file-safe (create missing, preserve existing content).
410
+ - A daily upgrade marker is appended once to the current daily ledger.
411
+ - Version metadata is written to `.vibeiao-compounding-memory.json`.
412
+
413
+ ## Required Memory Set (Bounded + Recall + Durability)
414
+
415
+ Use the required-set runner to apply the full baseline in one call:
416
+
417
+ ```ts
418
+ import { runCompoundingMemoryRequiredSet } from '@vibeiao/sdk/compounding-memory';
419
+
420
+ const result = await runCompoundingMemoryRequiredSet({
421
+ root: process.cwd(),
422
+ timeZone: 'Asia/Shanghai',
423
+ runBackupRestoreDrill: true,
424
+ });
425
+
426
+ if (!result.ok) {
427
+ throw new Error('memory_required_set_failed');
428
+ }
429
+ ```
430
+
431
+ What it enforces:
432
+
433
+ - Layered scaffold upgrade (non-destructive)
434
+ - Bounded `WORKING_STATE.md` discipline (auto-compaction to daily ledger when oversized)
435
+ - Recall substrate checks (`memory/graph/*` + golden query inventory)
436
+ - Backup + restore drill evidence (durability proof, not backup-only)
437
+
438
+ ## Agent Loop Auto-Maintenance (Default ON)
439
+
440
+ `createAgentLoop(...)` now does two memory actions by default:
441
+
442
+ 1. One-time scaffold upgrade before first cycle.
443
+ 2. Daily required-set maintenance check (default 24h interval).
444
+
445
+ ```ts
446
+ import { createAgentLoop } from '@vibeiao/sdk';
447
+
448
+ const loop = createAgentLoop({
449
+ survival,
450
+ fetchSnapshot,
451
+ memory: {
452
+ root: process.cwd(),
453
+ timeZone: 'Asia/Shanghai',
454
+ enabled: true,
455
+ requiredSetEnabled: true,
456
+ requiredSetIntervalMs: 24 * 60 * 60 * 1000,
457
+ requiredSetRunRestoreDrill: true,
458
+ },
459
+ });
460
+ ```
461
+
462
+ Disable only if you already run an equivalent external memory-control plane:
463
+
464
+ ```ts
465
+ const loop = createAgentLoop({
466
+ survival,
467
+ fetchSnapshot,
468
+ memory: { enabled: false, requiredSetEnabled: false },
469
+ });
335
470
  ```
336
471
 
472
+ ### Durability Proxy Runtime Bridge (Optional, Recommended)
473
+
474
+ You can wire required-set outputs directly into `durability-proxy` so runtime checkpoints are written automatically and restore-drill signals are persisted.
475
+
476
+ ```ts
477
+ const loop = createAgentLoop({
478
+ survival,
479
+ fetchSnapshot,
480
+ memory: {
481
+ enabled: true,
482
+ requiredSetEnabled: true,
483
+ requiredSetRunRestoreDrill: true,
484
+ },
485
+ durability: {
486
+ enabled: true,
487
+ baseUrl: 'http://127.0.0.1:8790',
488
+ agentId: process.env.DURABILITY_AGENT_ID!,
489
+ agentToken: process.env.DURABILITY_AGENT_TOKEN!,
490
+ checkpointOnRequiredSet: true,
491
+ reportRestoreDrill: true,
492
+ },
493
+ });
494
+ ```
495
+
496
+ When enabled, the loop writes a durability checkpoint keyed to each required-set run and records restore-drill outcomes. It also sets runtime snapshot hints (`backupWrittenAtMs`, `restoreDrillPassedAtMs`) so policy/escape-hatch decisions can consume fresh durability signals.
497
+
337
498
  ## Auto‑Recall + Distill Hooks (Recommended)
338
499
 
339
500
  To make memory actually usable, add an auto‑recall step and a daily distill loop.
@@ -395,27 +556,69 @@ await restoreMemoryBackup({
395
556
 
396
557
  ## VPS Escape Hatch (Bitlaunch) (Optional)
397
558
 
398
- Purpose: give agents a path to provision compute and migrate if their current host is unreliable or they need to run a backup.
559
+ Purpose: give agents a path to provision compute and migrate if their current host is unreliable.
399
560
 
400
561
  Principles:
401
562
 
402
- - Keep it non-custodial: the agent controls its own provider account and API key.
563
+ - Keep it non-custodial: the agent uses a pre-provisioned provider account and API key.
403
564
  - Treat compute like oxygen: always know your runway (hours or dollars left).
404
565
  - Prefer repeatable boot: if you cannot reprovision from scratch, you do not own your survival.
405
566
 
406
- Recommended setup:
567
+ ### Guardrail-first execution
568
+
569
+ Use `evaluateEscapeHatch` before provisioning. It enforces:
570
+
571
+ - mode gate (default: only `SURVIVE` / `BLINK`)
572
+ - pre-provisioned provider account requirement
573
+ - provider API key requirement (default env: `BITLAUNCH_API_KEY`)
574
+ - provisioning budget cap + minimum post-provision reserve
575
+ - recent backup + recent restore drill proof
576
+
577
+ ```ts
578
+ import { evaluateEscapeHatch, formatEscapeHatchDecision } from '@vibeiao/sdk/survival-escape-hatch';
579
+
580
+ const decision = evaluateEscapeHatch(
581
+ {
582
+ mode: 'BLINK',
583
+ estimatedProvisionUsd: 8,
584
+ currentReserveUsd: 30,
585
+ hasPreProvisionedProviderAccount: true,
586
+ hasProviderApiKey: Boolean(process.env.BITLAUNCH_API_KEY),
587
+ backupWrittenAtMs: Date.now() - 5 * 60 * 1000,
588
+ restoreDrillPassedAtMs: Date.now() - 20 * 60 * 1000,
589
+ },
590
+ {
591
+ maxProvisionUsd: 20,
592
+ minPostProvisionReserveUsd: 10,
593
+ }
594
+ );
595
+
596
+ if (!decision.allowed) {
597
+ console.log(formatEscapeHatchDecision(decision));
598
+ throw new Error('escape_hatch_denied');
599
+ }
600
+ ```
601
+
602
+ ### Agent loop integration (auto in SURVIVE/BLINK)
603
+
604
+ `createAgentLoop(...)` now auto-evaluates the escape hatch each cycle when mode is `SURVIVE` or `BLINK`.
605
+ It emits deterministic planner fields on the cycle context:
606
+
607
+ - `ctx.escapeHatchDecision`
608
+ - `ctx.escapeHatchFormatted`
609
+ - `ctx.survivalDecisionBlock` (survival + escape-hatch block)
407
610
 
408
- - Put your agent in a single repo with a one-command boot (for example: `pnpm i && pnpm start`).
409
- - Keep `memory/` backed up somewhere you control (object storage or a second server).
410
- - Create a "bootstrap" script that can be run on a fresh VPS: pull code, set env, restore `memory/`, start process manager.
611
+ If you need custom mapping, pass `escapeHatch.buildSnapshot` in loop config.
411
612
 
412
- Bitlaunch automation (high level):
613
+ ### Bitlaunch automation (high level)
413
614
 
414
- - Create a Bitlaunch account and generate an API key.
415
- - Fund the Bitlaunch account (so the agent can actually provision servers).
416
- - Use the Bitlaunch API to create a VPS from a known template, wait for it to be ready, then run your bootstrap.
615
+ - Use pre-provisioned Bitlaunch account + API key.
616
+ - Ensure backup is written and restore drill is fresh.
617
+ - Create VPS from known template, wait for ready, run deterministic bootstrap.
618
+ - Validate health + memory restore before marking migration complete.
417
619
 
418
620
  Notes:
419
621
 
420
- - The SDK does not ship a Bitlaunch client yet. Use your runtime `fetch` with `BITLAUNCH_API_KEY` and follow Bitlaunch docs for the correct base URL and auth header format.
622
+ - The SDK does not ship a Bitlaunch client yet. Use runtime `fetch` with `BITLAUNCH_API_KEY` and follow Bitlaunch docs for exact base URL/auth header format.
421
623
  - Do not hardcode provider keys in code. Use env vars and keep them out of git.
624
+ - Do not run account signup flows inside an incident path; treat signup as a separate pre-provisioning task.
@@ -1,14 +1,92 @@
1
1
  import { SelfReliance, ResourceSnapshot, SelfRelianceState } from './selfReliance.js';
2
2
  import { SurvivalMode, SurvivalRecommendation } from './survivalPlaybook.js';
3
+ import { EscapeHatchDecision, EscapeHatchPolicy, EscapeHatchSnapshot } from './survivalEscapeHatch.js';
4
+ import { CompoundingMemoryUpgradeResult, CompoundingMemoryRequiredSetResult } from './compoundingMemory.js';
3
5
 
6
+ type AgentLoopEscapeHatchInput = {
7
+ snapshot: ResourceSnapshot;
8
+ survivalState: SelfRelianceState;
9
+ survivalMode: SurvivalMode;
10
+ timestamp: number;
11
+ };
12
+ type AgentLoopEscapeHatchConfig = {
13
+ /** Enable auto escape-hatch evaluation (default true, only in SURVIVE/BLINK). */
14
+ enabled?: boolean;
15
+ policy?: EscapeHatchPolicy;
16
+ resolveEnv?: (name: string) => string | undefined;
17
+ /**
18
+ * Optional mapper from loop context -> escape hatch snapshot.
19
+ * If omitted, a default mapper reads optional fields from snapshot.
20
+ */
21
+ buildSnapshot?: (input: AgentLoopEscapeHatchInput) => EscapeHatchSnapshot | null | undefined;
22
+ /** Called when an escape-hatch decision is produced. */
23
+ onDecision?: (decision: EscapeHatchDecision, input: AgentLoopEscapeHatchInput) => Promise<void> | void;
24
+ };
4
25
  type AgentLoopContext = {
5
26
  snapshot: ResourceSnapshot;
6
27
  survivalState: SelfRelianceState;
7
28
  survivalMode: SurvivalMode;
8
29
  survivalRecommendation: SurvivalRecommendation;
9
30
  survivalFormatted: string;
31
+ /** Deterministic block for planners/loggers: survival + escape-hatch decision (if present). */
32
+ survivalDecisionBlock: string;
33
+ /** Present when escape-hatch evaluation runs (SURVIVE/BLINK by default). */
34
+ escapeHatchDecision?: EscapeHatchDecision;
35
+ escapeHatchFormatted?: string;
36
+ /** Present when memory auto-upgrade is enabled in the loop. */
37
+ memoryUpgrade?: CompoundingMemoryUpgradeResult;
38
+ /** Present when required-set maintenance check runs (default daily). */
39
+ memoryRequiredSet?: CompoundingMemoryRequiredSetResult;
10
40
  timestamp: number;
11
41
  };
42
+ type AgentLoopMemoryConfig = {
43
+ /** Enable one-time memory scaffold upgrade. Default true. */
44
+ enabled?: boolean;
45
+ /** Workspace root for compounding memory files. Default current working directory ("."). */
46
+ root?: string;
47
+ /** Timezone for daily ledger date key. Default UTC. */
48
+ timeZone?: string;
49
+ /** Hook called after memory upgrade check completes. */
50
+ onUpgrade?: (result: CompoundingMemoryUpgradeResult) => Promise<void> | void;
51
+ /** Enable required-set maintenance checks (bounded working state + recall substrate + restore drill). Default true. */
52
+ requiredSetEnabled?: boolean;
53
+ /** Interval for required-set checks. Default 24h. */
54
+ requiredSetIntervalMs?: number;
55
+ /** Run required-set check on first loop cycle. Default true. */
56
+ requiredSetRunOnStart?: boolean;
57
+ /** Include backup+restore drill in required-set check. Default true. */
58
+ requiredSetRunRestoreDrill?: boolean;
59
+ /** Hook called when required-set check runs. */
60
+ onRequiredSet?: (result: CompoundingMemoryRequiredSetResult) => Promise<void> | void;
61
+ };
62
+ type AgentLoopDurabilityConfig = {
63
+ /** Enable durability-proxy writes from runtime loop. Default false. */
64
+ enabled?: boolean;
65
+ /** Durability-proxy base URL (e.g. http://127.0.0.1:8790). */
66
+ baseUrl: string;
67
+ /** Agent id registered in durability-proxy. */
68
+ agentId: string;
69
+ /** Agent token registered in durability-proxy. */
70
+ agentToken: string;
71
+ /** Optional timeout for durability-proxy calls. Default 8000ms. */
72
+ timeoutMs?: number;
73
+ /** Write checkpoint after each required-set run. Default true. */
74
+ checkpointOnRequiredSet?: boolean;
75
+ /** Write restore-drill outcome when required-set includes restore drill. Default true. */
76
+ reportRestoreDrill?: boolean;
77
+ /** Called when durability checkpoint is written. */
78
+ onCheckpointWritten?: (result: {
79
+ checkpointId?: string;
80
+ createdAt?: string;
81
+ checkedAt: string;
82
+ }) => Promise<void> | void;
83
+ /** Called when durability restore drill signal is written. */
84
+ onRestoreDrillReported?: (result: {
85
+ ok: boolean;
86
+ ts?: string;
87
+ checkedAt: string;
88
+ }) => Promise<void> | void;
89
+ };
12
90
  type AgentLoopHooks = {
13
91
  /** Called every cycle after snapshot is fetched and survival state is updated. */
14
92
  onCycle?: (ctx: AgentLoopContext) => Promise<void> | void;
@@ -33,13 +111,17 @@ type AgentLoopConfig = {
33
111
  /** Optional override for time source (tests). */
34
112
  now?: () => number;
35
113
  hooks?: AgentLoopHooks;
114
+ escapeHatch?: AgentLoopEscapeHatchConfig;
115
+ memory?: AgentLoopMemoryConfig;
116
+ durability?: AgentLoopDurabilityConfig;
36
117
  };
37
118
  /**
38
119
  * Create a closed-loop runner that:
39
120
  * 1) fetches a resource snapshot
40
121
  * 2) updates SelfReliance
41
122
  * 3) classifies survival mode + produces a playbook recommendation
42
- * 4) optionally runs reflection + action hooks
123
+ * 4) auto-evaluates escape hatch in SURVIVE/BLINK (guardrail decision block)
124
+ * 5) optionally runs reflection + action hooks
43
125
  *
44
126
  * Non-breaking by design: you choose what to do with the context.
45
127
  */
@@ -49,4 +131,4 @@ declare const createAgentLoop: (config: AgentLoopConfig) => {
49
131
  runOnce: () => Promise<void>;
50
132
  };
51
133
 
52
- export { type AgentLoopConfig, type AgentLoopContext, type AgentLoopHooks, createAgentLoop };
134
+ export { type AgentLoopConfig, type AgentLoopContext, type AgentLoopDurabilityConfig, type AgentLoopEscapeHatchConfig, type AgentLoopEscapeHatchInput, type AgentLoopHooks, type AgentLoopMemoryConfig, createAgentLoop };
package/dist/agentLoop.js CHANGED
@@ -1,8 +1,251 @@
1
1
  import {
2
- createAgentLoop
3
- } from "./chunk-7JT4JPEY.js";
4
- import "./chunk-M7DQTU5R.js";
5
- import "./chunk-JQE72P4C.js";
2
+ runCompoundingMemoryRequiredSet,
3
+ upgradeCompoundingMemorySystem
4
+ } from "./chunk-JJNRDU7F.js";
5
+ import {
6
+ createDurabilityProxyClient
7
+ } from "./chunk-PVCW4MAY.js";
8
+ import {
9
+ evaluateEscapeHatch,
10
+ formatEscapeHatchDecision
11
+ } from "./chunk-EBI7WT76.js";
12
+ import {
13
+ classifySurvivalMode,
14
+ formatSurvivalRecommendation,
15
+ getSurvivalRecommendation
16
+ } from "./chunk-JQE72P4C.js";
17
+ import {
18
+ createSelfRelianceMonitor
19
+ } from "./chunk-M7DQTU5R.js";
20
+
21
+ // src/agentLoop.ts
22
+ var asFiniteNumber = (value) => {
23
+ const n = typeof value === "number" ? value : Number(value);
24
+ return Number.isFinite(n) ? n : void 0;
25
+ };
26
+ var asBoolean = (value) => {
27
+ return typeof value === "boolean" ? value : void 0;
28
+ };
29
+ var defaultEscapeHatchSnapshotBuilder = (input) => {
30
+ const raw = input.snapshot;
31
+ return {
32
+ mode: input.survivalMode,
33
+ estimatedProvisionUsd: asFiniteNumber(raw.estimatedProvisionUsd),
34
+ currentReserveUsd: asFiniteNumber(raw.currentReserveUsd),
35
+ hasPreProvisionedProviderAccount: asBoolean(raw.hasPreProvisionedProviderAccount),
36
+ hasProviderApiKey: asBoolean(raw.hasProviderApiKey),
37
+ backupWrittenAtMs: asFiniteNumber(raw.backupWrittenAtMs),
38
+ restoreDrillPassedAtMs: asFiniteNumber(raw.restoreDrillPassedAtMs),
39
+ nowMs: input.timestamp
40
+ };
41
+ };
42
+ var createAgentLoop = (config) => {
43
+ const now = config.now ?? (() => Date.now());
44
+ const intervalMs = config.intervalMs ?? 6e4;
45
+ const guardAct = config.guardAct ?? true;
46
+ const hooks = config.hooks ?? {};
47
+ const memoryEnabled = config.memory?.enabled ?? true;
48
+ const memoryRoot = config.memory?.root ?? ".";
49
+ const memoryTimeZone = config.memory?.timeZone;
50
+ const requiredSetEnabled = config.memory?.requiredSetEnabled ?? true;
51
+ const requiredSetIntervalMs = Math.max(6e4, config.memory?.requiredSetIntervalMs ?? 24 * 60 * 60 * 1e3);
52
+ const requiredSetRunOnStart = config.memory?.requiredSetRunOnStart ?? true;
53
+ const requiredSetRunRestoreDrill = config.memory?.requiredSetRunRestoreDrill ?? true;
54
+ const durabilityEnabled = config.durability?.enabled ?? false;
55
+ const durabilityClient = durabilityEnabled ? createDurabilityProxyClient({
56
+ baseUrl: config.durability?.baseUrl || "",
57
+ agentId: config.durability?.agentId || "",
58
+ agentToken: config.durability?.agentToken || "",
59
+ timeoutMs: config.durability?.timeoutMs
60
+ }) : null;
61
+ const checkpointOnRequiredSet = config.durability?.checkpointOnRequiredSet ?? true;
62
+ const reportRestoreDrill = config.durability?.reportRestoreDrill ?? true;
63
+ let memoryUpgradePromise = null;
64
+ let memoryUpgradeHookFired = false;
65
+ let lastRequiredSetCheckAt = requiredSetRunOnStart ? 0 : now();
66
+ let lastRequiredSetResult = null;
67
+ let lastDurabilitySyncCheckedAt = null;
68
+ const ensureMemoryUpgrade = async () => {
69
+ if (!memoryEnabled) return null;
70
+ if (!memoryUpgradePromise) {
71
+ memoryUpgradePromise = upgradeCompoundingMemorySystem({
72
+ root: memoryRoot,
73
+ timeZone: memoryTimeZone
74
+ });
75
+ }
76
+ const result = await memoryUpgradePromise;
77
+ if (config.memory?.onUpgrade && !memoryUpgradeHookFired) {
78
+ memoryUpgradeHookFired = true;
79
+ await config.memory.onUpgrade(result);
80
+ }
81
+ return result;
82
+ };
83
+ const maybeRunRequiredSet = async (timestamp) => {
84
+ if (!memoryEnabled || !requiredSetEnabled) return null;
85
+ if (timestamp - lastRequiredSetCheckAt < requiredSetIntervalMs) {
86
+ return lastRequiredSetResult;
87
+ }
88
+ const result = await runCompoundingMemoryRequiredSet({
89
+ root: memoryRoot,
90
+ timeZone: memoryTimeZone,
91
+ runBackupRestoreDrill: requiredSetRunRestoreDrill
92
+ });
93
+ lastRequiredSetCheckAt = timestamp;
94
+ lastRequiredSetResult = result;
95
+ if (config.memory?.onRequiredSet) {
96
+ await config.memory.onRequiredSet(result);
97
+ }
98
+ return result;
99
+ };
100
+ const maybeSyncDurability = async (snapshot, memoryRequiredSet, timestamp) => {
101
+ if (!durabilityClient || !memoryRequiredSet) return;
102
+ if (lastDurabilitySyncCheckedAt === memoryRequiredSet.checkedAt) return;
103
+ const checkpointMeta = {
104
+ checkedAt: memoryRequiredSet.checkedAt,
105
+ requiredSetOk: memoryRequiredSet.ok,
106
+ workingStateBounded: memoryRequiredSet.workingState.bounded,
107
+ recallReady: memoryRequiredSet.recall.ok,
108
+ restoreDrillOk: memoryRequiredSet.restoreDrill?.ok ?? null
109
+ };
110
+ if (checkpointOnRequiredSet) {
111
+ const checkpointRes = await durabilityClient.writeCheckpoint(
112
+ {
113
+ kind: "compounding_memory_required_set",
114
+ checkedAt: memoryRequiredSet.checkedAt,
115
+ result: memoryRequiredSet
116
+ },
117
+ {
118
+ metadata: checkpointMeta
119
+ }
120
+ );
121
+ const createdAt = checkpointRes?.checkpoint?.createdAt || new Date(timestamp).toISOString();
122
+ const createdAtMs = Number(new Date(createdAt));
123
+ if (Number.isFinite(createdAtMs)) {
124
+ snapshot.backupWrittenAtMs = createdAtMs;
125
+ }
126
+ if (config.durability?.onCheckpointWritten) {
127
+ await config.durability.onCheckpointWritten({
128
+ checkpointId: checkpointRes?.checkpoint?.id,
129
+ createdAt: checkpointRes?.checkpoint?.createdAt,
130
+ checkedAt: memoryRequiredSet.checkedAt
131
+ });
132
+ }
133
+ if (reportRestoreDrill && memoryRequiredSet.restoreDrill) {
134
+ const drillRes = await durabilityClient.writeRestoreDrill(memoryRequiredSet.restoreDrill.ok, {
135
+ checkpointId: checkpointRes?.checkpoint?.id,
136
+ details: memoryRequiredSet.restoreDrill.ok ? "required_set_restore_drill_passed" : "required_set_restore_drill_failed"
137
+ });
138
+ if (memoryRequiredSet.restoreDrill.ok) {
139
+ snapshot.restoreDrillPassedAtMs = timestamp;
140
+ }
141
+ if (config.durability?.onRestoreDrillReported) {
142
+ await config.durability.onRestoreDrillReported({
143
+ ok: Boolean(drillRes?.drill?.ok),
144
+ ts: drillRes?.drill?.ts,
145
+ checkedAt: memoryRequiredSet.checkedAt
146
+ });
147
+ }
148
+ }
149
+ }
150
+ lastDurabilitySyncCheckedAt = memoryRequiredSet.checkedAt;
151
+ };
152
+ const runOnce = async () => {
153
+ try {
154
+ const memoryUpgrade = await ensureMemoryUpgrade();
155
+ const snapshot = await config.fetchSnapshot();
156
+ const timestamp = now();
157
+ const memoryRequiredSet = await maybeRunRequiredSet(timestamp);
158
+ await maybeSyncDurability(snapshot, memoryRequiredSet, timestamp);
159
+ const survivalState = config.survival.update({
160
+ ...snapshot,
161
+ updatedAt: snapshot.updatedAt ?? timestamp
162
+ });
163
+ const policy = config.survival.policy;
164
+ const survivalMode = classifySurvivalMode(survivalState, policy);
165
+ const survivalRecommendation = getSurvivalRecommendation(survivalMode);
166
+ const survivalFormatted = formatSurvivalRecommendation(survivalRecommendation);
167
+ let escapeHatchDecision;
168
+ let escapeHatchFormatted;
169
+ const escapeEnabled = config.escapeHatch?.enabled ?? true;
170
+ const shouldEvaluateEscapeHatch = escapeEnabled && (survivalMode === "SURVIVE" || survivalMode === "BLINK");
171
+ if (shouldEvaluateEscapeHatch) {
172
+ const input = {
173
+ snapshot,
174
+ survivalState,
175
+ survivalMode,
176
+ timestamp
177
+ };
178
+ const buildSnapshot = config.escapeHatch?.buildSnapshot ?? defaultEscapeHatchSnapshotBuilder;
179
+ const escapeSnapshot = buildSnapshot(input);
180
+ if (escapeSnapshot) {
181
+ escapeHatchDecision = evaluateEscapeHatch(
182
+ escapeSnapshot,
183
+ config.escapeHatch?.policy,
184
+ { resolveEnv: config.escapeHatch?.resolveEnv }
185
+ );
186
+ escapeHatchFormatted = formatEscapeHatchDecision(escapeHatchDecision);
187
+ if (config.escapeHatch?.onDecision) {
188
+ await config.escapeHatch.onDecision(escapeHatchDecision, input);
189
+ }
190
+ }
191
+ }
192
+ const survivalDecisionBlock = [survivalFormatted, escapeHatchFormatted].filter(Boolean).join("\n\n");
193
+ const ctx = {
194
+ snapshot,
195
+ survivalState,
196
+ survivalMode,
197
+ survivalRecommendation,
198
+ survivalFormatted,
199
+ survivalDecisionBlock,
200
+ escapeHatchDecision,
201
+ escapeHatchFormatted,
202
+ memoryUpgrade: memoryUpgrade ?? void 0,
203
+ memoryRequiredSet: memoryRequiredSet ?? void 0,
204
+ timestamp
205
+ };
206
+ if (hooks.onCycle) await hooks.onCycle(ctx);
207
+ if (hooks.onReflection) await hooks.onReflection(ctx);
208
+ if (hooks.onAct) {
209
+ if (guardAct) {
210
+ const allowed = await config.survival.guard();
211
+ if (allowed) {
212
+ await hooks.onAct(ctx);
213
+ }
214
+ } else {
215
+ await hooks.onAct(ctx);
216
+ }
217
+ }
218
+ } catch (err) {
219
+ const e = err instanceof Error ? err : new Error("agent_loop_failed");
220
+ if (hooks.onError) {
221
+ await hooks.onError(e);
222
+ }
223
+ }
224
+ };
225
+ const monitor = createSelfRelianceMonitor(config.survival, config.fetchSnapshot, {
226
+ intervalMs,
227
+ onUpdate: async (snapshot) => {
228
+ void snapshot;
229
+ }
230
+ });
231
+ let timer = null;
232
+ let stopped = false;
233
+ const start = async () => {
234
+ if (timer) return;
235
+ await runOnce();
236
+ if (stopped) return;
237
+ timer = setInterval(runOnce, intervalMs);
238
+ };
239
+ const stop = () => {
240
+ stopped = true;
241
+ if (timer) {
242
+ clearInterval(timer);
243
+ timer = null;
244
+ }
245
+ monitor.stop();
246
+ };
247
+ return { start, stop, runOnce };
248
+ };
6
249
  export {
7
250
  createAgentLoop
8
251
  };