agentctl is a command-line tool that manages AI agent harness configurations from a
single declarative workspace.toml. It reads one source of truth and compiles it into
the native config directories each harness expects, so the same setup works across tools
and machines.
The tool follows a Terraform-like workflow: validate checks the workspace, preview
shows diffs before anything is written, and apply writes changes after confirmation.
It targets OpenCode, Agy (Antigravity CLI), Claude Code, and Codex. Each harness is a single renderer module, so adding or adjusting support stays isolated.
[global], overridable per harness (each harness has its own model family).system_prompt, applied where the native config supports instructions.SKILL.md plus scripts) copied into each harness's skills/ folder.allowed_dirs, auto_approve, require_approval declared once; mapped to each
harness's permission schema (Agy command(...), Claude Bash(...), Codex approval_policy).extra, passed through to the native config.Two rules keep the setup unified:
[global] and overridden per harness where they differ.
A harness omits what it inherits. Set skills = [] to override a default with nothing.$KEY / ${KEY} environment expansion at render time, so secrets stay out of the file.agentctl binary.Build from source:
cargo install --path agentctl
A workspace.toml lives at the root of a folder that represents the configuration (commonly the project itself):
[global]
model = "claude-sonnet-4"
max_spend_limit = 5.0
system_prompt = "You are a senior software engineer."
skills = ["plan_make"]
allowed_dirs = ["./src", "./tests"]
auto_approve = ["git status", "ls", "cargo test"]
require_approval = ["git push", "npm install"]
[skills.plan_make]
type = "skill"
path = "skills/plan-make"
[[harnesses]]
provider = "opencode"
config_path = "~/.dotfiles/opencode"
extra = { instructions = ["{env:HOME}/.benjamin-plus/injected-instruction.md"] }
[[harnesses]]
provider = "agy"
model = "Gemini 3.7 Flash (Medium)" # overrides the global model
config_path = "~/.gemini/antigravity-cli"
extra = { allowNonWorkspaceAccess = true, enableTelemetry = false }
Skill path values may be relative to the workspace file or absolute. config_path
points to a harness directory, not a single file, and supports ~ and $HOME.
agentctl validate
Parses the workspace and checks that skill references resolve and providers are known. Useful in CI.
agentctl preview
Renders every harness and prints colored unified diffs for each generated file, plus a summary of skill directory changes. Nothing is written.
agentctl apply # shows diffs, then prompts
agentctl apply --yes # writes without prompting
Before any change, apply copies the harness directory into a timestamped backup
(<dir>.<nanos>.bak). Files are written atomically (temp file plus rename).
All commands accept -c <path> to point at a workspace file; the default is ./workspace.toml.
agentctl/
├── agentctl-core/ # library: model, parsing, rendering
│ └── src/
│ ├── model.rs # workspace data model and validation
│ ├── parse.rs # TOML parsing entry points
│ ├── expand.rs # env var expansion
│ └── render/ # Renderer trait + per-harness impls
│ ├── opencode.rs # OpenCode renderer
│ ├── agy.rs # Agy renderer
│ ├── claude.rs # Claude Code renderer
│ └── codex.rs # Codex renderer
├── agentctl/ # CLI binary
│ └── src/
│ ├── cli.rs # clap-based CLI definition
│ ├── commands/ # validate, preview, apply, common
│ └── output.rs # colored unified diffs
├── config/ # example workspace for this repo
│ ├── workspace.toml
│ └── skills/ # local skill copies
└── Cargo.toml # Cargo workspace
| Provider | Rendered files | Notes |
|---|---|---|
opencode | opencode.jsonc, system_prompt.md, skills/ | Provider config managed manually for now |
agy | settings.json, skills/ | system_prompt/require_approval not emitted |
claude | settings.json, skills/ | Guardrails map to permissions.allow/ask |
codex | config.toml, skills/ | Guardrails approximated by approval_policy/sandbox_mode |
Primitives a harness cannot express are reported as // not rendered: ... notices during preview.
Found a bug, typo, or have a suggestion? Open an issue at github.com/RomanVolkov/agentctl/issues.
Please include a description of the problem, steps to reproduce, and expected versus actual behavior.
Licensed under the MIT License.
Built with the Rust ecosystem:
Roman Volkov
Questions, suggestions, or collaboration? Open an issue on GitHub.
11 commits
Rust
100.0%
agentctl is a command-line tool that manages AI agent harness configurations from a
single declarative workspace.toml. It reads one source of truth and compiles it into
the native config directories each harness expects, so the same setup works across tools
and machines.
The tool follows a Terraform-like workflow: validate checks the workspace, preview
shows diffs before anything is written, and apply writes changes after confirmation.
It targets OpenCode, Agy (Antigravity CLI), Claude Code, and Codex. Each harness is a single renderer module, so adding or adjusting support stays isolated.
[global], overridable per harness (each harness has its own model family).system_prompt, applied where the native config supports instructions.SKILL.md plus scripts) copied into each harness's skills/ folder.allowed_dirs, auto_approve, require_approval declared once; mapped to each
harness's permission schema (Agy command(...), Claude Bash(...), Codex approval_policy).extra, passed through to the native config.Two rules keep the setup unified:
[global] and overridden per harness where they differ.
A harness omits what it inherits. Set skills = [] to override a default with nothing.$KEY / ${KEY} environment expansion at render time, so secrets stay out of the file.agentctl binary.Build from source:
cargo install --path agentctl
A workspace.toml lives at the root of a folder that represents the configuration (commonly the project itself):
[global]
model = "claude-sonnet-4"
max_spend_limit = 5.0
system_prompt = "You are a senior software engineer."
skills = ["plan_make"]
allowed_dirs = ["./src", "./tests"]
auto_approve = ["git status", "ls", "cargo test"]
require_approval = ["git push", "npm install"]
[skills.plan_make]
type = "skill"
path = "skills/plan-make"
[[harnesses]]
provider = "opencode"
config_path = "~/.dotfiles/opencode"
extra = { instructions = ["{env:HOME}/.benjamin-plus/injected-instruction.md"] }
[[harnesses]]
provider = "agy"
model = "Gemini 3.7 Flash (Medium)" # overrides the global model
config_path = "~/.gemini/antigravity-cli"
extra = { allowNonWorkspaceAccess = true, enableTelemetry = false }
Skill path values may be relative to the workspace file or absolute. config_path
points to a harness directory, not a single file, and supports ~ and $HOME.
agentctl validate
Parses the workspace and checks that skill references resolve and providers are known. Useful in CI.
agentctl preview
Renders every harness and prints colored unified diffs for each generated file, plus a summary of skill directory changes. Nothing is written.
agentctl apply # shows diffs, then prompts
agentctl apply --yes # writes without prompting
Before any change, apply copies the harness directory into a timestamped backup
(<dir>.<nanos>.bak). Files are written atomically (temp file plus rename).
All commands accept -c <path> to point at a workspace file; the default is ./workspace.toml.
agentctl/
├── agentctl-core/ # library: model, parsing, rendering
│ └── src/
│ ├── model.rs # workspace data model and validation
│ ├── parse.rs # TOML parsing entry points
│ ├── expand.rs # env var expansion
│ └── render/ # Renderer trait + per-harness impls
│ ├── opencode.rs # OpenCode renderer
│ ├── agy.rs # Agy renderer
│ ├── claude.rs # Claude Code renderer
│ └── codex.rs # Codex renderer
├── agentctl/ # CLI binary
│ └── src/
│ ├── cli.rs # clap-based CLI definition
│ ├── commands/ # validate, preview, apply, common
│ └── output.rs # colored unified diffs
├── config/ # example workspace for this repo
│ ├── workspace.toml
│ └── skills/ # local skill copies
└── Cargo.toml # Cargo workspace
| Provider | Rendered files | Notes |
|---|---|---|
opencode | opencode.jsonc, system_prompt.md, skills/ | Provider config managed manually for now |
agy | settings.json, skills/ | system_prompt/require_approval not emitted |
claude | settings.json, skills/ | Guardrails map to permissions.allow/ask |
codex | config.toml, skills/ | Guardrails approximated by approval_policy/sandbox_mode |
Primitives a harness cannot express are reported as // not rendered: ... notices during preview.
Found a bug, typo, or have a suggestion? Open an issue at github.com/RomanVolkov/agentctl/issues.
Please include a description of the problem, steps to reproduce, and expected versus actual behavior.
Licensed under the MIT License.
Built with the Rust ecosystem:
Roman Volkov
Questions, suggestions, or collaboration? Open an issue on GitHub.
11 commits
Rust
100.0%