Skip to content

stub(#78): implement discovery + tunnel + peer-proxy + tasks endpoints (full dispatch scope)#80

Merged
yzs15 merged 10 commits into
paper/v3-integrationfrom
paper/v3/stub-discovery-tunnel-fix
Jul 7, 2026
Merged

stub(#78): implement discovery + tunnel + peer-proxy + tasks endpoints (full dispatch scope)#80
yzs15 merged 10 commits into
paper/v3-integrationfrom
paper/v3/stub-discovery-tunnel-fix

Conversation

@yzs15

@yzs15 yzs15 commented Jul 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #78 by extending tools/eval/agentserver-stub with the 3 endpoints agentsdk actually calls that were 404-ing (GET /api/agent/discovery/agents, POST /api/agent/discovery/cards, WS /api/tunnel/<sid>), plus the peer-proxy path (ANY /api/agent/peer/<short>/proxy/...) and the 4 task-poll endpoints (POST /api/agent/tasks, GET /api/agent/tasks/poll, PUT /api/agent/tasks/{id}/status, GET /api/agent/tasks/{id}) so that a real driver→slave dispatch has a complete wire path.

Base: paper/v3-integration @ 1748802. Branch: paper/v3/stub-discovery-tunnel-fix.

Process

Written using superpowers:brainstormingwriting-planssubagent-driven-development on a fresh worktree, with 10 rounds of adversarial codex exec review (real codex CLI, not self-review):

Stage Rounds to CLEAN Findings resolved
spec 4 6 P0 + 2 P1 → 2 P0 + 2 P1 → 1 P0 + 2 P1 → CLEAN
plan 4 3 P0 + 1 P1 → 1 P0 + 3 P1 → 1 P0 + 2 P1 → CLEAN
code 2 0 P0 + 3 P1 → CLEAN
Total 10 11 P0 + 13 P1 addressed

Full revision logs in docs/superpowers/specs/2026-07-06-agentserver-stub-discovery-tunnel-design.md §12 and the corresponding plan doc.

Wire contract

Every added endpoint was cross-checked against the actual caller in the loom repo — not the SDK-side comment:

Endpoint Caller in loom repo Test
POST /api/agent/discovery/cards internal/tunnel/tunnel.go:180 PublishCard discovery_test.go — bearer / body / storage
GET /api/agent/discovery/agents internal/driver/capability_tools.goagentsdk.DiscoverAgents discovery_test.go — bearer / workspace isolation / agent_id == sandbox_id
WS /api/tunnel/<sid>?token=<t> agentsdk.Client.Connect tunnel_test.go — auth + yamux + reconnect race + double-close panic guard
ANY /api/agent/peer/<short>/proxy/<rest>[?q] internal/driver/agentsdk_client.go:33 PeerProxy peerproxy_test.go — POST body forward + prefix strip + raw query preserve + method whitelist + cross-workspace 404
POST /api/agent/tasks agentsdk.Client.DelegateTask tasks_test.go — cross-workspace 403 (matches real agentserver) + 201 response shape
GET /api/agent/tasks/poll[?sandbox_id=] internal/poller/poller.go:98NO query string (default to caller) tasks_test.go — no-query positive test + optional-query mismatch 403 + atomic batch-of-5
PUT /api/agent/tasks/{id}/status poller (result field) AND agentsdk.Task.Complete (output field) — both shapes accepted tasks_test.go — both paths
GET /api/agent/tasks/{id}?include_output=true driver WaitForTask tasks_test.go — string-JSON result unwrapped into output

Yamux HTTP-over-stream protocol

agentsdk uses the internal github.com/agentserver/agentserver/internal/tunnel protocol (1B streamType + 4B big-endian metaLen + JSON HTTPStreamMeta per stream). Since that package is internal/, we can't import it — copied 4 files (433 lines) into tunnel_transport.go with a SOURCE header + byte-for-byte alignment (including ws.SetReadLimit(-1) to disable the default 32 KiB nhooyr limit — critical for larger response bodies).

Concurrency

Single sync.RWMutex over all 6 maps (byProxy / byTunnel / cards / tunnels / tasks / tasksBySandbox). Yamux stream I/O is done outside the lock (spec §6). Reconnect race guarded via unregisterTunnel(sid, t) that only deletes when s.tunnels[sid] == t — verified by TestTunnelUnregister_DoesNotDeleteNewAfterReconnect polling over a 1s stability window.

Verification (spec §1.3, 5 layers)

Real smoke against a full stack (deploy.sh --stub) with the real codex CLI driving driver-agent serve-mcp:

L Layer Result
L1 go test -race -timeout 60s ./tools/eval/agentserver-stub/... 42/42 pass, go vet clean
L2 deploy.sh --stub bring-up + slave.log inspection ≥30s 0 x 404, tunnel connected in slave.log
L3 codex fix-driver.inspect_capabilities PASS: 1 slave (eval-slave) discovered
L4 codex fix-driver.dry_run_contract 🟡 endpoint returns runnable=true, recommended_route=direct_slave, target=eval-slave correctly, but capability_snapshots observer table stays empty because no non-test caller invokes observerstore.WriteSnapshot — spun off as follow-up #81 (loom-side gap, not stub scope)
L5 codex fix-driver.submit_contract_taskwait_task 🟡 stub-side flow OK, but slave-side codex-agent dispatch chain doesn't complete in the local smoke — spun off as follow-up #82

L4/L5 divergences are exactly the "spec §1.3 fallback: land a follow-up issue" clause: the stub is shape-compatible with every wire the driver/slave actually speak, but two loom-side link gaps prevent the full observation loop. Recording them so Phase 3 real-run worktrees see the gap upfront.

Files

+ tools/eval/agentserver-stub/tunnel_transport.go   (433 lines, copied w/ SOURCE header)
+ tools/eval/agentserver-stub/discovery.go          (105)
+ tools/eval/agentserver-stub/tunnel.go             (114)
+ tools/eval/agentserver-stub/peerproxy.go          (155)
+ tools/eval/agentserver-stub/tasks.go              (330)
M tools/eval/agentserver-stub/server.go             (+60 lines: new fields, Close(), mux wiring)
+ tools/eval/agentserver-stub/{discovery,tunnel,peerproxy,tasks,tunnel_transport}_test.go   (34 new tests)
+ docs/superpowers/specs/2026-07-06-agentserver-stub-discovery-tunnel-design.md
+ docs/superpowers/plans/2026-07-06-agentserver-stub-discovery-tunnel.md

Follow-ups (opened separately)

🤖 Generated with Claude Code

yuzishu and others added 9 commits July 6, 2026 20:12
Round-4 codex review = CLEAN. 4 rounds of adversarial review addressed
6 P0 + 6 P1 findings; see §12 revision log for the diff summary.

Fixes #78.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Round-4 codex review CLEAN. 4 rounds addressed 5 P0 + 6 P1 findings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ct race guard

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Close

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- P1-1: acceptAndDiscard loop drains agent-opened control streams
        (previously would stall yamux accept backlog after long-lived heartbeats)
- P1-2: pollTaskEntry now includes skill + timeout_seconds so slave poller
        forwards them to dispatch (internal/poller/poller.go decodes both)
- P1-3: peer-proxy strips Authorization/Cookie/Proxy-Authorization headers
        before forwarding — target agent must not see driver's bearer token

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CI 'go mod tidy -diff' rejected the indirect classification after
tunnel_transport.go started importing both packages directly.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@yzs15 yzs15 merged commit 86bc2fd into paper/v3-integration Jul 7, 2026
10 checks passed
@yzs15 yzs15 deleted the paper/v3/stub-discovery-tunnel-fix branch July 7, 2026 03:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant