Skip to content

feat(symphony): port tracker + orchestrator (dispatch stubbed)#3

Merged
Ddell12 merged 2 commits intodevfrom
phase-2-orchestrator
Apr 30, 2026
Merged

feat(symphony): port tracker + orchestrator (dispatch stubbed)#3
Ddell12 merged 2 commits intodevfrom
phase-2-orchestrator

Conversation

@Ddell12
Copy link
Copy Markdown
Owner

@Ddell12 Ddell12 commented Apr 30, 2026

Summary

  • Problem: Phase 1 landed only the symphony_dispatches schema and CRUD. There was no runtime — no Linear/GitHub polling, no slot accounting, no retry, no dispatch path.
  • Why it matters: This is the autonomous-dispatch brain Symphoney owned (which Archon does not). Without it, the consolidation plan can't move on to Phase 3 (real workflow execution).
  • What changed: Ported tracker + orchestrator + config from docs/symphoney-legacy/ into packages/symphony/. New startSymphonyService() + bun packages/symphony/src/cli/dev.ts entrypoint. dispatchIssue is a stub that writes a symphony_dispatches row with workflow_run_id = null and logs symphony.dispatch_skipped. Plus a follow-up commit fixing env-indirection on all tracker fields (surfaced by manual smoke).
  • What did NOT change (scope boundary): No real workflow execution (Phase 3). No HTTP API (Phase 3). No agent/codex/claude SDK integration — those layers are dropped, not ported. No approval gates (skipped per consolidation plan). No web UI (Phase 4).

UX Journey

Before

Linear / GitHub                  Archon                       Operator
───────────────                  ──────                       ────────
issue created                    (nothing watches trackers)
                                                              must invoke /workflow run
                                                              manually for every issue

After (Phase 2 stub)

Linear / GitHub                  Symphony orchestrator       symphony_dispatches table
───────────────                  ─────────────────────       ─────────────────────────
issue created ──poll(30s)──────▶ fetchCandidateIssues
                                 sortForDispatch
                                 eligibilityForDispatch
                                 dispatchIssue (stub) ──────▶ INSERT (workflow_run_id=NULL)
                                 log symphony.dispatch_skipped

[Phase 3 will replace the stub with executeWorkflow(...) and reconcile workflow_run.status]

Architecture Diagram

Before

@archon/symphony (Phase 1)
  └── src/db/dispatches.ts         # CRUD only
  └── migrations/022_symphony_dispatches.sql

After

@archon/symphony (Phase 2)
  ├── [+] src/tracker/{types,errors,normalize,linear,github}.ts
  ├── [+] src/orchestrator/{state,dispatch,retry,orchestrator}.ts
  ├── [+] src/config/{parse,coerce,defaults,snapshot}.ts
  ├── [+] src/service.ts            ===▶ @archon/core/db (singleton pool)
  ├── [+] src/cli/dev.ts            (Bun entrypoint)
  ├── [+] symphony.yaml.example     (user-copied to ~/.archon/symphony.yaml)
  └── [~] src/index.ts              (now exports startSymphonyService)
        ===▶ @archon/paths (createLogger)
        ===▶ @archon/core/db (IDatabase, getDatabase, SqliteAdapter)
        ===▶ graphql-request (Linear)
        ===▶ @octokit/rest (GitHub)

Connection inventory:

From To Status Notes
@archon/symphony/orchestrator @archon/symphony/db/dispatches new stub dispatchIssue calls insertDispatch
@archon/symphony/orchestrator @archon/core/db new via getDatabase() singleton
@archon/symphony/tracker/linear graphql-request new new direct dep
@archon/symphony/tracker/github @octokit/rest new new direct dep
@archon/symphony/service @archon/paths new getArchonHome() for default config path
symphony tests → @archon/core/db/adapters/sqlite SqliteAdapter new per-test temp db

Label Snapshot

  • Risk: risk: low
  • Size: size: L
  • Scope: dependencies (new package gains 2 npm deps; behavior is package-scoped)
  • Module: symphony:orchestrator, symphony:tracker, symphony:config

Change Metadata

  • Change type: feature
  • Primary scope: multi (new package + new deps; no other Archon packages modified)

Linked Issue

Validation Evidence (required)

