Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/agent-tool-contract-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gh-symphony/cli": patch
---

Document host-side GitHub and Linear tracker tools, their execution-context and credential boundaries, and Docker verification for #732.
1 change: 1 addition & 0 deletions AGENT_TEST.md
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ Control worker behavior with the `STUB_SCENARIO` environment variable:
| Restart failure isolation | `packages/orchestrator/src/service.test.ts` seeds a due retrying run whose restart checkout fails and verifies the failed run/project diagnostics, retained retry backoff, and healthy later-candidate dispatch within the same tick. | TC-17 seeds the due retrying run with an unavailable clone source, performs one refresh, and checks the failed retry diagnostics, future retry entry, and same-tick healthy dispatch. |
| Linear MCP runtime credentials | `packages/tool-linear-graphql/src/tool.test.ts`, `packages/runtime-codex/src/runtime.test.ts`, and `packages/runtime-claude/src/mcp-compose.test.ts` verify that resolved Linear credentials reach the built-in MCP server and API keys are used as raw Authorization values. | The standard Docker `happy` scenario verifies the worker/runtime container path remains healthy; Linear network calls stay unit-covered because E2E uses the isolated file tracker and no live Linear credentials. |
| Codex host-side dynamic tools | `packages/runtime-codex/src/runtime.test.ts`, `packages/worker/src/worker-protocol.test.ts`, `packages/worker/src/codex-dynamic-tools.test.ts`, and `packages/worker/src/codex-initialize.test.ts` cover advertised schemas, the conditional `experimentalApi` initialize capability, conditional `thread/start.dynamicTools`, `item/tool/call` responses, structured failures, and issue-context forwarding. | `docker compose -f docker-compose.e2e.yml exec -T symphony-e2e node /app/e2e/host-dynamic-tool-e2e.mjs` runs the built worker helper through the real provider adapter, stubbing only its HTTP boundary, and verifies one host-side call. For a real Codex smoke, replay the captured `initialize` with `capabilities: { "experimentalApi": true }` before the captured dynamic-tool `thread/start`; Codex must return a thread result rather than error `-32600`. Also replay the no-tools shape with `capabilities: {}` and no `dynamicTools` key; it must return a thread result rather than error `-32600`. |
| Host-side Codex and Claude tracker tools | `packages/runtime-codex/src/runtime.test.ts`, `packages/worker/src/worker-protocol.test.ts`, `packages/worker/src/codex-dynamic-tools.test.ts`, `packages/worker/src/codex-initialize.test.ts`, `packages/runtime-claude/src/mcp-http-server.test.ts`, and `test/e2e/claude/claude-docker.spec.ts` cover adapter-advertised schemas, conditional `experimentalApi`, `thread/start.dynamicTools`, `item/tool/call` responses, structured failures, normalized issue context, Claude HTTP MCP contract convergence, and child credential stripping. | [TC-19](e2e/scenarios/19-host-side-tracker-tools.md) runs the built Codex helper and Claude Docker black-box suite through adapter-owned provider tools with only the HTTP boundary stubbed. Both perform query, comment, and Project-state mutation calls; the established Claude fixture also asserts that the child environment and generated MCP configuration contain no raw provider credential. |
| Codex turn silence and approval posture | `packages/worker/src/worker-protocol.test.ts` proves every app-server output resets `turn_timeout_ms`, a silent turn is terminated, and an unhandled approval request receives JSON-RPC `-32601`; `codex-policy.test.ts`, `codex-startup.test.ts`, and `workflow-loader.test.ts` prove only `approval_policy: never` can reach startup. | `./e2e/run-e2e.sh happy 60` confirms the Docker worker lifecycle remains healthy after the worker protocol/configuration changes. The actual Codex app-server timing and approval paths are unit-covered because the Docker fixture uses a stub worker. |
| Broker-conditional tracker credential boundary | `packages/runtime-codex/src/runtime.test.ts`, `packages/runtime-claude/src/adapter.test.ts`, and `packages/core/src/runtime/mcp-compose.test.ts` cover brokerless compatibility, brokered raw-token stripping, and `$VAR` source metadata. | `pnpm e2e:claude` launches a broker-configured Claude worker and verifies the child sees no raw GitHub aliases while retaining the broker secret. The standard Docker happy scenario remains the brokerless lifecycle regression path. |
| Workflow reload revision signal | `packages/core/src/workflow-loader.test.ts` proves the revision is short, content-derived, and non-secret; `packages/core/src/observability/snapshot-builder.test.ts` proves snapshots expose the applied revision; `packages/orchestrator/src/service.test.ts` proves dispatch events carry it and that polling/concurrency reload on the next tick. | Start the Docker E2E environment, inject the happy-path issue, then verify `/api/v1/state` has a `workflow.revision` matching `sha256:<12 hex chars>` and the run's `events.ndjson` has the same `workflowRevision` on `run-dispatched`. |
Expand Down
6 changes: 3 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,10 @@ All work must be classified against these layers (per `AGENTS.md`):
```
cli (published entrypoint; bundles the rest via devDependencies)
orchestrator ──→ core, runtime-claude, runtime-codex, tracker-file, tracker-github, tracker-linear
worker ────────→ core, extension-github-workflow, runtime-claude, runtime-codex, tool-github-graphql, tracker-github
worker ────────→ core, extension-github-workflow, runtime-claude, runtime-codex, tool-github-graphql, tracker-github, tracker-linear
control-plane ─→ core, dashboard
runtime-{claude,codex} ─→ core, tool-github-graphql, tool-linear-graphql
tracker-{github,linear,file} ─→ core (+tool-github-graphql for github)
runtime-{claude,codex} ─→ core, tool-github-graphql, tool-linear-graphql (Claude also uses tracker adapters for host-side tools)
tracker-{github,linear,file} ─→ core (+tool-github-graphql for github; +tool-linear-graphql for linear)
extension-github-workflow, dashboard, tool-github-graphql ─→ core
```

