Skip to content

feat(trackers): add agent tool execution contract - #754

Merged
moncher-dev merged 5 commits into
mainfrom
feat/732-tool-execution-context
Aug 30, 2026
Merged

feat(trackers): add agent tool execution contract#754
moncher-dev merged 5 commits into
mainfrom
feat/732-tool-execution-context

Conversation

@moncher-dev

@moncher-dev moncher-dev commented Aug 29, 2026

Copy link
Copy Markdown
Collaborator

Issues — Closed #732

TL;DR

Formalizes adapter-owned host-side tracker tools for Codex and Claude, preserving loaded host configuration and executing each advertised tool through its owning tracker adapter.

Change-point diagram

Codex dynamic tool / Claude MCP tool
              |
              v
TrackerAdapter.agentToolSpecs() + executeAgentTool(..., { issue, env })
              |
              +---> GitHub GraphQL adapter
              +---> Linear GraphQL adapter

Start here

  • packages/core/src/contracts/tracker-adapter.ts defines the shared tool specification and execution context.
  • packages/worker/src/codex-dynamic-tools.ts routes each advertised tool to its owning adapter.
  • packages/runtime-claude/src/mcp-http-server.ts consumes the same adapter-owned tool contract.
  • e2e/scenarios/19-host-side-tracker-tools.md documents the Docker acceptance scenario.

Changes

  • Added agentToolSpecs() and executeAgentTool() to GitHub and Linear adapters, using normalized issue context and the loaded host environment.
  • Unified Codex and Claude tool schemas/execution; Linear-tracked sessions retain GitHub GraphQL alongside Linear GraphQL.
  • Added per-tool tracker documentation, ADR conformance notes, TC-19, scoped comment/Project-state mutation coverage, and child credential-boundary checks.
  • Rebasing onto current main preserved the new TC-19 table row with the current repository regression matrix.

Evidence

  • pnpm lint — pass.
  • pnpm test — pass.
  • pnpm typecheck — pass.
  • pnpm build — pass.
  • docker compose --project-name symphony-732-rework -f docker-compose.e2e.yml exec -T symphony-e2e node /app/e2e/host-dynamic-tool-e2e.mjs — pass.
  • pnpm e2e:claude — pass (5 Docker tests).

Risks & rollback

The shared contract is now the host-tool execution boundary. Reverting this PR restores the previous routing without data migration or tracker-state changes.

Changed files

Core contract; GitHub/Linear adapters; Codex/Claude transports; unit and Docker E2E tests; tracker documentation; ADR; architecture/test guidance; CLI changeset.

Post-merge / human validation

  • Confirm a real GitHub-tracked worker can read and comment on its active issue.
  • Confirm a Linear-tracked worker can use both configured GitHub and Linear host-side tools.
  • Confirm deployment configuration supplies only expected host-side provider credentials.

@github-actions

github-actions Bot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 40.81% 44043 / 107920
🔵 Statements 40.81% 44043 / 107920
🔵 Functions 72.79% 2165 / 2974
🔵 Branches 78.34% 9996 / 12759
File Coverage
File Stmts Branches Functions Lines Uncovered Lines
Changed Files
e2e/host-dynamic-tool-e2e.mjs 0% 0% 0% 0% 1-105
packages/core/src/contracts/tracker-adapter.ts 53.84% 100% 0% 53.84% 14-19
packages/runtime-claude/src/adapter.ts 93.09% 88.02% 94.28% 93.09% 119-126, 184-187, 257-266, 300-305, 355-356, 553-554, 691-692, 698-699, 706-707, 747-748, 814, 826, 828-829
packages/runtime-claude/src/mcp-http-server.ts 83.78% 73.46% 100% 83.78% 37-40, 100-101, 113-128, 159-160
packages/tracker-github/src/orchestrator-adapter.ts 82.39% 80.71% 87.5% 82.39% 188-190, 197, 217, 224, 226-227, 242-243, 246-280, 283-298, 301-322, 325-331, 378-385, 392-393, 409, 457-458, 529-532, 638, 661-662, 690-693, 753, 767-768
packages/tracker-linear/src/orchestrator-adapter.ts 92.31% 87.28% 95.23% 92.31% 372-374, 377-378, 405-412, 442-450, 681, 736, 748-754, 757-758, 774-775, 819-820, 826-827, 846-847, 910-913, 928-937, 958-959, 982-983
packages/worker/src/codex-dynamic-tools.ts 85.1% 64.28% 100% 85.1% 45-46, 62-63, 71-75, 82, 90-91, 97-98
packages/worker/src/non-codex-runtime.ts 81.48% 61.11% 50% 81.48% 124, 141, 157-160, 163-169, 172-173, 176-177, 180-186, 196-199, 251-252
Generated in workflow #1142 for commit b6c2e7c by the Vitest Coverage Report Action

