Skip to main content
Running a HarnessAgent on your laptop is easy. Shipping it to production is another story — replicas must share sessions, users must stay isolated, untrusted code must be sandboxed, and pods must be able to resume mid-conversation after a restart. This page only covers what changes between single-node and distributed production: which components must be swapped, what to swap them with, and why the builder throws IllegalStateException when you miss something.
Fastest path to production: use DistributedStore to configure all distributed components at once:
Mixed stores (e.g. MySQL for state + Redis for sandbox locks) are also supported:

Alternative: aistio hosted store

If you run an aistio control plane, it can host BaseStore / sandbox lock & snapshot / MessageBus / AsyncToolRegistry / TaskRepository / optional SessionTurnGate. You still supply one AgentStateStore (Redis/MySQL/Postgres/OSS); core provides getVersioned / saveIfVersion, but storage stays off the control plane:
Enable with --enable-hosted-store on the control plane (Postgres recommended). withAgentStateStore includes hosted TaskRepository; subagent background tasks in SandboxFilesystem mode need this path. Redis/Postgres/MySQL/InMemory AgentStateStore backends support versioning CAS; others remain LWW. Turn gate + ConflictPolicy.FAIL are optional for multi-replica duplicate-turn reduction; correctness comes from CAS. Auth is a shared internal token with tenant from the request body — not for mutually untrusted multi-tenant agents on one CP. queueDrain is destructive (ack-on-read). See Distributed Storage — aistio Hosted Store.

At a glance: single-node defaults vs. distributed production

DistributedStore capability matrix

Each component solves a different production problem:
  • AgentStateStore: persists the agent’s runtime session state, including conversation history, compaction summaries, permission rules, Plan Mode state, and tool state. This is what lets another replica, or a restarted process, continue the same (userId, sessionId).
  • BaseStore: provides shared KV-backed workspace storage for RemoteFilesystemSpec, carrying paths such as MEMORY.md, memory/, skills/, and sessions/. In multi-replica deployments, it lets different pods see the same long-term memory and shared files.
  • SandboxSnapshotSpec: persists sandbox workspace snapshots. When a sandbox container is destroyed, a pod restarts, or the next request lands on a new node, it restores the previous workspace instead of losing pip install output, generated files, or temporary project state.
  • SandboxExecutionGuard: serializes command execution for the same sandbox slot across nodes. With shared scopes such as AGENT or GLOBAL, multiple replicas may try to execute against the same sandbox at once; the guard uses Redis/MySQL locking to avoid concurrent workspace writes and sandbox start/stop races.
OSS does not provide a SandboxExecutionGuard — object storage is unsuitable for distributed locking. OSS users who need sandbox concurrency control can mix in a Redis guard via DistributedStore.builder().
The key validation chain:
  • filesystem(RemoteFilesystemSpec) without stateStore(...) or distributedStore(...)build() throws IllegalStateException.
  • filesystem(SandboxFilesystemSpec) with a local AgentStateStorebuild() logs a warning; in production always supply a distributedStore.

1. State store: put AgentState somewhere durable first

Recommended: use distributedStore(...) for one-line setup. The detailed table below is for advanced users who need individual control over AgentStateStore.
AgentState (conversation context, compaction summary, permission rules, Plan Mode state, tool state) only survives across processes through an AgentStateStore. Redis with any of the three client adapters through RedisAgentStateStore.builder():
Per-tenant isolation. A bare sessionId only covers single-tenant. In production, set both userId and sessionId on each call’s RuntimeContext so multi-tenant calls can’t cross-read — the store addresses each slot by the (userId, sessionId) pair (RedisAgentStateStore folds userId into the Redis key; MysqlAgentStateStore folds it into the primary key). Compose any other dimensions (tenant, agent) into the sessionId string yourself:
Full mechanics in Context & AgentState.

2. Filesystem mode & IsolationScope: deciding “who shares files with whom”

Three modes recap (details in Filesystem): IsolationScope is the multi-user isolation key. Both shared-store and sandbox modes use the same scope to decide how namespaces are bucketed:
anonymousUserId is a production detail — RuntimeContext.userId is often null (system tasks, scheduler triggers, admin operations). Don’t fall back to the empty string, or every anonymous caller ends up in one shared bucket.

3. Remote-mode BaseStore stores: KV choice — and why OSS is the wrong fit

RemoteFilesystemSpec sits on top of a BaseStore interface. Two built-in implementations:

What about OSS / NAS / S3?

Do not implement a BaseStore against OSSMEMORY.md / memory/YYYY-MM-DD.md / agents/<id>/context/<sid>/ get written several times a second; OSS latency and per-request cost will blow up immediately. The correct division of labour:

RemoteFilesystemSpec routing table

To prevent key collisions across subsystems, the spec slices the workspace into independent namespace segments: Each segment is then bucketed by IsolationScope (USERagents/<agentId>/users/<userId>/). A Redis key ends up looking like agentscope:store:item:agents\0X\0users\0alice\0memory\0memory/2026-06-02.md.

CompositeFilesystem: two-layer reads + write-through

RemoteFilesystemSpec.toFilesystem(...) actually produces a CompositeFilesystem: a base LocalFilesystem without shell (fallback for local templates) plus one OverlayFilesystem per route (upper = RemoteFilesystem, lower = read-only LocalFilesystem template). Effect: writes always go to Remote; reads check Remote first, fall back to the local template. That is the “two-layer read architecture” described in Workspace instantiated for Remote mode — the local <workspace>/AGENTS.md is a seed (synced via team git), and Remote takes over as soon as it has been written to.

