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/friendly-provider-workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@gh-symphony/cli": patch
---

Generate provider-form tracker configuration in new workflows and document migration from deprecated flat tracker keys (#710).
59 changes: 32 additions & 27 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,45 +311,48 @@ Use a Project single-select field:
```yaml
tracker:
kind: github-project
project_id: PVT_kwDOxxxxxx
state_field: Status
priority:
source: project-field
field: Priority
values:
Urgent: 0
High: 1
Medium: 2
Low: 3
provider:
project_id: PVT_kwDOxxxxxx
state_field: Status
priority:
source: project-field
field: Priority
values:
Urgent: 0
High: 1
Medium: 2
Low: 3
```

Or use exact repository labels:

```yaml
tracker:
kind: github-project
project_id: PVT_kwDOxxxxxx
state_field: Status
priority:
source: labels
labels:
P0: 0
P1: 1
P2: 2
provider:
project_id: PVT_kwDOxxxxxx
state_field: Status
priority:
source: labels
labels:
P0: 0
P1: 1
P2: 2
```

Or disable priority dispatch explicitly:

```yaml
tracker:
kind: github-project
priority:
source: disabled
provider:
priority:
source: disabled
```

Lower numbers dispatch first. If an issue has multiple configured priority labels, Symphony uses the lowest numeric value and emits `priority.label_conflict_resolved`. If an active issue carries an unmapped configured-source value, it resolves to `priority = null` and emits `priority.unmapped`.

Legacy `tracker.priority_field: Priority` remains supported for existing workflows, but it is deprecated because it uses live Project option order. Project field definitions are cached for the process lifetime, so field creation, removal, and option changes take effect after the daemon restarts. To migrate, replace it with `tracker.priority.source: project-field`, copy the exact field name, and write explicit option-name-to-number mappings. If both legacy and explicit config are present, explicit `tracker.priority` wins and diagnostics warn about the conflict.
Legacy `tracker.priority_field: Priority` remains supported for existing workflows, but it is deprecated because it uses live Project option order. Project field definitions are cached for the process lifetime, so field creation, removal, and option changes take effect after the daemon restarts. To migrate, replace it with `tracker.provider.priority.source: project-field`, copy the exact field name, and write explicit option-name-to-number mappings. If both legacy and explicit config are present, explicit `tracker.provider.priority` wins and diagnostics warn about the conflict.

`gh-symphony workflow validate` reports local config errors and legacy priority warnings. Strict front-matter failures use stable workflow error codes; with `--json`, `workflow validate` includes both `error.code` and `error.path`. `gh-symphony doctor` additionally checks live Project/repository drift: missing fields, missing labels, unmapped live options, stale configured mappings, and active issues that currently resolve to `priority = null` because their priority-like value is unmapped.

Expand Down Expand Up @@ -708,8 +711,9 @@ host:
```yaml
tracker:
kind: github-project
endpoint: https://github.example/api/graphql
project_id: PVT_xxx
provider:
endpoint: https://github.example/api/graphql
project_id: PVT_xxx
```

Then initialize and validate the repository runtime:
Expand All @@ -722,9 +726,9 @@ gh-symphony doctor --smoke --issue owner/repo#123
```

`GITHUB_GRAPHQL_API_URL` remains an optional process-level override. If both
`tracker.endpoint` and `GITHUB_GRAPHQL_API_URL` are set, keep them identical;
`tracker.provider.endpoint` and `GITHUB_GRAPHQL_API_URL` are set, keep them identical;
`doctor` reports the resolved endpoint and warns when they disagree. During
dispatch, the GitHub tracker injects the configured `tracker.endpoint` into the
dispatch, the GitHub tracker injects the configured `tracker.provider.endpoint` into the
worker as `GITHUB_GRAPHQL_API_URL`, so worker-side `github_graphql` calls do not
fall back to `https://api.github.com/graphql`.

Expand Down Expand Up @@ -752,7 +756,8 @@ tick; dispatch events also include `workflowRevision`.

The generated file includes:

- **Lifecycle**: `active_states`, `terminal_states`, explicit `blocker_check_states`, and `planning_states` derived from the status column mapping. Lifecycle state names are matched case-insensitively after trimming. Missing blocker configuration defaults to the first active state; an explicit `blocker_check_states: []` disables blocker gating as an intentional spec divergence. Planning remains disabled unless configured explicitly.
- **Lifecycle**: core `tracker.active_states` and `tracker.terminal_states`, plus provider-owned `blocker_check_states` and `planning_states`, derived from the status column mapping. Lifecycle state names are matched case-insensitively after trimming. Missing blocker configuration defaults to the first active state; an explicit `tracker.provider.blocker_check_states: []` disables blocker gating as an intentional spec divergence. Planning remains disabled unless configured explicitly.
- **Tracker provider**: adapter-owned settings are generated under `tracker.provider`. Flat tracker keys are deprecated aliases and will be removed in the next major release (#679).
- **Runtime**: `agent_command` derived from `gh-symphony workflow init`
- **Hooks**: `after_create` hook path
- **Scheduler**: `poll_interval_ms`
Expand All @@ -775,7 +780,7 @@ Available template variables:
| `{{execution_phase}}` | `planning`, `implementation`, or null |
| `{{guidelines}}` | Prompt guidelines from WORKFLOW.md |

`tracker.planning_states` classifies matching states as `planning`; it does not
`tracker.provider.planning_states` classifies matching states as `planning`; it does not
impose a built-in plan-only gate or make a state eligible for dispatch.
Use `execution_phase` in the prompt body when policy should change agent
behavior, for example:
Expand Down
42 changes: 42 additions & 0 deletions docs/adr/2026-08-29_tracker-provider-alias-deprecation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# ADR: Retain flat tracker keys as deprecated provider aliases

- **Date**: 2026-08-29
- **Status**: Accepted
- **Related Issues**: #669, #679, #710
- **Related Spec**: `docs/symphony-spec.md` §5.3.1, §6.1 (read-only)

## Context

The upstream configuration model places adapter-owned tracker settings in the
opaque `tracker.provider` object. Existing GitHub Symphony workflows used flat
`tracker.*` keys such as `project_id`, `endpoint`, `state_field`, `priority`,
and `pickup_labels`. Removing those keys immediately would break committed
workflows and installed repository runtimes.

## Decision

New generated workflows, reference workflows, and skill templates use
`tracker.provider`. The parser continues to promote supported flat tracker keys
into that provider object as deprecated, non-breaking aliases. Diagnostics from
`gh-symphony workflow validate` and `gh-symphony repo doctor` identify the
aliases and print a copyable normalized provider block.

The aliases are scheduled for removal in the next major release. The removal
work is tracked separately in #679 and must not begin before its required sign
off.

## Upstream conformance and divergence

Provider-form configuration aligns with the upstream specification. Retaining
flat aliases is an intentional, time-bounded repository compatibility extension
rather than an upstream-spec change. `docs/symphony-spec.md` remains
unchanged.

## Consequences

- Newly initialized repositories follow the provider-owned configuration
boundary without migration work.
- Existing repositories keep working while operators migrate using doctor
output.
- Documentation consistently labels flat keys as deprecated and directs new
configuration to `tracker.provider`.
41 changes: 28 additions & 13 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,26 @@ Human-readable `gh-symphony repo status`, `gh-symphony project status`, and
their `--watch` dashboards show the applied revision; `--json` exposes the
same metadata for automation.

Tracker connection settings are persisted in runtime `config.json`; changing
them does not reconfigure an already-running daemon. This is an intentional
repository-local live-reload divergence. In standalone project mode, stop and
start the daemon to apply tracker endpoint, credential, or binding changes. In
repo-embedded mode, those settings are fixed when `gh-symphony repo init`
creates the runtime, so run `repo init` again before restarting `repo start`.
### Tracker provider binding and live reload

`repo init` writes the runtime's selected tracker adapter and repository/project
binding to `config.json` and its project record. Changing that binding — for
example, switching `tracker.kind`, moving to a different initialized project,
or changing a runtime-owned provider path — requires running `gh-symphony repo
init` again and restarting the daemon. A `WORKFLOW.md` edit never rewrites
`config.json`.

The workflow policy passed to the already-selected adapter is live-reloaded on
every reconciliation tick: core `tracker.active_states`,
`tracker.terminal_states`, `tracker.state_field`, and the provider-owned
`blocker_check_states` and `planning_states` apply on the next tick. The
Comment on lines +44 to +46

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, 비블로킹 — 제 3회차 제안문의 오류입니다] state_field를 core로 분류한 건 제가 틀렸습니다. 그대로 적용해 주신 건 맞고, 문장 안의 분류만 어긋납니다.

state_field는 provider 소유 키입니다:

  • 생성기 3곳 모두 provider: 아래 4칸으로 emit — generate-workflow-md.ts:106 (provider.state_field = input.stateFieldName), generate-reference-workflow.ts:89, skills/templates/gh-symphony.ts:117
  • DEPRECATED_TRACKER_PROVIDER_KEYS (packages/core/src/workflow/parser.ts:459)에 "state_field"가 들어 있음 = flat tracker.state_field가 deprecated alias
  • 같은 파일 :336-337이 이미 tracker.state_field를 flat alias 목록에 올려두어, 지금 :45와 한 파일 안에서 상충합니다

reload 타이밍(next tick) 자체는 맞습니다 — dependencies.workflowLifecycle.stateFieldName으로 매 tick 넘어가는 것 확인했습니다. provider 소유이면서 tick마다 리로드되는 blocker_check_states/planning_states와 같은 부류이므로, 문장에서 core 목록이 아니라 provider-owned 목록으로 옮기면 됩니다.

Suggested change
every reconciliation tick: core `tracker.active_states`,
`tracker.terminal_states`, `tracker.state_field`, and the provider-owned
`blocker_check_states` and `planning_states` apply on the next tick. The
every reconciliation tick: core `tracker.active_states` and
`tracker.terminal_states`, plus the provider-owned `state_field`,
`blocker_check_states`, and `planning_states` apply on the next tick. The

approve를 막지 않습니다. 머지 전에 한 줄만 적용해 주시면 되고, 넘어가셔도 재차 제기하지 않겠습니다.


Generated by Claude Code

remaining provider settings — `project_id` or `project_slug`, `endpoint`,
`priority`, `priority_field_name`, and `pickup_labels` — are read from the
project record that `repo init` wrote, so editing them in `WORKFLOW.md` has no
effect until the runtime is initialized again and the daemon restarts. Existing
workers keep the policy and tracker dependencies captured for their own run.
This is the same tick-based reload boundary described above, not a
watcher-driven update.

## Runtime, Retry, and Hook Divergences

Expand Down Expand Up @@ -123,7 +137,7 @@ is pending adapter work in #660-B.

## Workflow Lifecycle Policy

`tracker.blocker_check_states` selects the workflow states where the GitHub and
`tracker.provider.blocker_check_states` selects the workflow states where the GitHub and
Linear adapters derive `dispatchable: false` from unresolved `blocked_by`
dependencies. The orchestrator consumes that normalized result and does not
interpret provider blocker semantics. When the field is omitted, the default is
Expand All @@ -136,7 +150,7 @@ GitHub source-closed blockers and blockers whose Project workflow state is in
`tracker.terminal_states` are resolved. Linear uses its workflow-state relation
data directly. In both adapters, `blocked_by` remains best-effort metadata and
`dispatchReason` identifies an unresolved dependency. Omitting
`planning_states` keeps planning disabled; blocker defaults do not enable the
`tracker.provider.planning_states` keeps planning disabled; blocker defaults do not enable the
planning/human-review execution phase. Linear `blocked_by` metadata is derived
from inverse relations of type `blocks`; source-side relations describe issues
blocked by the current issue and are not blockers of it.
Expand Down Expand Up @@ -285,7 +299,7 @@ comma-separated strings) and must be configured explicitly unless the selected
adapter supplies lifecycle defaults.

