Concepts

How it works

The pieces of One Connect, how a command becomes an event, how a run is supervised from spawn to handoff, and what survives a restart.

The big picture#

 vendor CLIs (your logins)          acc daemon (127.0.0.1)                     you
 claude / codex / cursor  ◀─ runner ─┤ event log (SQLite, append-only)  ├─ WebSocket ─▶ canvas, consoles
      │  stdout/stderr → redact → durable logs → typed events          │              issue list
      └─ acc MCP server ────────────▶ assignments · approvals · findings ├─ REST ─────▶ inbox
                                     │ rules: refill · stall · retry · limits           phone
 git worktree per assignment ◀──────┤ outbox → GitHub / Linear / local tracker
 Obsidian vault · Graphify  ◀───────┤ shared context (contract, brief, code graph)
  • The Mac app is a Tauri 2 shell: Rust owns the native window, real terminals (PTYs), the project registry, the private connection to each engine and OS integration. It does not ship a separate Chromium runtime.
  • The engine (the acc daemon) is TypeScript on a bundled Node runtime. It owns assignments, vendor processes, evidence and tracker sync. The app starts one engine per project.
  • The dashboard is a React app. It applies the same reducer as the daemon to the same events, so every client sees the same state.
  • Agents run natively through their vendors' official tools. Only the project you are developing runs in Docker: its apps, services, databases and checks.

Packages#

PackageWhat it contains
packages/corePure code shared by the daemon and the browser: the config schema, entities, the lifecycle state machine, typed events, the reducer, the handoff parser, the redactor, pre-flight, the assignment template, model ranking and adapter profiles
packages/daemonThe supervisor: services, the SQLite store, the detached runner, agent and tracker adapters, git, shared context, the acc MCP server, rules, the HTTP and WebSocket server, phone access and the CLI
packages/webThe React dashboard. App.tsx gates sign-in, then shows the desktop shell or the paired-phone layout
packages/desktopThe Tauri app: Rust sources, icons and bundling configuration

Event flow#

Every state change follows the same path:

command (UI / CLI / rule / agent via MCP)
  → validate against current state
  → typed events (+ tracker outbox rows, + log offsets) in ONE SQLite transaction
  → applyEvent()  (packages/core/src/reducer.ts)
  → broadcast over WebSocket → the browser applies the SAME reducer
  • Control events (lifecycle, runs, agents, alerts) go to every client. Transcript events go only to consoles subscribed to that run; other clients receive the updated run summary.
  • Every event carries an actor (human, agent or system), so the log is also the audit log.
  • On start, the daemon loads the latest snapshot and replays the tail. acc rebuild replays everything.

Runs#

Starting an assignment works like this:

  1. Worktree. The engine creates <data>/worktrees/<project>/<agent>-<KEY> on branch <agent>/<KEY>-<slug> from the integration branch, and installs per-worktree hooks: pre-commit enforces the path scope and pre-push blocks protected branches.
  2. Launch spec. The agent's adapter builds the command line, working folder, input mode and MCP configuration. The child environment is sanitized: API-key variables are stripped.
  3. Detached runner. A runner process spawns the vendor CLI in its own process group. It redacts every output line before writing stdout.jsonl and stderr.log, forwards your messages from inbox.jsonl, and writes exit.json at the end.
  4. Tailing. The supervisor tails those files from offsets stored in SQLite, in the same transaction as the events it derives. The vendor parser turns lines into typed events, sessions, usage, failures and the final message.
  5. Finalize. On exit, the engine:
    1. classifies the outcome;
    2. collects the commits;
    3. finds the handoff: from MCP, then the final message, then a commit body;
    4. verifies its evidence against the commands it actually recorded;
    5. moves the lifecycle;
    6. lets the rules react: refill, retry or pause;
    7. re-runs the required checks in the local offline Docker runtime at the exact SHA, and fails closed if the runtime is unavailable;
    8. posts the handoff, with what was verified, to the tracker.

Restart safety. Runners outlive the daemon. On boot, each run is reattached (only if ps shows its exact runner command line, so a reused PID is never mistaken for it), collected (it finished while the daemon was down), or marked orphaned with an alert.

Agents talk to One Connect, not to each other#

Each run gets the acc MCP server, authenticated with a per-run token from a file only you can read; the token never appears on a command line. Agents read their assignment, post handoffs, file findings, ask you questions, read shared context and message teammates through it. Claude runs use --strict-mcp-config and Codex runs use --ignore-user-config, so your personal MCP servers are never exposed to agents. The full list is in Agent tools.

Trackers#

A lifecycle change and its tracker write commit together; the write waits in an outbox. A worker delivers outbox rows in order per issue, with backoff. Two failed attempts raise a critical "out of sync" alert. Polling picks up edits made in the tracker and flags drift: an issue closed there without evidence, or in progress there without a live run. GitHub and Linear stay in dry run until you set live: true.

Data directory#

When you run the engine from source, its data lives in ~/Library/Application Support/AgentControlCenter on macOS, $XDG_STATE_HOME/agent-control-center on Linux, or ACC_DATA_DIR. The Mac app keeps one such folder per project under its own app data.

acc.sqlite            event log, outbox, snapshots, tail offsets, sessions
local-tracker.sqlite  the local tracker (if used)
runs/<run>/           spec.json, runner.json, stdout.jsonl, stderr.log, exit.json, checks/*.log, mcp.json (0600)
worktrees/<project>/  one per assignment
hooks/<assignment>/   per-worktree hooks + scope.json
context/<project>/    detached integration worktree with graphify-out/
secrets/install-token (0600)

The daemon refuses data folders inside temp directories, because the OS wipes them and run evidence would be lost.

Inside the daemon#

The daemon is layered so that a part can be replaced without touching the rest:

  1. Core is pure and shared with the browser; its index uses no Node APIs.
  2. Kernel (kernel.ts) holds the interfaces services depend on: the event log and projected state, time, configuration, redaction, alerts and network egress, plus command groups.
  3. Services each declare what they need as a type next to their class, so a new dependency shows up in review and a test can construct the service with fakes.
  4. The composition root (daemon.ts) builds the services, registers decision executors and run-stop hooks, and starts and stops them.
  5. Delivery (server/, phone/, cli.ts) talks to the composition root. Each API area is one route module; http.ts only enforces Host, Origin and authentication.

Recipes for adding to each layer are in Extending One Connect.

Edit this page on GitHub