Skip to content

feat(sdk-go): add harness package for CLI-based coding agent dispatch#271

Merged
AbirAbbas merged 3 commits intomainfrom
feat/go-sdk-harness
Mar 16, 2026
Merged

feat(sdk-go): add harness package for CLI-based coding agent dispatch#271
AbirAbbas merged 3 commits intomainfrom
feat/go-sdk-harness

Conversation

@AbirAbbas
Copy link
Contributor

Summary

  • Adds a new harness/ package to the Go SDK that enables dispatching tasks to external coding agents (opencode, claude-code) via subprocess execution with structured output extraction
  • Implements the Go equivalent of the Python SDK's harness subsystem — the core missing capability needed to port pr-af from Python to Go
  • Wires harness into the Agent struct via HarnessConfig and agent.Harness() method for seamless integration

What's included

New sdk/go/harness/ package (7 files)

  • provider.goProvider interface and Options struct with sensible defaults
  • opencode.go — OpenCode CLI provider (subprocess with opencode run)
  • claudecode.go — Claude Code CLI provider (subprocess with claude --print --output-format json)
  • runner.go — Main orchestration: provider dispatch → schema validation → retry loop, with transient error detection and exponential backoff
  • schema.go — Prompt suffix generation, JSON Schema file management, cosmetic JSON repair, stdout fallback extraction, follow-up prompt construction
  • cli.go — Subprocess execution with timeout, environment merging, ANSI stripping
  • result.goRawResult, Result, FailureType, Metrics types

Agent integration (sdk/go/agent/)

  • harness.goHarnessConfig type, lazy HarnessRunner(), and agent.Harness() convenience method
  • agent.go — Added HarnessConfig to Config struct and harnessRunner field to Agent

Test plan

  • 30 unit tests covering schema parsing, cosmetic repair, stdout fallback, retry logic, provider binary-not-found, successful execution, option merging, transient error detection
  • Full Go SDK test suite passes (go test ./... — all 6 packages green)
  • Integration test with real opencode/claude binaries (manual)

🤖 Generated with Claude Code

Implements the Go equivalent of the Python SDK's harness subsystem,
enabling structured output extraction from external coding agents
(opencode, claude-code) via subprocess execution.

New harness/ package:
- Provider interface with OpenCode and ClaudeCode implementations
- Runner with schema validation, retry logic, and transient error handling
- Schema utilities: prompt suffix generation, cosmetic JSON repair,
  stdout fallback extraction, follow-up prompt construction
- CLI subprocess execution with timeout and environment management

Agent integration:
- HarnessConfig on agent.Config for default provider settings
- agent.Harness() method mirrors Python SDK's .harness() API
- Lazy-initialized runner with per-call option overrides

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@AbirAbbas AbirAbbas requested a review from a team as a code owner March 16, 2026 00:07
@github-actions
Copy link
Contributor

github-actions bot commented Mar 16, 2026

Performance

SDK Memory Δ Latency Δ Tests Status
Go 211 B -25% 0.58 µs -42%

✓ No regressions detected

Provider fixes validated through end-to-end testing:
- opencode: use -p flag (not "run" subcommand), -c for cwd, -q for
  quiet mode; model is config/env-based not a CLI flag
- claude-code: unset CLAUDECODE env var to allow spawning from within
  a Claude Code session
- cli: support unsetting env vars (empty value = remove from env)

Add examples/go_harness_demo with structured output extraction test
for both providers.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@santoshkumarradha
Copy link
Member

Will get on this. Should we also think about clearing python garbage collection?

@AbirAbbas
Copy link
Contributor Author

Will get on this. Should we also think about clearing python garbage collection?

already did/merged XD

…, SDK conventions

- Remove dead package-level sync.Once/Runner variables (cross-agent contamination risk)
- Guard CleanupTempFiles against '.' directory (destructive file deletion)
- Replace bare log.Printf with configurable Runner.Logger (matches SDK convention)
- Fix context cancellation leak in schema retry loop
- Fix StructToJSONSchema to use reflect for proper type inference
- Replace bubble sort with sort.Slice in extractJSONBlocks
- Handle json.MarshalIndent errors in BuildPromptSuffix/BuildFollowupPrompt
- Move isExecNotFound/truncate helpers from opencode.go to cli.go
- Add provider name constants (ProviderOpenCode, ProviderClaudeCode)
- Document env empty-string-means-unset convention
- Document mergeOptions zero-value override semantics
- Document cosmeticRepair brace-counting limitations
- Tighten writeSchemaFile directory permissions (0o755 → 0o700)
- Remove unused variable in runner_test.go
- Update .env.example to use generic placeholders
- Add loadEnv production-use note in demo
Copy link
Member

@santoshkumarradha santoshkumarradha left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review — Approved ✅

All 18 findings from the initial review have been addressed:

CRITICAL (2/2 fixed)

  • ✅ Removed dead package-level sync.Once/*Runner variables — eliminated cross-agent contamination risk
  • ✅ Guarded CleanupTempFiles against "." directory — prevents destructive file deletion in shared workspaces

HIGH (5/5 fixed)

  • ✅ Replaced all log.Printf calls with configurable Runner.Logger (defaults to io.Discard) — matches existing SDK convention (agent.go uses cfg.Logger)
  • ✅ Fixed StructToJSONSchema to use reflect for proper type inference (string/int/float/bool/slice/struct)
  • ✅ Fixed context cancellation leak in schema retry loop — now returns immediately on ctx.Done()
  • ✅ Documented cosmeticRepair brace-counting limitations
  • ✅ Replaced bubble sort with sort.Slice in extractJSONBlocks

MEDIUM (7/7 fixed)

  • ✅ Documented env empty-string-means-unset convention in RunCLI and Options.Env
  • ✅ Documented mergeOptions zero-value override semantics
  • ✅ Moved isExecNotFound/truncate from opencode.go to cli.go
  • ✅ Added provider name constants (ProviderOpenCode, ProviderClaudeCode)
  • ✅ Added error handling for json.MarshalIndent in BuildPromptSuffix/BuildFollowupPrompt
  • ✅ Tightened writeSchemaFile directory permissions (0o7550o700)
  • ✅ Added production-use note to demo's loadEnv function

LOW (4/4 fixed)

  • ✅ Updated .env.example to use generic placeholders
  • ✅ Removed unused origExecute variable in runner_test.go
  • ✅ Added godoc to agent/harness.go
  • ✅ Documented math/rand auto-seed behavior

Verification

  • go build ./...
  • go vet ./...
  • go test -race ./harness/... — 50 tests pass, race detector clean ✅
  • Full SDK test suite — 405 pass, 2 pre-existing failures in ai package (unrelated) ✅

@AbirAbbas AbirAbbas added this pull request to the merge queue Mar 16, 2026
Merged via the queue into main with commit eaaed38 Mar 16, 2026
17 checks passed
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.

2 participants