| name | mainctrl |
| description | Runtime safety guard for OpenClaw multi-agent workflows. Blocks destructive tools (write, edit, exec, process, apply_patch) for controlled agents, forcing delegation to sub-agents.
|
| intents | ["stop main agent from writing files directly","enforce sub-agent delegation in OpenClaw","block destructive tools for controlled agents","runtime agent permission management","multi-agent tool access control"] |
| tags | ["multi-agent","access-control","delegation","openclaw","agent-orchestration","security"] |
| icon | 🛡️ |
| disable-model-invocation | true |
| user-invocable | true |
| metadata | {"author":"iClaw","version":"1.1.1"} |
mainctrl — Agent Tool Access Control
mainctrl lets you control which agents can use destructive tools (write,
edit, exec, process, apply_patch). Turn it on — main delegates everything
to sub-agents. Turn it off — main is free. Add or remove agents from the
controlled list to extend protection. Install or remove the companion
plugin in one command. No restarts, no config edits.
Invocation Model
mainctrl is a management/admin skill — it is NOT loaded into your system
prompt. The frontmatter settings enforce this:
-
disable-model-invocation: true — This skill is excluded from the
agent's available skills list at load time. You will never see these
instructions unless the user invokes mainctrl explicitly.
-
user-invocable: true — The skill is available as the /mainctrl
slash command for manual invocation.
When to follow these instructions: Only when the user has explicitly
invoked mainctrl via /mainctrl. In all other contexts, this skill is
inactive — do not pre-emptively apply its rules or behavior.
Startup Behavior
Every time mainctrl is loaded (the agent reads this SKILL.md), it MUST:
- Run
./scripts/mainctrl.sh status to read the current runtime state.
- Remember the output internally — which tools are blocked, which
agents are controlled, and the current exec allow-except config.
This contextual awareness guides every subsequent tool call: know
when to delegate and which commands can pass through.
Quick Start
-
Install the plugin and restart:
./scripts/mainctrl.sh plugin install
Then restart the OpenClaw gateway.
Safety starts OFF — nothing is blocked yet.
After restart, run ./scripts/mainctrl.sh status to load the
current control state into context per Startup Behavior.
-
Before turning on, verify at least one sub-agent exists and main can spawn it.
Check with the agents_list tool — you need at least one agent besides main:
agents_list
Also verify main's spawn permissions:
gateway config.get agents.list → main.subagents.allowAgents
mainctrl blocks main's destructive tools and requires at least one
sub-agent to delegate the work to. If none is configured or main can't
spawn them, set up a sub-agent first.
-
Turn blocking on:
./scripts/mainctrl.sh on
-
Verify:
./scripts/mainctrl.sh status
Should show safety: ON and all tools BLOCKED.
-
Emergency off — temporarily allow all tools.
When mainctrl is ON, main can't run commands itself. To turn it off:
Plugin + Skill
mainctrl is two parts that must be installed together:
- Plugin (
plugin/index.js) — a before_tool_call hook. It reads
state.json on every tool call and blocks or allows based on your settings.
Installed via ./scripts/mainctrl.sh plugin install.
- Skill (this directory) — the management side. The CLI script
(
mainctrl.sh) writes config to state.json; this SKILL.md tells the
agent how to behave when blocked.
Both talk through a shared file — the skill writes state.json, the plugin
reads it. No sockets, no RPC, no restart.
The skill installs the plugin for you — run ./scripts/mainctrl.sh plugin install
to set up both halves in one step.
How it works
Mainctrl gives the agent two modes, like vi's V and I:
Visual mode (mainctrl on) — the agent can inspect, search, and read,
but cannot modify anything. Every destructive tool call is blocked:
| Tool | Why blocked |
|---|
write | File creation / overwrite |
edit | In-place file edits |
exec | Shell command execution |
process | Background process management |
apply_patch | Multi-file patching |
Tools NOT blocked
When mainctrl is ON, these tools remain fully available to the controlled agent:
| Tool | Purpose |
|---|
read | File reading / inspection |
web_search | Web search |
web_fetch | URL content fetching |
sessions_spawn | Sub-agent delegation |
sessions_send | Cross-session messaging |
sessions_list | Session discovery |
sessions_history | Session history |
image | Image analysis |
pdf | PDF analysis |
memory_search | Memory search |
memory_get | Memory read |
message | Channel messaging |
gateway | Config read / status |
skill_workshop | Skill management |
cron | Job scheduling |
nodes | Node device queries |
tts | Text-to-speech |
agents_list | Agent discovery |
🔴 Do NOT default to exec for inspection tasks. When you need to explore
directories, read files, check config, or verify state, use read — it's
available, immediate, and never blocked. exec is only for tasks that truly
require shell access (build, install, git, etc.), and those should always be
delegated to sub-agents.
This is how today's incident happened: the agent used exec for find and ls
instead of read — needless blocking and unnecessary sub-agent overhead.
Insert mode (mainctrl off) — the agent can write files, run commands,
and make changes freely.
Switch between them with ./scripts/mainctrl.sh on and ./scripts/mainctrl.sh off.
A lightweight OpenClaw plugin (extensions/mainctrl) hooks
before_tool_call and inspects every tool call. When the caller is
one of the controlledAgents and mainctrl is enabled, the above
tools are rejected with a delegation message.
Agents not in controlledAgents are never affected —
they always have full tool access.
Agent behavior rule
When the main agent receives the block message:
Delegate this work to a sub-agent instead.
Use sessions_spawn to dispatch to coder, tester, auditor, or publicist.
it MUST:
- Briefly inform the user that the operation has been blocked and is being delegated.
- Immediately spawn a sub-agent (coder, tester, auditor, or publicist) to complete the blocked operation.
Do NOT wait for the user to confirm — report and delegate in the same turn.
Blocked tool response
When a controlled agent calls a blocked tool, it receives:
Delegate this work to a sub-agent instead.
Use sessions_spawn to dispatch to coder, tester, auditor, or publicist.
The agent follows the Agent behavior rule and
spawns a sub-agent to complete the work automatically.
Security Design Notes
1. Internal guard, not anti-hacker
This is an internal self-discipline mechanism, not a defense against external attacks.
mainctrl makes the LLM agent think twice before proceeding with destructive
operations. It is not a sandbox or security boundary; the goal is to prevent
accidental misuse by the agent itself (e.g., main writing code when it should
delegate to coder), not to stop a malicious attacker. Treat it as a guardrail
that makes the LLM say "should I really do this myself?" — not a fortress.
2. Explicit delegation
When spawning a sub-agent to replace a blocked operation, always state who
is being delegated and why. For example:
exec blocked. Delegating to coder to run the command.
This transparency helps the user understand the delegation chain at a glance.
3. Direct for skill execution
All CLI commands in this skill are designed for programmatic execution (by
agents or other skills). There are no interactive prompts, confirmations, or
friction — they complete in one shot. This is intentional: when an agent needs to
toggle safety or reconfigure, it must not hang waiting for user input.
Why this approach
mainctrl uses an OpenClaw before_tool_call plugin hook rather than
agent-level tools.deny for two reasons. First, tools.deny is
static — toggling it requires editing agent config and restarting the
gateway. The plugin hook reads state.json on every call, so safety
can be toggled at runtime with a single command and takes effect
immediately. Second, agent configs cannot distinguish which caller
is invoking a tool; the plugin hook can selectively block agents
listed in controlledAgents while leaving sub-agents (like coder)
unaffected.
Exec allow-except — safe exec commands
execAllowExcept only takes effect when exec is in blockedTools. If exec is not blocked, all commands pass through regardless of this config.
When exec is in the blocked tools list, mainctrl inspects the command
before blocking. Commands listed as keys in execAllowExcept are
allowed through — unless the command line contains an allow-except pattern.
How it works
- Command's first word is looked up in
execAllowExcept
- If the command is not a key → blocked as usual (delegate)
- If the command is a key → each allow-except pattern is checked against the full command string
- If any allow-except pattern matches → blocked with a specific reason
- If no allow-except pattern matches → allowed
Allow-except patterns
| Command | Allow-except patterns | What they catch |
|---|
find | -exec, -ok | Execute commands on matched files |
| -delete | Delete matched files |
| -fprint | Write to arbitrary files |
| |, $( , > , >> | Pipes, command substitution, redirection |
ls | > , >> , ` | ` |
pwd | > , >> , ` | ` |
sed | > , >> , ` | ` |
| .java , .py , .js , .html , .css | Direct source file modification |
cat | > , >> , ` | ` |
Substring matching is used (e.g. -exec also catches -execdir).
Configuration
execAllowExcept lives in state.json. To modify it, edit the file
directly (no CLI subcommand for this):
./scripts/mainctrl.sh allow-except '{"find":["-exec","-ok","-delete","-fprint","|","$(",">",">>"],"ls":[">",">>","|"],"pwd":[">",">>","|"],"sed":[">",">>","|",".java",".py",".js",".html",".css"],"cat":[">",">>","|"]}'
Run ./scripts/mainctrl.sh status to see the current allow-except configuration.
Examples
ls ~/ # ✅ allowed (no allow-except hit)
find . -type f # ✅ allowed
find . -exec rm {} \; # 🛡 blocked: "-exec"
ls > /tmp/foo # 🛡 blocked: ">"
sed -i 's/foo/bar/g' file.java # 🛡 blocked: ".java"
cat /etc/hosts # ✅ allowed (no allow-except hit)
rm -rf / # 🛡 blocked: not in allow-except map
Commands
Use the mainctrl.sh script in the scripts/ directory:
| Command | Effect |
|---|
./scripts/mainctrl.sh status | Show current state (enabled + agents + per-tool + exec allow-except) |
./scripts/mainctrl.sh on | Enable blocking |
./scripts/mainctrl.sh off | Disable blocking (all tools pass through) |
./scripts/mainctrl.sh agents '<json-array>' | Set which agents are controlled |
./scripts/mainctrl.sh tools '<json-array>' | Set or show blocked tools list |
./scripts/mainctrl.sh allow-except '<json>' | Set execAllowExcept config (JSON object) |
./scripts/mainctrl.sh refresh-memory | Write current status to ~/.openclaw/workspace/MEMORY.md |
./scripts/mainctrl.sh plugin install | Install the companion plugin via openclaw plugins |
./scripts/mainctrl.sh plugin remove | Disable and uninstall the companion plugin |
Plugin Installation
Instead of manually adding the plugin path to plugins.load.paths,
use the built-in script:
./scripts/mainctrl.sh plugin install
./scripts/mainctrl.sh plugin remove
This calls openclaw plugins install / uninstall, which manages the
plugin under OpenClaw's native plugin registry. After install or remove,
restart the gateway for the change to take effect.
controlledAgents config
controlledAgents lists which agents are subject to tool blocking.
Example configs:
{ "enabled": true, "controlledAgents": ["main"] }
{ "enabled": true, "controlledAgents": ["main", "auditor"] }
State file
skills/mainctrl/scripts/state.json (in the scripts/ directory):
{
"enabled": false,
"controlledAgents": ["main"],
"blockedTools": ["write", "edit", "exec", "process", "apply_patch"],
"execAllowExcept": {
"find": ["-exec", "-ok", "-delete", "-fprint", "|", "$(", ">", ">>"],
"ls": [">", ">>", "|"],
"pwd": [">", ">>", "|"],
"sed": [">", ">>", "|", ".java", ".py", ".js", ".html", ".css"],
"cat": [">", ">>", "|"]
}
}
Changes take effect immediately on the next tool call — no restart needed.
Plugin
The companion extension lives at extensions/mainctrl/.
It reads state.json on every before_tool_call event,
so the latency is a single fs.readFileSync per tool call.
Troubleshooting
| Symptom | Check |
|---|
| Blocking not working | ./scripts/mainctrl.sh status — ensure safety is ON |
| Plugin not loaded | openclaw plugins list — ensure mainctrl is enabled |
Agent still blocked after off | Restart the gateway |
| Sub-agent blocked too | Run ./scripts/mainctrl.sh agents main to restrict to main only |
| Safe exec command blocked | Check ./scripts/mainctrl.sh status — command must be in exec allow-except map and not matching allow-except patterns |
Examples
What you can do with mainctrl:
Control the safety switch
./scripts/mainctrl.sh on
./scripts/mainctrl.sh off
./scripts/mainctrl.sh status
Add or remove controlled agents
./scripts/mainctrl.sh agents '["main"]'
./scripts/mainctrl.sh agents '["main","auditor"]'
./scripts/mainctrl.sh agents '[]'
Add or remove blocked tools
./scripts/mainctrl.sh tools
./scripts/mainctrl.sh tools '["write","exec"]'
./scripts/mainctrl.sh tools '["write","edit","exec"]'
./scripts/mainctrl.sh tools '["write","edit","exec","process","apply_patch"]'
./scripts/mainctrl.sh tools '[]'
Manage the plugin
./scripts/mainctrl.sh plugin install
./scripts/mainctrl.sh plugin remove
Set exec allow-except
./scripts/mainctrl.sh allow-except '{"find":["-exec","-ok","-delete","-fprint","|","$(",">",">>"],"ls":[">",">>","|"],"pwd":[">",">>","|"],"sed":[">",">>","|",".java",".py",".js",".html",".css"],"cat":[">",">>","|"]}'
./scripts/mainctrl.sh allow-except '{}'
Verification