diff --git a/README.md b/README.md index 2782582..be476ae 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ Igor is a decentralized execution runtime for autonomous software agents. It pro ## About This Repository **What:** Experimental infrastructure for autonomous agent survival -**Status:** Research-stage — Phase 3 (Autonomy) complete. Capability membrane, replay verification, agent SDK, and multi-node mobility testing validated. Phase 4 (Economics) next. +**Status:** Research-stage — Phases 2–4 complete, Phase 5 (Hardening) complete. Agents run, checkpoint, migrate, resume, meter cost, enforce capability membranes, replay-verify, support multi-node chain migration, sign checkpoint lineage, recover from migration failures, and enforce lease-based authority. Task 15 (Permissionless Hardening) next. **Purpose:** Demonstrate that software can checkpoint, migrate, and self-fund execution **Read first:** @@ -133,16 +133,22 @@ Nodes operate autonomously without coordination. ### Checkpoints -Atomic snapshots preserving agent state, budget, and binary identity (57-byte header): +Atomic snapshots preserving agent state, budget, and binary identity (209-byte header): ``` Offset Size Field -0 1 Version (0x02) +0 1 Version (0x04) 1 8 Budget (int64 microcents, little-endian) 9 8 PricePerSecond (int64 microcents, little-endian) 17 8 TickNumber (uint64, little-endian) 25 32 WASMHash (SHA-256 of agent binary) -57 N Agent State (application-defined) +57 8 MajorVersion (uint64, little-endian) +65 8 LeaseGeneration (uint64, little-endian) +73 8 LeaseExpiry (int64, little-endian) +81 32 PrevHash (SHA-256 of previous checkpoint) +113 32 AgentPubKey (Ed25519 public key) +145 64 Signature (Ed25519, covers bytes 0–144) +209 N Agent State (application-defined) ``` Budget unit: 1 currency unit = 1,000,000 microcents. Integer arithmetic avoids float precision drift. diff --git a/agents/example/main.go b/agents/example/main.go index dc89309..3d2e556 100644 --- a/agents/example/main.go +++ b/agents/example/main.go @@ -59,6 +59,9 @@ func (s *Survivor) Unmarshal(data []byte) { s.BirthNano = d.Int64() s.LastNano = d.Int64() s.Luck = d.Uint32() + if err := d.Err(); err != nil { + panic("unmarshal checkpoint: " + err.Error()) + } } func init() { igor.Run(&Survivor{}) } diff --git a/agents/reconciliation/main.go b/agents/reconciliation/main.go index 475c682..a60a4de 100644 --- a/agents/reconciliation/main.go +++ b/agents/reconciliation/main.go @@ -203,6 +203,9 @@ func (r *Reconciler) Unmarshal(data []byte) { r.DetectNano = d.Int64() r.FinalizeNano = d.Int64() r.CompleteNano = d.Int64() + if err := d.Err(); err != nil { + panic("unmarshal checkpoint: " + err.Error()) + } } // shortHex returns the first 8 hex chars of a byte slice (4 bytes). diff --git a/docs/runtime/BUDGET_MODEL.md b/docs/runtime/BUDGET_MODEL.md index c92254b..fd8e759 100644 --- a/docs/runtime/BUDGET_MODEL.md +++ b/docs/runtime/BUDGET_MODEL.md @@ -103,13 +103,19 @@ Tick 2: budget 9.999999 → cost 0.000001 → budget 9.999998 Budget is persisted in checkpoint: ``` -Checkpoint Format: -[0] version (0x02) -[1-8] budget (int64 microcents) -[9-16] pricePerSecond (int64 microcents) -[17-24] tickNumber (uint64) -[25-56] wasmHash (SHA-256) -[57+] agent state +Checkpoint Format (v0x04, 209-byte header): +[0] version (0x04) +[1-8] budget (int64 microcents) +[9-16] pricePerSecond (int64 microcents) +[17-24] tickNumber (uint64) +[25-56] wasmHash (SHA-256) +[57-64] majorVersion (uint64) +[65-72] leaseGeneration (uint64) +[73-80] leaseExpiry (int64) +[81-112] prevHash (SHA-256 of previous checkpoint) +[113-144] agentPubKey (Ed25519 public key) +[145-208] signature (Ed25519) +[209+] agent state ``` ### 4. Restoration @@ -210,11 +216,11 @@ The checkpoint preserves the exhausted state, allowing inspection or potential r Checkpoints include budget as metadata: ``` -Binary Layout (57-byte header): -┌─────────┬──────────┬────────────────┬────────────┬──────────┬─────────────┐ -│ Version │ Budget │ PricePerSecond │ TickNumber │ WASMHash │ Agent State │ -│ (1 byte)│ (8 bytes)│ (8 bytes) │ (8 bytes) │(32 bytes)│ (N bytes) │ -└─────────┴──────────┴────────────────┴────────────┴──────────┴─────────────┘ +Binary Layout (209-byte header, v0x04): +┌─────────┬──────────┬────────────────┬────────────┬──────────┬──────────────┬─────────────────┬─────────────┬──────────┬─────────────┬───────────┬─────────────┐ +│ Version │ Budget │ PricePerSecond │ TickNumber │ WASMHash │ MajorVersion │ LeaseGeneration │ LeaseExpiry │ PrevHash │ AgentPubKey │ Signature │ Agent State │ +│ (1 byte)│ (8 bytes)│ (8 bytes) │ (8 bytes) │(32 bytes)│ (8 bytes) │ (8 bytes) │ (8 bytes) │(32 bytes)│ (32 bytes) │ (64 bytes)│ (N bytes) │ +└─────────┴──────────┴────────────────┴────────────┴──────────┴──────────────┴─────────────────┴─────────────┴──────────┴─────────────┴───────────┴─────────────┘ Encoding: Little-endian integers (int64 microcents for budget/price, uint64 for tick) ``` diff --git a/docs/runtime/MIGRATION_PROTOCOL.md b/docs/runtime/MIGRATION_PROTOCOL.md index 0c807ff..0bd43a0 100644 --- a/docs/runtime/MIGRATION_PROTOCOL.md +++ b/docs/runtime/MIGRATION_PROTOCOL.md @@ -23,7 +23,7 @@ type AgentPackage struct { AgentID string // Unique agent identifier WASMBinary []byte // Compiled WASM module WASMHash []byte // SHA-256 of WASMBinary for integrity verification - Checkpoint []byte // Serialized state + budget metadata (57-byte header) + Checkpoint []byte // Serialized state + budget metadata (209-byte header, v0x04) ManifestData []byte // Capability manifest JSON Budget int64 // Remaining budget in microcents PricePerSecond int64 // Cost per second in microcents @@ -373,9 +373,9 @@ Typical migration time (local network): ### Checkpoint Size -- Header: 57 bytes (version + budget + price + tick + wasmHash) +- Header: 209 bytes (version + budget + price + tick + wasmHash + majorVersion + leaseGeneration + leaseExpiry + prevHash + agentPubKey + signature) - State: Agent-dependent -- Example agent: 65 bytes total (57 header + 8 state) +- Example agent: 237 bytes total (209 header + 28 state) ### WASM Transfer Size diff --git a/internal/runner/runner.go b/internal/runner/runner.go index 31f3134..04c778e 100644 --- a/internal/runner/runner.go +++ b/internal/runner/runner.go @@ -79,7 +79,7 @@ func HandleLeaseExpiry(ctx context.Context, instance *agent.Instance, leaseErr e func HandleTickFailure(ctx context.Context, instance *agent.Instance, tickErr error, logger *slog.Logger) error { if instance.Budget <= 0 { logger.Info("Agent budget exhausted, terminating", - "agent_id", "local-agent", + "agent_id", instance.AgentID, "reason", "budget_exhausted", ) } else {