Expand Down
7 changes: 7 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ header metadata rather than directories.
Architecture documentation scoped to a single package lives in that package's
`README.md` (for example [packages/control-plane/README.md](../packages/control-plane/README.md)).

## trackers/

Provider-specific host-side agent-tool contracts:

- [GitHub](trackers/github.md) — `github_graphql`
- [Linear](trackers/linear.md) — `linear_graphql`

## designs/

| Document | Layers | Status |
Expand Down
28 changes: 24 additions & 4 deletions docs/adr/2026-08-28_agent-tool-isolation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- **Date**: 2026-08-28
- **Status**: Accepted
- **Related Issues**: #671, #672, #673, #675
- **Related Issues**: #671, #672, #673, #675, #732
- **Related Spec**: `docs/symphony-spec.md` §10.5, §11.5, §15.1, §15.3, §15.5, §17.5
- **Symphony Layers**: Policy, Execution, Integration, Observability

Expand Down Expand Up @@ -204,13 +204,33 @@ tracked as a divergence, not an alternative architecture.
The target architecture conforms to the upstream tool, secret-handling, and
scope-narrowing requirements. `docs/symphony-spec.md` remains unchanged.

Until #673 ships, the current agent-started MCP subprocess model is an
Phase 2 is implemented through the adapter-owned `agentToolSpecs()` and
`executeAgentTool(name, args, context)` contract. Both Codex dynamic tools and
the Claude HTTP MCP server source their schemas and execution from that
contract. GitHub is advertised for repository/PR work in every tracker mode;
Linear is added for Linear-tracked projects, and each call is routed to its
owning adapter with the worker's resolved host environment. The Docker
host-tool and Claude HTTP-MCP black-box tests verify query, comment, and
Project-state mutation calls while the child receives only tool results and the
provider credential remains host-side. Brokerless Codex credential inheritance
remains the explicit temporary #700 compatibility divergence. The per-provider
contracts are recorded in [GitHub tracker tools](../trackers/github.md) and
[Linear tracker tools](../trackers/linear.md). This closes the host-transport
portion of #673 without editing the upstream specification.

