Skip to content

feat(SYMPH-71): teach merge agent about GitHub merge queue#50

Closed
ericlitman wants to merge 95 commits intoOasAIStudio:mainfrom
mobilyze-llc:eric/symph-71-teach-merge-agent-about-github-merge-queue-eliminate-polling
Closed

feat(SYMPH-71): teach merge agent about GitHub merge queue#50
ericlitman wants to merge 95 commits intoOasAIStudio:mainfrom
mobilyze-llc:eric/symph-71-teach-merge-agent-about-github-merge-queue-eliminate-polling

Conversation

@ericlitman
Copy link

@ericlitman ericlitman commented Mar 22, 2026

Summary

  • Add Merge Queue Context section to the merge stage prompt explaining what gh pr merge returns when merge queue is active (queue added vs auto-merge enabled responses)
  • Remove redundant Step 1 (mergeability pre-check) since gh pr merge handles this internally
  • Replace sleep/poll loops with gh pr checks --watch --required --fail-fast — a single blocking call that waits for all checks
  • Add explicit DO NOT list — no retries, no --admin, no code modifications in merge stage
  • Add merge queue rejection path — if queue CI fails on rebased code, signal [STAGE_FAILED: rebase]
  • Preserve Step 2b (rebase brief) and Workpad sections exactly as-is

Addresses token waste from polling (SYMPH-24: 1.47M tokens, SYMPH-52: 2.07M tokens on sleep/check loops).

Changes applied to both:

  • pipeline-config/templates/WORKFLOW-template.md
  • pipeline-config/workflows/WORKFLOW-symphony.md

Tool Output > TypeScript

No errors (markdown-only change, no TypeScript files modified)

Tool Output > Tests

Test Files  4 failed | 39 passed | 1 skipped (44)
Tests  12 failed | 563 passed | 3 skipped (578)

All failures are pre-existing on main (same 4 failed test files, same 11-12 failures).
No test regressions from this change.

SAST Output

Not applicable — no TypeScript/executable code changes, only markdown template updates.

Verification

  • ✅ LiquidJS template renders with Merge Queue Context section present
  • ✅ All existing tests pass (pre-existing failures unchanged)
  • ✅ Step 2b (rebase brief) preserved word-for-word
  • ✅ Workpad section preserved word-for-word
  • [STAGE_COMPLETE] and [STAGE_FAILED: rebase] signal contract unchanged

Test plan

  • Verify LiquidJS template renders merge queue section (done, passes)
  • Verify pnpm vitest run has no new failures (done, all pre-existing)
  • Deploy to staging and run a merge stage with merge queue active — confirm agent does not poll

🤖 Generated with Claude Code

ericlitman and others added 30 commits March 16, 2026 20:29
Three extensions to symphony-ts for multi-model autonomous pipeline:

1. Runner abstraction (Task 5.1): Extract runner interface from Codex client,
   add ClaudeCodeRunner and GeminiRunner via Vercel AI SDK providers.
   Per-state runner selection via YAML config.

2. State machine (Task 5.2): Multi-stage workflows with typed transitions
   (agent/gate/terminal stages), per-stage config overrides, rework loops
   with configurable limits. Backward compatible — no stages = flat dispatch.

3. Ensemble gate (Task 5.3): Gate stages spawn parallel review agents,
   collect two-layer verdicts (JSON gate + plain text feedback), aggregate
   results, post to Linear. Supports human and automated gates.

77 new tests (217 total), typecheck clean.
Pipeline configuration for the founder's autonomous dev pipeline:

- WORKFLOW.md with YAML frontmatter: Linear tracker, 5-stage state machine
  (investigate → implement → review → merge → done), per-stage runner/model
  overrides, ensemble gate with Codex + Gemini reviewers
- 6 LiquidJS prompt templates: global clauses (headless mode, scope
  discipline, design references, verify lines, $BASE_URL), investigate,
  implement, review-adversarial, review-security, merge
- Hook scripts: after-create (git clone + install), before-run (fetch + rebase)
- validate.sh: config validation (YAML parsing, file checks, stage flow)
- Gemini runner: replace static import with lazy dynamic import() for
  ESM-only ai-sdk-provider-gemini-cli (require() returns empty module)
- Claude Code runner: add model ID mapping (claude-sonnet-4-5 → sonnet)
  so YAML config can use standard Anthropic model names
- Claude Code runner: add AbortController to generateText() calls for
  subprocess cleanup on close()
