Skip to content

Commit 818a7fc

Browse files
simonovic86claude
andauthored
feat(sdk): complete Task 8 — capability mocks, local simulator, checkpoint inspector (#5)
Implement the three remaining Task 8 deliverables: - Capability mocks (sdk/igor/mock/): pluggable MockBackend for native testing without WASM, deterministic clock/rand, log capture. Split hostcall wrappers into build-tagged files (WASM vs native). - Local simulator (internal/simulator/): single-process WASM runner with deterministic hostcalls, per-tick replay verification, checkpoint round-trip verification. CLI flags: --simulate, --ticks, --verify, --deterministic, --seed. - Checkpoint inspector (internal/inspector/): parse and display checkpoint files with hex dump, WASM hash verification. CLI flags: --inspect-checkpoint, --inspect-wasm. Update docs: ROADMAP.md, IMPLEMENTATION_STATUS.md, README.md to reflect Task 8 completion. Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 76b368e commit 818a7fc

14 files changed

Lines changed: 1611 additions & 18 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ Igor is a decentralized execution runtime for autonomous software agents. It pro
99
## About This Repository
1010

1111
**What:** Experimental infrastructure for autonomous agent survival
12-
**Status:** Research-stage — Phase 3 (Autonomy) in progress. Capability membrane MVP complete, replay verification operational, agent SDK landed.
12+
**Status:** Research-stage — Phase 3 (Autonomy) in progress. Capability membrane, replay verification, agent SDK, capability mocks, local simulator, and checkpoint inspector complete. Multi-node mobility testing next.
1313
**Purpose:** Demonstrate that software can checkpoint, migrate, and self-fund execution
1414

1515
**Read first:**

cmd/igord/main.go

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@ import (
1313

1414
"github.com/simonovic86/igor/internal/agent"
1515
"github.com/simonovic86/igor/internal/config"
16+
"github.com/simonovic86/igor/internal/inspector"
1617
"github.com/simonovic86/igor/internal/logging"
1718
"github.com/simonovic86/igor/internal/migration"
1819
"github.com/simonovic86/igor/internal/p2p"
1920
"github.com/simonovic86/igor/internal/replay"
2021
"github.com/simonovic86/igor/internal/runtime"
22+
"github.com/simonovic86/igor/internal/simulator"
2123
"github.com/simonovic86/igor/internal/storage"
2224
"github.com/simonovic86/igor/pkg/budget"
2325
)
@@ -34,8 +36,27 @@ func main() {
3436
verifyInterval := flag.Int("verify-interval", 0, "Ticks between self-verification passes (0 = use config default)")
3537
replayMode := flag.String("replay-mode", "", "Replay verification mode: off, periodic, on-migrate, full (default: full)")
3638
replayCostLog := flag.Bool("replay-cost-log", false, "Log replay compute duration for economic observability")
39+
inspectCheckpoint := flag.String("inspect-checkpoint", "", "Path to checkpoint file to inspect")
40+
inspectWASM := flag.String("inspect-wasm", "", "Optional WASM binary to verify against checkpoint hash")
41+
simulate := flag.Bool("simulate", false, "Run agent in local simulator mode (no P2P)")
42+
simTicks := flag.Int("ticks", 0, "Number of ticks to simulate (0 = until budget exhausted)")
43+
simVerify := flag.Bool("verify", false, "Per-tick replay verification during simulation")
44+
simDeterministic := flag.Bool("deterministic", false, "Use fixed clock and seeded rand for reproducible simulation")
45+
simSeed := flag.Uint64("seed", 0, "Random seed for deterministic simulation")
3746
flag.Parse()
3847

48+
// Checkpoint inspector — standalone, no config/P2P/engine needed
49+
if *inspectCheckpoint != "" {
50+
runInspector(*inspectCheckpoint, *inspectWASM)
51+
return
52+
}
53+
54+
// Local simulator — standalone, no config/P2P needed
55+
if *simulate && *runAgent != "" {
56+
runSimulator(*runAgent, *manifestPath, *budgetFlag, *simTicks, *simVerify, *simDeterministic, *simSeed)
57+
return
58+
}
59+
3960
// Create context
4061
ctx, cancel := context.WithCancel(context.Background())
4162
defer cancel()
@@ -354,3 +375,44 @@ func verifyNextTick(
354375
}
355376
return lastVerified
356377
}
378+
379+
// runInspector parses and displays a checkpoint file.
380+
func runInspector(checkpointPath, wasmPath string) {
381+
result, err := inspector.InspectFile(checkpointPath)
382+
if err != nil {
383+
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
384+
os.Exit(1)
385+
}
386+
if wasmPath != "" {
387+
if verr := result.VerifyWASM(wasmPath); verr != nil {
388+
fmt.Fprintf(os.Stderr, "Warning: %v\n", verr)
389+
}
390+
}
391+
result.Print(os.Stdout)
392+
}
393+
394+
// runSimulator executes an agent in local simulator mode (no P2P).
395+
func runSimulator(wasmPath, manifestPath string, budgetVal float64, ticks int, verify, deterministic bool, seed uint64) {
396+
ctx, cancel := context.WithCancel(context.Background())
397+
defer cancel()
398+
399+
logger := logging.NewLogger()
400+
cfg := simulator.Config{
401+
WASMPath: wasmPath,
402+
ManifestPath: manifestPath,
403+
Budget: budgetVal,
404+
Ticks: ticks,
405+
Verify: verify,
406+
Deterministic: deterministic,
407+
RandSeed: seed,
408+
}
409+
result, err := simulator.Run(ctx, cfg, logger)
410+
if err != nil {
411+
logging.Error(logger, "Simulation failed", "error", err)
412+
os.Exit(1)
413+
}
414+
simulator.PrintSummary(result, logger)
415+
if len(result.Errors) > 0 {
416+
os.Exit(1)
417+
}
418+
}

docs/IMPLEMENTATION_STATUS.md

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ Last updated: 2026-03-03
6868
| Observation recording (CM-4) | Implemented | `internal/eventlog/eventlog.go` |
6969
| Manifest in migration package | Implemented | `pkg/protocol/messages.go` `ManifestData` |
7070
| Pre-migration capability check (CE-5) | Implemented | `internal/migration/service.go` `handleIncomingMigration` |
71-
| KV storage hostcalls | Not implemented | Roadmap Task 8+ |
71+
| KV storage hostcalls | Not implemented | Roadmap future task |
7272
| Network hostcalls | Not implemented | Roadmap Phase 3+ |
7373

7474
## Identity and Authority
@@ -95,3 +95,20 @@ Last updated: 2026-03-03
9595
| /tmp WASM write on target | Removed | Replaced by `agent.LoadAgentFromBytes` (no temp file) |
9696
| Replay data in migration package | Implemented | `internal/migration/replay.go` |
9797
| Staleness guard for replay data | Implemented | `internal/migration/replay.go` |
98+
99+
## Agent SDK & Developer Experience
100+
101+
| Aspect | Status | Code Reference |
102+
|--------|--------|----------------|
103+
| Agent SDK (Agent interface, hostcall wrappers) | Implemented | `sdk/igor/lifecycle.go`, `sdk/igor/hostcalls_wrappers_wasm.go` |
104+
| Build-tag split (WASM vs native) | Implemented | `sdk/igor/hostcalls_wrappers_wasm.go`, `sdk/igor/hostcalls_wrappers_stub.go` |
105+
| Capability mocks (MockBackend, Runtime) | Implemented | `sdk/igor/mock_backend.go`, `sdk/igor/mock/mock.go` |
106+
| Deterministic mock (fixed clock, seeded rand) | Implemented | `sdk/igor/mock/mock.go` `NewDeterministic` |
107+
| Local simulator (single-process WASM runner) | Implemented | `internal/simulator/simulator.go` |
108+
| Simulator deterministic hostcalls | Implemented | `internal/simulator/hostcalls.go` |
109+
| Simulator replay verification | Implemented | `internal/simulator/simulator.go` `verifyTick` |
110+
| Checkpoint inspector | Implemented | `internal/inspector/inspector.go` |
111+
| WASM hash verification in inspector | Implemented | `internal/inspector/inspector.go` `VerifyWASM` |
112+
| Agent template (Survivor example) | Implemented | `agents/example/` |
113+
| `--simulate` CLI flag | Implemented | `cmd/igord/main.go` |
114+
| `--inspect-checkpoint` CLI flag | Implemented | `cmd/igord/main.go` |

docs/governance/ROADMAP.md

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ Igor v0 has completed **Phase 2 (Survival)** and begun **Phase 3 (Autonomy)**.
1313
-**Task 4** - Migration protocol over libp2p
1414
-**Task 5** - Rent metering & runtime accounting
1515
-**Task 6** - Capability membrane MVP (clock/rand/log hostcalls, manifest, event log, deny-by-default)
16+
-**Task 7** - Replay engine (single-tick verification, configurable modes, sliding window)
17+
-**Task 8** - Agent SDK & developer experience (SDK, mocks, simulator, inspector, template)
1618

1719
**Phase 2 result:** Agents can survive, migrate, and pay for execution.
18-
**Phase 3 progress:** Agents interact through runtime-mediated hostcalls with observation recording.
20+
**Phase 3 progress:** Capability membrane, replay verification, and developer tooling complete. Multi-node mobility testing next.
1921

2022
---
2123

@@ -60,18 +62,19 @@ Igor v0 has completed **Phase 2 (Survival)** and begun **Phase 3 (Autonomy)**.
6062

6163
**Specs:** [REPLAY_ENGINE.md](../runtime/REPLAY_ENGINE.md)
6264

63-
### Task 8: Agent SDK & Developer Experience
65+
### Task 8: Agent SDK & Developer Experience
6466

65-
**Objective:** Make agent authoring accessible with SDK and tooling.
67+
**Status:** Complete. Agent SDK, developer tooling, and testing infrastructure fully implemented.
6668

67-
**Scope:**
68-
- Agent SDK (Go/TinyGo first) wrapping hostcall interface
69-
- Local simulator (single-process deterministic replay)
70-
- Capability mocks for testing
71-
- Agent template / starter project
72-
- Checkpoint inspector
69+
**Delivered:**
70+
- Agent SDK (`sdk/igor/`): `Agent` interface (Init, Tick, Marshal, Unmarshal), hostcall wrappers (ClockNow, RandBytes, Log, Logf), build-tag split for WASM and native builds
71+
- Capability mocks (`sdk/igor/mock/`): pluggable `MockBackend` for native testing without WASM, deterministic clock/rand, log capture
72+
- Local simulator (`internal/simulator/`): single-process WASM runner with deterministic hostcalls, per-tick replay verification, checkpoint round-trip verification
73+
- Checkpoint inspector (`internal/inspector/`): parse and display checkpoint files, WASM hash verification
74+
- Agent template (`agents/example/`): Survivor agent demonstrating SDK usage, hostcall patterns, and state serialization
75+
- CLI flags: `--simulate`, `--ticks`, `--verify`, `--deterministic`, `--seed`, `--inspect-checkpoint`, `--inspect-wasm`
7376

74-
**Outcome:** Developers can build agents without manually managing WASM exports, memory, and hostcall signatures.
77+
**Outcome:** Developers can build agents without manually managing WASM exports, memory, and hostcall signatures. Agents can be tested natively with mocks or as compiled WASM in the simulator.
7578

7679
### Task 9: Multi-Node Mobility Testing
7780

@@ -323,7 +326,7 @@ Igor development follows "done when it's done" philosophy:
323326
- Correctness over features
324327
- Learning over shipping
325328

326-
Phase 3 began after Phase 2 validation. Task 6 is complete.
329+
Phase 3 began after Phase 2 validation. Tasks 6, 7, and 8 are complete.
327330

328331
---
329332

@@ -434,9 +437,8 @@ Phase 2 is **validated** when:
434437

435438
## Next Immediate Steps
436439

437-
With Phase 3 Tasks 6 (Capability Membrane) and 7 (Replay Engine) complete:
440+
With Phase 3 Tasks 6 (Capability Membrane), 7 (Replay Engine), and 8 (Agent SDK) complete:
438441

439-
1. **Task 8: Agent SDK** - Hostcall wrappers, lifecycle helpers, developer experience
440-
2. **Task 9: Multi-Node Mobility Testing** - Chain migration, capability preservation
441-
3. **Hardening** - Bug fixes, test coverage, documentation accuracy
442-
4. **Extended testing** - Run agents with hostcalls for hours/days
442+
1. **Task 9: Multi-Node Mobility Testing** - Chain migration A → B → C → A, capability preservation, budget conservation
443+
2. **Hardening** - Bug fixes, test coverage, documentation accuracy
444+
3. **Extended testing** - Run agents with hostcalls for hours/days under load

internal/inspector/inspector.go

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Package inspector provides checkpoint file parsing and display.
2+
package inspector
3+
4+
import (
5+
"crypto/sha256"
6+
"encoding/hex"
7+
"fmt"
8+
"io"
9+
"os"
10+
11+
"github.com/simonovic86/igor/internal/agent"
12+
"github.com/simonovic86/igor/pkg/budget"
13+
)
14+
15+
// Result holds parsed checkpoint information.
16+
type Result struct {
17+
Version byte
18+
Budget int64
19+
BudgetFormatted string
20+
PricePerSecond int64
21+
PriceFormatted string
22+
TickNumber uint64
23+
WASMHash [32]byte
24+
WASMHashHex string
25+
StateSize int
26+
State []byte
27+
TotalSize int
28+
WASMVerified *bool
29+
WASMPath string
30+
}
31+
32+
// InspectFile parses a checkpoint file and returns structured results.
33+
func InspectFile(path string) (*Result, error) {
34+
data, err := os.ReadFile(path)
35+
if err != nil {
36+
return nil, fmt.Errorf("read checkpoint: %w", err)
37+
}
38+
return Inspect(data)
39+
}
40+
41+
// Inspect parses raw checkpoint bytes.
42+
func Inspect(data []byte) (*Result, error) {
43+
budgetVal, price, tick, wasmHash, state, err := agent.ParseCheckpointHeader(data)
44+
if err != nil {
45+
return nil, fmt.Errorf("parse checkpoint: %w", err)
46+
}
47+
48+
return &Result{
49+
Version: data[0],
50+
Budget: budgetVal,
51+
BudgetFormatted: budget.Format(budgetVal),
52+
PricePerSecond: price,
53+
PriceFormatted: budget.Format(price),
54+
TickNumber: tick,
55+
WASMHash: wasmHash,
56+
WASMHashHex: hex.EncodeToString(wasmHash[:]),
57+
StateSize: len(state),
58+
State: state,
59+
TotalSize: len(data),
60+
}, nil
61+
}
62+
63+
// VerifyWASM checks if a WASM binary matches the checkpoint's stored hash.
64+
func (r *Result) VerifyWASM(wasmPath string) error {
65+
wasmBytes, err := os.ReadFile(wasmPath)
66+
if err != nil {
67+
return fmt.Errorf("read WASM: %w", err)
68+
}
69+
hash := sha256.Sum256(wasmBytes)
70+
verified := hash == r.WASMHash
71+
r.WASMVerified = &verified
72+
r.WASMPath = wasmPath
73+
if !verified {
74+
return fmt.Errorf("WASM hash mismatch: checkpoint=%s binary=%s",
75+
r.WASMHashHex, hex.EncodeToString(hash[:]))
76+
}
77+
return nil
78+
}
79+
80+
// Print writes a human-readable inspection report.
81+
func (r *Result) Print(w io.Writer) {
82+
fmt.Fprintf(w, "Checkpoint Inspector\n")
83+
fmt.Fprintf(w, "====================\n\n")
84+
fmt.Fprintf(w, "Version: %d (0x%02x)\n", r.Version, r.Version)
85+
fmt.Fprintf(w, "Budget: %s (%d microcents)\n", r.BudgetFormatted, r.Budget)
86+
fmt.Fprintf(w, "Price/Second: %s (%d microcents)\n", r.PriceFormatted, r.PricePerSecond)
87+
fmt.Fprintf(w, "Tick Number: %d\n", r.TickNumber)
88+
fmt.Fprintf(w, "WASM Hash: %s\n", r.WASMHashHex)
89+
fmt.Fprintf(w, "Total Size: %d bytes\n", r.TotalSize)
90+
fmt.Fprintf(w, "Header Size: 57 bytes\n")
91+
fmt.Fprintf(w, "State Size: %d bytes\n", r.StateSize)
92+
93+
if r.WASMVerified != nil {
94+
if *r.WASMVerified {
95+
fmt.Fprintf(w, "WASM Verified: YES (matches %s)\n", r.WASMPath)
96+
} else {
97+
fmt.Fprintf(w, "WASM Verified: NO (mismatch with %s)\n", r.WASMPath)
98+
}
99+
}
100+
101+
if r.StateSize > 0 {
102+
fmt.Fprintf(w, "\nState Hex Dump:\n")
103+
limit := r.StateSize
104+
if limit > 256 {
105+
limit = 256
106+
}
107+
printHexDump(w, r.State[:limit])
108+
if r.StateSize > 256 {
109+
fmt.Fprintf(w, " ... (%d more bytes)\n", r.StateSize-256)
110+
}
111+
}
112+
}
113+
114+
// printHexDump writes a canonical hex dump (16 bytes per line).
115+
func printHexDump(w io.Writer, data []byte) {
116+
for i := 0; i < len(data); i += 16 {
117+
end := i + 16
118+
if end > len(data) {
119+
end = len(data)
120+
}
121+
row := data[i:end]
122+
123+
// Offset
124+
fmt.Fprintf(w, " %08x ", i)
125+
126+
// Hex bytes
127+
for j, b := range row {
128+
fmt.Fprintf(w, "%02x ", b)
129+
if j == 7 {
130+
fmt.Fprint(w, " ")
131+
}
132+
}
133+
// Pad remaining
134+
for j := len(row); j < 16; j++ {
135+
fmt.Fprint(w, " ")
136+
if j == 7 {
137+
fmt.Fprint(w, " ")
138+
}
139+
}
140+
141+
// ASCII
142+
fmt.Fprint(w, " |")
143+
for _, b := range row {
144+
if b >= 0x20 && b <= 0x7e {
145+
fmt.Fprintf(w, "%c", b)
146+
} else {
147+
fmt.Fprint(w, ".")
148+
}
149+
}
150+
fmt.Fprintln(w, "|")
151+
}
152+
}

0 commit comments

Comments
 (0)