$ bun run validate
# 11 packages type-check pass
# eslint clean (cache hit)
# prettier clean
# all packages parallel test:
#   @archon/symphony:test  43 pass / 0 fail / 143 expect() calls
#   (8 dispatches CRUD + 4 normalize + 13 dispatch + 9 snapshot
#    + 5 dispatch-loop + 4 multi-tracker)
# EXIT=0
  • Manual smoke (per Phase 2 exit criteria): Created Linear APP-292 in Symphony Smoke and a throwaway GitHub issue in Ddell12/archon-symphony-smoke-test#1. Ran bun packages/symphony/src/cli/dev.ts ~/.archon/symphony.yaml. Within ~500ms after startup, both symphony.dispatch_skipped events fired (Linear + GitHub). sqlite3 ~/.archon/archon.db showed two rows with workflow_run_id = NULL, status = pending. SIGTERM cleanly stopped the service. Test artifacts cleaned up.
  • Env-indirection fix verified: Re-ran the smoke with all-$VAR config; startup log shows projectSlug: "d0ef0b50e836", owner: "Ddell12", repo: "archon-symphony" resolved from env.

Security Impact (required)

  • New permissions/capabilities? Yes — Symphony reads Linear API + GitHub REST API on a 30s poll cycle.
  • New external network calls? Yeshttps://api.linear.app/graphql and https://api.github.com/repos/{owner}/{repo}/issues (configurable per tracker entry).
  • Secrets/tokens handling changed? YesLINEAR_API_KEY and GITHUB_TOKEN are read from env via $VAR indirection in YAML; never logged. ~/.archon/symphony.yaml is user-managed (gitignored equivalent — not in repo).
  • File system access scope changed? No — only writes to ~/.archon/archon.db via the existing IDatabase adapter.
  • Mitigation: The CLI dev entrypoint is opt-in (run only when needed). Phase 3 will add server-process integration with the same security posture as Archon's existing GitHub adapter.