`tracker.active_states` controls dispatch eligibility, while
`tracker.planning_states` classifies states for prompt policy and status
`tracker.provider.planning_states` classifies states for prompt policy and status
surfaces. Classification is independent of dispatch eligibility: a matching
planning state resolves to `planning` even when it is absent from
`active_states`. Both lists use trimmed, case-insensitive state matching.
Expand Down Expand Up @@ -316,15 +330,15 @@ tracker:
project_id: PVT_kwDOxxxxxx
endpoint: https://api.github.com/graphql
state_field: Status
active_states: [Todo, In Progress]
terminal_states: [Done]
blocker_check_states: [Todo]
planning_states: []
pickup_labels:
include: [agent-ready]
exclude: [blocked]
priority:
source: disabled
active_states: [Todo, In Progress]
terminal_states: [Done]
```

The documented GitHub Project lifecycle profile is `Status`, active states
Expand All @@ -335,7 +349,8 @@ or field is omitted. Flat `tracker.project_id`, `tracker.endpoint`,
`tracker.blocker_check_states`, and `tracker.planning_states` aliases remain
supported for compatibility, but `gh-symphony workflow validate` and
`gh-symphony repo doctor` warn and print a copyable `tracker.provider` block.
They are scheduled for removal in the next major release.
They are scheduled for removal in the next major release; see
[ADR 2026-08-29](adr/2026-08-29_tracker-provider-alias-deprecation.md).

## Skill Layering

Expand Down Expand Up @@ -381,7 +396,7 @@ container environments.
| Variable | Default | Read by | Audience | Notes |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GITHUB_GRAPHQL_TOKEN` | unset | CLI, orchestrator, GitHub tracker, Codex runtime, Claude runtime, Git credential helper | User-facing | Token-only GitHub auth source. Requires `repo`, `read:org`, and `project` scopes. Takes priority over `gh` CLI auth where both are supported. |
| `GITHUB_GRAPHQL_API_URL` | unset; GitHub tooling falls back to the public GitHub GraphQL endpoint unless tracker config injects an endpoint | CLI doctor, Codex runtime, Claude runtime | User-facing, GHES | Process-level GraphQL endpoint override. For GHES, prefer `tracker.endpoint` in `WORKFLOW.md`; if both are set, keep them identical. |
| `GITHUB_GRAPHQL_API_URL` | unset; GitHub tooling falls back to the public GitHub GraphQL endpoint unless tracker config injects an endpoint | CLI doctor, Codex runtime, Claude runtime | User-facing, GHES | Process-level GraphQL endpoint override. For GHES, prefer `tracker.provider.endpoint` in `WORKFLOW.md`; if both are set, keep them identical. |
| `GITHUB_PROJECT_ID` | unset; injected from project config for workers | Codex runtime, Claude runtime | Internal unless running a runtime launcher manually | Passed to GitHub GraphQL tooling so agent tools can target the active Project. |
| `LINEAR_API_KEY` | unset | CLI, Codex runtime, Claude runtime | User-facing for Linear tracker projects | Required for Linear repo startup. The built-in Linear MCP server receives it in its declared environment and uses it as the raw `Authorization` value. |
| `LINEAR_AUTHORIZATION` | unset | Codex runtime, Claude runtime | Advanced | Optional raw Linear authorization value for the built-in Linear MCP server; it takes priority over `LINEAR_API_KEY`. |
Expand Down
33 changes: 17 additions & 16 deletions docs/examples/linear-WORKFLOW.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
---
tracker:
kind: linear
endpoint: https://api.linear.app/graphql
api_key: $LINEAR_API_KEY
project_slug: symphony-0c79b11b75ea
provider:
endpoint: https://api.linear.app/graphql
api_key: $LINEAR_API_KEY
project_slug: symphony-0c79b11b75ea
pickup_labels:
include:
- agent
- dev-ready
exclude:
- no-agent
- needs-spec
blocker_check_states:
- Todo
planning_states: []
active_states:
- Todo
- In Progress
- Rework
pickup_labels:
include:
- agent
- dev-ready
exclude:
- no-agent
- needs-spec
terminal_states:
- Done
- Canceled
- Cancelled
- Duplicate
blocker_check_states:
- Todo
planning_states: []
polling:
interval_ms: 30000
workspace:
Expand Down Expand Up @@ -54,13 +55,13 @@ runtime:

