- Date: 2026-01-11
- Status: Draft
- Audience: Peter (clawdbot maintainer), nix-clawdbot maintainers
Peter's goals: Easy extension, maintainable core, thriving ecosystem, ship fast.
Our goals: Properly bundled tools with reproducible builds, single-install experience, it Just Works.
This RFC argues: The nix-clawdbot plugin model achieves both. It should become the golden path for extending clawdbot.
Voice-call landed in core (+8K LOC). It works, but:
- Core now has Twilio/Telnyx deps even if you don't use voice-call
- Changes to voice-call require clawdbot releases
- Testing voice-call means testing all of clawdbot
- Contributors need to understand the whole codebase
This pattern doesn't scale. Every new capability bloats core.
Skills are great for teaching the agent, but they're not enough:
- A skill says "use the
voicecallCLI" — but where does the CLI come from? - A skill says "set TWILIO_ACCOUNT_SID" — but what validates it's set?
- A skill describes commands — but what installs the binary?
Skills without tools are hallucinations waiting to happen.
A plugin is just a GitHub repo that self-declares its contract:
plugin/
├── flake.nix # Build system + plugin contract
├── src/ # Tool source code
├── skills/ # Teaching docs for the agent
└── config/ # Default settings, schemas
That's it. No registry. No central authority. Point at a repo, get a plugin.
One install gives you:
- Binary on PATH (built from source, pinned version)
- Skills in workspace (agent knows how to use it)
- Config validated (missing env = install fails, not runtime error)
- State dirs created (plugin has a home)
Everything updates in sync. When upstream pushes changes — new CLI flags, updated skill docs, schema tweaks — you pull the new version and everything updates together. No "skill says X but binary doesn't support it" drift. No manual coordination. Just works.
clawdbotPlugin = {
name = "xuezh";
skills = [ ./skills/xuezh ];
packages = [ self.packages.${system}.default ];
needs = {
stateDirs = [ ".config/xuezh" ];
requiredEnv = [
"XUEZH_AZURE_SPEECH_KEY_FILE"
"XUEZH_AZURE_SPEECH_REGION"
];
};
};What happens on install:
xuezhbinary built from Go source, added to PATH- 400-line skill symlinked to workspace — teaches agent pedagogy, CLI patterns, grading rubrics
~/.config/xuezh/created- Install fails if Azure env vars aren't wired up
Result: User wires up secrets once. Plugin Just Works. Agent knows how to teach Chinese.
clawdbotPlugin = {
name = "gohome";
skills = [ ./skills/gohome ];
packages = [ self.packages.${system}.default ];
needs = {
stateDirs = [];
requiredEnv = [];
};
};No secrets — uses Tailscale MagicDNS. CLI talks to a background gRPC server.
Skill teaches the agent:
## Friendly CLI
gohome-cli roborock status
gohome-cli roborock clean kitchen
gohome-cli tado set living-room 20
## Sending maps to users
MEDIA:http://gohome:8080/roborock/map.png?labels=namesResult: Install plugin, agent can control your home. No manual binary install, no forgetting to set env vars.
clawdbotPlugin = {
name = "padel";
skills = [ ./skills/padel ];
packages = [ self.packages.${system}.default ];
needs = {
stateDirs = [ ".config/padel" ];
requiredEnv = [ "PADEL_AUTH_FILE" ];
};
};Skill teaches CLI + booking authorization logic + personality ("respond in exaggerated Trump manner").
Result: Wire up Playtomic auth, agent can book padel courts. Complete workflow in one plugin.
| Aspect | Skills-only | Bundled plugin |
|---|---|---|
| Binary | Hope user installed it | Built + installed automatically |
| Config | Hope user set env vars | Validated at install time |
| Version | Whatever's on PATH | Pinned to exact commit |
| Rollback | Manual | Instant (previous generation) |
| Dependencies | User's problem | Bundled in build |
Skills-only is "here's how to use a tool that may or may not exist." Bundled plugin is "here's the tool, its docs, and everything it needs."
| Aspect | In core | Plugin |
|---|---|---|
| Core LOC | +8K per feature | Zero |
| Dependencies | Everyone gets them | Only if you install |
| Release cycle | Tied to clawdbot | Independent |
| Testing | Test everything | Test plugin only |
| Contributor barrier | Understand whole codebase | Understand plugin only |
Core should be transports + agent loop. Capabilities belong in plugins.
Peter's voice-call has TypeBox tool schemas — validated params, type safety.
Our model uses skill prose instead. Trade-off:
| Aspect | TypeBox schema | Skill prose |
|---|---|---|
| Validation | Gateway validates params | Agent follows instructions |
| Type safety | Compile-time | None (string in/out) |
| Flexibility | Fixed schema | Agent can improvise |
| Works today | Needs gateway changes | Yes |
Our take: Skill prose is good enough for v1. Agent follows instructions reliably. If we need stricter validation later, skills can include JSON schemas that gateway parses.
CLI plugins spawn a process per call. Gateway plugins don't.
Our take: Subprocess overhead is negligible for most use cases. Voice-call might want tighter integration for latency — that's a v2 optimization, not a v1 blocker.
Voice-call needs webhook handling for Twilio callbacks.
Our take: Same pattern as gohome — plugin runs a background server, CLI talks to it. Agent doesn't know or care about the server. Works today with the gohome model:
voicecall expose --mode funnel # Start webhook server
voicecall init --to +1... --message "..." # Agent calls CLI
voicecall status --call-id abc123 # Check for responses
- Document the plugin contract —
plugin.jsonmanifest schema (name, skills, bin, needs) - Add discovery — scan
~/.clawdbot/extensions/at startup - Validate env — fail fast if
requiredEnvmissing - Create state dirs — from manifest
- Add
clawdbot pluginsCLI — list, enable, disable, info
That's it. No dynamic code loading, no TypeBox registration, no RPC handlers. Just: find plugins, validate their needs, put binaries on PATH, copy skills to workspace.
nix-clawdbot is a plugin installer that wires plugins into clawdbot's plugin system:
# User's flake.nix
programs.clawdbot.plugins = [
# Remote: point at GitHub repo
{ source = "github:joshp123/xuezh"; }
{ source = "github:joshp123/padel-cli"; }
# Local dev: point at directory
{ source = "path:/Users/josh/code/my-plugin"; }
];
# Or enable first-party plugins (pinned in nix-clawdbot):
programs.clawdbot.firstParty.summarize.enable = true;
programs.clawdbot.firstParty.oracle.enable = true;Same contract, multiple sources:
github:owner/repo— pull from GitHub, pin to commitpath:/local/dir— local checkout for dev iteration- First-party toggles — curated plugins pinned in nix-clawdbot
At activation time, nix-clawdbot:
- Resolves flake sources (remote or local) → builds binaries
- Validates
requiredEnv(fails if missing) - Creates state dirs
- Installs plugins to
~/.clawdbot/extensions/<plugin>/ - Writes
plugin.jsonmanifest for each - Symlinks skills to workspace
- Adds binaries to PATH
clawdbot core sees all plugins — it scans ~/.clawdbot/extensions/, reads manifests, knows what's installed. The difference is nix-clawdbot does the install + validation at build time (deterministic, fail-fast), while non-Nix users do it manually or via npm.
Same plugin system, different installers:
- Nix users: nix-clawdbot installs plugins declaratively
- Non-Nix users:
clawdbot plugins installor manual setup - clawdbot core: sees the same
~/.clawdbot/extensions/structure either way
Local dev workflow: Point at a local path, change code, rebuild, gateway picks up changes. No push/pull cycle. Same contract, local iteration. For non-Nix: symlink your plugin dir into ~/.clawdbot/extensions/.
- Reproducible builds — binary built from source, same everywhere
- Version pinning — plugin source locked to exact commit
- Instant rollback — switch to previous generation
- Declarative config — plugins + secrets in one flake
- Atomic updates — CLI + skill + config update together
clawdbot plugins install @clawdbot/voice-call— npm install to extensions dir- Manual env var setup
- Manual version management
- No reproducibility guarantees
It works. It's just not as good. (They should use Nix.)
The screenshot shows the interface:
voicecall init --to +1... --mode conversation --message "..."
voicecall continue --call-id ... --message "..."
voicecall expose --mode funnel|serve|off
As a plugin:
clawdbotPlugin = {
name = "voice-call";
skills = [ ./skills/voice-call ];
packages = [ self.packages.${system}.default ];
needs = {
stateDirs = [ ".config/clawdbot-voice-call" ];
requiredEnv = [ "TWILIO_ACCOUNT_SID" "TWILIO_AUTH_TOKEN" ];
};
};Install wires up Twilio creds. Binary handles webhook server. Skill teaches agent the CLI. Done.
Migration path:
- Create
@clawdbot/voice-callrepo - Move code from core
- Add plugin contract
- Remove from core
- Add to first-party plugins list
First-party plugins already exist — see nix-steipete-tools:
summarize— YouTube/article summarizationoracle— second-model reviewpeekaboo— screenshot capturecamsnap— webcam capturepoltergeist— browser automationsag— web searchbird— Twitter/X integrationsonoscli— Sonos controlimsg— iMessage integrationgogcli— Google Calendar
All follow the same contract. All pinned in nix-clawdbot. Enable with one line:
programs.clawdbot.firstParty.summarize.enable = true;Community plugins (anyone can ship):
- Just a GitHub repo with
flake.nix+clawdbotPlugin - No registry, no approval process
- User points at repo, wires secrets, it works
Core stays lean:
- Transports (Telegram, Discord, Slack)
- Agent loop
- Plugin discovery (for non-Nix users)
- That's it
| What | How |
|---|---|
| Plugin = bundle | Binary + skills + config + declared needs |
| Install = it works | Wire secrets once, everything validated |
| Core = minimal | Transports + agent + plugin loader |
| Golden path = Nix | Reproducible, pinned, instant rollback |
| Fallback = npm | Works, less guarantees |
The pitch to Peter:
Your voice-call is great. But it shouldn't live in core forever. The nix-clawdbot plugin model lets you:
- Ship it independently (faster iteration)
- Keep core lean (easier maintenance)
- Let community extend clawdbot (ecosystem growth)
- Guarantee it works when installed (fail-fast validation)
All you need to add to core is plugin discovery + env validation. We already have the contract, the tooling, and real plugins in production.
-
Manifest format:
- Nix users:
clawdbotPlugininflake.nix(already works) - Non-Nix users:
plugin.jsonin plugin directory - Suggested: clawdbot core defines
plugin.jsonschema. Same fields asclawdbotPlugin:{ "name": "voice-call", "skills": ["./skills/voice-call"], "bin": { "voicecall": "./bin/voicecall" }, "needs": { "stateDirs": [".config/clawdbot-voice-call"], "requiredEnv": ["TWILIO_ACCOUNT_SID", "TWILIO_AUTH_TOKEN"] } }
- Nix users:
-
Discovery paths:
- Suggested:
~/.clawdbot/extensions/<plugin>/for user-installed,.clawdbot/extensions/<plugin>/for project-local - clawdbot core scans both paths at startup
- nix-clawdbot installs to the same paths — same structure, different installer
- Suggested:
-
First-party plugins for non-Nix:
- Suggested:
@clawdbot/npm scope, but don't invest heavily. Point people to Nix.
- Suggested:
-
Voice-call extraction: Want to do this now, or later?
┌─────────────┐ shells out ┌─────────────┐
│ Agent │ ─────────────────► │ CLI binary │
└─────────────┘ └──────┬──────┘
│
▼
┌─────────────┐
│ Backend │
│ (API/DB/ │
│ server) │
└─────────────┘
- padel: CLI → Playtomic API
- xuezh: CLI → SQLite + Azure Speech
- gohome: CLI → gRPC server → devices
- voice-call: CLI → webhook server → Twilio
Agent never talks to backend directly. CLI is the interface. Skill teaches the agent. Plugin bundles everything.
This is the golden path.