Siegclaw is a lightweight Rust orchestrator for request-driven coding automation.
Quickstart: docs/quickstart.md Architecture Diagram (SVG): docs/architecture.svg
Given an initial request prompt, Siegclaw:
- loads a flow pack (
flow.json, prompts, optional skills, optional agent definitions), - starts a local OpenCode server process,
- runs flow steps through OpenCode in a bounded controller loop,
- validates structured output plus controller-observable postconditions,
- stops on verified completion or iteration failure.
Siegclaw no longer runs its own native LLM + shell tool loop. OpenCode is now the coding engine.
- Siegclaw: supervision, prompt composition, state binding, verification, process control
- OpenCode server: coding execution, model/tool use, delegation, repository actions
- Flow pack: workflow schema, prompts, optional specialist agent definitions, optional skills
- External tools: MCP servers, shell commands, CLIs, HTTP targets, SSH targets, Docker, etc.
In this repo, Siegclaw starts OpenCode server in the same container/process namespace using:
opencode serve --hostname <host> --port <port><host>/<port> are derived from OPENCODE_URL (default http://127.0.0.1:4096).
- Read config, load
mcp.json, and loadflow.json. - Load flow-local skills and optional flow-local agent definitions.
- Inject MCP servers into OpenCode config (
opencode.json) as local MCP tools. - Start local OpenCode server.
- Build the system prompt from
soul.md+ optionalpolicy.md+ active skills + optional flow-defined agents + task context. - Execute flow steps in order from
flow.json. - For each step, run retries (
firstthenretry) until schema validation and controller verification both pass ormax_attemptsis exhausted. - Stop OpenCode server.
- Exit non-zero on unrecoverable errors.
src/main.rs- CLI entrypoint
- run/doctor mode dispatch
- local OpenCode process startup/shutdown
src/opencode.rs- OpenCode HTTP client (
/global/health,/session,/session/:id/message,/session/:id/diff)
- OpenCode HTTP client (
src/engine.rs- step loop
- retry handling
- schema + verification gating
src/verification.rs- controller-run verification rules
- local/ssh/http/docker probes
src/state.rs- runtime state store
- payload-to-state binding
src/prompting.rs- prompt loading
- prompt context rendering
src/capabilities.rs- flow-local/global skill loading
- optional flow-defined agent loading
src/readiness.rs- MCP config parsing + startup readiness checks (required MCP servers and OpenCode tool visibility)
src/config.rs- CLI/env config parsing
doctorsubcommand parsing
CLI:
siegclaw --init-prompt "read jira ticket TEST-1 and do the work" \
[--workdir /workspace] \
[--mcp-config mcp.json] \
--flow /path/to/flow/dir \
[--soul /path/to/soul.md] \
[--skills-dir /path/to/skills] \
[--max-iterations 6] \
[--opencode-url http://127.0.0.1:4096] \
[--opencode-session-id SESSION_ID] \
[--opencode-agent AGENT] \
[--opencode-model MODEL] \
[--opencode-stream-events] \
[--opencode-stream-heartbeat] \
[--log-prompts] \
[--opencode-home /path/to/opencode/home] \
[--opencode-auth-json '{...}'] \
[--opencode-auth-json-path /path/to/auth.json] \
[--opencode-config-json '{...}'] \
[--opencode-config-json-path /path/to/opencode.json]
# validate a flow pack without starting OpenCode
siegclaw doctor --flow /path/to/flow/dir [--skills-dir /path/to/skills] [--mcp-config mcp.json]OpenCode connection env:
SIEGCLAW_INIT_PROMPTOPENCODE_URLOPENCODE_SESSION_IDOPENCODE_AGENTOPENCODE_MODELOPENCODE_STREAM_EVENTS(true/false, defaultfalse)OPENCODE_STREAM_HEARTBEAT(true/false, defaultfalse)OPENCODE_SERVER_USERNAMEOPENCODE_SERVER_PASSWORDOPENCODE_HOMESIEGCLAW_FLOW_DIR(equivalent to--flow)SIEGCLAW_LOG_PROMPTS(true/false, defaultfalse)
OpenCode config/auth injection env:
OPENCODE_AUTH_JSONOPENCODE_AUTH_JSON_PATHOPENCODE_CONFIG_JSONOPENCODE_CONFIG_JSON_PATH
Notes:
- If
OPENCODE_HOMEis not set, Siegclaw uses<workdir>/.opencode-home. - If auth/config JSON env/path is provided, Siegclaw writes:
~/.local/share/opencode/auth.json~/.config/opencode/opencode.jsonunder the selected OpenCode home.
- If
--mcp-configis provided, Siegclaw injects those servers intoopencode.jsonunder themcpkey so OpenCode can call them directly.
--soul (or default ~/.siegclaw/soul.md) is the global base system prompt for OpenCode.
Use it for cross-flow invariants (quality/safety/validation honesty and communication style), not flow-specific policy.
Siegclaw renders these template variables in soul.md before sending the system prompt:
{{REQUEST_ID}}{{REQUEST_SUMMARY}}{{REQUEST_DESCRIPTION}}{{WORKDIR}}
Example snippet in soul.md:
Validation policy:
- Never mark compile/tests as passed unless actually observed.Siegclaw requires a flow pack directory. Full schema reference: docs/flow-schema.md
CLI:
--flow /path/to/flow/dirA flow pack contains:
flow.json(canonical JSON flow definition)- optional
policy.md(referenced bypolicy_prompt) - prompt files referenced by
flow.json - optional
skills/for flow-local skill prompts - optional
agents/for flow-defined specialist agent prompts - example specialist agent:
flows/jira-flow/agents/jira-agent.md
Shipped flow:
flows/jira-flow/flows/deploy-flow/flows/k8s-deploy-flow/- In Docker, mount
flows/from host and point--flowat that mounted path.
Notes:
- step names are dynamic; they are not hardcoded in Rust.
- each step can set
max_attempts; if omitted, Siegclaw uses--max-iterations. - flow defaults may define
agent,skills, andallowed_subagents. - flows may optionally define top-level
agentswith prompt files for specialist nested-agent patterns. - each step may override
agentand extendskills/allowed_subagents. - each step may export payload values into controller state via
bind. - each step may declare controller-run
verificationrules. - each step block supports:
system(optional)first(required)retry(optional)response_marker(optional)response_json_template(optional)
- flow-local skills are loaded from
<flow>/skillsbefore user/global skill directories. policy_promptcontent is injected into the system prompt before skills/task blocks.prerequisites.required_mcp_serversis validated against loadedmcp.json.
Siegclaw builds each step prompt from a layered context:
soul.md(global, cross-flow invariants)
- identity/voice
- quality and safety standards
- validation honesty
policy.md(flow-wide policy)
- phase ordering and ownership model
- flow-specific policy and workflow rules
- active capability blocks
- selected skills
- optional flow-defined specialist agents referenced by
allowed_subagents
- task + step context
- current request data
- bound controller state
- retry feedback
- current step prompt (
first/retry/ optionalsystem)
Flow capability routing:
agentselection resolves in this order: step override -> flow default -> CLI--opencode-agentskillsresolve per step; if the flow does not specify skills, Siegclaw injects no skills for that stepallowed_subagentsare injected into the current step system prompt as scoped delegation capabilities- if an allowed subagent matches a flow-defined agent, Siegclaw injects that agent definition into the system prompt for the current step
bindexports response values into controller state for later prompts and verifiers- step
verificationrules run in Siegclaw after the agent replies; controller evidence can block step completion and trigger retries - generic
probeverification lets a flow define controller-observable postconditions such as SSH checks on a deployed server whenlets flows express conditional postconditions like “only verify smoke test URL when it is present” or “only require rollout checks when changes were made”all/anylet flows compose postconditions without hardcoding branching logic into Rustdoctorvalidatesflow.json, prompt references, flow-local skills, configured skills, and MCP prerequisite references before runtime
Verifier notes:
- local verifiers now include
git_branch,git_status, andgit_remote - provider-backed verifiers include
github_prandjira_issue_status - provider-backed checks require controller-side credentials (
GITHUB_TOKENfor GitHub,JIRA_BASE_URL/JIRA_USER_EMAIL/JIRA_API_TOKENfor Jira) - probe transports now include
local,ssh,http, anddocker_exec - probe assertions now include HTTP body and JSON-path checks in addition to exit/stdout/stderr checks
- non-HTTP probes can also use JSON-path assertions against stdout, plus negated/numeric comparisons like
stdout_not_containsandstdout_json_path_number_compare
Use doctor to validate a flow pack before runtime:
siegclaw doctor --flow /path/to/flow/dir --mcp-config mcp.json [--skills-dir /path/to/skills]doctor checks:
flow.jsonparses and validates- referenced prompt files exist
- flow-local and configured skills resolve
- optional flow-defined agent prompts resolve
- MCP prerequisites referenced by the flow exist in
mcp.json
Prompt/state notes:
- bound state values are available to prompts through generic placeholders like
{{namespace}},{{state.namespace}}, or uppercase aliases like{{NAMESPACE}} - probe commands, URLs, headers, bodies, and literal assertions also support
{{...}}interpolation from payload/state values
- Siegclaw derives internal run IDs as:
<FLOW_NAME>-MMHHMMSS
- Example:
JIRA-FLOW-V2-03101542
- This is internal run identity and fallback context, not external Jira ticket identity.
steps/*.md(step-local prompts)- what to do in that step
- what not to do in that step
- exact output marker + JSON contract
Minimal shape:
{
"mcpServers": {
"jira": {
"command": "...",
"args": ["..."],
"env": {}
},
"github": {
"command": "...",
"args": ["..."],
"env": {}
}
}
}- Use MCP server env for Jira/GitHub credentials.
- Jira/GitHub MCP tools are called directly by OpenCode (not by Siegclaw).
mcp.example.jsonis wired to the official GitHub MCP server binary:- command:
github-mcp-server - args:
["stdio"] - transport:
"jsonl" - env:
GITHUB_PERSONAL_ACCESS_TOKEN
- command:
- For local (non-Docker) runs, install it with:
go install github.com/github/github-mcp-server/cmd/github-mcp-server@latest
- This repo includes:
mcp.mock-jira.jsonmcp.example.jsonmock/mock_jira_mcp.py
For flows that create branches/files/PRs on GitHub, the GitHub MCP token must allow:
Contents:Read and writePull requests:Read and writeMetadata:Read-only(required by GitHub)
If Contents is read-only, branch creation and file updates will fail with 403 Resource not accessible by personal access token.
JSON structured logs by default.
Primary sources:
CORE,AGENT,MCP,OPENCODE
Controller loop behavior:
- Siegclaw executes each step in
flow.jsonsequentially. - For each step attempt:
- renders prompt placeholders
- auto-injects
response_marker+response_json_templatecontract (if configured) - validates returned JSON against the step template
- On retry, Siegclaw injects controller feedback and previous response context.
- Flow completes when all steps pass their configured contract.
- After each completed step, Siegclaw logs
AGENT event=step_completion_verifiedwith:- verified response values used to advance, or
considered completed since no JSON values are defined for this step.
- Stream behavior:
- By default, OpenCode stream event logs are off.
- Use
--opencode-stream-events(orOPENCODE_STREAM_EVENTS=true) to emit OpenCode event/activity logs. - With stream events enabled, Siegclaw logs concise activity summaries (
event=stream_activity) and OpenCode request/response events. - Heartbeat/noise events (
session.idle,session.busy,session.diff) are hidden by default. - Use
--opencode-stream-heartbeattogether with--opencode-stream-eventsto include heartbeat activity logs. - Right before stopping the local OpenCode process, Siegclaw logs final aggregate token usage as
OPENCODE event=session_usage_summary.
- Prompt logging behavior:
- By default, rendered prompts are not logged.
- Use
--log-prompts(orSIEGCLAW_LOG_PROMPTS=true) to log full renderedsystem_promptanduser_promptper step attempt (AGENT event=step_prompt_dump).
Permission behavior:
- Siegclaw writes a default
opencode.jsonwithpermission: "allow"when no config is provided, so OpenCode runs non-interactively by default inside the container sandbox. - If you provide
OPENCODE_CONFIG_JSONorOPENCODE_CONFIG_JSON_PATH, your config is used as the base and Siegclaw merges/injects MCP servers from--mcp-config.
Dockerfile builds siegclaw and installs runtime dependencies including OpenCode CLI.
Build:
docker build -t siegclaw:dev .Run:
docker run --rm -it \
-v "$PWD":/workspace \
-w /workspace \
-e OPENCODE_URL=http://127.0.0.1:4096 \
siegclaw:dev \
--init-prompt "read jira ticket TEST-1 and do the work" \
--mcp-config /workspace/mcp.example.json \
--flow /workspace/flows/jira-flowEphemeral run (no repository persistence on host):
docker run --rm -it \
-v "$PWD/mcp.json":/run/config/mcp.json:ro \
-v "$PWD/flows":/run/flows:ro \
-v "$PWD/mock":/run/mock:ro \
-v "$HOME/.local/share/opencode/auth.json":/run/secret/auth.json:ro \
-v "$HOME/.config/opencode/opencode.json":/run/secret/opencode.json:ro \
-w /run \
-e OPENCODE_AUTH_JSON_PATH=/run/secret/auth.json \
-e OPENCODE_CONFIG_JSON_PATH=/run/secret/opencode.json \
siegclaw:dev \
--init-prompt "read jira ticket TEST-1 and do the work" \
--workdir /workspace \
--mcp-config /run/config/mcp.json \
--flow /run/flows/jira-flowNotes:
- Relative paths inside
mcp.jsonare resolved against themcp.jsonparent directory when they exist. - Using
-v "$PWD":/workspacepersists workspace state between runs.
Requirements:
- Rust stable
gitpython3node/npm
Commands:
cargo fmt
cargo test
cargo build- Exit
0: completed (validation + workflow criteria satisfied) - Exit non-zero: workflow failure (OpenCode, MCP bootstrap/config integration, flow response-template validation failure)
- OpenCode server is started as a local process; if startup fails, run fails.
- Siegclaw does not inspect or manage repository state itself; flows must instruct OpenCode to report required lifecycle fields.
- No persistent DB/state in Siegclaw.