## Linear Tracker Policy

`WORKFLOW.md` is the source of truth for Linear tracker setup. Use `tracker.kind: linear` with `tracker.project_slug`; do not use `tracker.project_id`, `projectId`, `project_id`, `teamId`, or `.gh-symphony/config.json` as Linear configuration inputs.
`WORKFLOW.md` is the source of truth for Linear tracker setup. Use `tracker.kind: linear` with `tracker.provider.project_slug`; do not use `tracker.project_id`, `projectId`, `project_id`, `teamId`, or `.gh-symphony/config.json` as Linear configuration inputs.

`LINEAR_API_KEY` must be available when running `gh-symphony repo init`, `gh-symphony repo start`, or `gh-symphony workflow preview ENG-123`. The orchestrator reads Linear by polling the configured project. Linear webhook setup is a non-goal and no webhook command is expected.

`tracker.pickup_labels` only controls whether active-state issues are eligible for new worker pickup. Exclude labels win over include labels. If `include` is omitted or empty, active-state issues remain pickup-eligible unless excluded. Do not use label changes to stop already running workers; move the Linear issue state to control interruption, review, and completion.
`tracker.provider.pickup_labels` only controls whether active-state issues are eligible for new worker pickup. Exclude labels win over include labels. If `include` is omitted or empty, active-state issues remain pickup-eligible unless excluded. Do not use label changes to stop already running workers; move the Linear issue state to control interruption, review, and completion.

`tracker.blocker_check_states` defaults to the first active state (`Todo` in this example). In those states, non-terminal blockers prevent dispatch. Linear blockers are derived only from inverse relations of type `blocks`; an explicit `blocker_check_states: []` disables this gate as an intentional repository-level divergence from the vendored Symphony specification. Planning remains disabled unless `planning_states` is explicitly configured.
`tracker.provider.blocker_check_states` defaults to the first active state (`Todo` in this example). In those states, non-terminal blockers prevent dispatch. Linear blockers are derived only from inverse relations of type `blocks`; an explicit `tracker.provider.blocker_check_states: []` disables this gate as an intentional repository-level divergence from the vendored Symphony specification. Planning remains disabled unless `tracker.provider.planning_states` is explicitly configured.

## Workpad Policy

Expand Down
Loading
Loading