Skip to content

feat(agent-runtimes): add github copilot sdk runtime - #21

Open
em-jones wants to merge 17 commits into
workos:mainfrom
em-jones:feat/github-copilot-runtime
Open

feat(agent-runtimes): add github copilot sdk runtime#21
em-jones wants to merge 17 commits into
workos:mainfrom
em-jones:feat/github-copilot-runtime

Conversation

@em-jones

Copy link
Copy Markdown

Summary

Adds CopilotSdkRuntime — a third agent execution backend that lets the pipeline run on a GitHub Copilot subscription via the official @github/copilot-sdk (GA).

The Copilot SDK is agentic: it drives the bundled Copilot CLI, which owns its own file/shell tools and authenticates as the logged-in Copilot user (or GITHUB_TOKEN/GH_TOKEN). That's the same subscription/resource win the Claude Agent SDK runtime exists for — so the new runtime sits beside ClaudeAgentSdkRuntime, not under LangChain.

Routing

provider / modelbackend
provider copilotCopilotSdkRuntime (new)
Claude (Anthropic)ClaudeAgentSdkRuntime
OpenAI / Google / OpenRouterLangChainRuntime
  • Matched by explicit provider copilot, checked beforeisClaudeModel — Copilot fronts both GPT and Claude model ids, so a name heuristic would collide with the other backends.
  • Read-only / mutable tool policy is enforced through the SDK's onPermissionRequest callback: read-only roles (scout/verifier/reviewer/closer) reject write requests; shell stays allowed for rg/find/git status. toolPolicyFor remains the single source of truth across all runtimes.
  • CASE_AGENT_RUNTIME=copilot forces the backend for debugging (joins pi|sdk|langchain).
  • Emits the same Langfuse spans + tool-activity callbacks as the other adapters (via the shared spawn-shared helpers), so a phase behaves identically regardless of backend.
  • Auth fail-fast:getAuthStatus() is checked before the first message, returning an actionable error instead of an opaque mid-stream throw.

Changes

  • src/agent/adapters/copilot-sdk-adapter.ts — new runtime
  • src/agent/adapters/provider-routing-runtime.ts — lazy getCopilot(), copilot-first select()
  • src/agent/config.tsisCopilotProvider classifier
  • src/__tests__/copilot-sdk-adapter.spec.ts — new contract test
  • docs/architecture/pipeline.md — routing table + override doc
  • package.json / bun.lock — add @github/copilot-sdk

Testing

  • bun run test669 passed, 2 skipped (8 new tests; no regressions)
  • New spec covers: event→span mapping, tool-activity callbacks, system-prompt replacement, the read-only permission seam (reject write, allow read/shell), mutable-role write approval, not-authenticated fail-fast, and the provider-router dispatch (provider:'copilot' + force-env).
  • bun run typecheck clean · bun run lint 0/0 · bun run format:check clean

Follow-ups

  • The adapter was written against @github/copilot-sdk@1.0.3 types (event names, permission/auth shapes verified against the installed .d.ts). Worth a live smoke test against a real Copilot subscription before relying on it in production runs.
  • Token-usage is mapped from the assistant.usage event; fields are best-effort (SDK marks several as optional).

