Skip to main content

How it works

AgentArea runs as four processes: an API, a Temporal worker, a Go manager for sandboxes and MCP servers, and a web dashboard. Most confusion about the platform comes from not knowing which of them does what, so this page follows a single task across all four. Read this before the rest of the documentation. Nearly every other page assumes you know where the thing it describes runs.

The problem

An agent task is not one operation. It is a policy decision, a durable orchestration, a series of model calls, and some number of commands executed in an isolated environment — potentially over hours, with a human approval in the middle. Put that in one process and you get a system where a deploy kills in-flight work, untrusted model output executes next to your database credentials, and an approval means holding an HTTP connection open until someone gets back from lunch. The four-process split is what avoids each of those, and each boundary costs something.

The four processes

Backed by PostgreSQL (state), Valkey (streams and cache), object storage (artifacts and logs), Temporal (workflow history), and OpenFGA or Ory Keto (authorization). The API and worker are the control plane. The MCP manager and everything it starts are the data plane. See control plane and data plane for why that boundary is drawn where it is.

The path a request takes

Starting a task: POST /v1/agents/{agent_id}/tasks/.

1. The API authenticates and scopes

The token is resolved into a UserContext carrying user_id, workspace_id, and accessible_workspaces. Every repository is constructed from this context, so reads are constrained to workspaces the caller can access before any handler logic runs.

2. The API resolves an effective policy

Before the task starts, the governance layer resolves the policy that will apply to it — budgets, which tools are permitted, what requires approval. The result is attached to the task and handed to the workflow. This is a snapshot taken at creation. The running workflow carries the effective policy in its state and is the canonical source for it, which means editing a policy does not retroactively change a task already in flight.

3. The API hands off to Temporal

TaskService persists the task, then TemporalTaskManager starts an AgentExecutionWorkflow. The API returns immediately with a task id. It does not wait for the agent. For local development, WORKFLOW__EXECUTION_ENGINE=direct swaps in DirectTaskManager, which runs the same logic in-process with no Temporal. Same interface, no durability. Production uses Temporal.

4. The worker runs the agent loop

A worker picks up the workflow and runs the reason-act loop: build context, call the model, get back either a final answer or tool calls, execute them, feed results back, repeat. Each model call and each tool call passes the governance interceptor pipeline first. Budget gates run earliest because they are the cheapest check, then security filters, then observers. A gate can deny the call or escalate it for human approval. Progress is published as events, which the API relays to clients over server-sent events (text/event-stream), and persisted to the database. The dashboard’s live view is that stream.

5. Tool calls reach the data plane

A tool call is one of two things. An MCP tool call goes to an MCP server instance — hosted by AgentArea in a container the Go manager started, or a remote server. Secrets are resolved server-side; the agent never sees them. A shell or skill execution goes to the sandbox. The client posts to POST /sandbox/executions on the Go manager, which persists a pending record and publishes a request event. A runner claims it from a Valkey Streams consumer group, executes it, writes logs and artifacts to object storage, and reports lifecycle events back. The workflow reads completion from the execution record. Output does not come back inline. Artifacts and logs are referenced by handle, so large payloads never enter workflow history.

6. The task reaches a terminal state

The workflow completes, fails, or is cancelled. Final state is persisted, a terminal event is published, and artifacts remain in object storage addressed by their content hash.

What Temporal is doing

Temporal gives the agent loop durability that a plain async function cannot have. It persists progress, not only results. Every completed activity — a model call, a tool call — is recorded in workflow history. If the worker crashes or you deploy mid-task, a new worker replays history and resumes from where it stopped. Completed work is not repeated. It lets a workflow wait without holding anything open. This is what makes human approval practical. When a tool call escalates, the workflow suspends. No connection is held, no thread blocked, no timeout to tune. Approval arrives later as a signal: It exposes live state without a database round trip. Queries — get_current_state, get_workflow_events, get_latest_events — read from the running workflow directly. It handles retries. Activities carry retry policies, so a transient provider error is retried by the platform rather than by code in the agent loop. The constraint this buys is real: workflow code must be deterministic. It cannot call the network, read a clock, or generate randomness directly — all of that belongs in activities. That rule is why the workflow file is structured the way it is, and why side effects live in activities even when inlining would be shorter.

What the Go MCP manager is doing

The Go service manages everything that runs in a container. It exists as a separate process in a different language because container orchestration is a poor fit for the Python async model, and because it is the piece that must be replaceable to support customer-hosted execution. It exposes four route groups: It runs against Kubernetes or Docker, selected by BACKEND_TYPE. An unrecognised value is refused at startup rather than falling back to a guess — a silent fallback here previously meant deployments that set BACKEND_TYPE: kubernetes ran on whatever auto-detection picked. MCP server instances and agent sandboxes run on the same isolated substrate, so one set of isolation and lifecycle behaviour covers both.

Why not one process?

A single process is easier to run and easier to reason about, and for a single-user local setup it would be enough. It fails on three specific things. Untrusted code execution would share an address space with credentials and the database connection. A deploy would kill in-flight tasks, because durability requires state to live outside the process doing the work. And the control/data split would be impossible, so customer-hosted execution — the requirement that makes the platform usable by regulated organizations — could not exist. The cost of splitting is paid in debugging: a failed task can fail in any of four places, and correlating them means following a task id across the API log, the workflow history, and a runner log.

Why not call the sandbox synchronously?

Because agent commands outlive HTTP connections, because returning stdout inline pulls payload through the control plane the boundary exists to protect, and because addressing a runner directly requires the control plane to know a URL — which forecloses a customer-hosted runner that connects outbound. A synchronous path exists for the managed Kubernetes warm pool as a compatibility route; it is not the production contract.

Limits

  • Policy is snapshotted at task creation. Editing a policy does not affect tasks already running. If you need an immediate change to take effect, cancel the in-flight tasks.
  • WORKFLOW__EXECUTION_ENGINE=direct has no durability. It is for development. A crash loses the task, and nothing resumes.
  • Determinism constrains workflow code. Non-deterministic calls in workflow code produce replay failures that surface later, on the resume path, not when the code is written.
  • Event delivery over SSE is not guaranteed. Events are persisted to the database as well; treat the stream as a live view, and the database as the record. A client that disconnects should reconcile rather than assume it saw everything.
  • The four processes must be version-compatible. They are deployed together and share schemas. Upgrading the API without the worker is not supported.
  • A task id is the only correlation handle across services. There is no single log stream that shows the whole path.