Skip to content
Closed
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
3 changes: 3 additions & 0 deletions .formatter.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
]
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ dist/
build/
*.egg-info/

# Elixir
_build/
deps/
.elixir_ls/
tmp/
/symphony

# JavaScript and frontend build artifacts
node_modules/
npm-debug.log*
Expand Down
65 changes: 58 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Caretta Symphony runs many Codex agents from Linear, across many repositories, with a live dashboard for the whole queue.

It is an independent Python implementation of OpenAI's draft [Symphony service specification](https://github.com/openai/symphony/blob/main/SPEC.md). OpenAI defined the core pattern: poll Linear, create an isolated workspace, run a coding agent, and reconcile the issue state. Caretta Symphony keeps that model and adds the operating layer we needed for a real multi-repo product.
It is an independent Elixir implementation of OpenAI's draft [Symphony service specification](https://github.com/openai/symphony/blob/main/SPEC.md). OpenAI defined the core pattern: poll Linear, create an isolated workspace, run a coding agent, and reconcile the issue state. Caretta Symphony keeps that model and adds the operating layer we needed for a real multi-repo product.

The core value is control: get the work out of the issue tracker, put each agent in the right repos, and see what all of them are doing while they run.

Expand Down Expand Up @@ -57,11 +57,17 @@ Raw Linear GraphQL mode is also supported.

Agents hand work to a review state. Symphony can move an issue to `Done` only after the required GitHub pull requests are merged into the configured base branch.

While an issue is in a review state, Symphony also polls Linear comments and linked GitHub PR feedback. New human-authored feedback moves the issue to the configured `rework_state` so Codex can continue from the existing workspace.

### Blocked issue escalation

When an agent or repository plan cannot continue without human input, Symphony creates or updates one Linear comment headed `## Symphony Blocked Escalation`. It mentions the Linear assignee when the tracker payload includes one; otherwise it uses `tracker.blocked_escalation_mentions`. A later human comment on the issue releases the block so the normal dispatcher can retry.

## Relationship to OpenAI Symphony

OpenAI's `openai/symphony` repository contains a language-agnostic spec and an experimental Elixir implementation.

Caretta Symphony is not an official OpenAI project. It does not include OpenAI's reference implementation code. It implements the public Symphony spec in Python and extends it for:
Caretta Symphony is not an official OpenAI project. It does not include OpenAI's reference implementation code. It implements the public Symphony spec in Elixir and extends it for:

- multi-repo product work
- repo planning before dispatch
Expand All @@ -73,7 +79,8 @@ Caretta Symphony is not an official OpenAI project. It does not include OpenAI's
## Install

```bash
python3 -m pip install -e ".[dev]"
mix deps.get
mix escript.build
```

## Run
Expand All @@ -89,8 +96,11 @@ tracker:
terminal_states: ["Closed", "Cancelled", "Canceled", "Duplicate", "Done"]
review_states: ["In Review", "Merging"]
handoff_state: In Review
rework_state: Rework
done_state: Done
merge_base_branch: dev
blocked_escalation_enabled: true
blocked_escalation_mentions: ["@operator"]
required_labels: ["codex"]
mcp_command: /Applications/Codex.app/Contents/Resources/codex app-server
workspace:
Expand All @@ -107,6 +117,30 @@ codex:
networkAccess: true
server:
port: 8765
self_healing:
enabled: false
base_branch: main
branch_prefix: codex/self-heal
workspace_root: ./.symphony-self-heal
stale_poll_ms: 120000
cooldown_ms: 900000
max_attempts: 3
validation_commands:
- mix format --check-formatted
- mix test
- mix escript.build
codex:
model: gpt-5.5
effort: xhigh
approval_policy: never
thread_sandbox: workspace-write
turn_sandbox_policy:
type: workspaceWrite
networkAccess: true
restart:
tmux_session: symphony-elixir
port: 8765
workflow_path: ./WORKFLOW.md
context:
coding:
enabled: true
Expand Down Expand Up @@ -152,13 +186,29 @@ Issue: {{ issue.identifier }} - {{ issue.title }}
Start Symphony:

```bash
symphony WORKFLOW.md --port 8765
./symphony WORKFLOW.md --port 8765
```

Open `http://127.0.0.1:8765` for the dashboard.

