State & Workspaces
Per-user workspace management and the StateManager API.
State & Workspaces
The Agent SDK manages per-user state automatically through workspaces. Each user gets an isolated workspace containing their conversation history, memory entries, MCP server configs, and run state. The StateManager provides a typed wrapper over the raw Ploy StateBinding.
Workspaces
A workspace is created automatically when a user sends their first message. It's stored in the state binding under the key agent:workspace:{userId}.
interface Workspace {
userId: string;
chatId: string;
platform: string;
messages: ChatMessage[]; // Conversation history (last 50 messages)
memory: Record<string, MemoryEntry>; // User memories (set via memory tools)
mcpServers: McpServer[]; // Installed MCP servers
debugMode: boolean; // Debug mode toggle
activeRunId: string | null; // Active workflow run ID (null if idle)
queue: QueuedMessage[]; // Messages queued during active runs
}Automatic Behaviors
The SDK handles these workspace operations automatically:
- Creation -- A default workspace is created on the user's first message
- Message trimming -- Conversation history is capped at 50 messages to prevent unbounded growth
- Memory injection -- Memory entries are appended to the system prompt (secrets are hidden)
- Message queuing -- Messages received during an active run are queued and processed in order
- Run tracking --
activeRunIdprevents concurrent runs for the same user
StateManager
The StateManager wraps the raw StateBinding with JSON-aware helpers. It's available on AgentContext.state, ToolContext.state, and HookContext.state.
API
interface StateManager {
get(key: string): Promise<string | null>;
set(key: string, value: string): Promise<void>;
delete(key: string): Promise<void>;
getJSON<T>(key: string): Promise<T | null>;
setJSON(key: string, value: unknown): Promise<void>;
}Usage in handle()
handle: async (req, env, agent) => {
// Read state directly
const visits = (await agent.state.getJSON<number>("visits")) ?? 0;
await agent.state.setJSON("visits", visits + 1);
return Response.json({ visits: visits + 1 });
};Usage in Tools
const myTool = defineTool({
name: "save_note",
description: "Save a note",
parameters: {
type: "object",
properties: { text: { type: "string" } },
required: ["text"],
},
async execute(args, ctx) {
await ctx.state.set(`note:${ctx.userId}`, args.text);
return "Note saved.";
},
});Usage in Hooks
hooks: {
async onRunComplete(ctx, response) {
const count = await ctx.state.getJSON<number>(`runs:${ctx.userId}`) ?? 0;
await ctx.state.setJSON(`runs:${ctx.userId}`, count + 1);
},
}Memory Entries
Memory entries are key-value pairs stored in the workspace. The agent can set, get, delete, and list them using the built-in memory tools. Memories are automatically injected into the system prompt so the agent remembers user preferences across conversations.
interface MemoryEntry {
value: string;
secret: boolean; // Hidden in debug output and system prompt
createdAt: number;
}Secret memories (e.g., API keys) are injected with the value [SECRET] in the system prompt and debug views, but the full value is available to tools.
State values are strings. Use getJSON/setJSON for structured data. The SDK
uses JSON.stringify/JSON.parse internally.
Next Steps
- createAgent() -- The
stateproperty onAgentContext - Tools -- Access state via
ctx.statein tool execute functions - Commands -- The
/resetcommand deletes the user's workspace
How is this guide?
Last updated on