em-jonesand others added 17 commits June 21, 2026 10:10
Two-phase plan replacing the custom DAG executor and granular event log
with LangGraph (orchestration + checkpointer) and Langfuse (observability),
preserving every existing feature.
- Phase 1 swaps orchestration to LangGraph; Phase 2 integrates Langfuse.
Each phase isolates exactly one breaking cutover (1.3 resume cutover,
2.2 event-log deletion) so a bisect localizes regressions to one phase.
- Resolves open decisions: LangGraph SQLite checkpointer for resume
(td stays a coarse human mirror), `interrupt` for human override,
re-point `ca watch` at the in-process callback stream, custom counter
channel for the revision budget.
- §9 test disposition triages every affected suite (die / port / keep /
audit / net-new), pinning the resume oracle and evidence-gate coverage.
- Langfuse self-hosted via podman-compose; dispatch host configurable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace custom DAG executor with LangGraph state machine.
Add pipeline-dispatch.ts for routing, update pipeline.ts to use
LangGraph engine, add parity tests, and include podman-compose
for local Langfuse deployment.
Remove legacy events layer (appender, reducer, schema, errors).
Add run-state.ts and readback.ts, restructure tests, and finalize
watch renderer/watcher for LangGraph state compatibility.
…ndency
This package is imported directly in src/langgraph/checkpointer.ts,
src/langgraph/engine.ts and test files; it was previously only a
transitive dependency of @langchain/langgraph.
Both the initial spawn and the retry spawn in runImplementPhase were
omitting langfuse: config.langfuse, leaving the implement phase without
observability coverage. Every other phase (scout, verify, review, close,
retrospective) passes the tracer. This restores consistent tracing.
The LangGraph engine's evaluatorNode recomputed revision routing with a
naive rubricFailed() (any verdict==='fail' → revise), discarding the
hard/soft classification that runReviewPhase already encodes. Reviewer
hard-gate failures (principle-compliance, scope-discipline) are
golden-principle violations — terminal aborts, not fixable-in-a-cycle
revisions — but the engine looped them as soft revisions until the
revision budget or an agent crash ended the run (the reviewer treadmill).
Mirror runReviewPhase's split: reviewer hard fail → phase failure
(routes to retrospective), soft fail → revise, verifier fail → revise.
Adds a langgraph-parity regression scenario pinning hard-fail → abort
(no revision cycle, no close).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ensureBranch created new task branches with `git checkout -b <name>`,
basing them on whatever HEAD happened to be. Launching a task while an
unrelated feature branch was checked out made the new branch inherit
that branch's entire diff as its baseline, so the reviewer reviewed the
inherited delta instead of the task's own work.
Add resolveBaseRef(): branch from origin's default HEAD, else local
main/master, falling back to HEAD only when no default branch exists.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Add vite.config.ts with fmt, lint, and build blocks matching .oxfmtrc.json
settings; includes bun-text-import plugin for .md/.yml/.yaml transforms
- Delete dead vitest.config.ts stub (unused since bun:test migration)
- Route package.json lint/format/format:check scripts through vp
- Add vite-plus ^0.2.1 to devDependencies
Scoped adoption: bun runtime, bun:test, bun:sqlite, and build:binary are
unchanged. vp check/fmt/lint/build/dev all pass on a clean tree.
46 unit + 9 standalone specs green (bun src/dev/run-tests.ts).
tsc --noEmit clean. bun src/index.ts --help boots.
The vitest, agent-SDK/LangChain, and vite-plus refactors left several docs
describing the old internals. Bring them current:
- Add docs/architecture/pipeline.md documenting the LangGraph StateGraph
engine, revision loop, fingerprint short-circuit, crash-resume
checkpointer, and the provider-routing agent runtime (Claude -> Agent SDK,
others -> LangChain, pi deprecated). This was wholly undocumented.
- README: replace "deterministic TypeScript pipeline executor" with the
LangGraph + checkpointer + provider-routing description and link the new
doc; fix `--task <file>` -> `--task <td-id>` (now a td issue handle);
fix storage layout (.todos/ td store, drop legacy .case/active and
tasks/active/*.task.json); fix `bun test` -> `bun run test`; correct the
default model id and model-resolution precedence.
- docs/failure-matrix.md: fix the `bun test` invocation to `bun run test`.
- Remove stale tasks/active/*.{md,task.json} examples that contradicted
tasks/README.md (tasks are td issues, not files); update CLAUDE.md project
structure and the bug-fix template's .task.json reference to match.
Conventions docs (testing.md, commits.md) were already current and are
untouched. No code changed.
Add CopilotSdkRuntime, a third agent backend that runs the pipeline on a
GitHub Copilot subscription via @github/copilot-sdk. The SDK is agentic —
it drives the bundled Copilot CLI, which owns its own file/shell tools and
authenticates as the logged-in Copilot user (or GITHUB_TOKEN), the same
subscription/resource win the Claude Agent SDK runtime exists for — so it
sits beside ClaudeAgentSdkRuntime rather than under LangChain.
Routing is by explicit provider `copilot`, matched before isClaudeModel:
Copilot fronts both GPT and Claude model ids, so a name heuristic would
collide with the other backends. Read-only/mutable tool policy is enforced
through the SDK onPermissionRequest callback (read-only roles reject `write`
requests; shell stays allowed for exploration), keeping toolPolicyFor the
single source of truth across all runtimes. CASE_AGENT_RUNTIME=copilot
forces the backend for debugging.
- src/agent/adapters/copilot-sdk-adapter.ts: new runtime, maps Copilot
session events to the shared Langfuse span + tool-activity callbacks
- provider-routing-runtime.ts: lazy getCopilot(), copilot-first select()
- config.ts: isCopilotProvider classifier
- copilot-sdk-adapter.spec.ts: event-mapping, permission seam, auth
fail-fast, and routing contract (8 tests)
- docs/architecture/pipeline.md: runtime-routing table + override
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@greptile-apps

Copy link
Copy Markdown

Too many files changed for review. (164 files found, 100 file limit)

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@em-jones