Compatibility / Migration

  • Backward compatible? Yes — purely additive. Existing Archon callers (server, cli, web, etc.) do not import from @archon/symphony.
  • Config/env changes? Yes — Symphony reads ~/.archon/symphony.yaml (new file). Users opt in by copying packages/symphony/symphony.yaml.example. No existing config files touched.
  • Database migration needed? No — the symphony_dispatches migration shipped in Phase 1 (PR Phase 1: scaffold packages/symphony #1, commit 6bc4eea).

Human Verification (required)

  • Verified scenarios: End-to-end smoke against real Linear (Symphony Smoke project) + real GitHub (throwaway repo). Both trackers dispatched on the first poll. Re-smoke after env-indirection fix confirmed $VAR resolution on non-secret fields. SIGTERM clean shutdown.
  • Edge cases checked: Issue with state having no state_workflow_map entry → symphony.dispatch_no_workflow_for_state warn log, no DB write, no claim. Existing dispatch_key on second tick → eligibilityForDispatch "already completed" gate prevents re-insert. Same raw issue id on Linear+GitHub → distinct dispatch_keys, both rows land. Tracker fetch failure on one tracker → Promise.allSettled lets the other proceed.
  • What was not verified: Phase 3 territory — actual workflow execution, reconciliation against remote_agent_workflow_runs.status, HTTP API, web UI. No real GitHub Actions or Linear automation interactions tested.

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: @archon/symphony only. The package is currently not imported by any Archon-side caller, so merging this is a no-op for the running server/CLI/web.
  • Potential unintended effects: If a user opts in via the CLI dev entrypoint AND has a misconfigured ~/.archon/symphony.yaml, Symphony will hit Linear/GitHub APIs every 30s. Logs are loud (tracker_fetch_failed) so this is observable.
  • Guardrails/monitoring for early detection: Structured pino logs on every tick (symphony.service_started, symphony.dispatch_skipped, symphony.tracker_fetch_failed, symphony.dispatch_db_conflict).

Rollback Plan (required)

  • Fast rollback command/path: git revert 50ce84c8 e1be19b9 on dev. The Phase 1 schema/CRUD is unaffected.
  • Feature flags or config toggles: Symphony is dormant unless a user runs bun packages/symphony/src/cli/dev.ts. There is no auto-start.
  • Observable failure symptoms: symphony.service_started log absent → service didn't boot. Repeated symphony.tracker_fetch_failed → API/auth issue. Repeated symphony.dispatch_db_conflict → DB schema drift.

Risks and Mitigations

  • Risk: Phase 2 stub adds rows but never updates workflow_run_id. If a user runs the CLI dev entrypoint long-term, symphony_dispatches will accumulate.
    • Mitigation: Phase 3 wires the real workflow run lifecycle. Until then, state.completed (in-memory) prevents same-key re-dispatch within one process; the unique constraint blocks duplicates across restarts.
  • Risk: Two new direct deps (graphql-request@^7.2.0, @octokit/rest@^22.0.0).
    • Mitigation: @octokit/rest@^22.0.0 already in @archon/adapters; graphql-request matches symphoney-codex's pinned version and was the upstream choice for Linear.

🤖 Generated with Claude Code

Ddell12 and others added 2 commits April 30, 2026 16:16
Lands the Phase 2 deliverable from
docs/symphoney-legacy/plans/2026-04-30-archon-symphony-consolidation.md.

- Linear + GitHub trackers behind a shared Tracker interface
- Orchestrator with tick loop, slot accounting, per-state caps,
  retry backoff. Re-keyed all in-memory state on dispatch_key
  ('<tracker>:<identifier>') so cross-tracker raw-id collisions
  are impossible.
- Config snapshot rewrite: trackers as a list, no agent/codex/claude
  blocks (those move to per-workflow YAML in Archon proper).
  ~/.archon/symphony.yaml.example committed under packages/symphony/.
- dispatchIssue stub: writes a symphony_dispatches row with
  workflow_run_id = null and logs symphony.dispatch_skipped. Phase 3
  replaces the stub with executeWorkflow.
- Unit + integration tests, including a multi-tracker fixture
  (same raw id on Linear and GitHub -> distinct dispatch_keys, both
  dispatch, no UNIQUE violation). 41 symphony tests, 0 fail.
- New CLI dev entrypoint: bun packages/symphony/src/cli/dev.ts.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…secrets

The Phase 2 manual smoke surfaced this: symphony.yaml.example uses
$LINEAR_PROJECT_SLUG, $GH_OWNER, $GH_REPO, but only $api_key and $token
were resolved through env-indirection. Non-secret fields were passed
literally to the underlying SDKs ('$LINEAR_PROJECT_SLUG' as a slug → 0
candidates with no error).

Threads `resolveEnvIndirection` (renamed locally to `resolveString`) through
every string field in `buildTracker` and `buildCodebases`: project_slug,
endpoint, repository (Linear), owner, repo (GitHub), and codebase_id /
repository (codebases). Behavior for hardcoded literals is unchanged —
`resolveEnvIndirection` returns the trimmed input when the value is not a
$VAR pattern.

Two new tests in snapshot.test.ts cover the $VAR-everywhere case and the
"env var unset → fall back to default/null" case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 30, 2026

Warning

Rate limit exceeded

@Ddell12 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 3 minutes and 50 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 25e20ffa-6847-4bde-a46d-b77664e2ccce

📥 Commits

Reviewing files that changed from the base of the PR and between 95b6cd1 and 50ce84c.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (24)
  • packages/symphony/package.json
  • packages/symphony/src/cli/dev.ts
  • packages/symphony/src/config/coerce.ts
  • packages/symphony/src/config/defaults.ts
  • packages/symphony/src/config/parse.ts
  • packages/symphony/src/config/snapshot.test.ts
  • packages/symphony/src/config/snapshot.ts
  • packages/symphony/src/index.ts
  • packages/symphony/src/orchestrator/dispatch-loop.test.ts
  • packages/symphony/src/orchestrator/dispatch.test.ts
  • packages/symphony/src/orchestrator/dispatch.ts
  • packages/symphony/src/orchestrator/multi-tracker.test.ts
  • packages/symphony/src/orchestrator/orchestrator.ts
  • packages/symphony/src/orchestrator/retry.ts
  • packages/symphony/src/orchestrator/state.ts
  • packages/symphony/src/service.ts
  • packages/symphony/src/test/fake-tracker.ts
  • packages/symphony/src/tracker/errors.ts
  • packages/symphony/src/tracker/github.ts
  • packages/symphony/src/tracker/linear.ts
  • packages/symphony/src/tracker/normalize.test.ts
  • packages/symphony/src/tracker/normalize.ts
  • packages/symphony/src/tracker/types.ts
  • packages/symphony/symphony.yaml.example
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch phase-2-orchestrator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 3 minutes and 50 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

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.

1 participant