- Add @ai-sdk/provider + transitive deps to package.json
- Add integration smoke test (skipped by default, RUN_INTEGRATION=1)
- 9 new unit tests for model mapping, abort, and provider behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Claude Code runner was spawning without bypassPermissions, causing the
agent to stall waiting for interactive permission approval in headless
mode. Adding permissionMode: "bypassPermissions" fixes E2E dispatch.

Also adds WORKFLOW-flat.md for flat (no state machine) E2E testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…pace cleanup

Three blocking issues prevented multi-stage pipelines from completing:

1. Stage transitions never fired: advanceStage only runs in onWorkerExit
   after ALL turns complete. Added [STAGE_COMPLETE] sentinel detection in
   the turn loop for early exit when agents signal stage completion.

2. Continuation turns lost stage context: buildContinuationPrompt had no
   stageName parameter. Added stage-aware continuation prompts with
   per-stage constraints (investigate/implement/merge).

3. Stale workspaces from prior runs: afterCreate hook only fires on new
   directories. Added workspace cleanup on fresh dispatch (attempt=null)
   before createForIssue.

Also includes: stage-aware beforeRun hook (skip rebase on feature branches),
runner/model overrides from stage config, Linear comment posting for
investigation notes, and WORKFLOW-staged.md with full stage definitions.

234 tests passing, build clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Use trimEnd().endsWith() instead of includes() to prevent false early exit
when an agent mentions [STAGE_COMPLETE] conversationally rather than as the
final output signal.

