Docs navigation
Get started
AI agents
Connect
Operate
Day to day
When it breaks
Govern access
Team & account
Access
Identity concepts
Provider guides
Account
Architecture and failure behavior
Which component decides what, what crosses each boundary, and what happens when a component disappears mid-work.
- Nothing to install — read this during a security review, design discussion, or failure investigation.
- A rough picture of the product from How it works, if this is your first page.
Everything below reflects the wire protocol, MCP contract, and runner lifecycle, with each limit beside the behavior it limits. Security model covers threats and guarantees; this page covers topology, state, and failure.
The four pieces#
| Component | Where it runs | What it is responsible for |
|---|---|---|
| Control plane | Hosted by us | The control plane authenticates every caller and enforces account access and action permissions. It pins the trusted pack version and hash. It evaluates policy, runs approvals, composes runbooks, and keeps searchable audit history. |
| Runner | Your host, one per host, outbound only | Loads and hashes local packs, decides what this host advertises, re-validates every argument, executes, redacts output before it leaves, and writes a hash-chained local journal. |
| MCP bridge | Optional, on the operator's workstation as a child process of the LLM client | Translates the client's stdio JSON-RPC into HTTPS calls to the control plane. It holds the API key and, where signing is used, the operator's signing key. It implements no policy and no action behavior. |
| Action packs | Versioned artifacts, installed on each runner from a registry | The executable contract: which actions exist, their arguments and limits, the exact program each one runs, and its redaction rules. A trusted content hash is the unit you review. |
One connection carries all of it: the runner dials out over TLS and keeps a websocket open, with no listener on the host and nothing to expose. The ports and hosts a runner needs are in Network requirements. Cloud LLM clients reach the control plane directly over OAuth and run no bridge at all.
One action, end to end#
- The runner decides what it offers. At boot and on reload, it validates installed packs and computes their content hashes. It applies local admission rules and advertises the resulting catalog.
- A caller asks for one declared action. The control plane authenticates an operator session, API key, or OAuth client. It then applies runner and pack scope to executable candidates and targeting.
- The control plane resolves the exact pack. The model-facing contract comes from the trusted manifest for one exact hash. A runner whose advertisement disagrees is excluded from the targets.
- Policy decides. Allow, deny, or require a human. A denial ends the run before any host is contacted. A require-approval decision parks it until an approver acts.
- One dispatch goes down the connection. It carries the action ID, exact argument bytes, reason, operation ID, and expected pack hash. A signed bridge also adds its attestation.
- The runner decides again. It re-hashes the pack, checks local admission, verifies the signature where enforced, re-validates every argument against the schema, clamps call options to the action envelope, and renders the pack-authored program.
- It executes, redacts, and reports. Output is bounded and redacted on the host before any chunk is sent. The runner writes its local journal line, streams progress, and sends one terminal result. The control plane stores the terminal result as the searchable record.
These two decisions shape the whole system: the control plane decides whether a request is allowed, the runner decides whether it will execute, and neither can make the other skip its own check.
Who owns which decision#
| Decision or data | Owner | What that means in practice |
|---|---|---|
| Who the caller is | Control plane | Sessions, API keys, OAuth clients, and runner tokens all authenticate here. Every bearer credential is stored hashed. |
| Which hosts a person or key can touch | Control plane | Runner and pack access limits actions. Members with operational read access can still inspect the workspace's fleet and catalog. |
| Which pack version and hash is trusted | Control plane | Trust binds one exact content hash — auto-pinned when the published catalog it reads carries that hash, decided by an admin otherwise. Installing bytes on a host does not trust them. |
| Allow, deny, or ask a human | Control plane | The first matching action override wins. Without one, the action risk tier selects its default decision. A missing policy denies the dispatch. |
| What this host will advertise or run at all | Runner | Local admission filters by action id and a risk ceiling. A rule in the host's config overrides anything the control plane asks for. |
| Whether the arguments are valid | Both, in that order | The control plane validates before dispatch. The runner validates again against the declared schema immediately before execution. |
| What OS privileges an action gets | You, on the host | Through the runner's service user and whatever sudo, capabilities, or per-action user you configured. emisar does not sandbox an action from the host. |
| The raw output bytes | Runner, and they stay there | Redaction runs on the host before a chunk is sent. The control plane only ever receives the redacted stream. |
| The searchable fleet history | Control plane | The durable record of what ran, who asked, which rule matched, and what came back. |
| The local forensic record | Runner | A hash-chained JSONL journal per host, appended to and never rewritten by the runner. |
What each component stores#
- — The control plane stores accounts, members, policies, approvals, trusted pack references, hashes, runbooks, exact action arguments, runner-returned output, and audit history. It stores every issued bearer credential as a hash. It stores OIDC client secrets reversibly because it must present them to the identity provider. Exact arguments can contain operator-supplied secrets, so protect database copies accordingly.
- — A runner stores its token, durable dispatch state, installed packs, signed-dispatch nonce store, and local journal; dispatch and nonce state prevent repeated execution, and only one runner process can own that state at a time.
- — The bridge holds the operator's API key in their own config directory, plus the signing key and certificate when signed dispatch is in use. It does not keep history or policy.
- — A registry holds immutable published pack artifacts. Distribution and trust stay separate on purpose — fetching bytes from a registry is not a decision to run them.
Residency, retention windows, and what we deliberately never store are in Security model.
When something drops out#
| What happens | What the system does | What you do |
|---|---|---|
| The control plane is unreachable before a dispatch | Nothing new is authorized, created, or sent — there is no offline dispatch path. An action already executing on a runner keeps running, and its result is delivered when the connection returns. | Wait. No cleanup is needed for work that had not been dispatched. |
| The runner is disconnected before its dispatch | Nothing executes. The run waits and is delivered as soon as that runner reconnects and re-advertises. If the host is still gone after a short grace window, the sweep ends the run as Error rather than leaving it queued. | Bring the host back and dispatch again through the normal path. Nothing is retried behind your back. |
| The network drops while an action is running | The runner process is still alive, so the child process keeps running. The runner reconnects with backoff, re-advertises, and replays its progress and terminal result until the control plane acknowledges them. | Nothing. The run settles on its real outcome once the connection is back. |
| The runner process or its host dies mid-execution | The child can be complete, still stopping, or terminated with the runner or host. If no terminal result is durable, the runner reports execution_outcome_unknown after restart. It never executes that dispatch again automatically. | Check the target system before you re-run. The side effect happened outside emisar's control, and only you can tell whether it landed. |
| The LLM client cancels, or the bridge disconnects | An operation the control plane already admitted keeps going. Cancellation stops the bridge wait and suppresses its reply. Closing the client input lets admitted requests finish. | Recover by operation id, with the same credential lineage that made the call. Never repeat the mutation because the response was lost. |
| An approval waits while the fleet changes | The decision is rechecked against current facts, and the send rechecks them again. A fact that moved makes the approval fail rather than dispatch something else. | Read the checks below, fix what moved, and dispatch again. |
| A pack changes after a runbook execution started | The execution uses the steps and exact pack references frozen at preflight. Later pack changes do not rewrite the plan. An invalid frozen fact stops execution instead of substituting another. | Re-run the runbook once the fleet is where you want it. |
| A runner or bridge is older than the supported minimum | The system marks it unsupported. Version enforcement is a deployment switch and is off today. An unsupported peer gets a warning, not a refusal. A version that cannot be parsed is never blocked. | Upgrade it to a supported version. The product is pre-1.0 today, so 0.x peers are not cross-version tested. Version 1.0 freezes the public compatibility contracts. |
Upgrade runners explains upgrade interruptions and recovery. Compatibility and deprecation defines the API contracts and current limits. Runs & History explains each status; when something is broken and you are not sure where, start at Troubleshooting.
Delivery, and what it does not promise#
Messages between a runner and the control plane are delivered at least once. A result is replayed until acknowledged, so a lost acknowledgement can produce a duplicate — the control plane does not apply output or terminal state again, and a retry returns recorded state instead of starting another execution.
Messages are delivered idempotently, but exactly-once delivery is not guaranteed. A
runner host can die after starting a process but before recording its outcome; when
that happens, emisar reports
execution_outcome_unknown
and refuses to guess by re-running it.
What a wait revalidates#
An approval can wait for hours while the fleet changes. When the approver decides, these facts are checked before the run is released, and the delivery facts are checked again at send:
- — The run is still parked and still approvable. A cancellation or an expiry in the meantime makes the approval fail rather than resurrect it.
- — The trusted pack hash still matches the hash frozen on the run. The action program still exists on the host. A changed trust decision stops dispatch instead of sending different bytes.
- — The account and target runner remain active. The initiating membership remains active and has the runner in scope. Any API key behind the request remains usable.
- — For a signed run, the original attestation is still inside its freshness window and its certificate's validity window. An approved run is re-dispatched with the signature it was created with, and a slow approval can outlive it — the freshness and validity windows are Signed dispatch's subject.
These are stop conditions, not a second policy evaluation. The decision the approver made is the decision that stands, and what is rechecked is whether it can still be carried out as approved. Approval rules, expiry, and standing grants are owned by Policies & Approvals.
The hosted boundary#
We host and maintain the control plane. Shipped changes land in the changelog. You control runner, bridge, and pack upgrades, so those components move on your schedule. The repository contains deployable control-plane code for evaluation. Supported self-hosted and air-gapped deployments are not generally available today.
The hosts and their privileges remain yours: raw action output never leaves them, and only the dispatch and the redacted result cross the boundary. The threat model behind it, including what it deliberately does not defend against, is in Security model.