For a larger anonymized workflow, see [`WORKFLOW.linear-mcp.example.md`](WORKFLOW.linear-mcp.example.md). For macOS background operation, adapt [`launchd/com.symphony.linear-mcp.example.plist`](launchd/com.symphony.linear-mcp.example.plist).

## Self-healing watchdog

Symphony can run a separate local watchdog when `self_healing.enabled: true` is configured. The watchdog polls `GET /api/v1/state`; when Symphony is unreachable, degraded, or stale, it runs a high-reasoning Codex repair agent in `.symphony-self-heal/worktrees/<run-id>`, validates the repair, deploys the validated escript artifact locally, restarts the managed tmux session, pushes a `codex/self-heal/...` branch, opens a ready PR into `main`, and requests GitHub auto-merge without bypassing branch protection.

Local deployment intentionally comes from the validated artifact, not from `main`. The PR is the audit and sync path back to GitHub.

Useful commands:

```bash
./symphony WORKFLOW.md --watchdog
./symphony WORKFLOW.md --self-heal-once --reason "manual diagnosis"
./symphony WORKFLOW.md --restart-managed
```

On macOS, `scripts/symphony-managed.sh` and `launchd/com.caretta.symphony.watchdog.plist` provide a launcher path that goes through `/bin/zsh -lc`, which avoids direct launchd escript startup failures.

## Status API

When `server.port` is set, Symphony exposes:
Expand All @@ -173,13 +223,14 @@ The status surface is unauthenticated. Keep `server.host` bound to `127.0.0.1` u
## Testing

```bash
python3 -m pytest
mix test
```

## Project layout

- `symphony/` - service implementation
- `tests/` - pytest coverage
- `lib/symphony/` - service implementation
- `test/` - ExUnit coverage
- `mix.exs` - Elixir project metadata and escript configuration
- `docs/IMPLEMENTATION.md` - implementation notes and conformance summary
- `WORKFLOW.linear-mcp.example.md` - anonymized multi-repo Linear MCP workflow
- `launchd/` - example macOS launch agent
Expand Down
49 changes: 44 additions & 5 deletions WORKFLOW.linear-mcp.example.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,11 @@ tracker:
terminal_states: ["Closed", "Cancelled", "Canceled", "Duplicate", "Done"]
review_states: ["In Review", "Merging"]
handoff_state: In Review
rework_state: Rework
done_state: Done
merge_base_branch: dev
blocked_escalation_enabled: true
blocked_escalation_mentions: ["@operator"]
required_labels: ["codex"]
mcp_command: /Applications/Codex.app/Contents/Resources/codex app-server
mcp_server: codex_apps
Expand All @@ -27,6 +30,31 @@ codex:
networkAccess: true
server:
port: 8765
self_healing:
enabled: false
base_branch: main
branch_prefix: codex/self-heal
workspace_root: ./.symphony-self-heal
stale_poll_ms: 120000
cooldown_ms: 900000
max_attempts: 3
validation_commands:
- mix format --check-formatted
- mix test
- mix escript.build
codex:
command: /Applications/Codex.app/Contents/Resources/codex app-server
model: gpt-5.5
effort: xhigh
approval_policy: never
thread_sandbox: workspace-write
turn_sandbox_policy:
type: workspaceWrite
networkAccess: true
restart:
tmux_session: symphony-elixir
port: 8765
workflow_path: ./WORKFLOW.md
context:
coding:
enabled: true
Expand Down Expand Up @@ -61,12 +89,12 @@ repositories:
local_path: /opt/symphony/example-repos/client/desktop-runtime
remote_url: https://github.com/ExampleOrg/desktop-runtime.git
aliases: ["desktop-runtime", "electron shell", "overlay", "live workflow", "local transcription", "runtime orchestrator"]
description: Desktop shell and live-session runtime; local capture, transcript batching, in-app suggestions, and host-side provider calls.
description: Desktop shell and live in-call runtime; local capture, transcript batching, in-app suggestions, and host-side provider calls. Do not choose this for saved-call history pages, post-call detail tabs, or follow-up email drafts unless the issue explicitly says desktop overlay or live runtime.
- slug: ExampleOrg/web-console
local_path: /opt/symphony/example-repos/product/web-console
remote_url: https://github.com/ExampleOrg/web-console.git
aliases: ["web-console", "web app", "Next.js", "onboarding", "settings", "history", "calendar", "CRM", "in-app assistant"]
description: Customer-facing web console, authenticated routes, calendar/CRM settings, history views, folders, and browser-side gateway proxy.
aliases: ["web-console", "web app", "Next.js", "onboarding", "settings", "history", "history tab", "post-call", "saved call", "call details", "follow-up email", "email draft", "calendar", "CRM", "in-app assistant"]
description: Customer-facing web console, authenticated routes, calendar/CRM settings, saved-call history views, post-call detail tabs, follow-up email drafts/templates, folders, and browser-side gateway proxy.
- slug: ExampleOrg/shared-contracts
local_path: /opt/symphony/example-repos/libs/shared-contracts
aliases: ["shared-contracts", "shared schema", "shared types", "API contracts"]
Expand Down Expand Up @@ -157,6 +185,16 @@ Symphony only releases the issue when it leaves the configured active states. Yo
- Do not post separate completion summary comments.
- Final assistant message should report completed actions and blockers only. Do not ask the human to do routine follow-up work.

