Architecture

Why Shiro separates long-lived services from per-run execution.

Architecture

Shiro’s architecture exists to answer one question: who owns the execution loop?

If your application owns it, every new requirement — retries, approvals, handoffs, memory, observability — lands as another if in a route handler. Shiro puts the loop in the runtime so application code defines what should happen, and the runtime records what did happen.

Engine

Owns registries, plugins, sessions, approval services, and event buses.
engine.event()

Design goal#

Make a run:

  1. Isolated — one runner, one execution state
  2. Shareable — one engine, many runs, shared providers and plugins
  3. Inspectable — events and traces you can open in Studio
  4. Composable — tools, approvals, memory, and handoffs as explicit stages

Engine: shared infrastructure#

The Engine is a long-lived service container. It owns:

  • provider registration (usually via plugins)
  • tool and agent registries
  • session / memory services when configured
  • approval services when configured
  • the event bus used for observability
import { Engine, TraceManager } from "@shiro-sdk/core";
import { OpenAIPlugin } from "@shiro-sdk/openai";

const traces = new TraceManager();
const engine = new Engine({ events: traces });

engine.use(
  new OpenAIPlugin({
    apiKey: process.env.OPENAI_API_KEY!,
    model: "gpt-5",
  })
);

Create it once per process. Register plugins before serving traffic. Do not hang per-user state on it — that belongs in sessions.

Runner: one execution#

engine.execute() creates a Runner. The runner keeps messages, tool state, handoff state, approval waits, validation attempts, and emitted events together for that run.

const result = await engine.execute(agent, "Look up INV-8821", {
  sessionId: "customer_48",
});

The runner is why a failed run can be explained. The state you need is not scattered across queue workers and console.log calls.

Providers: thin adapters#

A provider translates Shiro’s request shape into a vendor API and normalizes the response. It should not know about Studio, approvals, or memory. Vendor churn stays behind the adapter.

In practice you install a provider through a plugin:

engine.use(new OpenAIPlugin({ apiKey, model: "gpt-5" }));

The agent then selects it by id:

const agent = new Agent({
  name: "support",
  instructions: "Help with billing questions.",
  provider: "openai",
});

Tools: side effects with a policy boundary#

Tools run inside the runner. That is what makes them:

  • visible in traces
  • eligible for approval
  • subject to timeouts
  • attributable to an agent and run id

If you call the underlying function yourself, Shiro never sees it.

Memory and sessions#

Sessions hold conversation continuity. Memory retrieval and compaction feed context into the run and persist useful state afterward. Agents stay reusable; sessions carry identity.

Guardrails and approvals#

Guardrails decide whether a stage should continue. Approvals are explicit pauses before risky tools. Both are runtime events — reviewers and Studio see the same pause the runner sees.

Handoffs#

Handoffs transfer control to another registered agent inside the same run. Use them when responsibility, tools, or approval policy change — not just to shorten a prompt.

Traces and Studio#

Every important transition can emit an event. Tracing turns those into a structured record. Studio visualizes timelines, graphs, tools, memory, approvals, and metrics from that record.

Runner events ──► TraceManager / exporters ──► Studio / JSON / logs

Why not put everything on the agent?#

Agents that mutate themselves per user become impossible to share, cache, or test. Shiro keeps agents declarative and pushes identity into sessions, execution into runners, and infrastructure into the engine.

Common mistakes#

MistakeBetter approach
New Engine per requestOne engine per process
Provider calls outside ShiroRegister a plugin; let the runner call the provider
User state on the agentsessionId + memory
Tools invoked manuallytool() definitions on the agent
Debugging from text logs onlyExport traces; open them in Studio