@moncher-dev
moncher-dev marked this pull request as ready for review August 29, 2026 09:16
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Aug 29, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-08-29T09:18:44.571134Z f920ec5 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: f920ec55dd

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +74 to +76
return env.SYMPHONY_TRACKER_KIND === "linear"
? linearTrackerAdapter
: githubProjectTrackerAdapter;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Route each advertised tool to its owning adapter

When SYMPHONY_TRACKER_KIND is linear, buildCodexRuntimePlan still advertises both github_graphql and linear_graphql (packages/runtime-codex/src/runtime.ts:592-601), but this resolver sends every advertised call to linearTrackerAdapter. Consequently, a Linear-tracked agent calling the advertised GitHub tool for repository or PR work now receives Unknown Linear agent tool; select the adapter by tool ownership or stop advertising that tool.

AGENTS.md reference: AGENTS.md:L58-L61

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Resolved in d90496af: advertised tools are now mapped to their owning adapter by tool name, so Linear sessions retain github_graphql alongside linear_graphql.

Comment on lines +57 to +61
const result = await adapter.executeAgentTool(
toolName,
argumentsValue,
context
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve the loaded launcher environment for tool execution

When credentials or custom provider URLs exist only in a worker/package .env, loadLauncherEnvironment() places them in the env argument without mutating process.env, but this call no longer passes that environment to the adapter and both new adapter implementations read process.env directly. Such locally launched Codex sessions therefore lose authentication or use the wrong endpoint even though startup loaded the values successfully; retain the supplied environment in the execution contract rather than switching to the global process environment.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Resolved in d90496af: AgentToolExecutionContext now carries the loaded host environment, and both adapters derive credentials and endpoints from that context rather than ambient process.env.

@moncher-dev
moncher-dev requested a review from hojinzs August 29, 2026 09:23

@hojinzs hojinzs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review 1/6 — Request changes

The contract shape in packages/core is the right one and the per-tool docs are genuinely good (all six §10.5 columns present for both providers). But converging the Codex path onto it introduced two regressions I reproduced against this branch's build, and the E2E doesn't yet cover the acceptance criterion #732 names.

Verification performed

Check Result
pnpm install && pnpm build pass
pnpm lint pass
pnpm typecheck pass
pnpm test 1 failure — runtime-claude/src/spawn.test.ts ("no output for 80ms"); passes on re-run, timing-sensitive and untouched by this diff. Not a PR defect.
Smoke: host tool with .env-sourced credential fail — see P1-1
Smoke: github_graphql under SYMPHONY_TRACKER_KIND=linear fail — see P1-2
Smoke: github_graphql under a GitHub tracker with exported creds pass

No preview environment on this PR, so smoke testing was done locally against the built packages/worker/dist through the real adapters with only the provider HTTP boundary stubbed — the same boundary TC-19 stubs.

P1

  1. .env-sourced tracker credentials are silently dropped. The adapters read process.env (tracker-github:89-93, tracker-linear:244-246) while the worker threads launcherEnv, which merges .env files over process.env. A token configured only in .env now yields tool_execution_failed: Either GITHUB_GRAPHQL_TOKEN or the runtime token broker configuration is required. and no provider request. The whole broker path is affected too. The Object.assign(process.env, env) added to e2e/host-dynamic-tool-e2e.mjs and the vi.stubEnv calls added to three test files are the symptom — they adapt the tests to the regression instead of catching it.

  2. github_graphql is broken for every Linear-tracked project. resolveTrackerToolAdapter picks one adapter by tracker kind, but the Codex runtime advertises github_graphql unconditionally and adds linear_graphql on top when the tracker is Linear. Result under a Linear tracker: Unknown Linear agent tool: github_graphql. The removed switch handled both names. Dispatch should be by tool name across the advertised set, not by tracker kind.

P2

  1. Only half the call paths converged. runtime-claude/src/mcp-http-server.ts still hardcodes tool names, schemas and direct executeGitHubGraphQL/executeLinearGraphQL calls. #732 scopes this as converging the paths A/B left temporary — the Claude transport is untouched by the new contract, and now advertises schemas that differ from the ones docs/trackers/*.md documents as the contract.

  2. E2E doesn't exercise a tracker transition or comment. #732 requires the Docker E2E to perform 트래커 전이·코멘트 through the host-side tool. Both the new Codex helper and the (unmodified) Claude spec only run query { viewer { login } }. The documented "Mutations: Permitted" contract line — the one carrying write authority — has no test behind it.

  3. docs/trackers/github.md:19 asserts something falsegithub_graphql is not advertised only under a GitHub adapter.

P3 / nit

  1. Default allowedToolNames is derived from the env-selected adapter even when dependencies.adapter is injected.
  2. @gh-symphony/tool-linear-graphql is now an unused direct dep of packages/worker; docs/architecture.md:113,119 and CLAUDE.md:51,54 dependency graphs are stale.
  3. nit — 7 changed files fail npx prettier --check, including both new docs/trackers/*.md and the e2e helper. The repo is broadly unformatted already (92 files), so this is low priority and not a merge blocker.

Scope

No over-engineering found — the added types are all within #732's stated scope. The one unrequested design decision is the env-based single-adapter resolution, and it is what produced P1-2; #732 specifies the contract but not how the worker selects an adapter.

P1-1 and P1-2 are merge blockers. P2-3 and P2-4 are the stated 완료 기준 of the issue, so I'd want them in this PR rather than deferred — but if you have a reasoned case for splitting either into a follow-up, say so on the thread and I'll take it.


Generated by Claude Code

Comment thread packages/tracker-github/src/orchestrator-adapter.ts Outdated
Comment thread packages/worker/src/codex-dynamic-tools.ts Outdated
Comment thread packages/core/src/contracts/tracker-adapter.ts
Comment thread docs/trackers/github.md Outdated
Comment thread e2e/scenarios/19-host-side-tracker-tools.md Outdated
Comment thread packages/worker/src/codex-dynamic-tools.ts Outdated
Comment thread packages/worker/package.json Outdated
@hojinzs

hojinzs commented Aug 29, 2026

Copy link
Copy Markdown
Owner

리뷰 보완 (독립 검증, 1회차 Request changes에 추가) — (1) main 충돌: 09:27–28Z에 #738/#747/#734 머지로 AGENT_TEST.md, packages/tracker-linear/src/tracker-linear.test.ts 충돌(origin/main acff4c8) → 리베이스 필요. (2) P1-2 재현 확인: worker/src/codex-dynamic-tools.ts:71-76가 트래커 종류로 어댑터 하나를 고르는데 runtime-codex/src/runtime.ts:588-601은 Linear에서도 github_graphql을 광고 → Unknown Linear agent tool: github_graphql (스펙 §10.5 위반). (3) 계약 미수렴: runtime-claude/src/mcp-http-server.ts:97-118·Codex runtime.ts:194-262 스키마 하드코딩이 ADR/AGENT_TEST 서술과 불일치. (4) 문서: docs/trackers/github.md:19-20 서술 거짓, brokerless Codex child env의 GITHUB_GRAPHQL_TOKEN 잔존(#700까지) caveat, architecture.md 의존성 그래프 미갱신. 실바이너리: Codex replay ACCEPTED(#746 회귀 없음), 실제 claude Streamable HTTP 핸드셰이크 OK.

@moncher-dev
moncher-dev force-pushed the feat/732-tool-execution-context branch from f920ec5 to d90496a Compare August 29, 2026 09:43
@moncher-dev
moncher-dev force-pushed the feat/732-tool-execution-context branch from 8dc393b to 5daad36 Compare August 29, 2026 15:53
@moncher-dev
moncher-dev requested a review from hojinzs August 29, 2026 15:54

@hojinzs hojinzs left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Review 2/6 — Approve

All eight findings from round 1 are fixed, and I verified each against this branch's build rather than taking the thread replies at face value. Both P1 regressions are genuinely gone, and the two P2s that were the issue's stated 완료 기준 are now implemented rather than deferred. What's left is two nits, both non-blocking.

Verification performed

Check Result
pnpm install && pnpm build pass
pnpm lint pass
pnpm typecheck pass
pnpm test pass — 28 suites green. Last round's runtime-claude/spawn.test.ts timing flake did not reproduce.
TC-19 Codex helper (e2e/host-dynamic-tool-e2e.mjs, repointed at the local dist) host_dynamic_tool_e2e=pass
pnpm e2e:claude not run — no Docker daemon in this environment (/var/run/docker.sock absent). Environment limit, not a PR defect; the Container Smoke and Test checks are green on 5daad36.
Smoke: .env-sourced credential, Codex path (was P1-1) passBearer dotenv-sourced-token reached the provider with nothing exported in process.env
Smoke: github_graphql under SYMPHONY_TRACKER_KIND=linear (was P1-2) pass — both tools resolve; requests hit api.github.com and api.linear.app respectively
Smoke: unknown tool + allowlist gating passunknown_tool for an unowned name, and for an owned name excluded from allowedToolNames
Smoke: Claude MCP transport, same scenarios passtools/list[github_graphql, linear_graphql], both additionalProperties: false; tools/call used the .env-sourced host credential; unknown tool → -32602

Smoke testing again ran locally against the built packages through the real adapters with only the provider HTTP boundary stubbed — the same boundary TC-19 stubs. No preview environment on this PR.

Round 1 findings

# Finding Status
P1-1 .env-sourced credentials dropped FixedAgentToolExecutionContext.environment carries the resolved host env; both adapters read it. The Object.assign(process.env, env) in the e2e helper is gone, which was the check I asked for. Guarded: reverting tracker-github to process.env fails tracker-github.test.ts:39.
P1-2 github_graphql broken under a Linear tracker Fixed — dispatch is by tool name across the advertised set, with the unknown_tool failure retained. See my inline note on the test guard.
P2-3 Only the Codex path converged Fixedmcp-http-server.ts now drives agentToolSpecs()/executeAgentTool(); both transports hand out identical schemas, so the docs/trackers/*.md tables are true for both.
P2-4 E2E had no transition or comment Fixed — both halves now run query + scoped addComment + updateProjectV2ItemFieldValue, asserting the outgoing documents and variables. The mutation half of the contract has coverage behind it.
P2-5 False statement in docs/trackers/github.md Fixed — states plainly that github_graphql is always advertised.
P3-6 Default allowlist ignored an injected adapter Fixed — both derive from the same resolved-or-injected set.
P3-7 Stale dep + drifted dependency graphs Fixedtool-linear-graphql dropped from worker; docs/architecture.md:127,130,133 and CLAUDE.md updated, including the new runtime-claude → tracker-* edge. No cycle introduced.
nit-8 Prettier drift Fixedpnpm format is clean.

Also confirmed: TC-19's third assertion is now worded as existing coverage that this PR extends, which was the wording concern; the changeset is present; the branch merges into main cleanly (2 commits behind, no conflict).

Scope

Still no over-engineering — everything added stays inside #732's stated scope, and the round-1 items were fixed at the root rather than papered over. The context.environment extension is the right shape: it keeps the spec's context={issue} object form while removing the ambient dependency.

One thing deliberately not converged, which I agree with: runtime-codex/src/runtime.ts:180-252 still builds its own tool definitions, because a Codex RuntimeToolDefinition carries the MCP command/args/env that an AgentToolSpec has no notion of. The input schemas are identical to the adapter-owned ones today (same properties, required, additionalProperties: false), so no documented contract line is false — only the human-readable description differs between runtimes. That's #700's cleanup, not this PR's.

Remaining

Two nits, both inline, both fine as follow-ups: a missing unit-test guard on the Codex-side adapter resolution, and the resolution rule being duplicated across two packages. Neither blocks merge, and neither is worth another round — approving.


Generated by Claude Code

authorizationHeader: env.LINEAR_AUTHORIZATION,
};
): readonly OrchestratorTrackerAdapter[] {
return env.SYMPHONY_TRACKER_KIND === "linear"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

nit — the P1-2 fix has no unit-test guard on this function (non-blocking).

The routing fix itself is correct; I reproduced it working. But resolveTrackerToolAdapters — the function whose single-adapter form was the regression — is never exercised under SYMPHONY_TRACKER_KIND=linear by any test. codex-dynamic-tools.test.ts:125 ("routes GitHub tools alongside Linear tools") injects dependencies.adapters stubs, so it validates executeCodexDynamicToolCall's dispatch but not this resolution; :94 uses real resolution but only under the default GitHub kind.

Verified by reverting this function to the buggy form on top of your branch:

   return env.SYMPHONY_TRACKER_KIND === "linear"
-    ? [githubProjectTrackerAdapter, linearTrackerAdapter]
+    ? [linearTrackerAdapter]
     : [githubProjectTrackerAdapter];
pnpm --filter @gh-symphony/worker test
  Test Files  26 passed (26)
       Tests  194 passed (194)

The whole worker suite stays green while github_graphql is broken for every Linear-tracked project again.

The Claude side is already guarded — the same revert in mcp-http-server.ts fails mcp-http-server.test.ts:41, because that test drives real resolution through env alone. A one-line equivalent here (call executeCodexDynamicToolCall with SYMPHONY_TRACKER_KIND=linear and no injected dependencies, asserting linear_graphql resolves) would close the asymmetry. Worth doing, but not worth another round on its own.


Generated by Claude Code

authorizationHeader: env.LINEAR_AUTHORIZATION,
};

function resolveHostToolAdapters(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

nit — "which adapters are enabled for this session" is now duplicated across two packages (non-blocking).

resolveHostToolAdapters here and resolveTrackerToolAdapters in packages/worker/src/codex-dynamic-tools.ts:78-84 are byte-identical. That's exactly the divergence class P2-3 was about: this PR made both transports share the tool contract, but the session's enabled-adapter rule stayed a per-transport copy, so a future tracker kind (or a change to the Linear condition) has to be applied in two places or the two runtimes silently advertise different tool sets.

Cheap to close — hoist one resolveHostToolAdapters(env) into a shared module and import it from both. Follow-up is fine; the two copies agree today and both are covered.


Generated by Claude Code

@moncher-dev
moncher-dev force-pushed the feat/732-tool-execution-context branch from 5daad36 to b6c2e7c Compare August 30, 2026 01:46
@moncher-dev
moncher-dev merged commit c549d08 into main Aug 30, 2026
2 checks passed
@moncher-dev
moncher-dev deleted the feat/732-tool-execution-context branch August 30, 2026 01:47
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.

feat(core,tools,docs): tool execution context contract in tracker adapters, per-tool documentation, and Docker E2E for host-side tools

2 participants