How this page relates to context management
This page explains where session state lives, how it is persisted and restored, and how tools access the current call’s state. For preparing each model request, adding business information, tracking tasks, and compacting history, see Context management.
Harness projects task state into transient TASK_STATE and loads MEMORY.md as reference data.
These generated messages are not thereby appended to durable history. Plain ReActAgent does
not automatically enable the Harness material and budget policies.
Stateless Agent Engine
ReActAgent and HarnessAgent separate model/tool configuration from conversation state. Configure a shared Builder at startup and build and close an independent Agent per request. Stable identity and storage continue a conversation without retaining the previous request’s Java object. Lifecycle guidance and the shared-instance option are centralized in Agent.
Instances still own execution gates, caches and background resources, and should be closed after execution. A new instance restores committed state. If a plain Core Agent has no log or state store configured, history held only in the old instance is not transferred.
Tools and middleware access the current session through the injected RuntimeContext.getAgentState(), without a global current-state field. Creating an Agent does not copy all business dependencies or replace authorization. Shared dependencies must support concurrent access. The application coordinates execution of the same conversation across instances; see Concurrent usage.
AgentState
AnAgentStateStore persists an AgentState (io.agentscope.core.state.AgentState) — a complete snapshot of everything that makes the agent restartable:
Execution controls are separate from
AgentState: each invocation owns an independent interrupt signal. See Per-session interrupt below.
In LEGACY or Core agents without native logging, the end of each call writes the entire AgentState to the state store under the key agent_state, addressed by the call’s (userId, sessionId). The next call() with the same (userId, sessionId) loads it back automatically. A shared store lets other instances load successfully persisted state. It does not guarantee real-time consistency between concurrent calls or recover unsaved progress and external side effects.
Task state is not automatic business acceptance
Todo is updated by todo_write or the application; the application sets task identity and objective through beginTask. The optional proposal tool only creates candidates; a trusted caller confirms or rejects through decide. The application maintains subject versions, and explicit VerificationService calls produce verification summaries. Persisting fields does not automatically infer them from user messages, PLAN.md or tool text. Progress completion, confirmed requirements and passed checks have different meanings; none automatically establishes overall completion. Harness taskContext options control projection. See Optional task information for the field/writer mapping and display configuration.Session Log and AgentStateStore
call / streamEvents and AgentSession use the same conversation storage configuration. Multi-turn context, history queries and persistence do not require session scheduling. Introduce AgentSession for background tasks, durable queues or coordinated interruption and continuation. For direct calls, start with the Quick Start.
HarnessAgent defaults to EVENT_LOG: existing native checkpoints plus applicable facts restore AgentState, and saves commit checkpoints to the log. AgentStateStore is no longer that session’s recovery authority. LEGACY and Core agents without native logging use the state-store flow below.
Native history follows Workspace Filesystem, including distributed backends, or an explicit sessionLogStore. Keep stable agentId, userId, sessionId, namespace and storage consistent. See Session logs for inspection, reconciliation and continuation. StateStore-only examples below explicitly select LEGACY; default EVENT_LOG also needs shared native history.
The auto-persistence and recovery flow
ReActAgent itself; HarnessAgent inherits it for free. The agent instance holds no fixed session — each call reads / writes the slot named by its RuntimeContext (falling back to the builder-time defaultSessionId).
Reasoning mainly updates in-memory state. Normal completion, controlled failure/interruption and shutdown paths attempt to save agent_state; forced process termination may prevent saving. Administrative operations such as clearContext can also save explicitly. Execution observations and verification reports write separate state keys, so the store does not necessarily receive only one write per call.
Built-in and extension implementations
Anything implementingio.agentscope.core.state.AgentStateStore works. Pick by deployment shape:
Switching is one call at builder time:
Resume saved sessions across processes and machines
With a shared store, each call reloads the slot’s persisted state. This example assumes node A has finished saving before node B continues, not concurrent execution of the same session:- Recovery: another node can load the last successfully saved snapshot. Unsaved progress may be lost; reconcile external tool effects before replaying work.
- Rolling deploys: graceful shutdown attempts to save, and a new instance loads persisted state. Allow shutdown time and verify that state formats and business configuration remain suitable.
- Cross-surface continuity: Web UI and CLI can continue saved sessions with the same store and identity slot; the application must authorize access.
Multi-user isolation
sessionId and userId solve different problems:
sessionId— which conversation this is; independentAgentStatesnapshot.userId— which user owns this conversation; also drives which user’s namespace files land in, see Filesystem.
(userId, sessionId) (with RedisAgentStateStore the userId becomes part of the Redis key) rather than relying on filesystem path bucketing.
Reading and writing AgentState directly
For out-of-loop reads such as administration or auditing, address state by identity. Do not mutate it concurrently with an active call. Obtaining or changing the object does not itself persist it or provide a transaction or authorization check:
Clearing a session’s conversation context
In EVENT_LOG mode,getAgentState() returns a detached native projection. clearContext(), permission changes and saveAgentState() commit a native checkpoint with writer leases and version checks. Existing get→mutate→save calls remain supported, but stale snapshots are rejected; prefer updateAgentState(rc, reason, mutation) for atomic administration. Clearing working context retains full history. Direct AgentStateStore edits do not affect native sessions.
To let a user start a fresh topic without creating a new session, call clearContext. It keeps the
same (userId, sessionId) and preserves non-conversation state such as permissions, tools, tasks,
and Plan Mode. It clears the historical message buffer and compaction summary, then immediately
persists a native checkpoint in EVENT_LOG mode, or saves the configured AgentStateStore in LEGACY mode.
The 1.0
Memory interface (InMemoryMemory / LongTermMemory, etc.) is @Deprecated(forRemoval = true) in 2.0. New code should use AgentState.getContext() + an AgentStateStore; Memory remains only as a source-compat shim.Per-session interrupt
Each execution owns a runtime-onlyInterruptControl. It is neither stored on AgentState nor persisted with conversation history. A session-targeted interrupt resolves the currently admitted execution:
session.interrupt(), or session.interrupt(runId) to reject a stale request targeting a newer execution. Continue the original task with session.resume(turnId); see the session guide.
The reasoning loop checks its execution’s signal at cooperative checkpoints. A user interrupt produces an interrupted recovery reply and saves conversation state. The deprecated no-argument interrupt() targets the default session’s current execution, never the most recently used context.
AgentState.shutdownInterrupted is a separate, persisted recovery marker. Graceful shutdown binds both the execution control and the state resolved for that call; queued calls have no state to save. No interrupt flag is carried into the next run or loaded on another node.
Concurrent usage
Configure this Builder at startup, then build separate instances for Alice and Bob. The workspace must persist between requests; replicas also need access to the same native log backend.AgentSession with a background owner. Per-instance gates do not coordinate other instances. Native journal writer fencing prevents competing commits; it does not guarantee automatic waiting or FIFO order across instances.
Interrupting from another request requires the currently executing instance; a new Agent does not hold the old call’s interrupt signal. Close request Agents after execution to release their caches and runtime resources. Close application-owned model clients and pools at application shutdown.
RuntimeContext — per-call metadata
RuntimeContext (in io.agentscope.core.agent) is a lightweight per-call carrier passed to agent.call(msgs, ctx); hooks and tools share it for the duration of one call. Its free-form / typed attributes are not automatically persisted or included in model messages; its sessionId / userId fields select which AgentState slot the state store loads and saves for this call. At call entry, the framework injects the call-scoped AgentState onto the RuntimeContext so that middleware, tools, and hooks can access the correct per-call state via ctx.getAgentState().
runId correlation
EveryRuntimeContext carries a never-null runId: a non-blank value supplied via builder().runId(x) is kept as-is; otherwise (unset or blank) build() generates one (32-char hex). Its purpose is to tie a single execution together across the execution layer and the product layer:
- Middleware, tools, logs, and tracing can all correlate one invocation via
ctx.getRunId()— under multi-session concurrency, grepping a single runId recovers the full trace of that call; - Handles created by
prepareRun/prepareCalladopt the context’s runId, sorun.runId() == ctx.getRunId()— naturally aligned with theAgentRunRegistryregistration key, the SSESESSION_RUN_STARTEDevent, and the id the frontend uses to cancel a run; - Subagent contexts are derived via
RuntimeContext.builder(parentRc), which copies the runId, so subagents spawned throughagent_spawninherit the parent call’s id — even when the subagent gets an independent sessionId, the chain id keeps the whole execution linked.
RuntimeContext for each prepared execution. To retain session attributes while starting another run, use RuntimeContext.builder(previous).runId(null).build(); this also applies when resuming the same Turn after an external tool result. Derived subagent contexts can retain the parent runId for correlation. With native Session Log enabled, a prepared execution cannot reuse a runId already recorded in that session, and AgentRunRegistry rejects concurrent duplicate runIds in the Service.
Related pages
- Context management — organize model input, track task progress, and bound context through summarization and tool-result offloading
- Agent — full
ReActAgentAPI and builder fields - Memory — long-term memory, background maintenance
- Permissions — persistence of permission rules