Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ make agent # Build example WASM agent → agents/research/example/a
make agent-heartbeat # Build heartbeat WASM agent → agents/heartbeat/agent.wasm
make agent-pricewatcher # Build price watcher WASM agent → agents/pricewatcher/agent.wasm
make agent-sentinel # Build treasury sentinel WASM agent → agents/sentinel/agent.wasm
make agent-x402buyer # Build x402 buyer WASM agent → agents/x402buyer/agent.wasm
make test # Run tests: go test -v ./...
make lint # golangci-lint (5m timeout)
make vet # go vet
Expand All @@ -29,6 +30,7 @@ make demo # Build + run bridge reconciliation demo
make demo-portable # Build + run portable agent demo (run → stop → copy → resume → verify)
make demo-pricewatcher # Build + run price watcher demo (fetch prices → stop → resume → verify)
make demo-sentinel # Build + run treasury sentinel demo (effect lifecycle → crash → reconcile)
make demo-x402 # Build + run x402 payment demo (pay for premium data → crash → reconcile)
make clean # Remove bin/, checkpoints/, agent.wasm
```

Expand Down Expand Up @@ -66,7 +68,7 @@ Atomic writes via temp file → fsync → rename. Every checkpoint is also archi
- `cmd/igord/` — CLI entry point, subcommand dispatch (`run`, `resume`, `verify`, `inspect`), tick loop
- `internal/agent/` — Agent lifecycle: load WASM, init, tick, checkpoint, resume, budget deduction
- `internal/runtime/` — wazero sandbox: 64MB memory limit, WASI with fs/net disabled
- `internal/hostcall/` — `igor` host module: clock, rand, log, wallet hostcall implementations
- `internal/hostcall/` — `igor` host module: clock, rand, log, wallet, http, x402 payment hostcall implementations
- `internal/inspector/` — Checkpoint inspection and lineage chain verification (`chain.go`: `VerifyChain`)
- `internal/storage/` — `CheckpointProvider` interface + filesystem impl + checkpoint history archival
- `internal/eventlog/` — Per-tick observation event log for deterministic replay
Expand All @@ -81,11 +83,12 @@ Atomic writes via temp file → fsync → rename. Every checkpoint is also archi
- `pkg/manifest/` — Capability manifest parsing and validation
- `pkg/protocol/` — Message types: `AgentPackage`, `AgentTransfer`, `AgentStarted`
- `pkg/receipt/` — Payment receipt data structure, Ed25519 signing, binary serialization
- `sdk/igor/` — Agent SDK: hostcall wrappers (ClockNow, RandBytes, Log, WalletBalance), lifecycle plumbing (Agent interface), Encoder/Decoder with Raw/FixedBytes/ReadInto for checkpoint serialization, EffectLog for intent tracking across checkpoint/resume
- `sdk/igor/` — Agent SDK: hostcall wrappers (ClockNow, RandBytes, Log, WalletBalance, WalletPay, HTTPRequest), lifecycle plumbing (Agent interface), Encoder/Decoder with Raw/FixedBytes/ReadInto for checkpoint serialization, EffectLog for intent tracking across checkpoint/resume
- `sdk/igor/effects.go` — Effect lifecycle primitives: EffectLog, IntentState (Recorded→InFlight→Confirmed/Unresolved→Compensated), the resume rule (InFlight→Unresolved on Unmarshal)
- `agents/heartbeat/` — Demo agent: logs heartbeat with tick count and age, milestones every 10 ticks
- `agents/pricewatcher/` — Demo agent: fetches BTC/ETH prices from CoinGecko, tracks high/low/latest across checkpoint/resume
- `agents/sentinel/` — Treasury sentinel: monitors simulated treasury balance, triggers refills with effect-safe intent tracking, demonstrates crash recovery and reconciliation
- `agents/x402buyer/` — x402 payment demo: encounters HTTP 402 paywall, pays from budget via wallet_pay hostcall, receives premium data, crash-safe payment reconciliation
- `agents/research/example/` — Original demo agent (Survivor) from research phases
- `agents/research/reconciliation/` — Bridge reconciliation demo agent (research phase)
- `scripts/demo-portable.sh` — End-to-end portable agent demo
Expand Down
19 changes: 18 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.PHONY: help bootstrap build build-lab clean test lint vet fmt fmt-check tidy agent agent-heartbeat agent-reconciliation agent-pricewatcher agent-sentinel run-agent demo demo-portable demo-pricewatcher demo-sentinel gh-check gh-metadata gh-release
.PHONY: help bootstrap build build-lab clean test lint vet fmt fmt-check tidy agent agent-heartbeat agent-reconciliation agent-pricewatcher agent-sentinel agent-x402buyer run-agent demo demo-portable demo-pricewatcher demo-sentinel demo-x402 gh-check gh-metadata gh-release

.DEFAULT_GOAL := help

Expand All @@ -10,6 +10,7 @@ HEARTBEAT_AGENT_DIR := agents/heartbeat
RECONCILIATION_AGENT_DIR := agents/research/reconciliation
PRICEWATCHER_AGENT_DIR := agents/pricewatcher
SENTINEL_AGENT_DIR := agents/sentinel
X402BUYER_AGENT_DIR := agents/x402buyer

# Go commands
GOCMD := go
Expand Down Expand Up @@ -58,6 +59,7 @@ clean: ## Remove build artifacts
rm -f agents/research/reconciliation/agent.wasm
rm -f agents/pricewatcher/agent.wasm
rm -f agents/sentinel/agent.wasm
rm -f agents/x402buyer/agent.wasm
@echo "Clean complete"

test: ## Run tests (with race detector)
Expand Down Expand Up @@ -136,6 +138,13 @@ agent-sentinel: ## Build treasury sentinel demo agent WASM
cd $(SENTINEL_AGENT_DIR) && $(MAKE) build
@echo "Agent built: $(SENTINEL_AGENT_DIR)/agent.wasm"

agent-x402buyer: ## Build x402 buyer demo agent WASM
@echo "Building x402buyer agent..."
@which tinygo > /dev/null || \
(echo "tinygo not found. See docs/governance/DEVELOPMENT.md for installation" && exit 1)
cd $(X402BUYER_AGENT_DIR) && $(MAKE) build
@echo "Agent built: $(X402BUYER_AGENT_DIR)/agent.wasm"

demo: build agent-reconciliation ## Build and run reconciliation demo
@echo "Building demo runner..."
@mkdir -p $(BINARY_DIR)
Expand All @@ -158,6 +167,14 @@ demo-sentinel: build agent-sentinel ## Run the treasury sentinel demo (effect li
@chmod +x scripts/demo-sentinel.sh
@./scripts/demo-sentinel.sh

demo-x402: build agent-x402buyer ## Run the x402 payment demo (pay for premium data, crash recovery)
@echo "Building paywall server..."
@mkdir -p $(BINARY_DIR)
$(GOBUILD) -o $(BINARY_DIR)/paywall ./agents/x402buyer/cmd/paywall
@echo "Running x402 Payment Demo..."
@chmod +x scripts/demo-x402.sh
@./scripts/demo-x402.sh

check: fmt-check vet lint test ## Run all checks (formatting, vet, lint, tests)
@echo "All checks passed"

Expand Down
7 changes: 7 additions & 0 deletions agents/x402buyer/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.PHONY: build clean

build:
tinygo build -target=wasi -no-debug -o agent.wasm .

clean:
rm -f agent.wasm
25 changes: 25 additions & 0 deletions agents/x402buyer/agent.manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"capabilities": {
"clock": { "version": 1 },
"rand": { "version": 1 },
"log": { "version": 1 },
"http": {
"version": 1,
"options": {
"allowed_hosts": ["localhost"],
"timeout_ms": 5000,
"max_response_bytes": 8192
}
},
"x402": {
"version": 1,
"options": {
"allowed_recipients": ["paywall-provider"],
"max_payment_microcents": 1000000
}
}
},
"resource_limits": {
"max_memory_bytes": 67108864
}
}
114 changes: 114 additions & 0 deletions agents/x402buyer/cmd/paywall/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// SPDX-License-Identifier: Apache-2.0

// Mock x402 paywall server for the x402buyer demo.
//
// Endpoints:
//
// GET /api/premium-data
// - No X-Payment header → 402 with payment terms
// - X-Payment header present → 200 with premium data
//
// Payment terms are binary-encoded in the response body:
//
// [amount:8 LE][recipient_len:4 LE][recipient][memo_len:4 LE][memo]
package main

import (
"encoding/binary"
"fmt"
"log"
"math/rand/v2"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)

const (
paymentAmount int64 = 100_000 // 0.10 currency units (100,000 microcents)
paymentRecipient string = "paywall-provider"
paymentMemo string = "premium-data-access"
)

func main() {
addr := ":8402"
if envAddr := os.Getenv("PAYWALL_ADDR"); envAddr != "" {
addr = envAddr
}

mux := http.NewServeMux()
mux.HandleFunc("/api/premium-data", handlePremiumData)
mux.HandleFunc("/health", func(w http.ResponseWriter, _ *http.Request) {
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "ok")
})

srv := &http.Server{
Addr: addr,
Handler: mux,
ReadHeaderTimeout: 5 * time.Second,
}

// Graceful shutdown on SIGINT/SIGTERM.
done := make(chan os.Signal, 1)
signal.Notify(done, syscall.SIGINT, syscall.SIGTERM)

go func() {
log.Printf("[paywall] Listening on %s", addr)
if err := srv.ListenAndServe(); err != http.ErrServerClosed {
log.Fatalf("[paywall] Server error: %v", err)
}
}()

<-done
log.Println("[paywall] Shutting down...")
srv.Close()
}

func handlePremiumData(w http.ResponseWriter, r *http.Request) {
payment := r.Header.Get("X-Payment")

if payment == "" {
// No payment — return 402 with payment terms.
log.Printf("[paywall] 402 → %s (no payment header)", r.RemoteAddr)
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(http.StatusPaymentRequired)
w.Write(encodePaymentTerms(paymentAmount, paymentRecipient, paymentMemo))
return
}

// Payment present — return premium data.
log.Printf("[paywall] 200 → %s (payment received, %d bytes)", r.RemoteAddr, len(payment))
w.Header().Set("Content-Type", "application/octet-stream")
w.WriteHeader(http.StatusOK)

// Simulate premium data: a randomized market insight.
insights := []string{
"BTC dominance rising to 58.3%, altcoin rotation expected",
"ETH gas fees averaging 12 gwei, L2 adoption accelerating",
"DeFi TVL crossed $95B, new ATH since 2022",
"Stablecoin supply expanding, USDC market cap up 15% MoM",
"On-chain whale activity: 3 wallets accumulated 12,000 BTC this week",
}
insight := insights[rand.IntN(len(insights))]
fmt.Fprintf(w, `{"insight":"%s","timestamp":%d,"tier":"premium"}`, insight, time.Now().Unix())
}

// encodePaymentTerms encodes payment parameters for the 402 response body.
// Format: [amount:8 LE][recipient_len:4 LE][recipient][memo_len:4 LE][memo]
func encodePaymentTerms(amount int64, recipient, memo string) []byte {
size := 8 + 4 + len(recipient) + 4 + len(memo)
buf := make([]byte, size)
off := 0
binary.LittleEndian.PutUint64(buf[off:], uint64(amount))
off += 8
binary.LittleEndian.PutUint32(buf[off:], uint32(len(recipient)))
off += 4
copy(buf[off:], recipient)
off += len(recipient)
binary.LittleEndian.PutUint32(buf[off:], uint32(len(memo)))
off += 4
copy(buf[off:], memo)
return buf
}
Loading
Loading