## Credentialed And Data Operations

- You run under the same macOS user context as Symphony. Before declaring missing non-GitHub auth, inspect configured local auth and secret sources without printing secret values:
- `which supabase && supabase projects list`
- `which aws && aws sts get-caller-identity`
- local repo `.env*` files, Vercel env, AWS Secrets Manager/SSM names, Supabase project links, and connected MCP tools when relevant.
- Never paste secret values into Linear, PRs, terminal summaries, or final messages. Load credentials into the command environment or an untracked temporary file only when required for the operation.
Comment on lines +190 to +194

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Don’t point the agent at raw .env* files.

Line 165 is too loose. In an unattended run, “inspect local repo .env* files” is how secret values end up in tool transcripts. Restrict this to existence checks and variable names only, with explicit redaction.

Suggested wording
-- local repo `.env*` files, Vercel env, AWS Secrets Manager/SSM names, Supabase project links, and connected MCP tools when relevant.
+- local repo `.env*` filenames and variable names only (never values), Vercel env names, AWS Secrets Manager/SSM secret names, Supabase project links, and connected MCP tools when relevant.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- You run under the same macOS user context as Symphony. Before declaring missing non-GitHub auth, inspect configured local auth and secret sources without printing secret values:
- `which supabase && supabase projects list`
- `which aws && aws sts get-caller-identity`
- local repo `.env*` files, Vercel env, AWS Secrets Manager/SSM names, Supabase project links, and connected MCP tools when relevant.
- Never paste secret values into Linear, PRs, terminal summaries, or final messages. Load credentials into the command environment or an untracked temporary file only when required for the operation.
- You run under the same macOS user context as Symphony. Before declaring missing non-GitHub auth, inspect configured local auth and secret sources without printing secret values:
- `which supabase && supabase projects list`
- `which aws && aws sts get-caller-identity`
- local repo `.env*` filenames and variable names only (never values), Vercel env names, AWS Secrets Manager/SSM secret names, Supabase project links, and connected MCP tools when relevant.
- Never paste secret values into Linear, PRs, terminal summaries, or final messages. Load credentials into the command environment or an untracked temporary file only when required for the operation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WORKFLOW.linear-mcp.example.md` around lines 162 - 166, Update the guidance
that currently says "inspect local repo `.env*` files" to explicitly restrict
checks to only the presence of `.env*` files and the names of variables (no file
contents or values), require explicit redaction of any discovered variable
values, and forbid opening or pasting raw `.env*` contents into agent/tool
transcripts; ensure the revised sentence references `.env*` and variable name
checks, instructs to only log existence and variable keys, and reiterates "Never
paste secret values" so tools like terminal summaries, Linear, or PRs never
include secret values.

- For Supabase/Postgres data migrations, a PR or migration script alone is not completion. Record dry-run output and either apply output or a concrete verified reason the data operation must not be run.
- If the issue explicitly asks to move, copy, backfill, delete, or repair production rows or cloud resources, do not move it to `In Review` just because code was written. Move it to `In Review` only after the operation has been executed and verified, or after the requester explicitly converts the issue to a code-only preparatory task.
Comment on lines +195 to +196

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Record evidence, not raw data-operation output.

Lines 167-168 and Line 202 ask for dry-run/apply output to be recorded. For production data work, that can include row contents, identifiers, or other customer data. The workflow should require redacted evidence instead.

Suggested wording
-- For Supabase/Postgres data migrations, a PR or migration script alone is not completion. Record dry-run output and either apply output or a concrete verified reason the data operation must not be run.
+- For Supabase/Postgres data migrations, a PR or migration script alone is not completion. Record redacted execution evidence only (command, timestamp, affected counts/checksums, verification query results) and either a verified apply summary or a concrete verified reason the data operation must not be run. Never paste raw row data or sensitive output.

-- For credentialed data or cloud operations, the requested operation is executed and verified, with dry-run/apply output or read-only verification recorded. A code-only helper script is not enough unless the requester explicitly asked only for a helper script.
+- For credentialed data or cloud operations, the requested operation is executed and verified, with redacted execution evidence or read-only verification recorded. A code-only helper script is not enough unless the requester explicitly asked only for a helper script.

Also applies to: 202-202

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@WORKFLOW.linear-mcp.example.md` around lines 167 - 168, Update the two
workflow bullets that currently require "dry-run/apply output" (the paragraph
starting "For Supabase/Postgres data migrations..." and the second instance at
line 202) to require "redacted evidence" instead of raw data: change wording to
insist on redacted dry-run/apply logs or summarized verification artifacts that
omit or mask row contents, identifiers, PII, and any customer data, and include
examples of acceptable evidence (screenshots with redactions, checksums/counts,
audit IDs, or anonymized diffs) and an explicit note that full raw outputs must
never be uploaded to the issue tracker; ensure both occurrences (the existing
migration bullet and the later rule about moving issues to In Review) reference
"redacted evidence" and the same acceptable-evidence examples so reviewers know
what to expect.


