Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"hooks": {
"Notification": [
{
"hooks": [
{
"type": "command",
"command": "/Users/broomva/broomva/core/symphony/scripts/conversation-bridge-hook.sh",
"timeout": 5
}
]
}
]
}
}
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

44 changes: 44 additions & 0 deletions EXTENDING.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ pub fn create_tracker(config: &TrackerConfig) -> Result<Box<dyn TrackerClient>,
match config.kind.as_str() {
"linear" => Ok(Box::new(LinearClient::new(/* ... */))),
"github" => Ok(Box::new(GithubClient::from_slug(/* ... */))),
"markdown" => Ok(Box::new(MarkdownClient::with_journal(/* ... */))),
other => Err(TrackerError::UnsupportedKind(other.into())),
}
}
Expand All @@ -113,6 +114,49 @@ pub fn create_tracker(config: &TrackerConfig) -> Result<Box<dyn TrackerClient>,
- Otherwise, GitHub's native `open`/`closed` is used
- Pull requests are automatically filtered out (GitHub's issues API includes PRs)

#### Markdown Files (`tracker.kind: markdown`)
- Reads `.md` files from a local directory — no external API or credentials required
- `project_slug`: path to the issues directory (e.g., `./tasks/`)
- `api_key`: not required (set to `unused` or leave empty)
- States: read from YAML front matter `state:` field in each `.md` file
- State transitions: rewrites the `state:` line in the file's front matter
- Obsidian-compatible: issues are regular markdown files with YAML front matter

**Issue file format:**
```markdown
---
id: TASK-001
title: Fix the auth bug
state: Todo
priority: 1
labels: [bug, auth]
blocked_by:
- id: TASK-000
identifier: TASK-000
state: Done
created_at: "2026-01-15T10:00:00Z"
---

Description of the task goes here.
```

**Lago journaling (optional):** When `endpoint` is configured (e.g., `http://localhost:8080`), every state transition is journaled to `{issues_dir}/.journal.jsonl` using Lago's `EventPayload::Custom` schema. If the endpoint points to a running Lago daemon, a session is created on startup. The journal works without Lago running — entries can be batch-imported later.

Journal entry format:
```json
{
"event_id": "0195...",
"session_id": "symphony",
"branch_id": "main",
"timestamp": "2026-03-19T10:00:00Z",
"payload": {
"type": "Custom",
"event_type": "symphony.tracker.state_transition",
"data": { "issue_id": "TASK-001", "from_state": "Todo", "to_state": "Done" }
}
}
```

### Key Requirements

- **State normalization**: Always use `trim().to_lowercase()` when comparing states
Expand Down
27 changes: 16 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,14 @@ created: 2026-03-06
> A Rust implementation of the [Symphony](https://github.com/openai/symphony) orchestration spec by OpenAI.
> For vault navigation see [[docs/Symphony Index|Symphony Index]]. For the canonical spec see [[SPEC]].

A Rust-based orchestration service that polls an issue tracker (Linear), creates isolated per-issue workspaces, and runs coding agent sessions automatically.
A Rust-based orchestration service that polls an issue tracker (Linear, GitHub, or local Markdown files), creates isolated per-issue workspaces, and runs coding agent sessions automatically.

Symphony turns your issue backlog into autonomous coding work — it watches for "Todo" issues, clones your repo into a sandboxed workspace, runs a coding agent (like Claude Code), and manages retries, concurrency, and lifecycle hooks.

## How It Works

```
Linear (Todo issues)
Tracker (Linear / GitHub / Markdown files)
┌────────────────────────┐
Expand Down Expand Up @@ -49,19 +49,19 @@ Linear (Todo issues)
└─────────────────────┘
```

**Poll loop**: Fetches active issues from Linear → filters by project & state → dispatches up to `max_concurrent_agents` workers.
**Poll loop**: Fetches active issues from the configured tracker → filters by project & state → dispatches up to `max_concurrent_agents` workers.

**Per-issue worker**: Creates workspace directory → runs lifecycle hooks (clone repo, rebase, etc.) → renders prompt template with issue data → launches coding agent → runs post-hooks (commit, etc.).

**Reconciliation**: On each tick, refreshes running issue states from Linear. If an issue moves to a terminal state (Done/Canceled), the worker is cleaned up.
**Reconciliation**: On each tick, refreshes running issue states from the tracker. If an issue moves to a terminal state (Done/Canceled), the worker is cleaned up.

## Quick Start

### Prerequisites

- Rust 1.85+ (edition 2024)
- A [Linear](https://linear.app) API key
- A coding agent CLI (e.g., `claude`)
- One of: [Linear](https://linear.app) API key, GitHub token, or a local `tasks/` directory with `.md` files
- `gh` CLI (if using GitHub hooks)

### Build
Expand Down Expand Up @@ -182,17 +182,22 @@ Open `http://localhost:8080` for a live HTML dashboard showing running/retrying

## Architecture

Rust workspace with 7 crates:
Rust workspace with 8 crates:

| Crate | Responsibility |
|-------|---------------|
| `symphony-core` | Domain types: Issue, Session, Workspace, OrchestratorState |
| `symphony-config` | WORKFLOW.md loader, typed config, live file watcher |
| `symphony-tracker` | Linear GraphQL client, issue fetching, state normalization |
| `symphony-tracker` | Tracker adapters (Linear, GitHub, Markdown), issue fetching, state normalization |
| `symphony-workspace` | Per-issue directory lifecycle, hook execution, path safety |
| `symphony-agent` | Coding agent subprocess management (CLI pipe + JSON-RPC modes) |
| `symphony-orchestrator` | Poll loop, dispatch, reconciliation, retry queue |
| `symphony-observability` | Structured logging, HTTP dashboard + REST API |
| `symphony-arcan` | Arcan runtime adapter — bridges Symphony to the Agent OS stack |

### Dashboard

A separate TypeScript/React dashboard lives in `dashboard/` (Turborepo, Bun). It provides a web UI for monitoring Symphony orchestrator state, issues, and agent sessions.

### Key Features

Expand All @@ -211,9 +216,9 @@ Rust workspace with 7 crates:

| Field | Required | Default | Description |
|-------|----------|---------|-------------|
| `tracker.kind` | yes | — | Tracker type (`linear`) |
| `tracker.api_key` | yes | — | API key (supports `$ENV_VAR` syntax) |
| `tracker.project_slug` | yes | — | Linear project slug ID |
| `tracker.kind` | yes | — | Tracker type (`linear`, `github`, or `markdown`) |
| `tracker.api_key` | yes (linear/github) | — | API key (supports `$ENV_VAR` syntax); not needed for markdown |
| `tracker.project_slug` | yes | — | Linear slug, `owner/repo`, or directory path for markdown |
| `tracker.active_states` | no | `["Todo"]` | States that make an issue eligible |
| `tracker.terminal_states` | no | `["Done","Canceled"]` | States that trigger cleanup |
| `polling.interval_ms` | no | `30000` | Poll interval in milliseconds |
Expand Down Expand Up @@ -257,7 +262,7 @@ make fmt # cargo fmt --all
Contributions are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.

Key extension points:
- **Tracker plugins**: Implement the `TrackerClient` trait to add support for GitHub Issues, Jira, etc.
- **Tracker plugins**: Implement the `TrackerClient` trait to add support for Jira, etc. (Linear, GitHub, and Markdown are built-in)
- **Agent runners**: The agent runner supports any CLI that speaks line-delimited JSON on stdout
- **Workflow templates**: Create new `WORKFLOW.md` examples for different use cases

Expand Down
73 changes: 73 additions & 0 deletions SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
---
name: symphony
description: >
Rust orchestration engine for coding agents. Polls issue trackers
(Linear, GitHub), creates isolated per-issue workspaces, and runs
coding agent sessions with lifecycle hooks, retry/backoff, concurrency
control, and a live HTTP dashboard. Includes control metalayer
(CONTROL.md / setpoints) for grounded development workflows.
trigger_words:
- symphony
- coding agent orchestration
- issue tracker automation
- WORKFLOW.md
- control metalayer
- agent dispatch
- lifecycle hooks
- Linear automation
- agent orchestrator
---

# symphony

Rust orchestration engine that polls issue trackers (Linear, GitHub), creates
isolated workspaces, and runs coding agents automatically.

## Install

```bash
cargo install symphony-cli # from source
curl -fsSL https://raw.githubusercontent.com/broomva/symphony/master/install.sh | sh # binary
docker pull ghcr.io/broomva/symphony:latest # container
```

## Quick Start

```bash
symphony init # scaffold WORKFLOW.md (Linear default)
symphony init --tracker github # GitHub Issues template
symphony validate WORKFLOW.md # verify config
symphony start WORKFLOW.md # run daemon
```

## Key Capabilities

- **Tracker integration** -- Linear and GitHub Issues out of the box; extensible
via `TrackerClient` trait.
- **Lifecycle hooks** -- `after_create`, `before_run`, `after_run`,
`before_remove` with timeout enforcement.
- **Control metalayer** -- CONTROL.md with setpoints, sensors, and feedback
loops for grounded agent development.
- **Concurrency & retry** -- Slot-based dispatch with configurable
`max_concurrent_agents`; exponential backoff on failure.
- **Live dashboard** -- HTTP server with HTML dashboard and REST API for
state, issues, and manual refresh.
- **Arcan runtime** -- Optional dispatch through the Arcan HTTP daemon instead
of local subprocesses.

## Agent Lifecycle

```
Poll tracker -> fetch active issues -> sort by priority -> dispatch workers
|-- after_create hook (clone repo)
|-- before_run hook (rebase)
|-- render prompt + run agent (max_turns)
|-- after_run hook (commit, push, create PR)
|-- pr_feedback hook (capture review comments)
|-- done_state transition (auto-close issue)
+-- retry (1s continuation / exponential backoff)
```

## License

Apache-2.0
7 changes: 7 additions & 0 deletions crates/symphony-arcan/src/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

//! HTTP client for the Arcan agent runtime daemon.

use std::collections::HashMap;

use serde::{Deserialize, Serialize};
use tracing::info;

Expand Down Expand Up @@ -49,6 +51,9 @@ pub struct CreateSessionRequest {
pub owner: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub policy: Option<PolicyConfig>,
/// Optional metadata to attach to all events in this session.
#[serde(skip_serializing_if = "Option::is_none")]
pub metadata: Option<HashMap<String, String>>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -261,6 +266,7 @@ mod tests {
session_id: Some("sess-123".into()),
owner: Some("symphony".into()),
policy: None,
metadata: None,
};
let manifest = client.create_session(&req).await.unwrap();
assert_eq!(manifest.session_id, "sess-123");
Expand All @@ -285,6 +291,7 @@ mod tests {
session_id: Some("sess-123".into()),
owner: None,
policy: None,
metadata: None,
};
let err = client.create_session(&req).await.unwrap_err();
match err {
Expand Down
Loading
Loading