sakusen
Health Pass
- License — License: MIT
- Description — Repository has a description
- Active repo — Last push 0 days ago
- Community trust — 13 GitHub stars
Code Pass
- Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Pass
- Permissions — No dangerous permissions requested
No AI report is available for this listing yet.
Your command center for CLI coding agents
作戦
Sakusen is a daemon that orchestrates coding agents through long-lived, multi-step workflows. An agent is just a shell command you declare in config — Claude Code, opencode, aider, a raw model CLI, anything — and Sakusen talks to it through a small environment-variable contract. Each task runs in its own git worktree on its own branch, advances through whatever steps you define in config — anything from a single "implement" step to a full plan/implement/review/approve/merge chain with loops and human gates — and reports back to a terminal UI where you stay in the driver's seat.
You decide what runs, how many run at once, where the human gates go, and how finished work lands on your base branch. Sakusen just keeps the agents on the rails.
⚠️ Early days — breaking changes expected. Sakusen is under active development. Config formats, CLI flags, and the database schema may change without notice between releases. Pin to a tagged version if you need stability.
flowchart LR
tui["sakusen tui<br/>(control)"] <--> daemon["sakusen daemon<br/>(orchestrator)"]
daemon <--> agents["coding agents<br/>(your commands)<br/>in git worktrees"]
daemon --> db[("SQLite<br/>tasks.db")]
Why
- You stay in control. Human-approval gates pause any step until you sign off. Tmux steps drop you straight into the agent's session for back-and-forth.
- Parallelism without conflicts. Every task gets a dedicated git worktree and branch, so N agents can work concurrently on the same repo without stepping on each other.
- Workflows, not one-shots. Chain planning, implementation, review, and final-approval steps. Loop the review/implement cycle until it converges. Pass artifacts between steps.
- It survives a reboot. Tasks live in SQLite. Logs are persisted per step. Stop the daemon, restart it, pick up where you left off.
- Local first. No cloud, no telemetry. A Go binary, a Unix socket, a SQLite file under
~/.config/sakusen/.
Install
go install github.com/Bakaface/sakusen/cmd/sakusen@latest
Requires Go 1.25+. The binary lands in $(go env GOBIN) (defaults to $(go env GOPATH)/bin) — make sure that's on your PATH.
To pin to a specific release, replace @latest with a tag (e.g. @v0.1.0). To verify what you installed:
sakusen version
Building from a checkout works too:
git clone https://github.com/Bakaface/sakusen && cd sakusen
go build -o sakusen ./cmd/sakusen
Quick Start
# Inside any git repo:
sakusen init # writes .sakusen.yml + user-owned agent scripts under .sakusen/agents/
sakusen daemon start # starts the background daemon (Unix socket)
sakusen tui # opens the TUI
The scaffolded config defines two Claude Code agent records (claude headless, claude-tmux interactive) backed by editable scripts in .sakusen/agents/ (they require claude and jq on PATH). Swap the commands to use any other tool — Sakusen itself has no agent hardcoded.
In the TUI, press n to create a new task, pick a workflow, and watch it run. Press enter on a task to follow its live logs.
To run from the command line instead:
sakusen create "Add a /healthz endpoint" # creates a pending task
sakusen start <id> # kicks off its workflow
sakusen logs <id> # tails its logs
sakusen tasks # list, or `sakusen tasks <id>` for detail
How a task runs
- You create a task (TUI or
sakusen create) and pick a workflow. - The daemon picks it up when a worker slot is free (
max_workerscontrols concurrency). - A worktree is provisioned at
.worktrees/<branch>on a new branch derived fromgit.branch_template.worktree-sync-pathsandworktree-setup-commandsrun here (e.g. copy.env, runbun install). - Each workflow step spawns an agent in that worktree: Sakusen resolves the step's agent record (step → workflow →
default_agent→claude), writes the rendered prompt to a file, and runs the agent's shell command with theSAKUSEN_*env contract exported (SAKUSEN_PROMPT_FILE,SAKUSEN_RESULT_FILE, ...). Stdout is streamed to the unified task log and broadcast to the TUI. - Step context is captured at the end of each step (the agent's result text from
$SAKUSEN_RESULT_FILEby default, or a summary produced by yoursummarizer:command whensummarization_strategy: summarize_chat) and made available to later steps via{{steps.<name>.context}}. - Human gates pause the workflow on
human: truesteps; tmux-mode agents suspend the workflow at an interactive session until a turn-end sentinel lands or you advance manually. Loops jump back to an earlier step until an exit condition is met ormax_iterationsis reached. - On completion, depending on
on_complete(project-level, or the running workflow's override), Sakusen either leaves the work as acommiton the branch ormerges it into the base branch.
Workflow configuration
Workflows live in .sakusen.yml at the repo root as a flat workflows: list. Each entry is either a string ref (resolved to .sakusen/workflows/<name>.yml) or an inline workflow mapping.
A workflow may pin any subset of New Task screen fields (input, worktree, branch, checkout, target). Pinned fields are pre-filled and hidden from the form. If all fields are pinned the New Task screen is skipped entirely and the task is created immediately. (A separate top-level description: is human-readable metadata — a one-line summary surfaced in the workflow picker and via MCP list_workflows; it is not a pin and never becomes the task input.)
Minimal .sakusen.yml:
max_workers: 3
default_agent: claude
agents:
claude: # any shell command; this one is Claude Code headless
mode: headless
command: 'claude --dangerously-skip-permissions -p "$(cat "$SAKUSEN_PROMPT_FILE")" | tee "$SAKUSEN_RESULT_FILE"'
git:
base_branch: main
branch_template: sakusen/{{task_id}}-{{task_slug}}
on_complete: merge # commit | merge | none (per-workflow overridable)
workflows:
- name: sensible workflow
on_complete: commit # optional: override the project-level on_complete
steps:
- name: implementing
prompt: |
Implement task #{{task.id}}: {{task.title}}
{{task.input}}
timeout: 30m
- name: reviewing
prompt: |
Review the implementation.
## Implementation summary
{{steps.implementing.context}}
timeout: 20m
Workflows with no pins always prompt the New Task screen. Workflows that pin all fields (input + worktree + branch/checkout + target) skip the screen and create the task immediately — useful for predefined maintenance jobs or project bootstrapping pipelines:
workflows:
- name: housekeeping
description: "Run standard maintenance: linting, dead code removal" # metadata (picker/MCP)
input: "Audit and clean the codebase: lint, remove dead code." # pins the task input
worktree: true
branch: sakusen/housekeeping-{{task.id}}
target: main
steps:
- name: cleaning
prompt: "Audit and clean the codebase."
File pool is flat: .sakusen/workflows/<name>.yml (no tasks/, one-off/, or init/ subdirectories). Global pool: ~/.sakusen/workflows/<name>.yml.
Step options
| Key | Type | Notes |
|---|---|---|
name |
string | Step ID, used in {{steps.<name>.context}} and loop targets. |
prompt |
string | Templated prompt sent to the agent. |
agent |
string | Agent slug for this step. Step-level overrides workflow-level agent:, which overrides default_agent:. The agent's mode (headless/tmux) decides how the step executes. |
timeout |
duration | e.g. 30m. Default: 30 minutes. |
human |
bool | Pause and wait for explicit approval in the TUI. |
summarization_strategy |
enum | last_message, summarize_chat (default; runs your summarizer: command over the chat log), or none (no context captured). |
loop |
object | Jump back to an earlier step. See below. |
Loops
- name: reviewing
prompt: |
Review iteration {{loop.iteration}} of {{loop.max_iterations}}.
{{steps.implementing.context}}
If everything passes, output nothing.
loop:
goto: implementing
max_iterations: 3
exit_condition:
step_context_empty: reviewing # exit early when this step's output is empty
Loops must point to an earlier step, can't be human: or resolve to a tmux-mode agent, and can't overlap with other loops.
Template variables
Available in any step prompt:
{{task.id}},{{task.title}},{{task.input}},{{task.context}},{{task.slug}},{{task.branch}}
—task.inputis the user-supplied task input;task.contextis the summary written by the workflow's summarizer after the task completes; empty until then.{{tasks.<id>.<field>}}— reference another task's field by ID. Supported fields:title,branch,input,context.
References inside the task's owninput/contextare pre-expanded before being inlined into a step prompt
(single-pass; nested refs in the looked-up task's fields remain verbatim).
At create or edit time, references are validated:- missing task, cross-project, failed dependency, or unsupported field → request is rejected;
- active dependency → added automatically to
blocked_by; - completed dependency → no edge added (its fields are already resolvable);
- self-reference → resolved at runtime, but never added as a
blocked_byedge.{{tasks.<id>.context}}is only populated after the referenced task has been summarized.
{{git.base_branch}}{{steps.<step_name>.context}}— captured output of a prior step{{loop.iteration}},{{loop.max_iterations}}(inside a loop body)
Worktree provisioning
worktree-sync-paths:
copy: [".env", ".env.local"] # copied into each worktree
link: [".claude", "node_modules"] # symlinked
worktree-setup-commands: # run sequentially after sync
- bun install
- bun run db:migrate
tmux-setup-command: | # run once after tmux session creation
tmux split-window -h "tail -f .sakusen/logs/<id>/<step>.log"
Project layout reset
cmd/sakusen/ CLI entry points (daemon, tui, task CRUD)
internal/
config/ .sakusen.yml parsing, agent registry, project type auto-detection
daemon/ Background daemon: Unix socket server, scheduling, pub/sub
workflow/ Step engine, prompt templating, summarizer, merge logic
runner/ Agent command spawning (headless Process, RunSync, tmux wrapper scripts)
agent/ Agent state machine, concurrent worker manager
task/ Task model, status state machine, priority, dependencies
tui/ BubbleTea terminal UI (list, detail, prompt, animation)
db/ SQLite persistence and migrations
git/ Worktree, branch, merge, conflict-resolution operations
tmux/ Tmux session lifecycle, capture, monitoring
client/ IPC client (RPC + event subscription) for tui/cli
notify/ Desktop notifications
claude-code-plugin/ Companion Claude Code plugin (sakusen-configurer skill)
The daemon listens on a Unix socket at ~/.config/sakusen/daemon.sock (or $XDG_CONFIG_HOME/sakusen/) and persists state to tasks.db next to it. Project-level data (logs, the .worktrees/ directory) lives under .sakusen/ inside the repo.
TUI
Launch with sakusen tui. Add -g / --global to see tasks across every project Sakusen has tracked.
Common keys (full help with ctrl+h):
| Key | Action |
|---|---|
j / k / ↑↓ |
Move selection |
enter |
Open task detail / follow live logs |
n / N |
New task / new blocking task |
c |
Continue an awaiting-approval / completed / failed task |
s |
Stop the running step |
r / R |
Retry / revert |
t |
Attach to the task's tmux session |
b / alt+b |
Branch a new task off this one / toggle branch tree view |
D / A |
Detach branch from worktree / reattach |
o / e |
Open / edit step context (artifact) |
dd |
Delete task (worktree + branch + logs) |
/, ?, n, N |
Vim-style search and next/prev match |
gg, G, :N |
Jump to top, bottom, or line N |
In the detail view, j/k/G/gg/ctrl+u/ctrl+d scroll logs; esc toggles between follow and normal mode; e opens the log file in $EDITOR.
Agents
Every workflow step runs an agent: a shell command declared under the top-level agents: map. Steps pick one via agent: <slug> (step-level beats workflow-level beats default_agent:, which falls back to the claude slug). The agent record's mode decides how the step executes:
headless(default) — Sakusen spawns the command, streams its stdout to the task log, and reads the final result text from$SAKUSEN_RESULT_FILEwhen it exits.tmux— Sakusen runs the command inside a detached tmux session (<project>-<task_id>); the workflow pauses at the interactive session.
default_agent: claude
agents:
claude:
mode: headless
command: '"$SAKUSEN_PROJECT_PATH/.sakusen/agents/claude-headless.sh"'
claude-tmux:
mode: tmux
command: '"$SAKUSEN_PROJECT_PATH/.sakusen/agents/claude-tmux.sh"'
resume_command: 'claude --dangerously-skip-permissions --resume "$SAKUSEN_SESSION_ID"'
chat_log_command: '"$SAKUSEN_PROJECT_PATH/.sakusen/agents/claude-chat-log.sh"'
summarizer: # utility LLM: chat/step/task summaries + AI task titles
command: claude -p --output-format text --model haiku --dangerously-skip-permissions
max_prompt_bytes: 380000
Sakusen communicates with agents purely through environment variables: SAKUSEN_PROMPT_FILE (the fully-resolved step prompt), SAKUSEN_RESULT_FILE (headless result text), SAKUSEN_DONE_DIR/SAKUSEN_DONE_PREFIX (tmux turn-end sentinels), plus SAKUSEN_TASK_ID, SAKUSEN_STEP, SAKUSEN_WORKTREE, SAKUSEN_PROJECT_PATH, SAKUSEN_AGENT, and SAKUSEN_PURPOSE.
Tmux auto-advance is sentinel-driven: when the agent finishes a turn, something inside the session (a hook, the agent itself, an idle-watcher) writes "$SAKUSEN_DONE_DIR/$SAKUSEN_DONE_PREFIX-$(date +%s%N).json"; the daemon polls for these files and advances the workflow (unless the step has human: true). The file may carry a JSON payload with session_id (enables resume_command restore and chat_log_command lookup) and transcript_path. The scaffolded claude-tmux agent wires this up via a Claude Code Stop hook; agents that never write a sentinel are manual-advance — the task waits in tmux status until you advance it. Agents without a chat_log_command can't capture summarize_chat context for tmux steps. Without a summarizer: there are no AI titles or summaries (titles fall back to truncated input).
agent mode |
human |
Behavior |
|---|---|---|
| headless (default) | false | headless spawn + auto-advance on exit |
| headless | true | headless spawn, then pause at awaiting_approval |
| tmux | false | tmux + auto-advance on turn-end sentinel (manual-advance if the agent writes none) |
| tmux | true | tmux + manual approval (drop into the session, then press a/c) |
Press t in the TUI to attach to a tmux session. Sakusen detects nested-tmux situations (you're already inside tmux) and either switches client or nests a session, controlled by tmux_nested_attach_behavior (switch / nest).
sakusen attach <task_id> does the same from the shell.
Migrating from claude: / yolo: / print: configs
The old Claude-specific keys were removed and are now hard load errors with migration messages:
claude:(binary override) → define agents underagents:(runsakusen initin a fresh project for scaffolded records)yolo:→ put permission flags (e.g.--dangerously-skip-permissions) directly in the agent'scommandsystem_prompt:→ bake system-prompt flags into the agent'scommandor fold the text into step promptsallowed_summarization_models:→ thesummarizer:command; pick the model inside itprint:/tmux:(workflow and step level) → select an agent whosemodeisheadless(wasprint: true) ortmux(wasprint: false){{claude_command}}intmux-setup-command→{{agent_command}}or{{run_agent}}
CLI reference
Daemon
sakusen daemon start # start (foreground; background it with '&' or your service manager)
sakusen daemon stop # graceful shutdown
sakusen daemon status # is it running, what PID
Tasks
sakusen create <input> [--workflow w] [--priority high] [--title T]
[--branch tmpl] [--target main] [--checkout existing-branch]
[--no-worktree]
sakusen tasks [<id>] [--json] # list, or detail for one
sakusen edit <id> [--title T] [--input I] [--context C] [--priority P]
sakusen delete <id> [-y]
sakusen start <id> # manually kick off a pending task
sakusen stop <id> # stop a running task
sakusen retry <id> [--from-step name] # restart workflow (default) or jump to a specific step
sakusen revert <id> # revert all commits made by a completed task
sakusen continue <id> # resume an awaiting-approval / completed / failed task
sakusen logs <id> [step] [-n N]
sakusen cleanup [<id>] # remove worktree + branch + logs for completed/failed
sakusen agents [--json] # list running agents
sakusen depends-on add <id> <blocked-by-id> # mark <id> as blocked by another task
sakusen depends-on rm <id> <blocked-by-id> # remove a dependency
sakusen depends-on list <id> # list tasks blocking <id>
Worktree branch management
sakusen detach <id> # detach branch so you can check it out elsewhere
sakusen attach-branch <id> # reattach after detach
sakusen attach <id> # attach to the task's tmux session
TUI
sakusen tui [-g] # -g for cross-project view
Requirements
- Go 1.25+
- git (worktree support, ≥ 2.5)
- Whatever your configured agent commands need — the
sakusen initscaffold uses the Claude Code CLI (claude) andjq - tmux (only required if you use tmux-mode agents or
sakusen attach)
Reviews (0)
Sign in to leave a review.
Leave a reviewNo results found