This repository is a complete agent, not a library. Two virtual objects,
both keyed by agentId, run it:
Agent(agent/) is the controller. Its exclusive handlers decide everything about the conversation: start a turn, queue or steer a message, interrupt, profile, memories, approvals, schedules, sub-agents, notifications. It never runs a turn.AgentSession(session/) owns the transcript and runs one turn at a time indoTurn: model call, guardrails, tool batch, repeat. The turn's Restate invocation ID is itsturnId.
Every durable step of a turn is journaled. After a crash, the turn replays and reuses the recorded results. An external call that finished before its result was recorded can run again, so external writes need idempotency. Tools, approvals and sub-agents can wait for minutes or days without holding a process.
Load the restate-gen-sdk skill as well: every handler and tool here is
generator code, and its rules apply.
Detect context
packages/libs/core/src/agent-config.tsexists → this skill applies.- Paths in this skill:
- source paths such as
agent/service.tsare relative topackages/libs/core/src/; - test paths such as
test/local-agent.test.mjsare relative topackages/libs/core/; - paths that start with
packages/ordocs/are relative to the repository root.
- source paths such as
- Read
docs/agent-guide.mdbefore changing runtime behavior. It lists the invariants and the owner module of each concern. The docs index isdocs/README.md. - Check
git statusfirst. Never stage or print credential files (modal.sh,ngrok.yml,.env*).
What the agent is: agent-config.ts
One file defines the agent:
- the models (agent, guardrail, compactor);
- the context window and the compaction threshold;
baseInstructions;- the built-in
toolslist; - the
programToolswitch.
Change the agent there. The turn runtime in session/ knows no tool by
name except its own (searchTools, executeProgram) and humanApproval.
The controller in agent/ checks the grants of the tools that call it by
name (createSchedule, createSubAgent, ...), so renaming one of those
means editing agent/ too.
Models
Model IDs are provider:model, for example "anthropic:claude-sonnet-5".
The providers are openai, anthropic, google, xai, deepseek and
openai-compatible (for open models on Ollama, vLLM or OpenRouter).
- Each provider needs its API key in the environment. The table is in
docs/configuration.md#models. - When the agent model changes, set
context.windowTokensto its context window. - A running turn keeps its model. A change applies to new turns.
- To add a provider, add an entry to
PROVIDERSinmodel/provider.ts. Keep the AI SDK insidemodel/: the rest of the code imports message types frommodel/index.ts.
Find the right place for a goal
| Goal | Reference |
|---|---|
| Call an API or run code as a tool (a foreground tool) | references/tools.md |
| Wait on a timer, a person or a long job without blocking the turn (a pending tool) | references/tools.md |
| Expose a separately deployed Restate handler, or a remote MCP server | references/tools.md |
| Keep a large tool catalog out of model context, or compose calls in JavaScript | references/tools.md |
| A tool that changes the agent's own durable state | references/tools.md, then references/agent-handlers.md |
| A new client or UI operation, or a new transcript event | references/agent-handlers.md |
Change ask, steer, interrupt, deliver, turn end, lifecycle, sub-agents or notifications |
references/agent-controller.md |
| Change approvals, memories, compaction, schedules or what the model sees | references/runtime-customization.md |
| Run the sandbox on another platform, or add a workspace operation | references/sandboxes.md |
| Change models, providers, prompts or the context window | agent-config.ts; see "Models" above |
| Users, logins, per-user credentials, many agents per account | references/app-layer.md |
| Let a UI run tools itself (AG-UI frontend tools, CopilotKit) | references/client-tools.md |
| Test and validate a change | references/testing.md |
Turn a request into a change
When someone describes a product behavior rather than a file or API:
- Find its owner with the table above, then read the reference and the current source.
- Say which part already exists, which part is a customization, and which part needs a new app layer.
- Make it concrete: the typed contract, the owner module, the turn snapshot if the turn needs it, the client and UI path, and the tests.
Prefer changing the existing owner. Add an object or service only when it has its own key, lifecycle or deployment.
Do not present an extension pattern as existing code. Today the reference has:
- operator-configured MCP servers, with optional tokens from the environment;
- per-agent memories and schedules;
- a per-call model output limit;
- an optional local UI.
It has no account service, no user OAuth flow, no turn- or user-level token budget, and no eval service.
Working rules
- Built-in tools run inside
doTurn. Make one durable withrestate.run(toolRun), not by turning it into a service. Split out a service only when it should deploy, scale or be owned separately; then expose it as a discovered tool. - The turn owns its work. Every foreground call in one model response starts together and is joined. Pending work belongs to the turn and is cancelled with it. Never leave a spawned task unjoined.
- Cancellation is not failure. Return
failed(...)for what the model can fix; rethrow cancellation (the helpers intools-api.tsdo). - Trusted values come from the context. A tool reads
agentId,turnIdand grants from itsToolCallContext, never from model input. Handlers that act for a turn take itsturnIdand check it against the controller's live turn. - No exclusive call cycles. An exclusive handler never waits on a call
that comes back to an exclusive handler of the same key. This is why
AgentstartsdoTurnwith a one-way send. - The transcript is append-only and public. Tool arguments, results and
reasoning never enter it; a tool may add summary
transcriptentries. - Turns replay from their journal. Code between journaled steps must decide the same way every time. A change to the order or kind of a turn's steps affects new turns only: running turns finish on the version they started on, so ship the change as a new deployment version.
- Keep one owner per concern.
docs/agent-guide.md#where-a-change-belongsmaps each concern to its module. Prefer adding to the owner over a new abstraction, and update the docs that describe the behavior you change.
Rules to check before finishing
- The tool is in
agent-config.tstools, with a precisedescriptionand, if it needs usage guidance,instructions, notbaseInstructions. - External I/O runs in
restate.run/toolRunwith a bounded retry policy, or is a durable Restate call. External writes carry a stable idempotency key where the provider supports one; replay alone does not make them exactly-once. - A pending tool's
completederives everything from its input andtoolCallId. - New handlers state shared vs exclusive, retention, and
ingressPrivatefor internal callbacks, inagent/service.ts. - No exclusive call cycle between objects (Agent → X → the same Agent).
- Wire schemas in
packages/libs/typesmatch every caller, and the client and UI tables are updated for external handlers. - Tests cover the changed behavior, with a replay test for durable effects and turn-control changes.
-
pnpm lint && pnpm build && pnpm test && pnpm bundleandgit diff --checkpass, and the docs are updated.