## State Routing

- `Backlog`: out of scope. Do not modify the issue. Stop.
Expand All @@ -175,20 +213,21 @@ Symphony only releases the issue when it leaves the configured active states. Yo
- add or refine the implementation plan,
- mirror any issue-provided validation/test-plan items as required checklist items,
- record a compact environment stamp with host, absolute workspace path, and short commit SHA when available.
4. Reproduce or inspect the current behavior enough to make the fix target explicit, then implement the requested change.
4. Reproduce or inspect the current behavior enough to make the fix target explicit, then implement the requested change. For credentialed data or cloud operations, prove the access path first using configured CLIs, local env files, or secret stores, then run the required dry-run/apply or read-only verification without exposing secrets.
5. Run validation appropriate to the changed surface. Treat issue-provided validation instructions as mandatory.
6. Commit and push only the Symphony-prepared branch recorded in `.symphony-workspace.json` when changes are ready. Never push an inherited source checkout branch; if the current branch differs from the expected branch, stop and report the mismatch. Open or update the PR and attach/link the PR to the Linear issue. Prefer Linear attachments/links; use the workpad only if attachments are unavailable.
7. Before handoff, sweep existing PR feedback and checks:
- address or explicitly respond to actionable comments,
- confirm checks/validation are green or document a real external blocker,
- refresh the workpad so plan, acceptance criteria, validation, commit, and PR status match reality.
8. Move the issue to `In Review` only after the handoff bar below is satisfied. If blocked by missing non-GitHub auth, permissions, or required tooling, document the blocker in the workpad and move to `In Review` with a concise unblock note. `Done` is reserved for Symphony's merge gate after every required PR has merged into `dev`.
8. Move the issue to `In Review` only after the handoff bar below is satisfied. If blocked by missing non-GitHub auth, permissions, or required tooling after checking the configured local CLIs/env/secret stores, document the blocker in the workpad, leave the issue active, and report the blocker in the final message. `Done` is reserved for Symphony's merge gate after every required PR has merged into `dev`.

## Handoff Bar Before `In Review`

- Workpad exists and is current.
- Implementation is complete for the issue scope.
- Required validation/test-plan items are complete and recorded.
- For credentialed data or cloud operations, the requested operation is executed and verified, with dry-run/apply output or read-only verification recorded. A code-only helper script is not enough unless the requester explicitly asked only for a helper script.
- Symphony-prepared branch is pushed and PR is linked on the issue.
- PR feedback has been swept; no known actionable comments remain unaddressed.
- PR checks are passing, or any failure is documented as an external blocker that cannot be resolved in-session.
Expand Down
Loading