File: README.md
Symphony
Symphony turns project work into isolated, autonomous implementation runs, allowing teams to manage
work instead of supervising coding agents.
In this demo video, Symphony monitors a Linear board for work and spawns agents to handle the tasks. The agents complete the tasks and provide proof of work: CI status, PR review feedback, complexity analysis, and walkthrough videos. When accepted, the agents land the PR safely. Engineers do not need to supervise Codex; they can manage the work at a higher level.
[!WARNING]
Symphony is a low-key engineering preview for testing in trusted environments.
Running Symphony
Requirements
Symphony works best in codebases that have adopted
harness engineering. Symphony is the next step --
moving from managing coding agents to managing work that needs to get done.
Option 1. Make your own
Tell your favorite coding agent to build Symphony in a programming language of your choice:
Implement Symphony according to the following spec:
https://github.com/openai/symphony/blob/main/SPEC.md
Option 2. Use our experimental reference implementation
Check out elixir/README.md for instructions on how to set up your environment
and run the Elixir-based Symphony implementation. You can also ask your favorite coding agent to
help with the setup:
Set up Symphony for my repository based on
https://github.com/openai/symphony/blob/main/elixir/README.md
License
This project is licensed under the Apache License 2.0.
File: elixir/docs/logging.md
Logging Best Practices
This guide defines logging conventions for Symphony so Codex can diagnose failures quickly.
Goals
- Make logs searchable by issue and session.
- Capture enough execution context to identify root cause without reruns.
- Keep messages stable so dashboards/alerts are reliable.
Required Context Fields
When logging issue-related work, include both identifiers:
issue_id: Linear internal UUID (stable foreign key).issue_identifier: human ticket key (for exampleMT-620).
When logging Codex execution lifecycle events, include:
session_id: combined Codex thread/turn identifier.
Message Design
- Use explicit
key=valuepairs in message text for high-signal fields. - Prefer deterministic wording for recurring lifecycle events.
- Include the action outcome (
completed,failed,retrying) and the reason/error when available. - Avoid logging large payloads unless required for debugging.
Scope Guidance
AgentRunner: log start/completion/failure with issue context, plussession_idwhen known.Orchestrator: log dispatch, retry, terminal/non-active transitions, and worker exits with issue context. Includesession_idwhenever running-entry data has it.Codex.AppServer: log session start/completion/error with issue context andsession_id.
Checklist For New Logs
- Is this event tied to a Linear issue? Include
issue_idandissue_identifier. - Is this event tied to a Codex session? Include
session_id. - Is the failure reason present and concise?
- Is the message format consistent with existing lifecycle logs?
File: elixir/docs/token_accounting.md
Codex Token Accounting
This document explains how Codex reports token usage through the app-server protocol and how Symphony should account for it.
It is based on the current Codex source in codex-rs, especially:
app-server/README.mdprotocol/src/protocol.rsapp-server/src/bespoke_event_handling.rsapp-server-protocol/src/protocol/v2.rsexec/src/event_processor_with_jsonl_output.rsstate/src/extract.rs
Short Version
last_token_usagemeans "the latest increment".total_token_usagemeans "the cumulative total so far".thread/tokenUsage/updatedis the live streaming notification for token usage.turn/completedcarries final turn state, and turn-level usage is exposed separately from the live thread token stream.- Generic
usagefields are event-specific. Do not assume everyusagepayload is a cumulative thread total.
Primary Source Semantics
Codex defines TokenUsageInfo like this:
pub struct TokenUsageInfo {
pub total_token_usage: TokenUsage,
pub last_token_usage: TokenUsage,
pub model_context_window: Option,
}The important behavior is in append_last_usage:
pub fn append_last_usage(&mut self, last: &TokenUsage) {
self.total_token_usage.add_assign(last);
self.last_token_usage = last.clone();
}That gives the core semantics:
last_token_usage: the newest chunk of usage that was just addedtotal_token_usage: the accumulated total after adding that chunk
This is the most important accounting rule in the Codex source.
Event Types
`codex/event/token_count`
Codex core emits token count events containing TokenUsageInfo.
These events can carry:
info.total_token_usageinfo.last_token_usageinfo.model_context_window
Symphony sees these events wrapped inside the app-server message stream.
Meaning:
total_token_usageis an absolute cumulative snapshotlast_token_usageis the delta that produced that snapshot
`thread/tokenUsage/updated`
The app-server converts token count events into a dedicated thread-scoped notification:
let notification = ThreadTokenUsageUpdatedNotification {
thread_id: conversation_id.to_string(),
turn_id,
token_usage,
};ThreadTokenUsage is defined as:
pub struct ThreadTokenUsage {
pub total: TokenUsageBreakdown,
pub last: TokenUsageBreakdown,
pub model_context_window: Option,
}And it is populated directly from TokenUsageInfo:
impl From<CoreTokenUsageInfo> for ThreadTokenUsage {
fn from(value: CoreTokenUsageInfo) -> Self {
Self {
total: value.total_token_usage.into(),
last: value.last_token_usage.into(),
model_context_window: value.model_context_window,
}
}
}Meaning:
thread/tokenUsage/updatedis the canonical live notification for token usagetokenUsage.totalis an absolute thread totaltokenUsage.lastis the latest increment that produced that total
The app-server README is explicit: token usage streams separately via thread/tokenUsage/updated.
`turn/completed`
The app-server README says turn/completed carries final turn state and token usage.
There are two important details:
- The app-server protocol
turn/completednotification contains a finalturnobject. - The
execevent processor also emits a turn-completed event that includes ausagestruct.
In the exec event processor, the turn-completed usage is built from the most recent captured total_token_usage:
if let Some(info) = &ev.info {
self.last_total_token_usage = Some(info.total_token_usage.clone());
}Then on turn completion:
let usage = if let Some(u) = &self.last_total_token_usage {
Usage {
input_tokens: u.input_tokens,
cached_input_tokens: u.cached_input_tokens,
output_tokens: u.output_tokens,
}
}Important consequence:
- a turn-completed
usagepayload is not the same schema asThreadTokenUsage - it should be interpreted in the context of the specific event that emitted it
- it must not be blindly mixed with
thread/tokenUsage/updatedaccounting
Generic `usage`
Codex uses the word usage in multiple places.
That does not mean all usage maps have the same semantics.
Examples:
thread/tokenUsage/updated.tokenUsage.total: absolute cumulative thread totalthread/tokenUsage/updated.tokenUsage.last: latest delta- turn-completed
usage: event-specific completion usage payload
Rule:
- never classify a
usagemap by name alone - classify it by event type and payload path
What The Metrics Mean
Absolute totals
These are safe high-water-mark style counters:
info.total_token_usagetokenUsage.totalonthread/tokenUsage/updated
Use these when you want:
- live dashboard totals
- stable per-thread accumulation
- recovery after missed intermediate events
Deltas
These are incremental additions:
info.last_token_usagetokenUsage.lastonthread/tokenUsage/updated
Use these only when:
- no absolute total is available
- you are explicitly handling additive updates
Context window
model_context_window is not spend. It is the model's context limit.
Codex also has logic that can "fill to context window", which sets:
total_token_usage.total_tokens = context_windowlast_token_usage.total_tokens = delta
So total_tokens can reflect context-window normalization behavior, not just a raw upstream token report.
For Symphony, model_context_window should be displayed or logged separately from spend.
Recommended Accounting Strategy For Symphony
Track usage per active Codex thread.
For each thread, keep:
absolute_total: latest accepted absolute total snapshotaccumulated_total: the total you expose in UI/APIlast_seen_turn_id
Preferred source order
When a token-related event arrives, use this precedence:
thread/tokenUsage/updated.tokenUsage.totalTokenCountEvent.info.total_token_usage
Ignore these for accounting:
thread/tokenUsage/updated.tokenUsage.lastTokenCountEvent.info.last_token_usage- generic
usagemaps - turn-completed
usage
Do not treat generic params.usage as equivalent to a cumulative thread total unless the event type makes that meaning explicit.
Algorithm
If an absolute total is present
- Treat it as a thread-level snapshot.
- If it is greater than or equal to the stored
absolute_total, replace the stored absolute total. - Set exposed totals from that absolute snapshot.
- Do not add the corresponding delta again.
If no absolute total is present
- Ignore the event for accounting.
- Keep the last accepted absolute high-water mark unchanged.
Why this matters
If you misclassify a per-turn usage payload as an absolute thread total, later turns can appear to stall because a smaller per-turn number is compared against a larger cumulative baseline.
What Symphony Should And Should Not Do
Do
- Prefer
thread/tokenUsage/updatedfor live reporting. - Treat
tokenUsage.totalas authoritative for thread totals. - Key accounting by
thread_id, not just issue id. - Expect one thread to span multiple turns when Symphony reuses a live Codex thread.
Do not
- Do not treat every
usagemap as absolute. - Do not count
tokenUsage.lastorlast_token_usageinto dashboard totals. - Do not add turn-completed
usageon top of already-counted live thread totals unless you can prove it represents missing spend. - Do not reset accounting just because a new turn starts on the same thread.
Practical Interpretation For Symphony Logs
When reading raw app-server events:
codex/event/token_count- useful if you are inspecting nested
info.total_token_usage
- useful if you are inspecting nested
thread/tokenUsage/updated- best source for live dashboard and API totals
turn/completed- best used as end-of-turn state, not as an unconditional additive token event
Why `total_token_usage` Is The Durable Choice
Codex itself consistently prefers cumulative totals when it needs durable state:
- the state extractor stores
info.total_token_usage.total_tokens - the exec event processor caches the last
total_token_usageand uses that on turn completion
That is a strong signal for Symphony:
- use absolute totals as the main accounting surface
- ignore last/delta values for totals
Recommended Symphony Documentation Contract
If Symphony documents token reporting externally, the contract should be:
- Live token totals come from Codex thread-scoped cumulative usage.
- Incremental usage may also be emitted, but Symphony does not use it for totals.
- Turn-completed usage is event-specific and should not be assumed to be a fresh additive increment.
- Reporting is thread-based, and multiple turns can occur on one thread.
Implementation Checklist
- Prefer
thread/tokenUsage/updated.tokenUsage.total - Fallback to
info.total_token_usage - Ignore
lastfor totals - Key totals by
thread_id - Do not classify generic
usageby field name alone - Do not double-count turn-completed usage after live updates
File: elixir/README.md
Symphony Elixir
This directory contains the current Elixir/OTP implementation of Symphony, based onSPEC.md at the repository root.
[!WARNING]
Symphony Elixir is prototype software intended for evaluation only and is presented as-is.
We recommend implementing your own hardened version based onSPEC.md.
Screenshot
How it works
- Polls the configured tracker for candidate work (included adapters: Linear, GitHub Issues, Jira
Cloud, Asana, and GitLab) - Creates a workspace per issue
- Launches Codex in App Server mode inside the
workspace - Sends a workflow prompt to Codex
- Keeps Codex working on the issue until the work is done
During app-server sessions, the selected tracker adapter may advertise provider-native tools. The
Linear serves linear_graphql, GitHub Issues serves github_api, Jira Cloud servesjira_rest, Asana serves asana_api, and GitLab serves gitlab_api. Symphony executes those
tools with configured host-side auth and removes declared tracker-token environment variables from
the Codex child, so the agent does not need a second tracker login.
If a claimed issue moves to a terminal state (Done, Closed, Cancelled, or Duplicate),
Symphony stops the active agent for that issue and cleans up matching workspaces.
If Codex reports that operator input, approval, or MCP elicitation is required, Symphony keeps the
issue claimed and exposes it as blocked in the runtime state, JSON API, and dashboard. Blocked
entries are in memory only; restarting the orchestrator clears that blocked map, so any still-active
tracker issue can become a dispatch candidate again after restart.
How to use it
- Make sure your codebase is set up to work well with agents: see
Harness engineering. - Get a new personal token in Linear via Settings โ Security & access โ Personal API keys, and
set it as theLINEAR_API_KEYenvironment variable. - Copy this directory's
WORKFLOW.mdto your repo. - Optionally copy the
commit,push,pull,land, andlinearskills to your repo.- The
linearskill expects Symphony'slinear_graphqlapp-server tool for raw Linear GraphQL
operations such as comment editing or upload flows.
- The
- Customize the copied
WORKFLOW.mdfile for your project.- To get your project's slug, right-click the project and copy its URL. The slug is part of the
URL. - When creating a workflow based on this repo, note that it depends on non-standard Linear
issue statuses: "Rework", "Human Review", and "Merging". You can customize them in
Team Settings โ Workflow in Linear.
- To get your project's slug, right-click the project and copy its URL. The slug is part of the
- Follow the instructions below to install the required runtime dependencies and start the service.
Prerequisites
We recommend using mise to manage Elixir/Erlang versions.
mise install
mise exec -- elixir --versionRun
git clone https://github.com/openai/symphony
cd symphony/elixir
mise trust
mise install
mise exec -- mix setup
mise exec -- mix build
mise exec -- ./bin/symphony ./WORKFLOW.mdBurrito releases
Symphony ships self-contained executables built with
Burrito. They embed Erlang/OTP, Elixir, and Symphony,
but still expect codex, git, and the selected tracker credentials on the target machine.
Supported release targets:
macos_arm64macos_x86_64linux_arm64linux_x86_64
v* tags publish all four targets with checksums. A manual workflow run builds the same
artifacts without creating a release.
After downloading the executable for your platform from a release:
chmod +x ./symphony-v0.0.1-macos_arm64
./symphony-v0.0.1-macos_arm64 ./WORKFLOW.mdConfiguration
Pass a custom workflow file path to ./bin/symphony when starting the service:
./bin/symphony /path/to/custom/WORKFLOW.mdIf no path is passed, Symphony defaults to ./WORKFLOW.md.
Optional flags:
--logs-roottells Symphony to write logs under a different directory (default:./log)--portalso starts the Phoenix observability service (default: disabled)
The WORKFLOW.md file uses YAML front matter for configuration, plus a Markdown body used as the
Codex session prompt.
Minimal example:
---
tracker:
kind: linear
provider:
project_slug: "..."
workspace:
root: ~/code/workspaces
hooks:
after_create: |
git clone [email protected]:your-org/your-repo.git .
agent:
max_concurrent_agents: 10
max_turns: 20
codex:
command: codex app-server
---
You are working on an issue from the configured tracker {{ issue.identifier }}.
Title: {{ issue.title }} Body: {{ issue.description }}Notes:
- If a value is missing, defaults are used.
tracker.kindselects an adapter. Adapter-owned endpoint, scope, and auth settings belong undertracker.provider; the current Linear adapter still accepts the older flatendpoint,api_key,project_slug, andassigneealiases for compatibility.tracker.required_labelsis optional. When set, an issue must have every
configured label to dispatch or continue running. Label matching ignores
case and surrounding whitespace. A blank configured label matches no issue.- Safer Codex defaults are used when policy fields are omitted:
codex.approval_policydefaults to{"reject":{"sandbox_approval":true,"rules":true,"mcp_elicitations":true}}codex.thread_sandboxdefaults toworkspace-writecodex.turn_sandbox_policydefaults to aworkspaceWritepolicy rooted at the current issue workspace
codex.turn_timeout_msis the maximum silence interval while a turn is streaming. Each
app-server update resets it; it is not a total turn runtime cap.- Supported
codex.approval_policyvalues depend on the targeted Codex app-server version. In the current local Codex schema, string values includeuntrusted,on-failure,on-request, andnever, and object-formrejectis also supported. - Supported
codex.thread_sandboxvalues:read-only,workspace-write,danger-full-access. - When
codex.turn_sandbox_policyis set explicitly, Symphony passes the map through to Codex
unchanged. Compatibility then depends on the targeted Codex app-server version rather than local
Symphony validation. - Workflows that run package managers or other commands that resolve external hosts should set
networkAccess: trueincodex.turn_sandbox_policy; otherwise DNS/network access may be denied
by the Codex turn sandbox. agent.max_turnscaps how many back-to-back Codex turns Symphony will run in a single agent
invocation when a turn completes normally but the issue is still in an active state. Default:20.- If the Markdown body is blank, Symphony uses a default prompt template that includes the issue
identifier, title, and body. - Use
hooks.after_createto bootstrap a fresh workspace. For a Git-backed repo, you can rungit clone ... .there, along with any other setup commands you need. - If a hook needs
mise execinside a freshly cloned workspace, trust the repo config and fetch
the project dependencies inhooks.after_createbefore invokingmiselater from other hooks. - For the Linear adapter,
tracker.provider.api_keyreads fromLINEAR_API_KEYwhen unset or
when value is$LINEAR_API_KEY. The legacy flattracker.api_keyalias behaves the same way. - Do not put a literal tracker token in a repo-owned
WORKFLOW.mdif Codex can read that
workspace. Use$VAR/host-side secret references so Symphony can keep the token out of the
child environment. - For path values,
~is expanded to the home directory. - For env-backed path values, use
$VAR.workspace.rootresolves$VARbefore path handling,
whilecodex.commandstays a shell command string and any$VARexpansion there happens in the
launched shell.
tracker:
provider:
api_key: $LINEAR_API_KEY
workspace:
root: $SYMPHONY_WORKSPACE_ROOT
hooks:
after_create: |
git clone --depth 1 "$SOURCE_REPO_URL" .
codex:
command: "$CODEX_BIN --config 'model=\"gpt-5.5\"' app-server"- If
WORKFLOW.mdis missing or has invalid YAML at startup, Symphony does not boot. - If a later reload fails, Symphony keeps running with the last known good workflow and logs the
reload error until the file is fixed. server.portor CLI--portenables the optional Phoenix LiveView dashboard and JSON API at/,/api/v1/state,/api/v1/, and/api/v1/refresh.
Linear adapter profile
- Config: use
tracker.kind: linearwithtracker.provider.endpoint(defaulthttps://api.linear.app/graphql),api_key(defaults toLINEAR_API_KEYand accepts$VAR), requiredproject_slug, and optionalassignee(a Linear user ID orme,
defaulting toLINEAR_ASSIGNEE).
The legacy flattracker.endpoint,api_key,project_slug, andassigneealiases remain
supported.required_labels,active_states, andterminal_statesstay undertracker. - Scope and paging: candidate reads filter the configured project slug and requested state names,
following Linear pages of 50. ID refreshes are also project-scoped and batch up to 50 IDs. Empty
state/ID lists return{:ok, []}without a Linear request. - Identity and normalization:
issue.idis the Linear issue ID andissue.native_refis currentlynil. Records missing a nonblank ID, identifier, title, or state are dropped from candidate
pages and fail ID refreshes. State keeps Linear's spelling; integer priorities are preserved and
other priority values becomenil; RFC 3339 timestamps are parsed and unusable timestamps becomenil. Labels are trimmed, lowercased, deduplicated, and blanks are dropped; blockers come from
inverseblocksrelations. - Dispatchability: the adapter marks an issue dispatchable only when optional assignee routing
matches and aTodoissue has no non-terminal blocker. The generic scheduler then applies
active/terminal states, required labels, claims, retries, and concurrency. - Tool: the Linear adapter advertises
linear_graphql, accepting either a raw query string or an
object with nonblankqueryand optional objectvariables. Symphony executes it host-side
with the session-bound endpoint/token and strips declared token environment variables from the
Codex child.project_slugscopes scheduler reads, not raw tool calls; the tool can access
whatever the configured Linear token can access. - Responsibility and errors:
linear_graphqladds no idempotency key, retry, scope guard, or
rate-limit policy, so workflows own idempotent mutations and handling provider errors. Read/config
failures use{:error, :missing_linear_api_token},{:error, :missing_linear_project_slug},{:error, :invalid_linear_endpoint},{:error, :invalid_linear_assignee},{:error, :missing_linear_viewer_identity},{:error, {:linear_api_status, status}},{:error, {:linear_api_request, reason}},{:error, {:linear_graphql_errors, errors}},{:error, :linear_unknown_payload}, or{:error, :linear_missing_end_cursor}. Tool results
are maps with"success", JSON-string"output", and text"contentItems"; invalid
arguments, missing auth, and transport failures return"success" => falsewith{"error": {"message": ...}}, while top-level GraphQL errors preserve the response body with"success" => false.
For portable reporting, map missing/invalid token, project, endpoint, assignee, or viewer errors
totracker_configortracker_auth, request failures totracker_transport, non-200 responses totracker_response(429istracker_rate_limited), GraphQL/unknown payload failures totracker_payload, and missing cursors totracker_pagination; logs and tool responses carry the
human-readable provider detail.
GitHub Issues adapter
- Config: use
tracker.kind: githubwith requiredtracker.provider.repoinowner/repoform,
optionaltoken(defaults toGITHUB_TOKENand accepts$VAR), and optionalapi_url
(defaulthttps://api.github.com, HTTPS only). Set explicitactive_statesandterminal_states; active entries may beopenand terminal entries may beclosed. - Reads and identity: polling is scoped to the configured repository;
issue.idis the
repository issue number,issue.identifierisGH-<number>, hidden or deleted404issues are
omitted on refresh, and pull requests returned by the Issues API are not dispatchable. - Tool and auth:
github_apiaccepts a relative RESTpathplus optionalparamsand JSONbody; Symphony executes it host-side with the session-bound token, removes configured tracker
credentials and provider authentication aliases from the Codex child, and leaves raw tool access
limited by that token's GitHub permissions.
Jira Cloud adapter
- Config: use
tracker.kind: jirawith providerbase_url,email,api_token, and requiredproject_key; the first three default toJIRA_BASE_URL,JIRA_EMAIL, andJIRA_API_TOKEN
and accept$VAR. Set explicit Jira-nativeactive_statesandterminal_states. - Issues and reads: candidate reads and ID refreshes stay scoped to the configured project and
requested statuses;issue.idis Jira's immutable ID andissue.identifieris the issue key. - Blockers: inward
Blockslinks populateblocked_by; issues in Jira'snewstatus category
wait until blockers reach configured terminal states, while in-progress categories keep running. - Tool:
jira_restsends relative/rest/api/3/requests host-side with configured Basic auth,
strips token environment variables from Codex, and can reach whatever the Jira credential can.
Asana adapter
- Config: use
tracker.kind: asanawith requiredtracker.provider.project_gid, optionalendpoint(defaulthttps://app.asana.com/api/1.0), andapi_key(defaults toASANA_PATand
accepts$VAR);active_statesandterminal_statesare project section names. - Scope: Symphony polls tasks in the configured project, treats their section as state, and omits
deleted or out-of-project tasks during ID refreshes. - Tool:
asana_apisends relative Asana REST requests host-side with the configured auth; Symphony
stripsASANA_PATand configured token variables from the Codex child, while raw tool calls are
not limited to the configured project.
GitLab adapter
- Configure
tracker.kind: gitlabwithtracker.provider.project_path, optionalapi_url, andapi_key(defaultGITLAB_PAT); useopenedandclosedtracker states. - Symphony reads project issues by IID and exposes route-safe
GL-identifiers. gitlab_apiforwards raw GitLab REST requests with host-side auth and keeps configured tracker
credentials and provider authentication aliases out of the Codex child.
Web dashboard
The observability UI now runs on a minimal Phoenix stack:
- LiveView for the dashboard at
/ - JSON API for operational debugging under
/api/v1/* - Bandit as the HTTP server
- Phoenix dependency static assets for the LiveView client bootstrap
- Tracker issue identifiers link to the tracker-provided URL when it uses
httporhttps
Project Layout
lib/: application code and Mix taskstest/: ExUnit coverage for runtime behaviorWORKFLOW.md: in-repo workflow contract used by local runs../.codex/: repository-local Codex skills and setup helpers
Testing
make allRun the real external end-to-end test only when you want Symphony to create disposable Linear
resources and launch a real codex app-server session:
cd elixir
export LINEAR_API_KEY=...
make e2eOptional environment variables:
SYMPHONY_LIVE_LINEAR_TEAM_KEYdefaults toSYME2ESYMPHONY_LIVE_SSH_WORKER_HOSTSuses those SSH hosts when set, as a comma-separated list
make e2e runs two live scenarios:
- one with a local worker
- one with SSH workers
If SYMPHONY_LIVE_SSH_WORKER_HOSTS is unset, the SSH scenario uses docker compose to start two
disposable SSH workers on localhost:. The live test generates a temporary SSH keypair,
mounts the host ~/.codex/auth.json into each worker, verifies that Symphony can talk to them
over real SSH, then runs the same orchestration flow against those worker addresses. This keeps
the transport representative without depending on long-lived external machines.
Set SYMPHONY_LIVE_SSH_WORKER_HOSTS if you want make e2e to target real SSH hosts instead.
The live test creates a temporary Linear project and issue, writes a temporary WORKFLOW.md, runs
a real agent turn, verifies the workspace side effect, requires Codex to comment on and close the
Linear issue, then marks the project completed so the run remains visible in Linear.
Run the opt-in GitHub Issues live test with a disposable/scratch repository:
cd elixir
export SYMPHONY_LIVE_GITHUB_REPO=owner/scratch-repo
export GITHUB_TOKEN=...
SYMPHONY_RUN_GITHUB_LIVE_E2E=1 mix test test/symphony_elixir/github_live_e2e_test.exsRun the opt-in Jira Cloud live test against a disposable project whose credential can browse,
create, comment on, transition, and delete issues:
cd elixir
export JIRA_BASE_URL=https://your-site.atlassian.net
export JIRA_EMAIL=...
export JIRA_API_TOKEN=...
export SYMPHONY_LIVE_JIRA_PROJECT_KEY=TEST
SYMPHONY_RUN_JIRA_LIVE_E2E=1 mix test test/symphony_elixir/jira_live_e2e_test.exsRun the opt-in Asana live E2E against disposable Asana resources:
cd elixir
export ASANA_PAT=...
export SYMPHONY_LIVE_ASANA_WORKSPACE_GID=...
# Required only when the workspace is an organization:
# export SYMPHONY_LIVE_ASANA_TEAM_GID=...
SYMPHONY_RUN_ASANA_LIVE_E2E=1 mix test test/symphony_elixir/asana_live_e2e_test.exsRun the opt-in GitLab live E2E against a disposable project:
cd elixir
export GITLAB_PAT=...
export SYMPHONY_LIVE_GITLAB_PROJECT_ID=...
SYMPHONY_RUN_GITLAB_LIVE_E2E=1 mix test test/symphony_elixir/gitlab_live_e2e_test.exsFAQ
Why Elixir?
Elixir is built on Erlang/BEAM/OTP, which is great for supervising long-running processes. It has an
active ecosystem of tools and libraries. It also supports hot code reloading without stopping
actively running subagents, which is very useful during development.
What's the easiest way to set this up for my own codebase?
Launch codex in your repo, give it the URL to the Symphony repo, and ask it to set things up for
you.
License
This project is licensed under the Apache License 2.0.
File: docs/symphony-smoke-board-review.md
Symphony Board Review Smoke Test
This brief note confirms this is a Symphony smoke test for the board review workflow.
File: docs/symphony-smoke-test-one.md
Symphony Smoke Test
Symphony picked up Jira issue SD-4 and handled it by adding this isolated smoke-test doc.