Found by: Gemini (P1 → triaged as P2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two P1 findings from Codex R2:

1. Workspace cleanup now only fires on initial stage (investigate) of
   staged pipelines. Flat dispatch and non-initial stages preserve
   existing workspaces, preventing data loss after service restarts.

2. Gate stages now claim the issue before firing ensemble review,
   preventing duplicate gate dispatch on subsequent poll ticks.
   Gate handler errors release the claim for retry.

Found by: Codex (2 P1s)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…L thresholds

Reviewers were operating blind (only saw issue metadata, no code). Now
gate-handler fetches `git diff origin/main...HEAD` and includes it in
the reviewer prompt. Reviewer `prompt` field renders as inline Review
Focus instructions. Both Gemini reviewers in WORKFLOW-staged.md now have
explicit PASS/FAIL criteria to prevent overly strict rework loops.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ture failures

Gemini rate limits (429) were treated as code review FAILs, causing
infinite rework loops even when reviewers that ran approved the code.

Changes:
- runSingleReviewer retries up to 3 times with exponential backoff
- Infrastructure failures return verdict "error" instead of "fail"
- aggregateVerdicts ignores "error" results (only counts real pass/fail)
- All-error still returns FAIL (can't skip review entirely)
- formatGateComment shows "ERROR" label for infrastructure failures

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: stage machine execution — early exit, stage-aware prompts, workspace cleanup
B1: parseReviewerOutput now detects rate-limit text in 200 responses
(e.g. "You have exhausted your capacity") and returns verdict "error"
instead of "fail", preventing false rework loops.

B4: handleEnsembleGate now posts a Linear comment when max rework
attempts are exceeded, so the issue doesn't silently stall.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adversarial review P2: empty catch {} in escalation path swallowed
errors silently. Now logs console.warn with issue identifier and error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
fix: rate-limit text detection + escalation comment for ensemble gate
Add linear_state field to stage definitions and escalation_state to top-level
config. The orchestrator now updates Linear issue states on stage dispatch
(In Progress), gate entry (In Review), and escalation (Blocked). Includes
WORKFLOW-staged.md config and active_states fix for In Review.

253 tests, typecheck clean. E2E validated: MOB-28 Todo→In Progress→In Review→Done.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#4)

* feat: workpad system — structured progress tracking on Linear tickets

Phase 11: Add structured workpad comments to Linear issues with sync_workpad
dynamic tool for token-efficient updates, fileUpload media flow, and
stage-specific workpad behavior (investigate creates, implement updates,
merge finalizes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R1 adversarial review — 1 P2 + test coverage gaps

- Check commentUpdate.success in response (Codex finding)
- Add 3 tests: missing comment field, empty id, update success=false (Sonnet finding)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat: failure signal parsing — route agent failures by class (verify/review/spec/infra)

* fix: R1 adversarial review — escalation side effects + state corruption guard + empty message fallback

* fix: R1 adversarial review — 2 P1s + 1 P2

P1: Persist spec-failure escalations to tracker (updateIssueState + postComment)
P1: Persist review-escalation side effects when max rework exceeded
P2: Add test verifying reworkCount threading to spawnWorker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R2 adversarial review — prevent redispatch of escalated issues

* feat: max retry safety net + review rework routing (Wave 3)

- scheduleRetry bounded by maxRetryAttempts (default 5), escalates to Blocked
- Continuation retries exempt from limit (delayType: "continuation")
- handleReviewFailure routes through downstream gate's onRework
- onRework field on StageDefinition for YAML-driven rework targets
- Escalation fires side effects (updateIssueState + postComment)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R1 adversarial review — 2 P1s + 1 P2

P1: Agent runner breaks early on [STAGE_FAILED: ...] signals (Codex finding)
  - Without this, multi-turn agents could overwrite the failure signal,
    and the orchestrator would never see it.

P1: completed set no longer permanently blocks resume (Codex finding)
  - Issues in escalation state (Blocked) remain blocked.
  - Issues moved to any other active state (Resume, Todo) get cleared
    from completed and re-dispatched.

P2: lastCodexMessage empty string now filtered (Gemini finding)
  - Both lastTurnMessage and lastCodexMessage check for empty strings
    before being passed as agentMessage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R2 adversarial review — findDownstreamGate includes agent stages with onRework

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…nce) (#8)

* feat: failure signal parsing — route agent failures by class (verify/review/spec/infra)

* fix: R1 adversarial review — escalation side effects + state corruption guard + empty message fallback

* fix: R1 adversarial review — 2 P1s + 1 P2

P1: Persist spec-failure escalations to tracker (updateIssueState + postComment)
P1: Persist review-escalation side effects when max rework exceeded
P2: Add test verifying reworkCount threading to spawnWorker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R2 adversarial review — prevent redispatch of escalated issues

* feat: max retry safety net + review rework routing (Wave 3)

- scheduleRetry bounded by maxRetryAttempts (default 5), escalates to Blocked
- Continuation retries exempt from limit (delayType: "continuation")
- handleReviewFailure routes through downstream gate's onRework
- onRework field on StageDefinition for YAML-driven rework targets
- Escalation fires side effects (updateIssueState + postComment)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R1 adversarial review — 2 P1s + 1 P2

P1: Agent runner breaks early on [STAGE_FAILED: ...] signals (Codex finding)
  - Without this, multi-turn agents could overwrite the failure signal,
    and the orchestrator would never see it.

P1: completed set no longer permanently blocks resume (Codex finding)
  - Issues in escalation state (Blocked) remain blocked.
  - Issues moved to any other active state (Resume, Todo) get cleared
    from completed and re-dispatched.

P2: lastCodexMessage empty string now filtered (Gemini finding)
  - Both lastTurnMessage and lastCodexMessage check for empty strings
    before being passed as agentMessage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: R2 adversarial review — findDownstreamGate includes agent stages with onRework

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: prevent completed issues from being re-dispatched after merge

Council Review Run 3 found that merged issues get re-dispatched because:
1. The merge/done stages had no linear_state, so the issue stayed "In Review"
   on Linear after completing the pipeline
2. The resume logic cleared the completed flag for ANY non-escalation active
   state, including "In Review"

Two fixes (defense in depth):
- Tighten resume guard: only "Resume" and "Todo" states clear completed flag
- Add linear_state: Done to the terminal stage so issues move to "Done" on
  Linear when the pipeline finishes
- advanceStage now fires updateIssueState for terminal stages with linearState

7 new regression tests covering all resume-guard scenarios and terminal
linearState behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: council R1 — add updateIssueState to dispatchIssue terminal path

Council review found that the gate-to-terminal path in dispatchIssue()
was missing the updateIssueState call, making the linear_state: Done
config dead code for gate-based workflows. Every successfully merged
issue hits this path (gate approval → continuation → dispatchIssue →
terminal short-circuit) and would never update the tracker to "Done".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Phase 17 — stall timeout, heartbeat, turn_failed race, graceful shutdown (#7)

* fix: Phase 17 — stall timeout, heartbeat, turn_failed race, graceful shutdown

- Add stall_timeout_ms: 900000 (15min) to WORKFLOW-staged.md config
- Add workspace file-change heartbeat to ClaudeCodeRunner (polls dir mtime every 5s, emits activity_heartbeat events to reset stall timer)
- Fix turn_failed race in agent runner (check lastTurn.status after signal checks)
- Fix graceful shutdown race in runtime-host (move resolveExit after waitForIdle, add pendingExitCode tracking, add agent_runner_starting/error diagnostic logs)

328 tests passing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: council R1 — heartbeat polls .git/index, guard catches all non-completed, fix test

- Heartbeat: poll .git/index mtime instead of workspace root dir (detects
  git staging/commits, not just root-level file creation)
- Guard: change `status === "failed"` to `status !== "completed"` to also
  catch `cancelled` turns
- Test: fix misleading test that used `completed` status when claiming to
  test `failed` + STAGE_FAILED interaction

Council review: 3 P2s found, 3 fixed. Cross-exam eliminated 3/10 findings.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>

* fix: Phase 20 — stress test bugs (heartbeat, merge race, hook resilience)

1. Heartbeat blind spot: ClaudeCodeRunner now watches workspace dir mtime
   alongside .git/index so review agents that never touch git still emit
   heartbeats and avoid stall timeout kills.

2. Merge abort race: reconcileRunningIssues() now skips terminal_state
   stop requests for workers in the final active stage (whose onComplete
   target is terminal). Prevents killing merge agents mid-flight.

3. beforeRun hook resilience: git fetch retries 3x with git lock
   handling, rebase is best-effort with abort fallback. Hook no longer
   fails the stage on git contention.

4. Stall timeout bumped from 15min to 30min.

332 tests (4 new), typecheck clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Closes open PRs and deletes remote branches when symphony-ts
removes a workspace, preventing orphaned branches.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…#9)

Extend CodexUsage interface with optional cacheReadTokens, cacheWriteTokens,
noCacheTokens, and reasoningTokens fields. Extract these from the AI SDK
provider's inputTokenDetails/outputTokenDetails. Add the 4 new fields to
LOG_FIELDS, emit them conditionally in structured logs, accumulate them in
LiveSession and CodexTotals, and add tests verifying extraction, accumulation,
and absence when the provider doesn't report them.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… token accounting (#10)

Emit a stage_completed log entry when a worker finishes, capturing
accumulated LiveSession token counts (input, output, total, cache, reasoning),
turn count, duration, and stage name. Adds stage_name and turns_used to
LOG_FIELDS and stage_completed to ORCHESTRATOR_EVENTS.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
… 28)

Restructured Linear into 7 teams (SYMPH, JONY, HSDATA, HSUI, HSMOB, STICK,
HOUSE) with distinct issue prefixes per product. Each team has a Pipeline
project with unique slugId. Added per-product WORKFLOW files, a WORKFLOW
template for onboarding new products, and a launcher script.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…rement (#11)

- Add promptChars and estimatedPromptTokens fields to AgentRunnerEvent
- Measure rendered prompt size in runner.ts turn loop before startSession/continueTurn
- Add turn_number, prompt_chars, estimated_prompt_tokens to LOG_FIELDS
- Log turn_number, prompt_chars, estimated_prompt_tokens in logAgentEvent (runtime-host.ts)
- Add tests verifying prompt size fields are correct and turn 1 > turn 2 for long templates
- Add tests verifying new fields appear in structured log entries

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…l shutdown with worker abort and bounded timeout (#12)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…summary (#13)

- Add 'shutdown_complete' to ORCHESTRATOR_EVENTS in src/domain/model.ts
- Add 'workers_aborted' and 'timed_out' to LOG_FIELDS in src/logging/fields.ts
- Change abortAllWorkers() to return the count of workers aborted
- Track shutdownStart, workersAborted, and timedOut flag in shutdown()
- Emit shutdown_complete log event after Promise.allSettled with workers_aborted, timed_out, and duration_ms fields
- Add two new tests: shutdown_complete logged with correct fields, and timed_out=true when timeout fires

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…atch and reconciliation summary (#14)

- Add poll_tick_completed to ORCHESTRATOR_EVENTS in src/domain/model.ts
- Add dispatched_count, running_count, reconciled_stop_requests to LOG_FIELDS in src/logging/fields.ts
- Extend PollTickResult with runningCount in src/orchestrator/core.ts
- Add duration timing around pollOnce() in runPollCycle()
- Emit poll_tick_completed info log with dispatched_count, running_count, reconciled_stop_requests, duration_ms
- Add tests for poll_tick_completed event emission and dispatched_count accuracy

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…atch and reconciliation summary (#15)

- poll_tick_completed already in ORCHESTRATOR_EVENTS (src/domain/model.ts)
- dispatched_count, running_count, reconciled_stop_requests in LOG_FIELDS (src/logging/fields.ts)
- logPollCycleResult() emits poll_tick_completed with dispatched_count, running_count,
  reconciled_stop_requests, duration_ms (src/orchestrator/runtime-host.ts)
- Duration timing around pollOnce() in runPollCycle()
- PollTickResult includes dispatchedIssueIds, runningCount, stopRequests (src/orchestrator/core.ts)
- Tests: poll_tick_completed event logged after successful poll; dispatched_count reflects dispatched issues
- Update conformance-test-matrix.md to document poll_tick_completed observability coverage

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…vent (#16)

Add five new fields to the stage_completed structured log event that give
operators a single-event view of total token cost per pipeline stage:

- total_input_tokens: sum of per-turn input tokens across the stage
- total_output_tokens: sum of per-turn output tokens across the stage
- total_cache_read_tokens: accumulated cache-read tokens across all turns
- total_cache_write_tokens: accumulated cache-write tokens across all turns
- turn_count: number of turns executed in the stage

New LiveSession fields codexTotalInputTokens and codexTotalOutputTokens
accumulate turn-level deltas. Per-turn deltas are computed correctly by
resetting lastReported* counters on session_started, so each turn's
absolute counter starts from zero for delta computation.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(SYMPH-8): add accumulator fields and summation logic for stage-level token totals

Add totalStageInputTokens, totalStageOutputTokens, totalStageTotalTokens,
totalStageCacheReadTokens, and totalStageCacheWriteTokens to LiveSession.
Accumulate turn deltas into these fields in applyCodexEventToSession().
Add single-turn, multi-turn, and zero-turn tests.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(SYMPH-8): add missing totalStage accumulator fields to runtime-host test fixtures

TypeScript compilation was failing because LiveSession object literals
in runtime-host tests were missing the new totalStage* fields added
to the LiveSession interface.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…#19)

Update total_input_tokens, total_output_tokens to use totalStage* accumulator
fields. Add total_total_tokens. Make total_cache_read_tokens and
total_cache_write_tokens conditional (omitted when zero) using totalStage*
accumulators. Existing input_tokens, output_tokens, total_tokens preserve
last-turn semantics.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…field (#20)

Add StageRecord and ExecutionHistory interfaces to model.ts. Add
issueExecutionHistory: Record<string, ExecutionHistory> to OrchestratorState
and initialize it as {} in createInitialOrchestratorState. Add a thin
scripts/test.mjs wrapper to translate --grep to vitest's -t flag so
mocha-compatible verify commands work.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
ericlitman and others added 20 commits March 21, 2026 20:14
…#67)

Change investigate.model and implement.model from claude-sonnet-4-5 to
claude-opus-4-6 in both WORKFLOW-symphony.md and WORKFLOW-template.md.
The review stage already uses Opus; merge stays on Sonnet for its
mechanical 3-command operation.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Add a "Pipeline" column to the running sessions table that shows total
elapsed time from first_dispatched_at to generated_at for multi-stage
issues. Single-stage issues (first_dispatched_at equals started_at) show
"—" in the Pipeline column.

Updates both the server-rendered HTML and the client-side SSE render
function. Adds formatPipelineTime helper to dashboard-render.ts and a
corresponding client-side JS function in the embedded script.

- Add <th>Pipeline</th> after "Runtime / turns" in table header
- Add <col> to colgroup (7 columns total)
- Add formatPipelineTime() server-side TypeScript helper
- Add formatPipelineTime() client-side JavaScript function
- Update colspan from 6 → 7 in empty state and detail rows
- Add unit tests in tests/observability/dashboard-render.test.ts
- Update colspan assertion in dashboard-server.test.ts

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat(SYMPH-43): add post-creation verification layer to freeze-and-queue.sh

Adds verify_issue_creation() helper that queries a Linear issue by ID after
each issueCreate/issueUpdate mutation and confirms project.slugId matches
the expected PROJECT_SLUG and (for sub-issues) parent.id matches PARENT_ID.
Logs warnings on mismatch; never exits. Called at all 4 creation sites:
trivial issue, parent update, parent create, and sub-issue create loop.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(SYMPH-43): move verify_issue_creation() before first call site

Fix P1 bash ordering bug: verify_issue_creation() was defined at line 894
but called at lines 161, 673, 739, and 839. Bash requires functions to be
defined (executed) before their call sites are reached at runtime. Moves
the function definition to line 51, before the trivial mode block where
the first call site lives.

Also removes INVESTIGATION-BRIEF.md (out-of-scope file from previous commit).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(SYMPH-56): implement fast-track label-based stage routing

Add FastTrackConfig interface and fastTrack field to StagesConfig, parse
fast_track YAML config key, validate fast_track.initial_stage references a
defined stage, and apply fast-track routing in dispatchIssue() when an issue
carries the configured label and has no cached stage. Update all existing
StagesConfig test stubs to include fastTrack: null and add 7 new unit tests
covering all specified scenarios. Enable fast_track config in WORKFLOW-symphony.md
and WORKFLOW-template.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: fix biome formatting in core.test.ts

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The blockedBy enforcement fix (removing the todo-only guard) was already
applied in commit 72ec271. This commit adds an explicit test for the
Resume state scenario mentioned in SYMPH-50 — verifying that issues in
Resume state are also rejected when their blockers are non-terminal.

SYMPH-50

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: convert all timestamps from UTC to US Eastern

All user-facing timestamps (dashboard, structured logs, session metrics)
now display in America/New_York timezone instead of UTC. Added a shared
formatEasternTimestamp() utility that handles DST transitions.

The only exception is linear-normalize.ts which must stay UTC for
Linear API compatibility.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: lint issues (import order, Number.parseInt)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
… Todo after relations (#73)

- Change sub-issue creation guard from TODO_STATE_ID to BACKLOG_STATE_ID so
  all sub-issues land in Backlog state at creation time
- After relation-creation loops complete, identify unblocked sub-issues (those
  whose identifier does not appear on the right side of any pair in
  CREATED_RELATIONS) and promote them to Todo via issueUpdate
- Blocked sub-issues remain in Backlog; logs show which were promoted vs blocked

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
…ticks (#75)

Replace $(cat "$SPEC_TMPFILE") with direct variable passing ($SPEC_CONTENT
and $sub_body) in all 6 GraphQL mutation call sites. The $(cat ...) pattern
inside double-quoted -v flags caused bash to interpret backticks in spec
content as command substitution, hanging the shell.

Also move function definitions (resolve_team_from_project, resolve_all_states,
LINEAR_CLI, state globals) before the --trivial code path so they are defined
before being called in all execution paths.

Remove unnecessary SPEC_TMPFILE temp file (no longer needed since descriptions
are passed directly via variables).

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(SYMPH-60): add auto-close parent issue on terminal state

After marking a sub-issue as Done (or Cancelled), query its parent issue
via Linear GraphQL. If the parent exists and ALL sibling sub-issues are
in terminal states, transition the parent to Done. Best-effort — errors
are logged, never block the pipeline.

- Add LINEAR_ISSUE_PARENT_AND_SIBLINGS_QUERY in linear-queries.ts
- Add checkAndCloseParent method on LinearTrackerClient
- Add optional autoCloseParentIssue callback on OrchestratorCoreOptions
- Fire callback in advanceStage() after terminal updateIssueState
- Wire callback in runtime-host.ts using LinearTrackerClient method
- Add 4 tests covering: fires on terminal, skips non-terminal, error
  resilience, and missing callback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(SYMPH-60): add fastTrack: null to test stage configs for StagesConfig compat

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…#76)

Add "State: Backlog (promoted to Todo after relations)" to sub-issue
dry-run output and update the summary line to mention the Backlog→Todo
flow. Trivial mode output remains unchanged.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
)

Restructure freeze-and-queue.sh sub-issue creation to create issues
directly in Todo state with sequential blocking relations added
immediately after each sub-issue, eliminating the Backlog staging
and promotion loop.

Changes:
- Create sub-issues using TODO_STATE_ID instead of BACKLOG_STATE_ID
- Iterate creation loop via SORTED_INDICES for priority ordering
- Add sequential blockedBy relation immediately after each sub-issue
  (except the first, which is immediately dispatchable)
- Move create_blocks_relation() and verify_issue_creation() function
  definitions before the creation loop
- Keep file-overlap relations as a second pass after creation
- Remove BACKLOG_STATE_ID fallback for sub-issues (lines 600-611)
- Remove "Moving unblocked sub-issues to Todo" promotion loop
- Keep parent issue lifecycle unchanged (Draft creation, Backlog transition)

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(SYMPH-66): add rebase failure class and orchestrator routing

Add "rebase" to FAILURE_CLASSES and update the STAGE_FAILED regex to
recognize [STAGE_FAILED: rebase] signals. Add handleRebaseFailure()
private method to core.ts that mirrors the first half of
handleReviewFailure() — checks current stage has onRework, calls
reworkGate(), posts a rebase comment, and schedules continuation retry.
Add formatRebaseComment() to gate-handler.ts. Add routing case in
handleFailureSignal().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* style(SYMPH-66): fix biome formatting in model.ts and test files

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…#78)

* feat(SYMPH-67): add rebase flow support to WORKFLOW prompts and hooks

Add merge conflict detection and rebase rework flow to the pipeline:
- Add on_rework: implement and max_rework: 2 to merge stage YAML config
- Update merge stage prompt to check PR mergeability and write REBASE-BRIEF.md on conflicts
- Update implement stage rework prompt to handle rebase rework via REBASE-BRIEF.md
- Add REBASE-BRIEF.md import block to before_run hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(SYMPH-67): restore fast_track config blocks removed out-of-scope

Restores the fast_track configuration in both WORKFLOW-template.md
(commented-out example) and WORKFLOW-symphony.md (active config for
trivial-labeled issues). These were accidentally removed in the
initial implementation but are not part of the SYMPH-67 spec.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Update the dry-run section to show sub-issues created in Todo state
with interleaved sequential blocking relations shown immediately after
each sub-issue, rather than in a separate section. File-overlap
relations remain as a separate second-pass section.

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* feat(SYMPH-70): Replace turn history with recent activity feed

Add RecentActivityEntry type and ring buffer (max 10) to LiveSession.
Populate from approval_auto_approved events by extracting tool name and
context (file basename for Read/Edit/Write/Glob, truncated command for
Bash, pattern for Grep). Pass through to RuntimeSnapshotRunningRow and
render in dashboard detail panel with relative timestamps.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: apply biome formatting to session-metrics and recent-activity test

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add recent_activity field to dashboard-render test fixture

The dashboard-render.test.ts fixture was missing the recent_activity
field added by SYMPH-70 to RuntimeSnapshotRunningRow.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…iled counts (#81)

* feat(SYMPH-69): Add issue title, fix UTC timestamps, add completed/failed counts

- Add issue_title field to RuntimeSnapshotRunningRow, populated from
  entry.issue.title
- Format last_event_at through formatEasternTimestamp() instead of
  passing raw UTC strings from Codex events
- Add completed and failed counts to RuntimeSnapshot.counts, computed
  from issueExecutionHistory final stage outcomes
- Add Completed and Failed metric cards to dashboard header (both
  server-side HTML and client-side JS render())
- Display issue title below identifier in running sessions table
- Verify pipeline total time works for both single and multi-stage
  issues via first_dispatched_at fallback

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(SYMPH-69): resolve rebase conflicts and align with format-timestamp.ts

- Remove duplicate formatEasternTimestamp from runtime-snapshot.ts (moved
  to format-timestamp.ts by earlier PR #72)
- Add invalid-date guard (returns "n/a") to format-timestamp.ts
- Fix non-null assertion → use Array.at(-1) with optional chaining
- Update tests to expect ISO-8601 Eastern format instead of human-readable
- Add missing issue_title and completed/failed fields to test fixtures

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* Add sops+age encrypted secrets (.sops.yaml + .env.enc)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* ops: add HOME, NODE_ENV, SoftResourceLimits to plist; bump ThrottleInterval to 60s

- symphony-ctl generate_plist: add HOME env var (launchd doesn't inherit it),
  NODE_ENV=production, SoftResourceLimits/NumberOfFiles 4096
- ThrottleInterval 30→60 for crash restart breathing room
- Example plist updated to match for reference consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Changes default dashboard bind address from 127.0.0.1 to 0.0.0.0 so the
dashboard is accessible from other machines on the network. Needed for
production server deployment where the dashboard is accessed remotely.
* fix: bind dashboard server to 0.0.0.0 for network access

Changes default dashboard bind address from 127.0.0.1 to 0.0.0.0 so the
dashboard is accessible from other machines on the network. Needed for
production server deployment where the dashboard is accessed remotely.

* fix: resolve symlinks in symphony-ctl SCRIPT_DIR

When symphony-ctl is invoked via a symlink, SCRIPT_DIR resolved to the
symlink directory instead of the real script directory. Wrap BASH_SOURCE
with realpath before dirname.
When symphony-ctl is symlinked (e.g. /usr/local/bin/symphony-ctl ->
ops/symphony-ctl), BASH_SOURCE[0] returns the symlink path, not the
target. This caused SYMPHONY_ROOT to resolve to /usr/local instead of
the project directory, which meant .env was never found and the plist
was installed without LINEAR_API_KEY — causing a silent crash-loop.

Use realpath to resolve the symlink before computing SCRIPT_DIR.

Co-authored-by: Claw Dilize <clawdilize@pro16.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Bug 1: Change all ID! to String! in GraphQL mutation variable
  declarations to match Linear's schema expectations
- Bug 2: Refactor create_blocks_relation() to use parameterized
  variables (-v flags) with single-quoted heredoc instead of
  inlining UUIDs via bash interpolation. Also parameterize the
  parent→Backlog transition mutation.
- Bug 3: Move create_blocks_relation() and verify_issue_creation()
  definitions above the trivial mode block so they are defined
  before being called

Co-authored-by: Claw Dilize <clawdilize@pro16.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
@ericlitman ericlitman force-pushed the eric/symph-71-teach-merge-agent-about-github-merge-queue-eliminate-polling branch from f4dc013 to 6719615 Compare March 22, 2026 19:06
@ericlitman ericlitman changed the title feat(SYMPH-71): teach merge agent about GitHub merge queue feat(SYMPH-71): teach merge agent about GitHub merge queue — eliminate polling waste Mar 22, 2026
ericlitman and others added 3 commits March 22, 2026 19:09
…CC provider (#87)

* feat(SYMPH-73): scaffold Slack bot module wiring Chat SDK → AI SDK → CC provider

Add slack-bot module that receives channel messages via @chat-adapter/slack,
invokes Claude Code via ai-sdk-provider-claude-code streamText, and posts
threaded replies with reaction indicators (eyes → checkmark).

- src/slack-bot/types.ts: ChannelProjectMap, SlackBotConfig, SessionMap types
- src/slack-bot/handler.ts: message handler with reaction lifecycle and paragraph chunking
- src/slack-bot/index.ts: Chat instance setup with SlackAdapter + MemoryStateAdapter
- .env.example: documents required environment variables
- tests/slack-bot/handler.test.ts: 11 tests for CC invocation, threading, chunking
- tests/slack-bot/reactions.test.ts: 3 tests for reaction lifecycle

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* feat(SYMPH-76): fix 3 bugs in freeze-and-queue.sh

- Bug 1: Change all ID! to String! in GraphQL mutation variable
  declarations to match Linear's schema expectations
- Bug 2: Refactor create_blocks_relation() to use parameterized
  variables (-v flags) with single-quoted heredoc instead of
  inlining UUIDs via bash interpolation. Also parameterize the
  parent→Backlog transition mutation.
- Bug 3: Move create_blocks_relation() and verify_issue_creation()
  definitions above the trivial mode block so they are defined
  before being called

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(SYMPH-73): resolve biome lint and format errors blocking CI

- Format package.json files array to single line
- Fix useTemplate lint error in handler.ts (concat → template literal)
- Reorder imports in index.ts for organizeImports compliance
- Replace generator failingStream (useYield error) with plain async iterable object
- Reformat test files to pass biome line-length checks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claw Dilize <clawdilize@pro16.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Remove projectId from sub-issue issueCreate mutations and defer it to
a batch issueUpdate pass after blocking relations are verified. Add
verify_issue_creation_parent_only() for sub-issue parent checks,
verify_blocking_relations() to confirm inverseRelations direction,
and assign_project_to_sub_issues() for post-verification assignment.
Update dry-run output to describe the deferred flow.

Co-authored-by: Claw Dilize <clawdilize@pro16.local>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ths (#90)

After migrating to launchd, logs moved to ~/Library/Logs/symphony/<product>/
but analyze, cleanup, and usage text still referenced /tmp/symphony-*.

Co-authored-by: Claw Dilize <clawdilize@pro16.local>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ericlitman ericlitman force-pushed the eric/symph-71-teach-merge-agent-about-github-merge-queue-eliminate-polling branch from f053d28 to 8b43f1e Compare March 22, 2026 21:12
@ericlitman ericlitman changed the title feat(SYMPH-71): teach merge agent about GitHub merge queue — eliminate polling waste feat(SYMPH-71): teach merge agent about GitHub merge queue Mar 22, 2026
Replace merge stage prompt to eliminate polling waste:
- Add Merge Queue Context section explaining gh pr merge responses
- Remove redundant mergeability pre-check (Step 1)
- Replace sleep/poll loops with gh pr checks --watch --required --fail-fast
- Add explicit DO NOT list (no retries, no --admin, no code mods)
- Add merge queue rejection path signaling [STAGE_FAILED: rebase]
- Preserve Step 2b (rebase brief) and Workpad sections unchanged

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ericlitman ericlitman force-pushed the eric/symph-71-teach-merge-agent-about-github-merge-queue-eliminate-polling branch from 8b43f1e to 89ad5a9 Compare March 22, 2026 21:25
@ericlitman ericlitman closed this Mar 22, 2026
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