stub(#78): implement discovery + tunnel + peer-proxy + tasks endpoints (full dispatch scope)#80
Merged
Conversation
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>
This was referenced Jul 7, 2026
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>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #78 by extending
tools/eval/agentserver-stubwith 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:brainstorming→writing-plans→subagent-driven-developmenton a fresh worktree, with 10 rounds of adversarialcodex execreview (real codex CLI, not self-review):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:
POST /api/agent/discovery/cardsinternal/tunnel/tunnel.go:180PublishCarddiscovery_test.go— bearer / body / storageGET /api/agent/discovery/agentsinternal/driver/capability_tools.go→agentsdk.DiscoverAgentsdiscovery_test.go— bearer / workspace isolation /agent_id == sandbox_idWS /api/tunnel/<sid>?token=<t>agentsdk.Client.Connecttunnel_test.go— auth + yamux + reconnect race + double-close panic guardANY /api/agent/peer/<short>/proxy/<rest>[?q]internal/driver/agentsdk_client.go:33PeerProxypeerproxy_test.go— POST body forward + prefix strip + raw query preserve + method whitelist + cross-workspace 404POST /api/agent/tasksagentsdk.Client.DelegateTasktasks_test.go— cross-workspace 403 (matches real agentserver) + 201 response shapeGET /api/agent/tasks/poll[?sandbox_id=]internal/poller/poller.go:98— NO query string (default to caller)tasks_test.go— no-query positive test + optional-query mismatch 403 + atomic batch-of-5PUT /api/agent/tasks/{id}/statusresultfield) ANDagentsdk.Task.Complete(outputfield) — both shapes acceptedtasks_test.go— both pathsGET /api/agent/tasks/{id}?include_output=trueWaitForTasktasks_test.go— string-JSONresultunwrapped intooutputYamux HTTP-over-stream protocol
agentsdkuses the internalgithub.com/agentserver/agentserver/internal/tunnelprotocol (1B streamType + 4B big-endian metaLen + JSON HTTPStreamMetaper stream). Since that package isinternal/, we can't import it — copied 4 files (433 lines) intotunnel_transport.gowith a SOURCE header + byte-for-byte alignment (includingws.SetReadLimit(-1)to disable the default 32 KiB nhooyr limit — critical for larger response bodies).Concurrency
Single
sync.RWMutexover all 6 maps (byProxy/byTunnel/cards/tunnels/tasks/tasksBySandbox). Yamux stream I/O is done outside the lock (spec §6). Reconnect race guarded viaunregisterTunnel(sid, t)that only deletes whens.tunnels[sid] == t— verified byTestTunnelUnregister_DoesNotDeleteNewAfterReconnectpolling over a 1s stability window.Verification (spec §1.3, 5 layers)
Real smoke against a full stack (
deploy.sh --stub) with the realcodexCLI drivingdriver-agent serve-mcp:go test -race -timeout 60s ./tools/eval/agentserver-stub/...deploy.sh --stubbring-up + slave.log inspection ≥30stunnel connectedin slave.logfix-driver.inspect_capabilitieseval-slave) discoveredfix-driver.dry_run_contractrunnable=true, recommended_route=direct_slave, target=eval-slavecorrectly, butcapability_snapshotsobserver table stays empty because no non-test caller invokesobserverstore.WriteSnapshot— spun off as follow-up #81 (loom-side gap, not stub scope)fix-driver.submit_contract_task→wait_taskL4/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
Follow-ups (opened separately)
observerstore.WriteSnapshothas no non-test caller; loom driver.dry_run_contract computes the hash but never persists it🤖 Generated with Claude Code