| tracker |
| kind |
project_slug |
api_key |
active_states |
terminal_states |
archive_state |
archive_after_days |
state_descriptions |
linear |
my-team-project |
$LINEAR_API_KEY |
Todo |
In Progress |
Verify |
Document |
|
Human Review |
Done |
Blocked |
Archive |
Closed |
Cancelled |
Canceled |
Duplicate |
|
Archive |
30 |
| Todo |
In Progress |
Verify |
Document |
Human Review |
Done |
Archive |
Triage; route to In Progress |
Plan + TDD implementation + self-critique |
Review + QA + Merge Gate |
Docs + wiki write-back; Done unless intervention |
Manual intervention or explicit review before Done |
Verified complete |
Auto-archived after 30 days idle |
|
|
| polling |
|
| wiki |
| sweep_every_n |
root |
10 |
./docs/llm-wiki |
|
| workspace |
| root |
~/symphony_workspaces |
|
| hooks |
| after_create |
before_run |
after_run |
before_remove |
bash "$SYMPHONY_WORKFLOW_DIR/scripts/symphony-setup-worktree.sh"
|
# NEVER `git reset --hard` inside a worktree — it discards in-progress
# work between turns. Just refresh remotes; let the agent decide if/when
# to rebase.
set -uo pipefail
git fetch origin --quiet || true
|
# Per-turn commit-or-amend. The branch stays at the same number of
# commits across turns (amends in place when HEAD is already a `wip:`
# commit), but every completed turn is durably written to .git/objects
# so even a hard crash (SIGKILL, host reboot) won't lose work. The
# orchestrator squashes everything into a single `<ID>: <title>` commit
# on exit — see auto_commit_on_done.
set -uo pipefail
git add -A -- . ':(exclude).symphony' 2>/dev/null || true
if git diff --cached --quiet 2>/dev/null; then
echo "run finished at $(date) (no changes)"
exit 0
fi
# Classify the staged diff so the wip subject carries machine-readable
# markers. `[no-test]` = production code changed with no paired test
# file in the same diff (workflow-v0.5.2 § B1 — review.md promotes it
# to a HIGH finding). `[scope-expand]` = a rewind dispatch (set by the
# orchestrator when SYMPHONY_REWIND_SCOPE is exported) but the diff
# touched a file outside the parsed scope list (workflow-v0.5.2 § A2).
# Both markers can stack.
STAGED_FILES="$(git diff --cached --name-only 2>/dev/null || true)"
PROD_CHANGED=0
TESTS_CHANGED=0
SCOPE_EXPAND=0
SCOPE_FILES=""
if [ -n "${SYMPHONY_REWIND_SCOPE:-}" ]; then
SCOPE_FILES="$(printf '%s' "$SYMPHONY_REWIND_SCOPE" \
| tr ',' '\n' \
| sed -n 's/.*"file"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')"
fi
NL=$(printf '\nx'); NL=${NL%x}
OLDIFS="$IFS"
IFS="$NL"
for f in $STAGED_FILES; do
[ -n "$f" ] || continue
case "$f" in
tests/*|*_test.py|*.test.ts|*.test.tsx|*_test.go)
TESTS_CHANGED=1
;;
esac
case "$f" in
tests/*|docs/*|kanban/*|.symphony/*|*.md|LICENSE|LICENSE.*|NOTICE|CHANGELOG*|README*|AGENTS.md|GEMINI.md)
: # carve-out: docs/license/wiki edits never count as production change
;;
*)
PROD_CHANGED=1
;;
esac
if [ -n "$SCOPE_FILES" ] && [ "$SCOPE_EXPAND" = 0 ]; then
in_scope=0
for s in $SCOPE_FILES; do
if [ "$f" = "$s" ]; then
in_scope=1
break
fi
done
if [ "$in_scope" = 0 ]; then
SCOPE_EXPAND=1
fi
fi
done
IFS="$OLDIFS"
PREFIX=""
if [ "$PROD_CHANGED" = 1 ] && [ "$TESTS_CHANGED" = 0 ]; then
PREFIX="${PREFIX}[no-test]"
fi
if [ -n "${SYMPHONY_REWIND_SCOPE:-}" ] && [ "$SCOPE_EXPAND" = 1 ]; then
PREFIX="${PREFIX}[scope-expand]"
fi
# Honors any pre-commit hooks in the host repo — if they fail, this
# turn's snapshot fails and the next turn picks up where files are.
MSG="$(sed -n '1{s/^[[:space:]]*//;s/[[:space:]]*$//;p;q;}' .symphony/commit-message.txt 2>/dev/null || true)"
[ -n "$MSG" ] || MSG="turn $(date -u +%FT%TZ)"
case "$MSG" in wip:*) COMMIT_MSG="$MSG" ;; *) COMMIT_MSG="wip: $MSG" ;; esac
LAST="$(git log -1 --format=%s 2>/dev/null || echo "")"
# On amend, preserve any markers the previous turn already set so a
# later test-passing turn doesn't drop the historical `[no-test]`.
# Markers are sticky within a wip subject.
PRIOR_PREFIX=""
case "$LAST" in
*"[no-test]"*) PRIOR_PREFIX="${PRIOR_PREFIX}[no-test]" ;;
esac
case "$LAST" in
*"[scope-expand]"*) PRIOR_PREFIX="${PRIOR_PREFIX}[scope-expand]" ;;
esac
MERGED_PREFIX=""
case "$PRIOR_PREFIX$PREFIX" in
*"[no-test]"*) MERGED_PREFIX="${MERGED_PREFIX}[no-test]" ;;
esac
case "$PRIOR_PREFIX$PREFIX" in
*"[scope-expand]"*) MERGED_PREFIX="${MERGED_PREFIX}[scope-expand]" ;;
esac
if [ -n "$MERGED_PREFIX" ]; then
COMMIT_MSG="${MERGED_PREFIX} ${COMMIT_MSG}"
fi
if [ "${LAST#wip:}" != "$LAST" ]; then
git -c user.email=symphony@local -c user.name=symphony \
commit --amend -m "$COMMIT_MSG" >/dev/null 2>&1 || true
else
git -c user.email=symphony@local -c user.name=symphony \
commit -m "$COMMIT_MSG" >/dev/null 2>&1 || true
fi
echo "run finished at $(date)"
|
# Detach the worktree before Symphony rmtree's the dir, otherwise
# `.git/worktrees/<ID>` lingers until `git worktree prune`. By this
# point the orchestrator has already auto-committed any leftover
# changes (see agent.auto_commit_on_done).
set -uo pipefail
HOST_REPO="${SYMPHONY_WORKFLOW_DIR:?}"
WORKTREE_PATH="$PWD"
ISSUE_ID="${SYMPHONY_ISSUE_ID:-$(basename "$WORKTREE_PATH")}"
for dir in ${SYMPHONY_BOARD_ROOT_NAME:-kanban}; do
git -C "$HOST_REPO" update-index --no-skip-worktree -- "$dir/$ISSUE_ID.md" 2>/dev/null || true
done
git -C "$HOST_REPO" worktree remove --force "$WORKTREE_PATH" 2>/dev/null || true
|
|
| agent |
| kind |
max_concurrent_agents |
max_turns |
max_total_turns |
crash_continuation |
scheduling_policy |
max_total_tokens |
max_total_tokens_by_state |
budget_exhausted_state |
max_attempts |
stage_contracts |
max_retries |
auto_triage_actionable_todo |
max_retry_backoff_ms |
max_concurrent_agents_by_state |
auto_commit_on_done |
auto_merge_on_done |
auto_merge_push_target |
feature_base_branch |
auto_merge_target_branch |
auto_merge_exclude_paths |
auto_merge_capture_untracked |
codex |
1 |
100 |
200 |
true |
fifo |
100000000 |
| In Progress |
Verify |
500000000 |
500000000 |
|
Blocked |
3 |
auto |
3 |
true |
300000 |
| Todo |
In Progress |
Verify |
Document |
1 |
1 |
1 |
1 |
|
true |
true |
true |
|
|
|
|
|
| codex |
| command |
model |
reasoning_effort |
approval_policy |
thread_sandbox |
turn_sandbox_policy |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
codex app-server |
gpt-5.5 |
high |
never |
workspace-write |
| type |
networkAccess |
workspaceWrite |
true |
|
3600000 |
20000 |
300000 |
|
| claude |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
claude -p --output-format stream-json --verbose --permission-mode acceptEdits --add-dir "$SYMPHONY_WORKFLOW_DIR" |
true |
3600000 |
20000 |
300000 |
|
| gemini |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
gemini -p "" |
true |
3600000 |
20000 |
300000 |
|
| agy |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
agy --print "$(cat)" |
true |
3600000 |
20000 |
300000 |
|
| kiro |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
kiro-cli chat --no-interactive --trust-all-tools "$(cat)" |
true |
3600000 |
20000 |
300000 |
|
| opencode |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
opencode run --format json --auto |
true |
3600000 |
20000 |
300000 |
|
| pi |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
pi --mode json -p "" |
true |
3600000 |
20000 |
300000 |
|
| prime_agent |
| command |
resume_across_turns |
turn_timeout_ms |
read_timeout_ms |
stall_timeout_ms |
prime-agent -p --mode json |
true |
3600000 |
20000 |
300000 |
|
| server |
|
| tui |
|
| prompts |
| base |
stages |
./docs/symphony-prompts/linear/base.md |
| Todo |
In Progress |
Verify |
Document |
Done |
./docs/symphony-prompts/linear/stages/todo.md |
./docs/symphony-prompts/linear/stages/in-progress.md |
./docs/symphony-prompts/linear/stages/verify.md |
./docs/symphony-prompts/linear/stages/document.md |
./docs/symphony-prompts/linear/stages/done.md |
|
|
This workflow uses stage-specific prompt files configured under prompts.
Customize docs/symphony-prompts/linear/ to change the agent instructions.
If the prompts block is removed, Symphony falls back to this short legacy body.
You are working on {{ issue.identifier }}: {{ issue.title }}.
Current state: {{ issue.state }}.
Follow the board state instructions configured for this workflow.