The Problem with Always-On Stacks
A serious red-team engagement can need any of the following — Sliver C2, BloodHound CE for Active Directory, Ghidra for reversing, mobile emulation, IoT firmware tooling, ICS protocol simulators. Booting every one of them on everydecepticon start would:
- Burn minutes of cold-start time on every launch.
- Consume gigabytes of RAM the operator never asked for.
- Make
decepticon stoptake five minutes to drain.
The Decepticon Model (ADR-0006)
The agent decides when a workload is needed and asks for it on the fly. The orchestrator carries three tools:| Tool | What it does |
|---|---|
ops_start("c2-sliver") | Spin up the workload’s containers on sandbox-net. Returns immediately with state: "starting". |
ops_stop("c2-sliver") | Graceful shutdown when the workload is no longer needed. |
ops_status() | Snapshot of every workload’s current state — used for polling fallbacks. |
How a workload spawn flows
The opscontrol Daemon
opscontrol is a small per-user daemon installed alongside the launcher. It owns the host’s Docker socket and runs Compose on behalf of the agent.
Lifecycle management
Three install modes, picked in order:- systemd user unit (Linux) —
decepticon opscontrol installwrites a unit at~/.config/systemd/user/decepticon-opscontrol.service. The init system supervises crash-recovery and survives reboot. - launchd LaunchAgent (macOS) — Same shape, written to
~/Library/LaunchAgents/. - Launcher-spawn fallback — On hosts without a recognized init system (Windows native, WSL2 without systemd), the launcher forks a detached daemon process and writes a PID file. Less robust than service-managed, but functional.
Security boundary
- Daemon runs as the operator’s UID, not root — it has only the Docker permissions the user already has via
dockergroup membership. - Agent talks to daemon over a Unix domain socket at
$DECEPTICON_HOME/run/ops.sock. Never TCP. Never network-reachable. - A strict allowlist caps what the agent can spawn:
ops_start("postgres") to escalate against the management plane.
Auto-Notification, Not Polling
The naive design would have the agent poll: callops_start("ad"), then loop on ops_status("ad") until ready, burning tokens and turns on no-ops.
Decepticon ships an OpsControlNotificationMiddleware that subscribes to daemon state events and injects state transitions directly into the agent’s next turn as a <system-reminder> HumanMessage. The agent’s logical flow is:
SandboxNotificationMiddleware — see Autonomous Execution.
Operator Overrides
For operators who prefer “everything up at launch” — typically CI regression runs that want the full matrix online before the agent starts — setCOMPOSE_PROFILES explicitly:
COMPOSE_PROFILES empty and let the agent drive the stack.
What ships in OSS
The opscontrol daemon, the threeops_* tools, the allowlist, the auto-notification middleware, and the systemd/launchd supervisor variants all ship in the OSS release. Workload profile definitions (c2-sliver, ad, reversing, …) live in docker-compose.yml next to the default stack, so adding a workload is a Compose-file change plus an allowlist entry — no agent code changes required.
Autonomous Execution
The agent’s other auto-notification surface — background bash commands also push completion notices, not polling.
Infrastructure
Where these workloads sit in the two-network topology.
