Running aFastest path to production: useHarnessAgenton 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 throwsIllegalStateExceptionwhen you miss something.
DistributedStore to configure all distributed components at once:
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 oneAgentStateStore (Redis/MySQL/Postgres/OSS); core provides getVersioned / saveIfVersion, but storage stays off the control plane:
--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 forRemoteFilesystemSpec, carrying paths such asMEMORY.md,memory/,skills/, andsessions/. 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 losingpip installoutput, generated files, or temporary project state.SandboxExecutionGuard: serializes command execution for the same sandbox slot across nodes. With shared scopes such asAGENTorGLOBAL, 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 aThe key validation chain:SandboxExecutionGuard— object storage is unsuitable for distributed locking. OSS users who need sandbox concurrency control can mix in a Redis guard viaDistributedStore.builder().
filesystem(RemoteFilesystemSpec)withoutstateStore(...)ordistributedStore(...)→build()throwsIllegalStateException.filesystem(SandboxFilesystemSpec)with a localAgentStateStore→build()logs a warning; in production always supply adistributedStore.
1. State store: put AgentState somewhere durable first
Recommended: usedistributedStore(...)for one-line setup. The detailed table below is for advanced users who need individual control overAgentStateStore.
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():
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:
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 aBaseStore against OSS — MEMORY.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 (USER → agents/<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
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)orNacosSkillRepository— 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
enableSkillManageToollets the agent draft new skills, always pair it withenableSkillPromotionGate(...); neverautoPromote=truein production. NacosSkillRepositoryisAutoCloseable— close it from Spring@PreDestroyor atry-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 installenvironment) - 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 nextcall() 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.
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:
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:
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. Eachcall() locates state via RuntimeContext’s (userId, sessionId), fully isolated.
RuntimeContext to identify the user/session. Different sessions run concurrently on the same agent instance:
8. Common pitfalls
- Forgetting to pass
RuntimeContext— without asessionId, all requests share thedefaultSessionIdstate, causing cross-talk. In multi-user scenarios, always passRuntimeContext.builder().userId(...).sessionId(...).build()to everycall()to ensure state isolation. See Agent — Multi-user Concurrency. java.nio.Filesfor workspace writes — under sandbox / Remote mode this lands in the wrong place. Always go throughagent.getWorkspaceManager(). Exception: builder-time seed files (initWorkspaceIfAbsent-style code) — no runtime context yet,java.nio.Filesis correct because you’re seeding the local template.tools.json’sallowfilters built-in tools too — when whitelisting, keepread_file/memory_search/agent_spawnand friends in the list, or every built-in gets stripped.IsolationScopechanges do not migrate existing data — pin it before launch. Changing it post-launch is equivalent to switching to a new namespace.- Local
AgentStateStoresingle-machine constraint: a K8s multi-replica build that pairs a distributed filesystem with a localJsonFileAgentStateStorethrowsIllegalStateExceptionon the very firstbuild(). This is intentional — you can’t park agent state on one pod’s local disk. NacosSkillRepositorynot closed — subscriptions leak; at fleet scale Nacos complains. Use Spring@PreDestroyordestroyMethod="close".- OSS / NAS without IAM —
OssSnapshotSpectakes platform AK/SK; RAM Role + STS temporary credentials is more robust. - Local
AgentStateStorewith sandbox mode is dev-only — the build-time warning is intentional; don’t ignore it in production.
Related pages
- Quickstart — end-to-end first
HarnessAgent - Harness Architecture — how capabilities cooperate
- Context & AgentState —
AgentState/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