Status: Draft — ready for review
Repo: Codename-11/ARC
Absorbs: Codename-11/axiom-supervisor (full merger)
Updated: 2026-04-01
ARC is a unified agent runtime control plane — a single CLI/TUI/dashboard that manages the full lifecycle of AI coding agents across runtimes. It replaces both the original ARC profile manager (v0.1) and the Axiom-Supervisor execution layer.
One binary. One config directory (~/.arc/). Every agent runtime — Claude Code, Codex CLI, Gemini CLI, OpenClaw, or anything that speaks MCP/HTTP/stdio.
What it does:
| Layer | Capability |
|---|---|
| Identity | Named profiles, credentials, auth (OAuth/API key/Bedrock/Vertex/Foundry), OS keyring, env isolation |
| Launch | Tool detection, shell shims, arc launch, per-profile flags, workspace-aware auto-selection |
| Supervision | Hook pipeline, risk classification, preflight/postflight, retry loops, scope tracking |
| Orchestration | Roundtable multi-agent discussions, interagent routing, source classification, task delegation |
| Observability | Debug traces, SQLite persistence, web dashboard, session tracking, alerts, benchmarks, OpenTelemetry |
| Sync | Shared config layer (MCP servers, CLAUDE.md), memory-to-Obsidian sync, profile inheritance |
| Protocols | MCP host + server, A2A agent cards + task lifecycle, HTTP REST API |
| Memory | Hierarchical memory (session/persistent/team), aging/TTL, relevance search, auto-extraction, cross-agent sharing |
| Skills | Directory-based skill loading, MCP-to-skill adapters, self-improving skillify (agent writes its own skills) |
| Tasks | First-class task tools (create/get/list/update/stop/output), cron scheduling, agent-to-agent messaging |
| Routing | Score-based prompt routing — tokenize input, score against tool/command registries, auto-dispatch |
What it is not:
- Not a replacement for agent CLIs — it wraps and supervises them
- Not an LLM — it's deterministic-first infrastructure that optionally calls LLMs for enhanced analysis
- Not a framework — it's a runtime control plane that works with any framework
- Deterministic over probabilistic — binary checks first (file changed? build passed?), LLM only sees structured metadata. Same input → same output.
- Safe by default —
logmode, neverenforceunless explicitly opted in. No audit loop traps. - Pluggable adapters — new runtime = new adapter, zero core changes. Common interface, runtime-specific integration depth.
- Profile-scoped everything — switch profile = switch identity + credentials + adapter + hook config + enforcement mode + launch flags + MCP servers.
- Graceful degradation — hook error → skip + log, never crash the pipeline. Watchdog unreachable → warn, don't block.
- Protocol-native — MCP for tool integration, A2A for agent orchestration. Standards over custom wire formats.
- Observable — every supervision decision produces a trace. Every trace is queryable. OpenTelemetry-compatible.
- Dark Factory ready — architecture supports fully autonomous operation: spec in → software out, with ARC as the control plane.
┌─────────────────────────────────────────────────────────────┐
│ ARC CLI / TUI / Web │
│ Profiles · Credentials · Dashboard · Doctor · Onboarding │
│ TUI: quick ops (Ink) · Web: deep observability (Express) │
├─────────────────────────────────────────────────────────────┤
│ Orchestration Layer │
│ Hook Pipeline · Risk Classifier · Retry Loop · Roundtable │
│ Session Tracker · Alert Engine · Scope Tracker · Traces │
│ Circuit Breaker · Dark Factory Controller │
├──────────┬──────────┬──────────┬──────────┬─────────────────┤
│ Claude │ Codex │ Gemini │ OpenClaw │ Generic │
│ Adapter │ Adapter │ Adapter │ Adapter │ Adapter │
│ │ │ │ │ │
│ SDK + │ Process │ Process │ Plugin │ MCP Server │
│ Hooks + │ Wrap + │ Wrap + │ API + │ or HTTP │
│ Plugin │ MCP + │ MCP + │ Hooks │ │
│ System │ JSON │ stdio │ │ │
├──────────┴──────────┴──────────┴──────────┴─────────────────┤
│ Protocol Layer │
│ MCP Host (connect to tool servers) │
│ MCP Server (expose ARC supervision as tools) │
│ A2A Agent Cards (capability discovery + task delegation) │
├─────────────────────────────────────────────────────────────┤
│ Storage Layer │
│ SQLite (traces, sessions, benchmarks, audit history) │
│ Profiles & Config (~/.arc/) │
│ OS Keyring (credentials) │
│ OpenTelemetry Export (OTLP) │
└─────────────────────────────────────────────────────────────┘
Agent launched via `arc launch <profile>`
↓
[Profile Resolution] — credentials, env, adapter, flags
↓ (if arc.json found)
[Workspace Override] — per-repo profile/adapter auto-selection
↓ (if inherits set)
[Profile Inheritance] — base profile merged with overrides
↓
[Adapter.launch()] — spawn agent process with correct env
↓
Message/event arrives from agent
↓
[Source Classifier] — human / agent / system / cron (deterministic)
↓
[Hook Pipeline] — sequential by priority, each hook is deterministic
- check() — pass / flag / block (no LLM)
- inject() — optional: add structured metadata to agent context
↓
[Circuit Breaker] — 3 consecutive failures → degrade + alert
↓
[Agent receives context] — structured metadata, not freeform LLM text
↓
[Agent responds]
↓
[Post-Process Hooks] — memory-sync, post-verify, roundtable advance
↓
[Trace Written] — SQLite + OpenTelemetry span
arc/
├── packages/
│ ├── core/ # Zero adapter dependencies
│ │ ├── hook-bus.ts # Hook registry + pipeline runner
│ │ ├── rule-engine.ts # Deterministic rule evaluation
│ │ ├── source-classifier.ts # Message source detection
│ │ ├── risk-classifier.ts # 5-tier keyword-based risk classification
│ │ ├── intent-expander.ts # LLM-optional intent expansion
│ │ ├── completion-checklist.ts
│ │ ├── completion-auditor.ts
│ │ ├── assumption-ledger.ts
│ │ ├── scope-tracker.ts
│ │ ├── retry-loop.ts
│ │ ├── session-tracker.ts
│ │ ├── circuit-breaker.ts # Failure tracking + degradation
│ │ ├── prompt-router.ts # Score-based intent → tool/command dispatch
│ │ ├── context-manager.ts # Auto-compaction, token budget, turn tracking
│ │ ├── alert-engine.ts
│ │ ├── event-bus.ts
│ │ ├── trace.ts # Debug trace builder
│ │ ├── config-loader.ts
│ │ ├── heuristics.ts
│ │ ├── types.ts
│ │ └── index.ts
│ │
│ ├── memory/ # Hierarchical memory system
│ │ ├── session-memory.ts # Per-session ephemeral memory
│ │ ├── persistent-memory.ts # Cross-session durable memory
│ │ ├── team-memory.ts # Shared across agents in multi-agent setups
│ │ ├── aging.ts # TTL + relevance decay
│ │ ├── relevance.ts # Context-aware memory search
│ │ ├── extractor.ts # Auto-extract memories from conversation
│ │ ├── sync.ts # Obsidian + external sync
│ │ └── index.ts
│ │
│ ├── skills/ # Skill system (higher-level than tools)
│ │ ├── loader.ts # Directory-based skill loading
│ │ ├── registry.ts # Skill registration + discovery
│ │ ├── mcp-adapter.ts # Wrap any MCP tool as a skill
│ │ ├── skillify.ts # Meta-skill: agent creates new skills from actions
│ │ ├── stuck-detector.ts # Self-aware loop/stuck detection + recovery
│ │ └── index.ts
│ │
│ ├── tasks/ # First-class task management
│ │ ├── task-tools.ts # TaskCreate/Get/List/Update/Stop/Output as tools
│ │ ├── cron.ts # CronCreate/Delete/List for scheduled tasks
│ │ ├── messaging.ts # Agent-to-agent messaging (SendMessageTool)
│ │ └── index.ts
│ │
│ ├── adapters/
│ │ ├── base.ts # Common adapter interface
│ │ ├── claude-code/ # SDK + Hooks + Plugin integration
│ │ │ ├── sdk-bridge.ts # SDK Control Protocol (stdin/stdout JSON)
│ │ │ ├── hooks.ts # All 27 hook event handlers
│ │ │ ├── plugin.ts # Plugin manifest + registration
│ │ │ └── index.ts
│ │ ├── codex/ # Process wrapper + JSON output + MCP
│ │ │ ├── process.ts # Spawn + JSON stream parser
│ │ │ ├── instructions.ts # instructions.md / AGENTS.md management
│ │ │ └── index.ts
│ │ ├── gemini/ # Process wrapper + stdio + MCP
│ │ │ ├── process.ts # Spawn + stdio capture
│ │ │ ├── instructions.ts # GEMINI.md management
│ │ │ └── index.ts
│ │ ├── openclaw/ # Native plugin adapter
│ │ │ ├── plugin.ts # openclaw.plugin.json + hook bus
│ │ │ ├── middleware.ts # Chat middleware
│ │ │ ├── session-bridge.ts
│ │ │ └── index.ts
│ │ └── generic/ # Fallback: MCP server or HTTP
│ │ ├── mcp-adapter.ts
│ │ ├── http-adapter.ts
│ │ └── index.ts
│ │
│ ├── protocols/
│ │ ├── mcp-host/ # Connect TO external MCP servers
│ │ │ ├── client.ts
│ │ │ ├── transports.ts # stdio, http, sse, ws
│ │ │ └── index.ts
│ │ ├── mcp-server/ # Expose ARC AS an MCP server
│ │ │ ├── server.ts
│ │ │ └── tools/
│ │ │ ├── expand-intent.ts
│ │ │ ├── classify-risk.ts
│ │ │ ├── derive-completion.ts
│ │ │ ├── audit-completion.ts
│ │ │ └── explain-trace.ts
│ │ └── a2a/ # Agent-to-Agent protocol
│ │ ├── agent-card.ts # Generate/serve /.well-known/agent.json
│ │ ├── task-lifecycle.ts # submitted → working → completed/failed
│ │ ├── discovery.ts # Find and register remote agents
│ │ └── index.ts
│ │
│ ├── profiles/ # Identity + credential management
│ │ ├── config.ts # Load/save/validate profiles
│ │ ├── inheritance.ts # Base + override resolution
│ │ ├── workspace.ts # arc.json auto-selection
│ │ ├── keyring.ts # OS keyring integration
│ │ ├── auth.ts # OAuth/API key/Bedrock/Vertex/Foundry
│ │ ├── detect.ts # Auto-detect installed tools
│ │ ├── import.ts # Import from existing tool configs
│ │ ├── shared.ts # Shared config layer sync
│ │ ├── swap.ts # Credential hot-swap (experimental)
│ │ └── types.ts
│ │
│ ├── hooks/ # Built-in hook implementations
│ │ ├── source-classify.ts # Priority 1: message source detection
│ │ ├── interagent-routing.ts # Priority 2: suppress bot→bot loops
│ │ ├── watchdog-pause.ts # Priority 5: auto-pause before destructive ops
│ │ ├── risk-detection.ts # Priority 10: keyword-based risk tiers
│ │ ├── subagent-inject.ts # Priority 15: inject rules into subagent prompts
│ │ ├── attempt-tracker.ts # Priority 20: retry counting
│ │ ├── roundtable.ts # Priority 50: multi-agent orchestration
│ │ ├── memory-sync.ts # Priority 85: Obsidian sync on session:end
│ │ ├── audit-score.ts # Priority 90: completion audit (LLM, log-only default)
│ │ └── post-verify.ts # Priority 95: gateway/service health checks
│ │
│ ├── cli/ # Commander.js commands
│ │ ├── index.ts # CLI entry point
│ │ ├── launch.ts # Launch agent with profile
│ │ ├── profile.ts # Profile CRUD
│ │ ├── shared.ts # Shared layer management
│ │ ├── doctor.ts # Diagnostics
│ │ ├── status.ts # Multi-runtime status
│ │ ├── setup.ts # Shell integration
│ │ ├── exec.ts # Execute with supervision
│ │ ├── audit.ts # Manual audit commands
│ │ ├── trace.ts # Query trace history
│ │ └── factory.ts # Dark Factory mode
│ │
│ ├── tui/ # Ink terminal UI
│ │ ├── Dashboard.tsx # Main TUI (profiles, status, quick actions)
│ │ ├── views/
│ │ │ ├── DashView.tsx
│ │ │ ├── ProfilesView.tsx
│ │ │ ├── SessionView.tsx
│ │ │ ├── SettingsView.tsx
│ │ │ ├── DoctorView.tsx
│ │ │ ├── AboutView.tsx
│ │ │ └── ...overlays
│ │ ├── components/
│ │ ├── theme.tsx
│ │ └── render.tsx
│ │
│ ├── dashboard/ # Web observability UI
│ │ ├── server.ts # Express server
│ │ ├── api.ts # REST endpoints for traces/sessions/benchmarks
│ │ ├── ui/ # Frontend (restructured from Supervisor's index.html)
│ │ │ ├── index.html
│ │ │ ├── styles/
│ │ │ ├── scripts/
│ │ │ └── components/ # Chart.js charts, trace inspector, etc.
│ │ ├── ai-analysis.ts # Optional LLM-powered trace analysis
│ │ └── auto-launch.ts # Auto-open dashboard
│ │
│ ├── db/ # SQLite persistence
│ │ ├── schema.ts
│ │ ├── traces.ts
│ │ ├── sessions.ts
│ │ ├── benchmarks.ts
│ │ └── index.ts
│ │
│ └── telemetry/ # OpenTelemetry integration
│ ├── provider.ts # TracerProvider setup
│ ├── spans.ts # Span helpers (preflight, postflight, hook, etc.)
│ ├── exporters.ts # OTLP, console, SQLite exporters
│ └── index.ts
│
├── scripts/ # Bootstrap, install, release
├── tests/
│ ├── core/
│ ├── adapters/
│ ├── protocols/
│ ├── hooks/
│ ├── e2e/
│ ├── integration/
│ └── benchmarks/
├── docs/
├── assets/
├── SPEC.md # This file
├── README.md
├── CHANGELOG.md
├── package.json
├── pnpm-workspace.yaml
├── tsconfig.json
└── tsup.config.ts
ARC maintains a persistent agent process pool for concurrent requests, with automatic fallback:
interface ProcessPool {
mode: 'direct' | 'broker';
// Broker mode: persistent background process on Unix domain socket
// Multiplexes requests with ownership tracking
// Returns BROKER_BUSY (-32001) when at capacity → client falls back to direct spawn
broker?: {
socketPath: string; // ~/.arc/run/broker.sock
maxConcurrent: number; // default: 3
idleTimeoutMs: number; // shutdown after idle period
};
// Direct mode: spawn agent as child process, stdin/stdout
// Used as fallback when broker is busy, or for one-off executions
}Behavior:
arc launchchecks for running broker → connects if available- If broker busy or unavailable → spawn direct child process
- Broker tracks ownership per socket connection — enables
interruptfrom different callers - Session-scoped cleanup on exit: broker shutdown, orphan kill, state prune
Per-workspace state management for long-running agent tasks:
~/.arc/jobs/
├── <workspace-slug>-<hash>/
│ ├── state.json # Job index (max 50 per workspace, pruned by update time)
│ └── <jobId>.json # Individual job payload + output
interface BackgroundJob {
id: string; // <prefix>-<base36-timestamp>-<random>
workspace: string;
profile: string;
pid: number; // for cancellation
status: 'running' | 'completed' | 'failed' | 'cancelled';
created: string;
updated: string;
description: string;
output?: string; // path to output file
}Jobs are detached child processes with PID tracking. arc jobs list, arc jobs cancel <id>, arc jobs output <id>.
Every adapter implements this common interface. Integration depth varies by runtime — Claude Code gets the deepest hooks, Gemini gets process-level wrapping. The core doesn't care.
export interface RuntimeAdapter {
readonly name: string; // 'claude-code' | 'codex' | 'gemini' | 'openclaw' | string
readonly capabilities: AdapterCapabilities;
// Lifecycle
launch(profile: Profile, options: LaunchOptions): Promise<AgentProcess>;
terminate(process: AgentProcess): Promise<void>;
isRunning(process: AgentProcess): boolean;
// Supervision hooks (called by core pipeline)
preflight?(ctx: HookContext): Promise<PreflightResult>;
postflight?(ctx: HookContext, response: AgentResponse): Promise<PostflightResult>;
// Context injection (adapter-specific mechanism)
injectContext?(process: AgentProcess, metadata: HookMetadata): Promise<void>;
// Monitoring
onOutput?(process: AgentProcess, handler: (data: OutputEvent) => void): void;
getStatus?(process: AgentProcess): Promise<AgentStatus>;
// Profile management
applyProfile?(profile: Profile): Promise<void>; // Write config files, env vars
detectInstallation?(): Promise<ToolDetection>; // Auto-detect installed tool
importConfig?(toolPath: string): Promise<Partial<Profile>>;
}
export interface AdapterCapabilities {
hooks: boolean; // Can fire lifecycle hooks (only Claude Code today)
sdkControl: boolean; // Has bidirectional control protocol (only Claude Code)
pluginSystem: boolean; // Supports plugins (only Claude Code)
mcpSupport: boolean; // Can connect to MCP servers (all three)
jsonOutput: boolean; // Structured JSON output (Claude Code, Codex)
sandboxing: boolean; // Native sandbox support
processWrap: boolean; // Can be wrapped at process level (all)
remoteSupport: boolean; // Can run on remote machines
permissionTier: PermissionTier; // Three-tier permission model
}
// Three-tier permission model (from claw-code research)
export type PermissionTier = 'coordinator' | 'interactive' | 'worker';
// coordinator: full tool access, can spawn sub-agents, manage tasks
// interactive: standard user-facing permissions, approval-gated
// worker: degraded permissions, no destructive ops, no spawningThe richest adapter. Three integration modes available simultaneously:
SDK Control Protocol — Bidirectional JSON over stdio. ARC spawns Claude Code via the SDK, then sends/receives control messages:
initialize— register hooks, MCP servers, system prompt, agentscan_use_tool/set_permission_mode— permission managementmcp_set_servers— dynamic MCP server managementapply_flag_settings— runtime config changeshook_callback— respond to hook events- Full message stream for monitoring
Plugin System — ARC registers as a Claude Code plugin:
arc-claude-plugin/
├── plugin.json # Manifest
├── hooks/hooks.json # All 27 event types
├── commands/ # Custom slash commands
└── agents/ # Custom agent types
HTTP Hooks — For hooks that need to call back to ARC's supervision pipeline:
{
"hooks": {
"PreToolUse": [{
"matcher": "*",
"hooks": [{ "type": "http", "url": "http://localhost:{{ARC_PORT}}/hooks/pre-tool-use" }]
}],
"PostToolUse": [{
"matcher": "*",
"hooks": [{ "type": "http", "url": "http://localhost:{{ARC_PORT}}/hooks/post-tool-use" }]
}]
}
}Hook capabilities:
PreToolUse→ can returnpermissionDecision(allow/deny/ask),updatedInput(modify tool args),additionalContextPostToolUse→ can returnadditionalContext,updatedMCPToolOutput(modify tool results)SessionStart→ injectadditionalContext, setwatchPathsStop→ final supervision pass, write traces- All 27 events available for monitoring
// Spawn with structured output
const proc = spawn('codex', [
'--json',
'--full-stdout',
'--approval-mode', profile.codex.approvalMode, // suggest | auto-edit | full-auto
'--model', profile.model,
'--project-doc', arcInstructionsPath, // injected behavioral constraints
prompt
], { env: profile.env });
// Parse JSON output stream for monitoring
proc.stdout.on('data', (chunk) => {
const events = parseJsonStream(chunk);
for (const event of events) {
pipeline.postflight(event);
telemetry.recordSpan(event);
}
});Integration points:
instructions.mdmanagement — ARC writes behavioral constraints per profile~/.codex/config.yamlmanagement — model, approval mode, MCP servers- MCP server — ARC exposes supervision tools that Codex can call
- JSON output stream — real-time monitoring and trace collection
- Sandbox enforcement — leverage OS-level sandboxing in
full-automode
const proc = spawn('gemini', [
'--non-interactive',
'--model', profile.model,
prompt
], { env: profile.env });
// stdio capture for monitoring (no structured JSON)
proc.stdout.on('data', (chunk) => {
const text = chunk.toString();
heuristics.analyze(text); // Keyword/pattern-based monitoring
telemetry.recordSpan({ type: 'output', content: text });
});Integration points:
GEMINI.mdmanagement — behavioral constraints per profile~/.gemini/settings.jsonmanagement — model, MCP servers, sandbox- MCP server — ARC as tool provider
- stdio capture — heuristic-based monitoring (less structured than Codex)
- Sandbox flag management
Maintains the existing openclaw.plugin.json structure:
{
"name": "arc",
"version": "2.0.0",
"hooks": {
"before_prompt_build": { "handler": "openclaw/hooks/before-prompt.ts", "priority": 40 },
"agent_end": { "handler": "openclaw/hooks/agent-end.ts", "priority": 40 },
"session_end": { "handler": "openclaw/hooks/session-end.ts", "priority": 40 }
}
}Integration points:
- Full lifecycle hooks via OpenClaw's plugin API
- Interagent routing (source classification + suppress rules)
- Roundtable orchestration (multi-agent turn management)
- Memory sync to Obsidian
- Shared state per session
For any agent runtime not explicitly supported:
MCP Server mode — ARC exposes 5 supervision tools:
expand_intent— structured intent expansion from raw promptclassify_risk— deterministic risk tier classificationderive_completion— generate completion checklistaudit_completion— post-execution auditexplain_trace— query and explain debug traces
HTTP REST mode — Same capabilities via REST API:
POST /api/preflight { message, sessionId }
POST /api/postflight { response, sessionId, traces }
GET /api/traces ?session=X&limit=50
GET /api/status
Every hook is a deterministic, synchronous check with optional async post-processing:
export interface Hook {
name: string;
priority: number; // lower = runs first; 0-100 range
enabled: boolean; // read from config at load time
events: HookEvent[]; // which lifecycle events trigger this hook
// Synchronous, deterministic — no LLM, no async, no side effects
check?(ctx: HookContext): HookResult;
// Optional: add structured metadata to agent context
inject?(ctx: HookContext): HookMetadata | undefined;
// Optional: runs after agent responds (async OK)
postProcess?(ctx: HookContext, response: AgentResponse): Promise<void>;
}
export type HookEvent =
| 'pre:message'
| 'pre:agent:respond'
| 'post:agent:respond'
| 'pre:subagent:spawn'
| 'pre:tool:config'
| 'pre:tool:gateway'
| 'pre:tool:exec'
| 'session:start'
| 'session:end';
export interface HookResult {
pass: boolean;
flag?: boolean; // pass but surface a warning
block?: boolean; // hard block (only in enforce mode)
reason?: string;
tier?: RiskTier;
metadata?: Record<string, unknown>;
}
export interface HookContext {
message: string;
source: MessageSource; // human | agent | system | cron
sessionId: string;
agentId: string;
profile: Profile; // full profile context
adapter: string; // which adapter is active
config: ArcConfig;
state: SessionState;
toolName?: string;
toolArgs?: unknown;
spawnTask?: string;
}type EnforcementMode = 'off' | 'log' | 'advise' | 'enforce';| Mode | Behavior |
|---|---|
off |
Disabled entirely |
log |
Runs all hooks, logs results, never blocks or retries (DEFAULT) |
advise |
Logs + injects suggestions into agent context, no forced retry |
enforce |
Logs + injects + forces retry on failed checks (max attempts applies) |
Default is log. enforce requires explicit opt-in per profile. In enforce mode, retry only triggers if audit confidence < 40%.
Events: pre:message
Classifies message source by comparing sender ID against known bot accounts and matching cron trigger patterns. Output: MessageSource = 'human' | 'agent' | 'system' | 'cron'. Must run first — all other hooks depend on source context.
Events: pre:agent:respond
Uses source classification + configurable routing rules to suppress bot→bot reply loops. @mention override always allows. Injects { should_suppress: true } when suppressing.
Events: pre:tool:config, pre:tool:gateway, matching pre:tool:exec
Auto-pauses external watchdog before destructive operations. Unpauses in postProcess. Non-blocking — if watchdog unreachable, skip + log warning.
Events: pre:message
Deterministic keyword match against configurable list → assigns risk tier. Five tiers: read-only → file-modification → build-affecting → deploy-affecting → destructive. In enforce mode only, destructive tier can block.
Events: pre:subagent:spawn
Injects compact rule summary into subagent task prompts. Configurable rule library (e.g., watchdog pause, obsidian sync, no cascade delete).
Events: post:agent:respond
Counts attempts per session+turn. In enforce mode, forces retry up to max. In log/advise, only tracks. Never retries in log mode.
Events: pre:message, pre:agent:respond, post:agent:respond
Multi-agent discussion orchestration. Detects trigger phrases, manages turn order across agents, records responses per round, signals synthesis when all rounds complete. State:
interface RoundtableState {
id: string;
topic: string;
agents: string[];
modes: Record<string, 'critic' | 'advocate' | 'neutral'>;
rounds: number;
currentRound: number;
currentTurnIndex: number;
responses: Array<{ agent: string; round: number; content: string }>;
status: 'active' | 'synthesizing' | 'complete';
startedAt: string;
}Events: session:end (default), optionally post:agent:respond
Compares mtime of mapped memory files against Obsidian counterparts. Configurable sync map. In log → flag drift. In advise → inject reminder. In enforce → auto-sync.
Events: post:agent:respond
Runs completion audit (optional LLM call). In log → only logs. In advise → injects suggestion. In enforce → triggers retry only if confidence < 40%. Hard confidence floor prevents marginal retries.
Events: post:agent:respond
ALLOW/BLOCK gate protocol for substantive code changes. When enabled, a supervisor agent (can be a different profile/runtime) reviews the primary agent's output:
- Gate only fires for substantive work (file changes, builds, deploys) — not status/reporting turns
- Supervisor receives: last assistant message, session context, changed files
- Supervisor must respond with structured first line:
ALLOW: <reason>orBLOCK: <reason> BLOCK→ primary agent must continue working with the feedback- Checks for: second-order failures, empty-state behavior, retry coverage, stale state, rollback risk
interface GateConfig {
enabled: boolean;
supervisorProfile: string; // profile to use for review
timeout: number; // max review time (default: 15 min)
onlySubstantive: boolean; // skip reporting/status turns
structuredOutput: boolean; // require ALLOW/BLOCK first line
}In log mode → gate runs but result is only traced. In advise → result injected as suggestion. In enforce → BLOCK forces agent to continue.
Events: post:tool:gateway, post:tool:config
After service restarts or config changes, polls health endpoints (up to 30s). Injects { gateway_healthy, watchdog_healthy, verified_at }. If service doesn't recover → inject alert, don't block.
interface Profile {
name: string;
inherits?: string; // base profile name
adapter: AdapterName; // claude-code | codex | gemini | openclaw | generic
// Identity
auth: AuthConfig; // OAuth | API key | Bedrock | Vertex | Foundry
env: Record<string, string>; // environment variables
// Launch
launchFlags: string[]; // per-profile persistent flags
model?: string; // default model override
approvalMode?: string; // adapter-specific approval mode
sandbox?: boolean; // enable sandboxing
// Supervision
enforcement: EnforcementMode; // off | log | advise | enforce
hooks: Record<string, HookConfig>; // per-hook enable/disable + config overrides
maxRetries: number; // default: 3
minConfidenceForRetry: number; // default: 0.40
// MCP
mcpServers: Record<string, MCPServerConfig>; // MCP servers for this profile
// Metadata
created: string;
lastUsed?: string;
tags?: string[];
}Profiles can inherit from a base profile and override specific fields:
{
"name": "work-claude",
"inherits": "base-claude",
"model": "claude-sonnet-4-20250514",
"enforcement": "advise",
"hooks": {
"risk-detection": {
"customKeywords": ["production", "deploy"]
}
}
}Resolution order: profile overrides → base profile → adapter defaults → global defaults.
Deep merge for objects, replace for primitives and arrays. hooks config merges per-hook (profile hook config extends base hook config).
Place arc.json in a project root to auto-select profile and configuration:
{
"profile": "work-claude",
"adapter": "claude-code",
"enforcement": "advise",
"hooks": {
"risk-detection": {
"blockTier": "deploy-affecting"
}
},
"mcpServers": {
"project-tools": {
"command": "npx",
"args": ["project-mcp-server"],
"env": {}
}
}
}When arc launch runs in a directory with arc.json, it merges workspace config with the named profile. Workspace config takes highest precedence (overrides even profile settings).
Resolution: arc.json → profile → base profile → adapter defaults → global defaults.
ARC as MCP Host:
- Connects to external MCP servers on behalf of managed agents
- Manages server lifecycle (start, stop, reconnect)
- Routes MCP tool calls through the supervision pipeline (risk check before tool execution)
- Supports all standard transports: stdio, HTTP, SSE, WebSocket
- Per-profile MCP server configuration
ARC as MCP Server: Exposes 5 supervision tools that any MCP-compatible agent can call:
| Tool | Description |
|---|---|
arc_expand_intent |
Expand raw prompt into structured TaskBrief |
arc_classify_risk |
Classify risk tier of a message/action |
arc_derive_completion |
Generate completion checklist for a task |
arc_audit_completion |
Audit whether a task is actually done |
arc_explain_trace |
Query and explain debug traces |
This means even agents ARC doesn't directly manage can use its supervision capabilities — just add ARC's MCP server to their config.
Each ARC-managed agent gets an Agent Card:
{
"name": "claude-work",
"description": "Claude Code agent for work projects",
"url": "http://localhost:{{ARC_PORT}}/a2a/claude-work",
"version": "1.0.0",
"capabilities": {
"streaming": true,
"pushNotifications": true
},
"skills": [
{ "id": "code-review", "name": "Code Review", "description": "..." },
{ "id": "implementation", "name": "Implementation", "description": "..." }
],
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"]
}Task lifecycle for cross-agent delegation:
Client Agent ARC Server Agent
│ │ │
│── POST /a2a/tasks/send ──────→│ │
│ { task, message } │── route to adapter ──────────→│
│ │ │── execute
│ │←── status: working ───────────│
│←── status: working ───────────│ │
│ │ │── done
│ │←── status: completed ─────────│
│←── status: completed ─────────│ │
│ { artifacts } │ │
Roundtable via A2A:
The roundtable hook uses A2A's task model internally — each agent's turn is a task with submitted → working → completed lifecycle. This replaces the custom state machine with a standard protocol.
ARC isn't limited to local CLI agents. The protocol layer supports remote agents:
Remote supervision via MCP:
- Remote agent connects to ARC's MCP server (HTTP/SSE transport)
- ARC supervision tools available over the network
- No process-level wrapping needed — agent calls ARC's tools voluntarily
Remote supervision via A2A:
- Remote agent has an Agent Card with its network endpoint
- ARC sends tasks and receives results via HTTP
- Full trace collection over the wire
Remote supervision via HTTP adapter:
- Generic HTTP REST API for any runtime
POST /api/preflight,POST /api/postflight- Agent calls ARC before/after execution
SSH-based remote wrapping:
- For CLI agents running on remote machines
- ARC spawns via SSH, captures stdio
- Same adapter interface, transport is SSH instead of local process
Prevents cascading failures when an agent or service is degraded.
interface CircuitBreakerConfig {
maxConsecutiveFailures: number; // default: 3
degradeAction: 'log' | 'pause' | 'alert'; // what to do when tripped
resetAfterMinutes: number; // auto-reset after cooldown
serialFallback: boolean; // fall back to serial execution (disable parallelism)
notifyChannels: string[]; // where to send alerts
}
interface CircuitBreakerState {
failures: number;
lastFailure?: string;
tripped: boolean;
trippedAt?: string;
degradedTo?: EnforcementMode;
}Behavior:
- Track consecutive hook/audit failures per session
- At threshold (default: 3) → trip the breaker
- Tripped actions:
- Degrade enforcement mode to
log(never force retries when things are broken) - Fire alert to configured channels
- If
serialFallback: disable parallel agent execution, switch to sequential - Log circuit breaker event as a trace
- Degrade enforcement mode to
- Auto-reset after cooldown period or manual reset via
arc reset-breaker
Every supervision decision produces a structured trace. ARC integrates with OpenTelemetry for export to any observability backend.
// Span hierarchy
arc.session // root span per session
├── arc.preflight // preflight pipeline
│ ├── arc.hook.source-classify
│ ├── arc.hook.risk-detection
│ └── arc.hook.interagent-routing
├── arc.agent.execution // agent work
│ ├── arc.tool.use // individual tool calls (from hooks)
│ └── arc.agent.output
├── arc.postflight // postflight pipeline
│ ├── arc.hook.audit-score
│ ├── arc.hook.post-verify
│ └── arc.hook.memory-sync
└── arc.circuit-breaker // if trippedAttributes on every span:
arc.profile— active profile namearc.adapter— adapter typearc.session_id— session identifierarc.enforcement_mode— current modearc.risk_tier— classified risk tier (if applicable)arc.hook.name— hook that produced this spanarc.hook.result— pass/flag/block
Export targets:
- SQLite (built-in, always on — powers the dashboard)
- OTLP (gRPC or HTTP) — for Jaeger, Grafana Tempo, Honeycomb, etc.
- Console (debug mode)
The endgame: fully autonomous agent execution. Spec in → software out, with ARC as the control plane.
┌─────────────────────────────────────────────────┐
│ Dark Factory │
│ │
│ Spec (arc.json + task definition) │
│ ↓ │
│ [Planner Agent] — breaks spec into waves │
│ ↓ │
│ [Wave Executor] — parallel independent tasks │
│ ├── Agent A (impl task 1) │
│ ├── Agent B (impl task 2) │
│ └── Agent C (impl task 3) │
│ ↓ │
│ [Verifier Agent] — read-only, cannot modify │
│ ↓ │
│ [Consensus Gate] — architect reviews high-risk │
│ ↓ │
│ [Next Wave] or [Complete] │
│ │
│ Circuit breaker: 3 failures → serial fallback │
│ All traces → OpenTelemetry │
│ All decisions → SQLite audit trail │
└─────────────────────────────────────────────────┘
Key patterns (absorbed from research):
- Scratchpad protocol — structured
DONE|{path}handoffs between agents - Wave-based parallel execution — group independent tasks, verify at wave boundary
- Read-only verifier — separate agent that cannot modify files, only validates
- Consensus gate — architect agent reviews high-risk changes before merge
- Circuit breaker — 3 consecutive failures → degrade + alert + serial fallback
CLI entry:
arc factory run --spec ./factory-spec.yaml --profile work-claude
arc factory status
arc factory abortFactory spec format:
name: "Feature X Implementation"
repo: "./my-project"
profile: work-claude
waves:
- name: "Foundation"
tasks:
- description: "Create database schema"
files: ["src/db/schema.ts"]
risk: file-modification
- description: "Create API types"
files: ["src/types/api.ts"]
risk: file-modification
- name: "Implementation"
tasks:
- description: "Implement API endpoints"
files: ["src/routes/*.ts"]
risk: build-affecting
depends_on: ["Foundation"]
- name: "Verification"
verifier: true
tasks:
- description: "Run tests, verify types, review code"
read_only: true
consensus_gate:
enabled: true
reviewer_profile: work-soren
threshold: build-affectingDark Factory is a future feature — the architecture supports it, but implementation is post-MVP. The core patterns (waves, verifier, consensus) inform the hook pipeline design today.
Hierarchical memory with aging, relevance search, and cross-agent sharing. Inspired by claw-code's 8-module memdir architecture, adapted for ARC's multi-runtime context.
interface MemoryEntry {
id: string;
content: string;
type: 'fact' | 'preference' | 'correction' | 'pattern' | 'decision';
scope: MemoryScope;
createdAt: string;
lastAccessed: string;
accessCount: number;
ttl?: number; // seconds until decay starts
relevanceScore: number; // 0-1, decays over time
tags: string[];
sourceSession: string;
sourceAgent?: string;
}
type MemoryScope = 'session' | 'persistent' | 'team';| Scope | Lifetime | Visibility | Storage |
|---|---|---|---|
| Session | Current session only | Single agent | In-memory |
| Persistent | Cross-session, decays via TTL | Single profile | SQLite (~/.arc/db/) |
| Team | Cross-session, shared | All agents in a team/roundtable | SQLite, synced via A2A or shared filesystem |
Memories decay in relevance over time. Each access refreshes the score.
function decayScore(entry: MemoryEntry, now: Date): number {
const age = (now.getTime() - new Date(entry.lastAccessed).getTime()) / 1000;
const halfLife = entry.ttl ?? 86400 * 7; // default: 7 days
const decay = Math.pow(0.5, age / halfLife);
const accessBoost = Math.min(entry.accessCount * 0.05, 0.3);
return Math.min(1, entry.relevanceScore * decay + accessBoost);
}Memories below a configurable threshold (default: 0.1) are archived, not deleted. Archived memories are excluded from search but recoverable.
Context-aware memory retrieval. Given a prompt/task, find applicable memories:
- Keyword match — tokenize input, match against memory content and tags (fast, deterministic)
- Scope filter — session > persistent > team (narrowest scope first)
- Recency bias — recently accessed memories score higher
- Type weighting — corrections and preferences rank above facts
No embedding search in v1 — deterministic keyword + recency is faster and more predictable. Embedding-based search is a future enhancement.
After each agent turn, scan the conversation for extractable memories:
- User corrections →
type: 'correction', high relevance, long TTL - Stated preferences →
type: 'preference', high relevance - Discovered patterns →
type: 'pattern', medium relevance - Decisions made →
type: 'decision', linked to session context
Extraction uses heuristic keyword/pattern matching (deterministic). Optional LLM-enhanced extraction for advise/enforce modes.
In multi-agent setups (roundtable, Dark Factory), team memory provides shared context:
- Agents can read/write to team memory scope
- Conflict resolution: last-write-wins with merge logging
- Team memories are tagged with source agent
- Roundtable decisions auto-written to team memory
- Sync via A2A task artifacts or shared SQLite
Automated memory consolidation task that runs in the background (or on-demand). Reviews recent session transcripts and synthesizes durable memories.
interface DreamTask {
type: 'dream';
status: 'running' | 'completed' | 'killed';
phase: 'orient' | 'gather' | 'consolidate' | 'prune';
sessionsReviewing: string[];
filesTouched: string[];
abortController: AbortController;
}Phases:
- Orient — scan existing memory files, read index, understand current state
- Gather — search recent transcripts for new signal (corrections, patterns, decisions)
- Consolidate — merge new signal into existing memories, resolve contradictions, convert relative dates to absolute
- Prune — update index, remove stale pointers, keep index under size budget
Trigger modes:
arc memory dream— manual trigger- Auto-dream after N sessions (configurable, default: off)
- Scheduled via cron (
arc cron create --type dream --schedule "0 3 * * *")
Dreams use the active profile's LLM. The dream prompt is deterministic — the agent decides what to consolidate.
Memory syncs to external systems via configurable sync map:
{
"memory": {
"syncTargets": {
"obsidian": {
"enabled": true,
"vaultPath": "/home/bailey/obsidian-vault",
"syncMap": {
"persistent:decisions": "3. System/Operations/Decisions.md",
"persistent:learnings": "3. System/Agents/Learnings.md"
}
}
}
}
}Skills are higher-level than tools — reusable, composable procedures that agents can learn, share, and create. Inspired by claw-code's 20-module skill architecture.
interface Skill {
name: string;
description: string;
trigger: string[]; // patterns that activate this skill
steps: SkillStep[]; // ordered steps
tools: string[]; // required tools
adapters: string[]; // compatible adapters (empty = all)
source: 'builtin' | 'user' | 'generated' | 'mcp';
created: string;
lastUsed?: string;
successRate: number; // tracked over invocations
}
interface SkillStep {
action: string; // tool call, prompt, or sub-skill
description: string;
conditional?: string; // skip if condition not met
onError: 'retry' | 'skip' | 'abort';
}Directory-based — drop a file, get a skill:
~/.arc/skills/
├── code-review.yaml # User-created skill
├── deploy-check.yaml # User-created skill
└── generated/ # Agent-generated skills (via skillify)
├── fix-eslint.yaml
└── setup-vitest.yaml
Skills also loaded from:
- Built-in skills bundled with ARC
- MCP tool adapters (any MCP tool wrapped as a skill)
- Profile-specific skills (in
~/.arc/profiles/<name>/skills/)
Wraps any MCP tool as a skill automatically:
function mcpToSkill(tool: MCPTool): Skill {
return {
name: `mcp:${tool.name}`,
description: tool.description,
trigger: [tool.name, ...extractKeywords(tool.description)],
steps: [{ action: `mcp:${tool.name}`, description: tool.description, onError: 'abort' }],
tools: [tool.name],
adapters: [],
source: 'mcp',
created: new Date().toISOString(),
successRate: 1.0
};
}Meta-skill that observes agent actions and generates new skills from demonstrated patterns:
- Observation — track sequences of tool calls that accomplish a coherent task
- Pattern detection — identify repeated 3+ step sequences across sessions
- Skill generation — create a new skill definition from the observed pattern
- Validation — test the generated skill on a similar task
- Registration — save to
~/.arc/skills/generated/if validation passes
# Manual trigger
arc skill create --from-session <sessionId> # generate skill from session transcript
arc skill create --interactive # guided skill creation
# Automatic (when enabled)
# Agent detects: "I've done this 3 times" → generates skill → asks user to approveSkillify respects the enforcement mode — in log mode it suggests skills, in advise it prompts the user, in enforce it auto-creates (with user notification).
Skills should define what an agent CAN'T do, not just what it can. Contract skills set behavioral boundaries:
name: safe-deployment
type: contract
description: "Safety boundaries for deployment operations"
constraints:
- "NEVER auto-apply review fixes — always present for approval"
- "NEVER run destructive operations without explicit confirmation"
- "NEVER bypass the consensus gate for build-affecting changes"
- "Always verify rollback path exists before deploy"
required_for:
- risk_tier: deploy-affecting
- risk_tier: destructiveContract skills are automatically loaded when risk classification triggers their required_for conditions. They inject constraints into the agent context before execution.
Standard output format for any code review hook or supervision gate:
interface ReviewOutput {
verdict: 'approve' | 'needs-attention';
summary: string;
findings: Array<{
severity: 'critical' | 'high' | 'medium' | 'low' | 'info';
title: string;
body: string;
file?: string;
line_start?: number;
line_end?: number;
confidence: number; // 0-1
recommendation: string;
}>;
next_steps: string[];
}Used by: supervision-gate hook, arc review command, Dark Factory verifier agent. The confidence + severity fields enable threshold-based auto-gating.
Self-aware recovery when an agent detects it's looping:
interface StuckDetection {
maxSimilarAttempts: number; // default: 3
similarityThreshold: number; // default: 0.85
recoveryStrategies: Array<'backtrack' | 'reframe' | 'escalate' | 'abort'>;
}Detection: compare the last N tool calls / outputs for high similarity. If stuck:
- Backtrack — revert to last known good state
- Reframe — inject a "you appear stuck, try a different approach" prompt
- Escalate — hand off to a different agent (roundtable) or alert the user
- Abort — stop execution, write trace, report
Tasks are first-class tools, not a separate API. Any agent managed by ARC can create, query, update, and complete tasks. This enables agent-to-agent delegation without custom protocols.
Exposed as both MCP tools (for agents) and CLI commands (for operators):
| Tool | Description |
|---|---|
TaskCreate |
Create a new task with description, assignee, priority, risk tier |
TaskGet |
Get task details by ID |
TaskList |
List tasks with filters (status, assignee, priority) |
TaskUpdate |
Update task fields (status, description, assignee) |
TaskStop |
Cancel a running task |
TaskOutput |
Attach output/artifacts to a completed task |
CronCreate |
Schedule a recurring task |
CronDelete |
Remove a scheduled task |
CronList |
List scheduled tasks |
SendMessage |
Send a message to another agent (by profile name or A2A agent card) |
created → assigned → working → [input-required] → completed | failed | cancelled
Maps directly to A2A task states. When A2A is enabled, tasks are interoperable with external A2A agents. When A2A is disabled, tasks are local-only in SQLite.
SendMessage enables direct communication between ARC-managed agents:
interface AgentMessage {
from: string; // profile name or agent ID
to: string; // profile name, agent ID, or A2A endpoint
content: string;
type: 'request' | 'response' | 'notification' | 'handoff';
taskId?: string; // link to a task
metadata?: Record<string, unknown>;
}Messages are routed through the hook pipeline — interagent-routing hook applies suppress rules, source-classify tags the source. This prevents bot→bot loops while allowing intentional cross-agent communication.
Score-based intent routing that auto-dispatches prompts to the correct tool, skill, or adapter without requiring explicit command prefixes.
interface RouteResult {
target: 'tool' | 'skill' | 'adapter' | 'command' | 'passthrough';
name: string;
confidence: number; // 0-1
matchedTokens: string[];
}
function routePrompt(input: string, registries: Registries): RouteResult[] {
const tokens = tokenize(input); // lowercase, strip punctuation, extract keywords
const candidates: RouteResult[] = [];
// Score against tool registry
for (const tool of registries.tools) {
const score = scoreMatch(tokens, tool.name, tool.description, tool.keywords);
if (score > 0.3) candidates.push({ target: 'tool', name: tool.name, confidence: score, matchedTokens: [...] });
}
// Score against skill registry
for (const skill of registries.skills) {
const score = scoreMatch(tokens, skill.name, ...skill.trigger);
if (score > 0.3) candidates.push({ target: 'skill', name: skill.name, confidence: score, matchedTokens: [...] });
}
// Score against CLI commands
for (const cmd of registries.commands) {
const score = scoreMatch(tokens, cmd.name, cmd.description);
if (score > 0.3) candidates.push({ target: 'command', name: cmd.name, confidence: score, matchedTokens: [...] });
}
return candidates.sort((a, b) => b.confidence - a.confidence);
}- Confidence ≥ 0.8 → auto-dispatch (no confirmation needed)
- Confidence 0.5–0.8 → suggest to user/agent, await confirmation
- Confidence < 0.5 → passthrough (no routing, agent handles normally)
- Multiple high-confidence matches → present options
Routing is deterministic (tokenization + keyword scoring). No LLM in the routing path.
Long-running agent sessions accumulate context. ARC manages context windows to prevent degradation.
After a configurable number of turns (default: 12), ARC triggers context compaction:
- Token budget check — estimate token count of session context
- If over budget → generate a compact summary of the conversation so far
- Replace old context with summary + recent turns (keep last 3-4 turns verbatim)
- Trace the compaction event (what was summarized, tokens saved)
Compaction uses the active agent's LLM (via the adapter). The compaction prompt is deterministic — the LLM call is the only non-deterministic part.
ARC defines a standard event stream for monitoring agent execution:
type StreamEvent =
| { type: 'message_start'; sessionId: string; turn: number }
| { type: 'command_match'; command: string; confidence: number }
| { type: 'tool_match'; tool: string; confidence: number }
| { type: 'permission_check'; tool: string; tier: PermissionTier; result: 'allow' | 'deny' | 'ask' }
| { type: 'message_delta'; content: string }
| { type: 'message_stop'; reason: string }
| { type: 'compaction'; turnsBefore: number; turnsAfter: number; tokensSaved: number }
| { type: 'phase_change'; phase: AgentPhase } // semantic phase tracking
| { type: 'stuck_detected'; strategy: string }
| { type: 'circuit_break'; failures: number };Permission enforcement varies by tier. Each launched agent gets a tier based on its role:
| Tier | Tool Access | Can Spawn | Can Delete | Approval Mode |
|---|---|---|---|---|
| Coordinator | Full | Yes (spawn workers) | With confirmation | Bypass |
| Interactive | Standard | Limited (sub-agents only) | With confirmation | User-gated |
| Worker | Degraded | No | No | Auto-deny destructive |
interface PermissionPolicy {
tier: PermissionTier;
allowPrefixes: string[]; // tool name prefixes allowed
denyPrefixes: string[]; // tool name prefixes blocked
requireApproval: string[]; // tools that always need approval at this tier
auditLog: boolean; // log all permission decisions
}Workers spawned by Dark Factory or roundtable default to worker tier. The orchestrating agent runs as coordinator. Interactive CLI sessions are interactive.
ARC is local-first — everything works offline from ~/.arc/. Cloud sync is optional, pluggable, and never required. ARC is a general-purpose tool — it ships with multiple sync providers and anyone can add more.
- Offline-first — ARC must work fully without network. Sync is a bonus, not a dependency.
- Credentials never leave the device — OS keyring stays local. Only non-secret profile data syncs.
- Conflict resolution — last-write-wins with conflict logging. Future: merge strategies for skills/memory.
- Pluggable backends — multiple built-in providers, plus a plugin interface for custom backends.
- Selective sync — user controls what syncs. Profiles can be marked
local-only. - No vendor lock-in — sync data is portable. Export/import between providers.
export interface SyncProvider {
readonly name: string; // 'axiom-vault' | 'git' | 's3' | string
// Lifecycle
connect(config: SyncConfig): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
// Auth (delegated to provider)
login(credentials: ProviderCredentials): Promise<SyncSession>;
logout(): Promise<void>;
getUser(): SyncUser | null;
// Device management
registerDevice(deviceName: string, platform: string): Promise<DeviceId>;
listDevices(): Promise<SyncDevice[]>;
// Data sync
push(key: string, value: unknown): Promise<void>;
pull(key: string): Promise<unknown | null>;
pullAll(prefix?: string): Promise<Record<string, unknown>>;
delete(key: string): Promise<void>;
// Realtime (optional)
subscribe?(key: string, callback: (change: SyncChange) => void): Unsubscribe;
// Batch
pushBatch(entries: Record<string, unknown>): Promise<void>;
pullSince(cursor: string): Promise<SyncDelta>;
}What crosses the wire (regardless of provider):
| Data | Sync Key | Syncs? | Notes |
|---|---|---|---|
| Profile definitions | profiles/<name>.json |
✅ | JSON, minus credentials |
| Global config | config/global.json |
✅ | Enforcement mode, defaults |
| Hook configs | config/hooks.json |
✅ | Per-hook enable/disable |
| User skills | skills/<name>.yaml |
✅ | Definitions only |
| Generated skills (skillify) | skills/generated/<name>.yaml |
✅ | Cross-device learning |
| Persistent memories | memory/<id>.json |
✅ | Persistent scope only |
| Team memories | memory/team/<id>.json |
✅ | Shared across agents |
| Plugin list | plugins/installed.json |
✅ | Names + versions |
| Preferences | preferences.json |
✅ | Theme, TUI, dashboard |
| Credentials | — | ❌ | OS keyring, never leaves device |
| Session memory | — | ❌ | Ephemeral, per-session |
| Full traces | — | ❌ | Too large, local SQLite only |
| Process state | — | ❌ | PIDs, sockets, broker — device-specific |
| Active sessions | — | ❌ | Runtime state |
All syncable data is serialized to flat JSON/YAML files under a common key structure. This means every provider — git, filesystem, S3, or API-based — works against the same data shape. Export/import between providers is trivial: it's just copying files.
ARC ships with three sync providers out of the box. No plugins required.
The most portable option. Sync data lives in a git repo — any host (GitHub, GitLab, self-hosted, bare repo on a NAS).
class GitSyncProvider implements SyncProvider {
readonly name = 'git';
// Repo structure mirrors the sync key layout:
// arc-sync-repo/
// ├── profiles/
// ├── config/
// ├── skills/
// ├── memory/
// └── preferences.json
async connect(config: SyncConfig) {
// Clone or open existing repo at config.path
// If remote URL provided, set origin
}
async push(key, value) {
// Write file at key path → git add → git commit → git push
// Commit message: "arc: update <key>"
}
async pull(key) {
// git pull → read file at key path
}
async pullSince(cursor) {
// cursor = last known commit hash
// git log --since=cursor → return changed files as delta
}
}Config:
{
"sync": {
"provider": "git",
"git": {
"repo": "git@github.com:user/arc-sync.git",
"branch": "main",
"localPath": "~/.arc/sync-repo",
"autoCommit": true,
"autoPush": true,
"commitPrefix": "arc:"
}
}
}Pros: Works with any git host. Version history for free. Teams can share a repo. Merge conflicts handled by git. Cons: No realtime push. Requires git installed. Merge conflicts can be messy for JSON files.
For local networks — NFS mount, SMB share, Syncthing folder, Dropbox, or any shared directory.
class FilesystemSyncProvider implements SyncProvider {
readonly name = 'filesystem';
// Simply reads/writes to a shared directory
// Uses file mtime for change detection
// Lockfile-based conflict prevention
async connect(config: SyncConfig) {
// Verify config.path exists and is writable
// Create lockfile for this device
}
async push(key, value) {
// Atomic write: temp file → rename at key path
// Update manifest.json with mtime + device ID
}
async pull(key) {
// Read file at key path
// Compare mtime against local version
}
async pullSince(cursor) {
// cursor = last sync timestamp
// Scan manifest.json for files modified since cursor
}
}Config:
{
"sync": {
"provider": "filesystem",
"filesystem": {
"path": "/mnt/nas/arc-sync",
"lockTimeout": 5000,
"watchInterval": 10000
}
}
}Pros: Dead simple. No account needed. Works on local network without internet. Syncthing/Dropbox gives you cloud sync without a server. Cons: No auth. No realtime events (polling only). Conflict resolution is basic (mtime-based).
For cloud-native setups. Works with AWS S3, MinIO, Cloudflare R2, Backblaze B2, or any S3-compatible API.
class S3SyncProvider implements SyncProvider {
readonly name = 's3';
// Objects stored at: s3://bucket/arc/<userId>/<key>
// Uses ETags for change detection
// Versioning for conflict recovery (if bucket versioning enabled)
async connect(config: SyncConfig) {
// Initialize S3 client with credentials
}
async push(key, value) {
// PutObject to s3://bucket/arc/<userId>/<key>
// Content-Type: application/json
}
async pull(key) {
// GetObject from s3://bucket/arc/<userId>/<key>
}
async pullSince(cursor) {
// ListObjectsV2 with prefix, filter by LastModified > cursor
}
}Config:
{
"sync": {
"provider": "s3",
"s3": {
"bucket": "my-arc-sync",
"region": "us-east-1",
"prefix": "arc/",
"endpoint": null,
"accessKeyId": "${ARC_S3_ACCESS_KEY}",
"secretAccessKey": "${ARC_S3_SECRET_KEY}"
}
}
}Pros: Scales infinitely. Works with many providers (AWS, MinIO, R2, B2). Bucket versioning gives free backup. Cons: Requires cloud account. No realtime. Credentials management.
Additional providers can be installed as plugins:
| Provider Plugin | Use Case |
|---|---|
sync-axiom-vault |
Axiom-Labs identity platform. Full auth, realtime SSE, device management, SSO across Axiom apps. |
sync-webdav |
WebDAV/CalDAV servers. Nextcloud, ownCloud, etc. |
sync-redis |
Redis/Valkey for fast team sync. Pub/sub for realtime. |
| Custom | Any backend via the SyncProvider interface. |
Axiom-Vault plugin (arc plugin install sync-axiom-vault):
- Uses
@axiom-labs/vaultSDK - Full auth (email/password, invite-only, future OAuth/SSO)
- Realtime sync via PocketBase SSE subscriptions
- Device management (register, list, last-seen)
- SSO across ClawPort, Agent-Forge, Voxel, SubFrame
- This is the provider Axiom-Labs uses internally, but it's not bundled — it's a first-party plugin
{
"sync": {
"enabled": true,
"provider": "axiom-vault",
"url": "https://vault.axiom-labs.cloud",
"autoSync": true,
"syncIntervalMs": 30000,
"syncOnLaunch": true,
"syncOnExit": true,
"localOnlyProfiles": ["dev-local"],
"exclude": ["traces/*", "jobs/*"]
}
}arc launch (or any mutation)
↓
[Local write to ~/.arc/]
↓ (if sync enabled)
[SyncProvider.push(key, value)]
↓
[Axiom-Vault stores per-user per-app]
↓ (realtime SSE to other devices)
[Other ARC instances receive update]
↓
[Merge into local ~/.arc/]
↓ (conflict?)
[Last-write-wins + conflict log]
First launch on new device:
arc login— authenticate with Axiom-Vaultarc sync pull— download all profile/skill/memory dataarc setup— install shims, shell integration (local)- Credentials: user must re-auth with each agent CLI locally (credentials never sync)
Sync provider auth is provider-specific:
| Provider | Auth Method |
|---|---|
git |
SSH keys or git credential helper (already configured on device) |
filesystem |
Filesystem permissions (no auth layer) |
s3 |
AWS-style access key + secret key (env vars or config) |
axiom-vault (plugin) |
Email/password → JWT. Future: OAuth2 SSO across Axiom apps. |
arc login prompts for provider-specific credentials. Tokens/sessions are stored locally in ~/.arc/sync/auth.json (encrypted with OS keyring where available).
For providers without auth (git via SSH, filesystem), arc login is a no-op — the provider uses whatever credentials the OS already has.
Agent credentials (OAuth tokens, API keys, cloud creds) are the most valuable and most dangerous data ARC manages. The default stance is credentials stay local — but ARC supports optional encrypted credential sync for users who need cross-device access.
- Never sync plaintext secrets. All credential sync uses envelope encryption — secrets are encrypted before they leave the device.
- Master passphrase required. Credential sync is gated behind a user-chosen passphrase that never leaves the device and is never stored. No passphrase = no credential sync.
- Per-secret opt-in. Not all credentials sync. Each secret is individually flagged
sync: true | false. Default:false. - Audit every access. Every credential read, write, sync, and agent injection is logged to the local audit trail.
- Revocable per-device. If a device is compromised, you can revoke its decryption key without rotating every secret.
interface CredentialStore {
// Local store (always available)
set(name: string, value: string, options?: CredentialOptions): Promise<void>;
get(name: string): Promise<string | null>;
list(): Promise<CredentialEntry[]>;
delete(name: string): Promise<void>;
// Sync (opt-in, requires master passphrase)
enableSync(passphrase: string): Promise<void>;
disableSync(): Promise<void>;
pushCredentials(): Promise<SyncResult>;
pullCredentials(passphrase: string): Promise<SyncResult>;
revokeDevice(deviceId: string): Promise<void>;
}
interface CredentialOptions {
sync: boolean; // default: false — must explicitly opt-in
profile?: string; // scope to a specific profile (null = global)
adapter?: string; // scope to a specific adapter
type: CredentialType; // categorization for UI and policy
rotateAfterDays?: number; // optional rotation reminder
tags?: string[]; // for filtering/grouping
}
type CredentialType =
| 'api-key' // API keys (Anthropic, OpenAI, Google, etc.)
| 'oauth-token' // OAuth access/refresh tokens
| 'ssh-key' // SSH private keys
| 'cloud-credential' // AWS/GCP/Azure creds
| 'service-token' // Service-specific tokens (GitHub PAT, etc.)
| 'certificate' // TLS certs, signing keys
| 'passphrase' // Passwords, passphrases
| 'custom'; // User-defined
interface CredentialEntry {
name: string;
type: CredentialType;
profile?: string;
adapter?: string;
sync: boolean;
created: string;
lastAccessed: string;
lastRotated?: string;
rotateAfterDays?: number;
tags: string[];
// value is NEVER in this listing — only retrieved via get()
}Master Passphrase (user-chosen, never stored)
↓ Argon2id (memory-hard KDF)
Master Key (derived, held in memory only during session)
↓ AES-256-GCM
Per-Secret Encryption (each secret encrypted individually)
↓
Encrypted Blob (synced via provider)
↓ (on target device)
Master Passphrase (re-entered on first pull)
↓ Argon2id
Master Key → AES-256-GCM decrypt → plaintext secret (into OS keyring)
Key details:
- Argon2id with high memory cost (64MB) and time cost (3 iterations) — resistant to brute force
- Each secret gets its own AES-256-GCM nonce — no nonce reuse
- Encrypted blobs include metadata (name, type, profile, tags) in the clear — only the value is encrypted
- Master key is derived per-session, held in memory, never written to disk
- On first setup:
arc secrets enable-sync→ prompts for passphrase → derives key → encrypts existing opt-in secrets → pushes to sync provider - On new device:
arc secrets pull→ prompts for passphrase → derives key → decrypts → stores in local OS keyring
Sync format (what the provider stores):
{
"version": 1,
"kdf": "argon2id",
"kdfParams": { "memory": 65536, "time": 3, "parallelism": 1 },
"secrets": [
{
"name": "anthropic-api-key",
"type": "api-key",
"profile": "work-claude",
"nonce": "base64...",
"ciphertext": "base64...",
"tag": "base64...",
"created": "2026-04-01T...",
"tags": ["llm", "production"]
}
],
"devices": [
{ "id": "laptop-abc", "name": "MacBook Pro", "registered": "2026-04-01T...", "lastSync": "..." },
{ "id": "server-xyz", "name": "Docker-Server", "registered": "2026-04-01T...", "lastSync": "..." }
]
}This file syncs via the same provider as everything else (git, filesystem, S3). The encryption is provider-agnostic — the provider just stores opaque blobs.
On each device, decrypted secrets go into the OS keyring (macOS Keychain, Windows Credential Manager, Linux libsecret/gnome-keyring) with fallback to an encrypted file at ~/.arc/secrets/vault.enc (encrypted with a device-specific key derived from machine ID + user passphrase).
~/.arc/secrets/
├── vault.enc # Encrypted fallback (when no OS keyring)
├── sync-state.json # Last sync timestamp, device registration
└── audit.log # Local credential access log
arc secret set anthropic-key "sk-ant-..." --type api-key --profile work-claude
arc secret set github-pat "ghp_..." --type service-token --sync # opt-in to sync
arc secret get anthropic-key
arc secret list # shows names, types, profiles — never values
arc secret list --show-values # requires confirmation, logged to audit
arc secret delete old-key
arc secret rotate anthropic-key # prompts for new value, updates everywhere
# Sync
arc secret enable-sync # set master passphrase
arc secret push # encrypt + push opt-in secrets
arc secret pull # pull + decrypt on this device
arc secret revoke-device <deviceId> # revoke a device's access
arc secret export --encrypted # export encrypted bundle (backup/migrate)
arc secret import <file> # import from encrypted bundleAgents need credentials to do real work — API keys for external services, cloud creds for deployments, tokens for git operations. ARC manages this through scoped injection with granular policies.
Today, agents get secrets via ad-hoc env vars (ANTHROPIC_API_KEY, AWS_ACCESS_KEY_ID). This is:
- All-or-nothing — the agent sees everything or nothing
- No audit trail — no record of which agent used which key
- No scoping — a code review agent gets the same deploy creds as a deploy agent
- No rotation awareness — agents use stale keys until they fail
Each secret can have an injection policy that controls which agents see it:
interface SecretInjectionPolicy {
// What can access this secret?
allowProfiles: string[]; // profile names (empty = all profiles)
allowAdapters: string[]; // adapter types (empty = all)
allowTiers: PermissionTier[]; // coordinator | interactive | worker (empty = all)
// How is it exposed?
injection: InjectionMethod;
// Guardrails
requireApproval: boolean; // prompt user before injecting? (default: false for interactive tier)
maxUsesPerSession: number; // rate limit (0 = unlimited)
auditLevel: 'silent' | 'log' | 'notify'; // how loudly to log access
expiresAt?: string; // auto-revoke after this date
}
type InjectionMethod =
| { type: 'env'; varName: string } // inject as environment variable
| { type: 'file'; path: string; format: 'raw' | 'json' | 'dotenv' } // write to temp file
| { type: 'mcp'; toolName: string } // expose via MCP tool call
| { type: 'prompt'; template: string } // inject into agent prompt context
| { type: 'header'; headerName: string }; // for HTTP-based agents# API key — available to all Claude Code profiles, injected as env var
arc secret set anthropic-key "sk-ant-..." \
--type api-key \
--inject env:ANTHROPIC_API_KEY \
--allow-adapter claude-code
# Deploy creds — only coordinator tier, requires approval, notify on use
arc secret set aws-prod "AKIA..." \
--type cloud-credential \
--inject env:AWS_ACCESS_KEY_ID \
--allow-tier coordinator \
--require-approval \
--audit notify
# GitHub PAT — all profiles, but rate-limited, log access
arc secret set github-pat "ghp_..." \
--type service-token \
--inject env:GITHUB_TOKEN \
--max-uses-per-session 50 \
--audit log
# Database password — only specific profile, written to temp file (cleaned up on session end)
arc secret set db-password "hunter2" \
--type passphrase \
--inject file:/tmp/arc-secrets/db.env:dotenv \
--allow-profile deploy-agent
# Expose a secret as an MCP tool (agent must explicitly request it)
arc secret set stripe-key "sk_live_..." \
--type api-key \
--inject mcp:get_stripe_key \
--allow-tier interactive \
--require-approvalarc launch <profile>
↓
[Profile Resolution]
↓
[Secret Policy Evaluation]
├── Filter secrets by: profile, adapter, tier
├── Check: requireApproval → prompt user if needed
├── Check: expiresAt → skip if expired
├── Check: maxUsesPerSession → track count
↓
[Injection by Method]
├── env → set in process environment before spawn
├── file → write to temp path, add to cleanup list
├── mcp → register as MCP tool (agent must call it)
├── prompt → append to system prompt / instructions
├── header → configure on HTTP adapter
↓
[Audit Log]
├── secret name, profile, adapter, tier, injection method
├── timestamp, session ID
└── usage count
The three-tier permission model gates secret access:
| Tier | Default Secret Access | Can See |
|---|---|---|
| Coordinator | All secrets for allowed profiles | Everything, including deploy/cloud creds |
| Interactive | Standard secrets, approval-gated for sensitive | API keys, service tokens. Deploy creds require approval. |
| Worker | Minimal — only secrets explicitly allowed | Only secrets with allowTiers: ['worker']. No cloud/deploy creds by default. |
Dark Factory workers (tier: worker) get the minimum viable secret set — enough to call APIs and run tools, but no deploy keys, no cloud creds, no admin tokens. The coordinator manages sensitive operations.
Session Start
↓
[Inject allowed secrets per policy]
↓
Session Active
├── Agent uses secrets via env/file/mcp
├── Each use logged (if audit level ≥ log)
├── maxUsesPerSession tracked
↓
Session End
├── Temp files cleaned up
├── MCP tools deregistered
├── Usage summary written to audit log
↓
[Rotation Check]
└── If secret.rotateAfterDays exceeded → surface reminder in next `arc status`
ARC as a distribution channel for agent tooling — not just a control plane.
arc plugin install codex-cc # install from registry
arc plugin install ./local-plugin # install from local directory
arc plugin update # pull upstream changes
arc plugin list # list installed plugins
arc plugin remove <name> # uninstallPlugins can provide:
- Adapters — new runtime support (e.g., Aider, Continue, custom agents)
- Hooks — additional supervision hooks
- Skills — additional skill definitions (including contract skills)
- MCP servers — tool providers
- Dashboard views — custom web dashboard panels
my-arc-plugin/
├── plugin.json # Manifest (name, version, capabilities, compatibility)
├── adapters/ # Optional: custom adapter(s)
├── hooks/ # Optional: hook implementations
├── skills/ # Optional: skill definitions
├── mcp/ # Optional: MCP server config
└── dashboard/ # Optional: custom dashboard views
- Curated registry of community/official plugins (hosted, searchable)
- Profile-scoped activation — some projects get Codex review plugin, others don't
- Upstream tracking — fork with auto-update for official plugins
- Version pinning and compatibility checks against ARC version
Named session threads enable cross-session task resumption:
interface SessionThread {
id: string;
name: string; // e.g., "ARC: Implement risk classifier"
profile: string;
adapter: string;
status: 'active' | 'suspended' | 'completed';
created: string;
lastActive: string;
transcript: string; // path to session transcript
}Behavior:
- Sessions are named with a prefix + task excerpt:
"ARC Task: <excerpt>" arc resume— list suspended sessions, pick one to continue- Heuristic follow-up detection: "continue", "keep going", "resume" → offer to resume last thread
- Thread state persisted in SQLite — survives process restarts
- Dark Factory waves use threads for per-task continuity
Restructured from Supervisor v1's monolithic index.html into a proper frontend.
Tech: Express backend + modular frontend (vanilla JS + Chart.js initially, upgradable to Preact/Lit later)
Views:
| View | What It Shows |
|---|---|
| Overview | Active profiles, running agents, health status across all runtimes |
| Sessions | Session list with filters (adapter, profile, time range), drill into any session |
| Traces | Full audit trail — every hook result, risk classification, retry decision |
| Risk | Risk distribution charts (by tier, adapter, time), keyword hit frequency |
| Tasks | Task board — active, assigned, completed, with agent assignment and A2A status |
| Skills | Skill registry — built-in, user, generated (skillify), MCP-adapted, success rates |
| Memory | Memory explorer — browse by scope/type, relevance scores, aging timeline |
| Benchmarks | Pipeline performance, ops/sec, comparison across adapter types |
| Agents (A2A) | Registered agent cards, capability matrix, task history |
| Factory | Dark Factory runs — wave progress, agent assignments, verify/consensus status |
| Settings | Per-profile config editor, hook toggles, enforcement mode |
Context-aware status display for both TUI and web dashboard. Two systems:
Active phase verbs — rotating verbs during agent execution, selected based on what the agent is actually doing:
type AgentPhase =
| 'thinking' // LLM generating
| 'reading' // file/resource access
| 'writing' // file creation/edit
| 'executing' // shell commands
| 'searching' // web/file search
| 'reviewing' // code review / audit
| 'planning' // task decomposition
| 'delegating' // spawning sub-agents / sending messages
| 'verifying' // running tests / checks
| 'recovering'; // stuck detection / retry
const phaseVerbs: Record<AgentPhase, string[]> = {
thinking: ['Reasoning...', 'Analyzing...', 'Considering...'],
reading: ['Reading...', 'Scanning...', 'Loading...'],
writing: ['Writing...', 'Creating...', 'Editing...'],
executing: ['Running...', 'Executing...', 'Building...'],
searching: ['Searching...', 'Looking up...', 'Querying...'],
reviewing: ['Reviewing...', 'Checking...', 'Inspecting...'],
planning: ['Planning...', 'Breaking down...', 'Organizing...'],
delegating: ['Delegating...', 'Assigning...', 'Handing off...'],
verifying: ['Verifying...', 'Testing...', 'Validating...'],
recovering: ['Retrying...', 'Recovering...', 'Adjusting...'],
};Completion verbs — summary verbs when a turn finishes:
const completionVerbs: Record<AgentPhase, string> = {
reading: 'Read', writing: 'Created', executing: 'Ran',
searching: 'Found', reviewing: 'Reviewed', verifying: 'Verified',
// ...
};Phase detection is deterministic — based on which tools are being called (file read → reading, bash → executing, etc.).
Optional animated agent avatar that reacts to agent state. Useful for dashboard status feedback and personality differentiation in multi-agent setups.
interface AgentPersona {
name: string; // display name
avatar: string; // emoji, icon URL, or sprite reference
personality?: string; // short personality prompt (injected into agent context)
states: Record<AgentPhase | 'idle' | 'error', PersonaState>;
}
interface PersonaState {
icon: string; // state-specific icon/sprite
animation?: string; // CSS animation class
statusText: string; // what to show in dashboard
}Personas are optional — configured per profile. In the web dashboard, each active agent gets a card with its persona, current phase, and live status. In the TUI, shown as a compact status line per agent.
Endpoints:
GET /dashboard # Web UI
GET /api/overview # Summary stats
GET /api/sessions?profile=X # Session list
GET /api/traces?session=X&limit=50 # Trace query
GET /api/risk/distribution # Risk chart data
GET /api/benchmarks # Performance data
GET /api/agents # A2A agent registry
GET /api/factory/:runId # Factory run status
POST /api/factory/run # Start factory run
{
"activeProfile": "work-claude",
"defaultAdapter": "claude-code",
"defaultEnforcement": "log",
"telemetry": {
"enabled": true,
"otlpEndpoint": null,
"sqliteEnabled": true,
"consoleEnabled": false
},
"dashboard": {
"port": 3200,
"autoOpen": false
},
"circuitBreaker": {
"maxConsecutiveFailures": 3,
"degradeAction": "log",
"resetAfterMinutes": 15,
"serialFallback": true,
"notifyChannels": []
},
"mcpServer": {
"enabled": true,
"port": 3201
},
"a2a": {
"enabled": false,
"port": 3202
},
"hooks": {
"source-classify": {
"enabled": true,
"priority": 1,
"botAccountIds": [],
"cronPatterns": ["CRON_TRIGGER", "heartbeat"]
},
"risk-detection": {
"enabled": true,
"priority": 10,
"keywords": ["rm -rf", "drop table", "delete from", "truncate"],
"customKeywords": [],
"blockTier": "destructive"
}
},
"memory": {
"enabled": true,
"autoExtract": true,
"decayThreshold": 0.1,
"defaultTtlDays": 7,
"teamMemoryEnabled": false,
"syncTargets": {
"obsidian": { "enabled": true, "vaultPath": "/home/bailey/obsidian-vault" }
}
},
"skills": {
"enabled": true,
"skillifyEnabled": false,
"skillifyMode": "suggest",
"stuckDetection": {
"enabled": true,
"maxSimilarAttempts": 3,
"recoveryStrategies": ["reframe", "escalate"]
}
},
"tasks": {
"enabled": true,
"persistToSqlite": true,
"a2aInterop": false
},
"routing": {
"enabled": true,
"autoDispatchThreshold": 0.8,
"suggestThreshold": 0.5
},
"context": {
"autoCompaction": true,
"compactionTurnThreshold": 12,
"keepRecentTurns": 4
},
"permissions": {
"defaultTier": "interactive",
"workerDenyPrefixes": ["rm", "drop", "truncate", "kill"],
"auditLog": true
},
"shared": {
"enabled": true,
"syncMcp": true,
"syncInstructions": true,
"syncMemory": true
}
}~/.arc/
├── config.json # Global config
├── profiles/
│ ├── work-claude/
│ │ ├── profile.json # Profile definition
│ │ ├── credentials/ # Auth material (keyring-backed)
│ │ └── config/ # Tool-specific config snapshot
│ ├── personal-codex/
│ │ └── ...
│ └── team-gemini/
│ └── ...
├── shared/ # Shared config layer
│ ├── mcp-servers.json
│ ├── instructions.md
│ └── memory/
├── skills/ # User + generated skills
│ ├── generated/ # Skillify-created skills
│ └── *.yaml # User-created skills
├── db/
│ └── arc.sqlite # Traces, sessions, benchmarks, memories, tasks
├── traces/ # Debug trace exports
└── factory/ # Dark Factory run state
| Command | Description |
|---|---|
arc |
Open TUI dashboard |
arc launch [profile] |
Launch agent with profile (detect adapter, apply env, start supervision) |
arc create <name> |
Create profile |
arc use <name> |
Switch active profile |
arc list |
List all profiles |
arc status |
Multi-runtime status (all profiles, active agents, health) |
arc doctor |
Diagnostics (tools, config, hooks, connectivity) |
arc dashboard |
Open web dashboard |
arc factory run |
Start Dark Factory execution |
| Command | Description |
|---|---|
arc profile create |
Interactive profile creation |
arc profile list |
List with status |
arc profile show <name> |
Detail view |
arc profile switch <name> |
Switch active |
arc profile delete <name> |
Remove |
arc profile import [name] |
Import from existing tool config |
arc profile clone <src> <dst> |
Duplicate a profile |
arc profile set-flags <name> |
Set persistent launch flags |
| Command | Description |
|---|---|
arc exec <prompt> |
Execute with full supervision pipeline |
arc audit <response> |
Manual completion audit |
arc expand <prompt> |
Manual intent expansion |
arc classify <message> |
Manual risk classification |
arc trace [session] |
Query trace history |
arc bench |
Run benchmark suite |
| Command | Description |
|---|---|
arc config get <key> |
Read config value |
arc config set <key> <val> |
Write config value |
arc shared status |
Shared layer status |
arc shared sync |
Force sync shared layer |
arc setup |
Install shims, PATH, shell integration |
arc update |
Self-update + refresh shims |
arc uninstall |
Remove shell integration |
| Command | Description |
|---|---|
arc skill list |
List all skills (builtin, user, generated, MCP) |
arc skill show <name> |
Show skill details and steps |
arc skill create --interactive |
Guided skill creation |
arc skill create --from-session <id> |
Generate skill from session transcript (skillify) |
arc skill delete <name> |
Remove a skill |
arc skill test <name> |
Dry-run a skill |
| Command | Description |
|---|---|
arc task create <desc> |
Create a task |
arc task list |
List tasks (active, completed, failed) |
arc task show <id> |
Task details + output |
arc task assign <id> <profile> |
Assign task to an agent profile |
arc task cancel <id> |
Cancel a running task |
arc task send <profile> <msg> |
Send message to another agent |
| Command | Description |
|---|---|
arc memory list |
List memories (filterable by scope, type, relevance) |
arc memory search <query> |
Relevance search across memories |
arc memory add <content> |
Manually add a memory |
arc memory prune |
Archive low-relevance memories |
arc memory sync |
Force sync to external targets (Obsidian) |
arc memory export |
Export memories as JSON |
| Command | Description |
|---|---|
arc plugin install <name> |
Install plugin from registry or local path |
arc plugin list |
List installed plugins |
arc plugin update [name] |
Update plugin(s) from upstream |
arc plugin remove <name> |
Uninstall plugin |
| Command | Description |
|---|---|
arc secret set <name> <value> |
Store a secret (with --type, --inject, --allow-*, --sync flags) |
arc secret get <name> |
Retrieve a secret value |
arc secret list |
List secrets (names, types, profiles — never values) |
arc secret delete <name> |
Remove a secret |
arc secret rotate <name> |
Prompt for new value, update everywhere |
arc secret enable-sync |
Set master passphrase, enable encrypted sync |
arc secret push |
Encrypt + push opt-in secrets to sync provider |
arc secret pull |
Pull + decrypt secrets to this device |
arc secret revoke-device <id> |
Revoke a device's access to synced secrets |
arc secret export --encrypted |
Export encrypted bundle for backup/migration |
| Command | Description |
|---|---|
arc login |
Authenticate with sync provider |
arc logout |
Clear sync session |
arc sync pull |
Pull all data from cloud |
arc sync push |
Push local changes to cloud |
arc sync status |
Show sync state, last sync, conflicts |
arc sync devices |
List registered devices |
| Command | Description |
|---|---|
arc resume |
List suspended sessions, pick one to continue |
arc jobs list |
List background jobs |
arc jobs cancel <id> |
Cancel a running background job |
arc jobs output <id> |
View job output |
| Command | Description |
|---|---|
arc factory run --spec <file> |
Start factory run |
arc factory status [runId] |
Check run status |
arc factory abort [runId] |
Abort run |
arc factory list |
List past runs |
~/.arc/directory structure preserved- Profile format extended (new fields are optional, old profiles work)
- TUI preserved and extended with new views
- CLI commands preserved, new commands added
@axiom-labs/arc-clinpm package continues
- Core modules (
src/core/*) move topackages/core/ - Adapters (
src/adapters/*) move topackages/adapters/ - Hooks extracted to
packages/hooks/ - Dashboard moves to
packages/dashboard/and gets restructured - Tests migrate with their modules
openclaw.plugin.jsonupdated to reference new package paths- MCP server moves to
packages/protocols/mcp-server/ - SQLite schema preserved, extended with profile/adapter columns
Updated 2026-04-02 per spec review. See DECISIONS.md for full rationale.
| Phase | What | Priority | Notes |
|---|---|---|---|
| 1 | Monorepo setup, migrate core + profiles from both projects | P0 | |
| 2 | Adapter interface + Claude Code adapter (deepest integration) | P0 | |
| 3 | Logging framework + arc logs command |
P0 | NEW. Everything after this needs logging. |
| 4 | Graceful shutdown + health checks | P0 | NEW. Process lifecycle before hook pipeline. |
| 5 | Codex + Gemini adapters (process wrappers) | P1 | |
| 6 | OpenClaw adapter (migrate existing plugin) | P1 | |
| 7 | Hook pipeline (migrate from Supervisor, add new hooks + timeouts) | P1 | |
| 8 | MCP dual-role (host + server) | P1 | |
| 9 | Profile inheritance + arc.json workspace selection |
P1 | Bumped from P2 — needed early. |
| 10 | Secret management (credential store, encryption, injection) | P1 | NEW. Secrets are profile-scoped. |
| 11 | Cloud sync (providers, data model, conflict resolution) | P2 | NEW. Needs secret store. |
| 12 | Web dashboard restructure (WebSocket real-time) | P2 | |
| 13 | OpenTelemetry integration | P2 | |
| 14 | Circuit breaker | P2 | |
| 15 | Memory system (session/persistent/team, aging, relevance search) | P2 | |
| 16 | Skill system (loader, registry, MCP adapter) | P2 | |
| 17 | Task management (first-class tools, agent messaging) | P2 | |
| 18 | Session continuity (thread resume, background jobs) | P2 | NEW. After tasks. |
| 19 | Context management (auto-compaction, token/cost budgets) | P2 | Includes cost budget. |
| 20 | Three-tier permission model | P2 | |
| 21 | Semantic phase indicators | P2 | Personas deferred to v2. |
| 22 | Skillify (self-improving skills) + stuck detector | P3 | |
| 23 | Plugin registry (manifest, install, update, MVP security) | P3 | NEW. After skills exist. |
| 24 | Remote agent support | P3 | |
| 25 | Dark Factory mode (in-process state machine) | P3 |
Deferred to v2: A2A protocol, prompt routing, agent personas/buddy system, S3 sync provider.
See DECISIONS.md for deferral rationale and v2 trigger conditions.
Cross-cutting (all phases): Auth on exposed servers, secret input safety, config/schema versioning, platform compatibility. See DECISIONS.md § Critical Fixes.
| Component | Technology |
|---|---|
| Language | TypeScript (strict mode) |
| Runtime | Node.js ≥ 20 |
| Build | tsup (bundler) |
| Monorepo | pnpm workspaces |
| CLI | Commander.js v14 |
| TUI | Ink v6 (React 19) + @inkjs/ui |
| Dashboard | Express + Chart.js (upgrade path: Preact/Lit) |
| Database | better-sqlite3 |
| MCP | @modelcontextprotocol/sdk |
| Telemetry | @opentelemetry/sdk-node |
| Testing | Vitest |
| Auth | @napi-rs/keyring (OS keyring) |
| CI | GitHub Actions (Ubuntu + Windows matrix) |
| Decision | Rationale |
|---|---|
| Absorb Supervisor entirely | Same author, same stack, complementary scopes. Two repos = friction. |
| Monorepo with packages | Keeps it one install but cleanly separable. Adapters are independently publishable. |
| Adapter interface as plugin boundary | New runtimes don't touch core. Claude Code gets deep hooks, others get process wrapping. Same pipeline either way. |
| MCP dual-role | Host for tool access, server for supervision tools. Maximizes interop with minimal protocol overhead. |
| A2A for agent orchestration | Standard protocol for multi-agent coordination. Replaces custom roundtable state machine with task lifecycle. |
| OpenTelemetry over custom traces | Standard observability. Export to any backend. Don't reinvent tracing. |
| Circuit breaker | Prevents audit loop traps (v1's worst bug). Fail open, not closed. |
Default log mode |
Learned from Supervisor v1 — enforce caused gateway restart storms. Ship safe, opt into strictness. |
| Dark Factory as future phase | Architecture supports it now (waves, verifier, consensus gate), but implementation is post-MVP. |
| Profile inheritance | Reduces config duplication. Base profile with per-use-case overrides. |
arc.json per repo |
Zero-friction per-project configuration. cd into repo → arc launch → correct profile. |
| Hierarchical memory with aging | Session memory is cheap. Persistent memory decays. Team memory enables multi-agent context sharing. No infinite accumulation. |
| Skills over tools | Tools are atomic operations. Skills are reusable procedures. MCP-to-skill adapter bridges the gap. Skillify makes the system self-improving. |
| Tasks as first-class tools | Not a separate API — agents create/manage tasks with the same tool interface they use for everything else. Maps to A2A task lifecycle. |
| Score-based prompt routing | No explicit command prefixes needed. Deterministic tokenization + scoring. Fast path for obvious matches, confirmation for ambiguous ones. |
| Three-tier permissions | Coordinator > Interactive > Worker. Dark Factory workers get degraded permissions by default. Prevents runaway sub-agents. |
| Stuck detection | Agents that loop get detected and recovered automatically. Better than timeout-based approaches. |
| Semantic phase indicators | Users and dashboards see what the agent is doing, not just "processing...". Deterministic phase detection from tool calls. |
| Agent personas | Optional but useful — personality differentiation in multi-agent setups, fun status feedback in dashboard. |
| Context management | Long sessions degrade. Auto-compaction at 12 turns prevents context window overflow. Token budget is a first-class constraint. |
| Local-first with optional sync | CLI tools must work offline. Sync is a bonus. Credentials never leave the device. |
| Three built-in sync providers | Git (portable), filesystem (simple), S3 (cloud-native). No plugins required for basic sync. Covers 90% of setups. |
| Auth is provider-specific | ARC doesn't build auth. Git uses SSH keys, S3 uses access keys, API providers bring their own. Clean separation. |
| SyncProvider as plugin interface | Built-in providers cover most users. Plugin interface enables custom backends (Axiom-Vault, WebDAV, Redis, etc.). |
| Encrypted credential sync | Secrets sync via Argon2id + AES-256-GCM envelope encryption. Per-secret opt-in. Passphrase never stored. Provider-agnostic — encrypted blobs over any backend. |
| Scoped secret injection | Secrets are policy-gated: by profile, adapter, and permission tier. Five injection methods (env, file, MCP tool, prompt, header). Audit trail on every access. Workers get minimum viable secrets. |
| Broker/Pool for process management | Persistent background process for concurrent requests. Fallback to direct spawn. Avoids cold-start latency. |
| ALLOW/BLOCK gate protocol | Simple, structured supervision. First-line parsing is reliable. Only gates substantive work — no overhead on status turns. |
| Plugin registry as distribution | ARC isn't just a tool — it's a platform. Curated plugin registry positions it as the distribution channel for agent tooling. |
| Session continuity / thread resume | Long-running tasks shouldn't die with the process. Named threads + transcript persistence enable cross-session work. |
This spec absorbs and supersedes: ARC v0.1 README/FEATURES.md, Axiom-Supervisor README/SPEC.md/TODO.md.
Incorporates patterns from: claw-code research (hierarchical memory, dream consolidation, skill system, skillify, stuck detection, task tools, three-tier permissions, prompt routing, auto-compaction, spinner verbs, buddy system). Codex Plugin research (broker/pool, ALLOW/BLOCK gate, background jobs, structured review schema, thread resume, inferred completion, skill-as-contract, plugin registry).
Last updated: 2026-04-02 — phases updated, deferrals applied, see DECISIONS.md