The generic GraphQL tools still require a caller to bind its document and
variables to the active issue: `nativeRef` is passed only as host context and
is not injected into arbitrary provider documents. This limitation is explicit
in the per-tool contracts and must be replaced by provider-level target
enforcement before treating arbitrary mutations as a complete §15.5 scope
boundary.

Before #673 shipped, the agent-started MCP subprocess model was an
intentional, documented repository-local divergence. #672 removes raw tracker
and broker values from coding-agent environments and `mcp.json`, isolates the
child home/configuration directory, replaces child-authenticated Git transport
with a host operation, and disables the agent-owned MCP path; it does not claim
that the subprocess arrangement is conformant. #673 is the conformance-closing
implementation.
that the subprocess arrangement is conformant.

## README security-posture draft for #675

Expand Down
6 changes: 3 additions & 3 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,13 @@ to Git subprocesses.
```
cli (bundles: orchestrator, worker, control-plane, dashboard, runtime-claude, tracker-github, core)
orchestrator ──→ core, runtime-claude, runtime-codex, tracker-file, tracker-github, tracker-linear
worker ────────→ core, extension-github-workflow, runtime-claude, runtime-codex, tool-github-graphql, tool-linear-graphql, tracker-github
worker ────────→ core, extension-github-workflow, runtime-claude, runtime-codex, tool-github-graphql, tracker-github, tracker-linear
control-plane ─→ core, dashboard
dashboard ─────→ core
runtime-claude ─→ core, tool-github-graphql, tool-linear-graphql
runtime-claude ─→ core, tool-github-graphql, tool-linear-graphql, tracker-github, tracker-linear
runtime-codex ──→ core, tool-github-graphql, tool-linear-graphql
tracker-github ─→ core, tool-github-graphql
tracker-linear ─→ core
tracker-linear ─→ core, tool-linear-graphql
tracker-file ───→ core
extension-github-workflow ─→ core
tool-github-graphql ─→ core
Expand Down
34 changes: 34 additions & 0 deletions docs/trackers/github.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# GitHub tracker tools

The GitHub Project tracker adapter exposes `github_graphql` to an agent runtime
as a host-side dynamic tool. The coding-agent child receives the schema and
the result only; the adapter uses the host's GitHub credential or token broker.

## `github_graphql`

| Property | Contract |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name | `github_graphql` |
| Input | An object with required `query: string`; optional `variables: object` and `operationName: string`; no additional top-level properties. |
| Mutations | Permitted. A mutation must be intentionally scoped to the active issue or its repository. |
| Scope | The worker supplies the normalized active issue `{ id, identifier, nativeRef }` to the adapter. `nativeRef` remains host-internal and is never sent as an extra GraphQL payload field. The tool is an arbitrary GitHub GraphQL transport, so callers must constrain their document and variables to that active issue/repository; it does not infer or rewrite a query's target. |
| Result | The provider GraphQL payload. For queries, Symphony adds the GitHub `rateLimit` selection when absent and may return normalized rate-limit metadata with the payload. |
| Errors | Invalid tool arguments, missing host authentication, HTTP failures, and GraphQL errors are returned to the runtime as structured tool failures. Unknown tool names are rejected. |
| Rate limits | GitHub GraphQL rate-limit headers and the GraphQL `rateLimit` field are measured by the host and applied to the shared GitHub rate-limit policy; callers should keep queries small and respect retry guidance. |

The GitHub tool is always advertised because GitHub repository and pull-request
operations remain available for Linear-tracked projects; `linear_graphql` is
added when the selected tracker is Linear. It is not an MCP subprocess and
must not be recreated by the coding-agent child. In the temporary brokerless
compatibility path, a Codex child can still inherit `GITHUB_GRAPHQL_TOKEN`
until #700 removes that legacy path; host-side tool execution itself reads the
worker's resolved environment and never passes that token in a tool schema,
result, or generated Claude MCP configuration.

## Safe usage

Use a named operation and pass the active issue/repository identifiers as
variables. Keep mutations narrow—for example, update the active issue's
project item or create a comment on that issue—and verify the returned node
identifier before performing a follow-up mutation. Do not use this tool to
enumerate or modify unrelated repositories, organizations, or projects.
Loading
Loading