WorkspaceIndex: optional SQLite index

Speeds up ls / glob / exists / grep under Remote mode — without it every call scans the full KV. WorkspaceIndex is a best-effort SQLite file (under <workspace>/.index/), failures degrade silently without affecting correctness.

4. Skill marketplaces: which SkillRepository to pick

Skills compose from low to high priority (details in Skill):

Marketplace stores

skillRepository(...) is additive; later registrations win on name collisions.

Production checklist

  • Prefer MysqlSkillRepository(writeable=false) or NacosSkillRepository — platform-side central governance, agents read-only; write-backs go through an admin console + review flow.
  • Don’t want the agent to see workspace/skills/? .disableDefaultWorkspaceSkills().
  • When enableSkillManageTool lets the agent draft new skills, always pair it with enableSkillPromotionGate(...); never autoPromote=true in production.
  • NacosSkillRepository is AutoCloseable — close it from Spring @PreDestroy or a try-with-resources, otherwise subscriptions leak.

5. When you need shell: pick a Sandbox + mandatory Snapshot

When you must use a sandbox:
  • the model might run untrusted code (Python / shell / npm install / compilation)
  • you need to recover the entire working directory across calls (node_modules, generated files, post-pip install environment)
  • you need hard user isolation (no peeking into another user’s processes)

Five sandbox stores

Snapshots are the sandbox’s distributed lifeline

Sandboxes are ephemeral by default — the next call() may land on a different node in a fresh container, losing every pip install and generated file. SandboxSnapshotSpec archives the workspace as tar so the next call() hydrates it back into a new container.
With distributedStore(...), the snapshot spec and execution guard are auto-injected — no manual configuration needed. To customize the OSS bucket or prefix, prefer configuring OssDistributedStore when you create it; only set SandboxSnapshotSpec explicitly on SandboxFilesystemSpec when you need a fully custom snapshot implementation.

Sandbox exec serialization: SandboxExecutionGuard

Under SESSION / USER scope, buckets are already partitioned by session/user and concurrent execs don’t collide. Under AGENT / GLOBAL scope with multiple replicas, N nodes can race to exec on the same sandbox slot. distributedStore(...) auto-injects the appropriate execution guard: The recommended path is still to inject the guard through DistributedStore:
Only override the guard explicitly when you need custom lock parameters, such as a lease TTL:
You can also implement SandboxExecutionGuard yourself to plug in Zookeeper, etcd, or any other lock mechanism.

Workspace projection: pushing seed files into the sandbox

SandboxFilesystemSpec projects AGENTS.md, skills, subagents, knowledge, .skills-cache (five roots) into the sandbox at start time by hydrating a content-hashed tar archive (incremental rewrites). Tweak it:

AgentRun-specific: NAS / OSS mounts

AgentRunFilesystemSpec is the only sandbox filesystem that natively supports multiple sandbox instances mounting the same directory (via NAS). When the business case is “one user sees the same workspace across different sessions”, AgentRun + NAS is more efficient than re-hydrating snapshots every time:
Full fields in the AgentRunNasMountConfig / AgentRunOssMountConfig source.

6. Multi-replica deployment checklist (combined)

Pulling the single-component picks above into one table:

7. A complete production builder template

The agent is stateless between calls — a singleton handles concurrent requests. Each call() locates state via RuntimeContext’s (userId, sessionId), fully isolated.
At call time, pass RuntimeContext to identify the user/session. Different sessions run concurrently on the same agent instance:

8. Common pitfalls

  • Forgetting to pass RuntimeContext — without a sessionId, all requests share the defaultSessionId state, causing cross-talk. In multi-user scenarios, always pass RuntimeContext.builder().userId(...).sessionId(...).build() to every call() to ensure state isolation. See Agent — Multi-user Concurrency.
  • java.nio.Files for workspace writes — under sandbox / Remote mode this lands in the wrong place. Always go through agent.getWorkspaceManager(). Exception: builder-time seed files (initWorkspaceIfAbsent-style code) — no runtime context yet, java.nio.Files is correct because you’re seeding the local template.
  • tools.json’s allow filters built-in tools too — when whitelisting, keep read_file / memory_search / agent_spawn and friends in the list, or every built-in gets stripped.
  • IsolationScope changes do not migrate existing data — pin it before launch. Changing it post-launch is equivalent to switching to a new namespace.
  • Local AgentStateStore single-machine constraint: a K8s multi-replica build that pairs a distributed filesystem with a local JsonFileAgentStateStore throws IllegalStateException on the very first build(). This is intentional — you can’t park agent state on one pod’s local disk.
  • NacosSkillRepository not closed — subscriptions leak; at fleet scale Nacos complains. Use Spring @PreDestroy or destroyMethod="close".
  • OSS / NAS without IAMOssSnapshotSpec takes platform AK/SK; RAM Role + STS temporary credentials is more robust.
  • Local AgentStateStore with sandbox mode is dev-only — the build-time warning is intentional; don’t ignore it in production.
  • Quickstart — end-to-end first HarnessAgent
  • Harness Architecture — how capabilities cooperate
  • Context & AgentStateAgentState / AgentStateStore / cross-node recovery
  • Compaction — conversation summarization, tool-result eviction, overflow recovery
  • Workspace — directory layout, two-layer reads, tools.json
  • Filesystem — three deployment modes, IsolationScope
  • Sandbox — sandbox details, five implementations, snapshot mechanics
  • Skill — four-layer composition, marketplace stores, self-learning loop
  • Middleware — custom observability / rate-limit / fallback middleware