npm.io
0.1.2 • Published yesterday

shirube-ai

Licence
MIT
Version
0.1.2
Deps
2
Size
721 kB
Vulns
0
Weekly
0

Shirube

Shirube is a TypeScript SDK for building production AI agents. You define an agent (instructions, model, tools), call run(), and Shirube owns the loop: talk to the LLM, execute tools, hand off to specialists, enforce guardrails, remember the user, and stop safely.

It is named after its author. The core loop is written in this repo — it is not a wrapper around the OpenAI or Claude agent frameworks.

import { Agent, tool } from "shirube-ai";
import { z } from "zod";

const weather = tool({
  name: "get_weather",
  description: "Current weather for a city.",
  parameters: z.object({ city: z.string() }),
  execute: async ({ input }) => ({ city: input.city, tempC: 21 }),
});

const agent = Agent.builder()
  .name("assistant")
  .instructions("Be concise. Use tools when you need facts.")
  .apiKey(process.env.OPENAI_API_KEY!)
  .tools([weather])
  .build();

const result = await agent.run("Weather in Paris?");
console.log(result.output); // final answer
console.log(result.model, result.turns, result.runId);

Who it is for

Use Shirube when you are shipping an agent that other people will actually talk to:

Use case What Shirube gives you
Customer support Tools for tickets/refunds, sessions per chat, graph memory of the customer, handoff to billing
Internal ops MCP tools from GitHub/Linear, approval on risky actions, traces for audit
Coding / research assistants Model routing (cheap model for easy questions), streaming UI events, structured JSON output
Multi-agent products One triage agent that transfers to specialists without looping

If you only need a one-off Chat Completions call, use the vendor SDK. Shirube exists for the rest of the stack: tools, memory, security, graph knowledge, MCP, and observability.

Why it exists

Vendor agent kits give you a loop. Production still needs:

  • Which model to call for this prompt (and a fallback if that provider is down)
  • How to remember a user across weeks without stuffing the whole history into every request
  • How to stop jailbreaks, leaks, and unapproved refunds
  • How to share tools with Cursor via MCP
  • How to keep a knowledge graph from filling with duplicates

Shirube implements those in-process, with security on by default.

Install

npm install shirube-ai zod

Requires Node 18.18+. Set OPENAI_API_KEY, or pass .apiKey(). Optional: mem0ai for hosted long-term memory, ANTHROPIC_API_KEY / GEMINI_API_KEY for other providers.


How an agent run works

  1. Input guardrails — reject jailbreaks, redact PII, enforce size limits.
  2. Load context — session history, long-term memory hits, graph facts for this user.
  3. Pick a model — the one you configured, or a routed GPT based on prompt complexity.
  4. Loop — send messages to the LLM. If it requests a tool, validate args, optionally require approval, execute, send the result back. Repeat until a final answer or maxTurns / timeout.
  5. Output guardrails — block leaks; optionally validate JSON against a Zod schema (and repair).
  6. Persist — write memory, append the session, queue graph extraction. Graph workers run in the background so this step does not wait on them.

You always get a RunResult: text (and optional parsed JSON), which agent finished, model used, token usage, traces, and events.


Quick start by feature

1. Define an agent

What: Name, instructions, and a model (or leave the model off and let Shirube route).

Use when: Every product surface — support bot, CLI assistant, Slack worker.

const agent = Agent.builder()
  .name("support")
  .instructions("You help customers. Prefer tools over guessing.")
  .apiKey(process.env.OPENAI_API_KEY!)
  // .model("gpt-4.1")  // optional — omit to auto-pick by complexity
  .build();

await agent.run("Where is my order?");
await run(agent, "Where is my order?"); // same thing, function style

Agent.create({ name, instructions, apiKey, ... }) accepts the same fields as a config object.

2. Tools

What: Functions the model can call. Each tool has a name, description, Zod input schema, and execute. Inputs are validated before execute. execute may be async.

Use when: The agent must hit your APIs (search, refund, calendar) instead of inventing answers.

const refund = tool({
  name: "refund",
  description: "Refund an order by id. Only after the user confirms.",
  parameters: z.object({ orderId: z.string() }),
  requireApproval: true, // fail-closed unless run() provides approval
  execute: async ({ input, context }) =>
    payments.refund(input.orderId, context.userId),
});

Agent.builder().tools([refund]) /* or .tool(refund) */;

await agent.run("Refund order_9", {
  userId: "cus_123",
  approval: async ({ tool: name }) => name !== "refund" || user.isAdmin,
});

If a tool throws, the loop does not crash: the model receives { error: "..." } and can recover. Unknown / invalid JSON args raise ToolError.

3. Model routing and providers

What: If you skip .model(), a classifier labels the prompt simple | moderate | complex | reasoning and picks a GPT from a catalog. You can also swap OpenAI for Claude or Gemini, and chain fallbacks.

Use when: You want cheap models on “what’s 2+2” and a stronger model on architecture questions; or you cannot go down when OpenAI 5xxs.

Tier Default model
simple gpt-4.1-mini
moderate gpt-4.1
complex / reasoning gpt-5
.modelRouter({
  mode: "hybrid", // skip the classifier call on obviously short prompts
  catalog: {
    classifier: "gpt-4.1-mini",
    simple: "gpt-4.1-mini",
    moderate: "gpt-4.1",
    complex: "gpt-5",
    reasoning: "gpt-5",
  },
})

.provider(new OpenAIProvider({ apiKey }))
.fallback(new AnthropicProvider({ apiKey: process.env.ANTHROPIC_API_KEY }))
// also: GeminiProvider
4. Memory and sessions

Shirube keeps four layers separate on purpose:

Layer Lifetime Example
Agent config Process Instructions, tools, rails
Run state One run() Messages, usage, handoff hops
Session One conversation sessionId or FileSession — “what did we just say?”
Long-term memory User / org Mem0 or in-memory search — “Ada prefers sci-fi”
Graph User / org People, projects, WORKS_ON edges — structured knowledge

Use sessions for chat UIs (turn N needs turn N-1). Use memory so next week’s ticket still knows the customer. Use the graph when you care about relationships, not just similar text.

.memory({ provider: "in-memory" })
.memory({ provider: "mem0", apiKey: process.env.MEM0_API_KEY })
.memory({ provider: "custom", instance: myProvider }) // search + add
.session(new FileSession("chat_9", "./.shirube/chat_9.json"))

await agent.run("Hello again", { userId: "ada", sessionId: "chat_9" });

userId scopes memory. Implement MemoryProvider to plug in Redis, pgvector, Zep, etc.

5. Graph memory (and three background workers)

What: An embedded property graph (nodes + typed edges). After each run, Shirube queues the conversation. Three independent timers — not the request thread — extract entities, build relationships, and clean the graph. On the next run, relevant facts are injected into the system prompt.

Use when: Support/ops agents that should remember “Ada works on Shirube, uses TypeScript” without you writing ETL.

import { Agent, graph, createFileGraph } from "shirube-ai";

graph.start(); // extract + relate + maintain

const agent = Agent.builder()
  .name("ops")
  .instructions("Use known facts about this user. Do not invent employers or projects.")
  .apiKey(process.env.OPENAI_API_KEY!)
  .graph(true)
  .build();

await agent.run("I'm Ada. I work on Project Shirube using TypeScript.", {
  userId: "ada",
});

await graph.flush(); // tests / shutdown only — production uses intervals
console.log(graph.contextFor("Shirube", "ada"));
Worker Job
Extract People, projects, technologies, preferences, facts from the transcript
Relate Edges such as WORKS_ON, USES, PREFERS — same pair updates confidence instead of duplicating
Maintain Merge duplicate nodes, decay stale edges, prune low-confidence noise

Persist with createFileGraph("./.shirube-graph.json") or pass your own GraphRuntime. Failures retry up to 3 times and are counted on graph.stats.

6. Handoffs (multi-agent)

What: Agent A exposes transfer_to_<name> tools. The model can delegate. Messages stay; the specialist continues the same run. Shirube blocks A→B→A loops and caps hops (maxHandoffs, default 4).

Use when: A triage bot should not answer invoices; a billing specialist should.

const billing = Agent.builder()
  .name("billing")
  .instructions("You only resolve invoices and charges.")
  .apiKey(key)
  .build();

const triage = Agent.builder()
  .name("triage")
  .instructions("Route billing to the billing agent. Handle everything else yourself.")
  .apiKey(key)
  .handoffs([billing])
  .maxHandoffs(3)
  .build();

const result = await triage.run("Why was I charged twice?");
result.handoffs;   // ["triage->billing"]
result.agentName;  // "billing"
7. Guardrails and approvals

What: Checks on input, output, and tool arguments/results. Built-in rails cover jailbreak, prompt injection, PII redaction, secret leak, system-prompt leak, and max input size. You add domain rails (no legal advice, no SSN).

Use when: The agent is public-facing or can call write APIs.

.security({ jailbreak: true, pii: { redact: true } })
.security(false) // opt out of builtins; custom rails still work

const noLegal: Guardrail = {
  name: "no_legal_advice",
  stage: "output",
  run: ({ text }) => {
    const hit = /sue|lawsuit/i.test(text);
    return { action: hit ? "block" : "allow", tripwireTriggered: hit };
  },
};

.inputGuardrails([]).outputGuardrails([noLegal]).toolGuardrails([])

block + tripwire → GuardrailTripwireError. redact rewrites text and continues.

8. Structured output

What: .output(zodSchema) asks for JSON, parses it, and retries a repair turn if validation fails.

Use when: You need { label, confidence } for a ticketing pipeline, not free-form chat.

const Ticket = z.object({
  label: z.enum(["bug", "billing", "other"]),
  confidence: z.number(),
});

const agent = Agent.builder()
  .name("classifier")
  .instructions("Classify the ticket.")
  .output(Ticket)
  .build();

const result = await agent.run("The app crashes on launch.");
result.outputParsed; // { label: "bug", confidence: 0.9 }

After the retry budget it throws OutputValidationError.

9. Streaming and events

What: runStream() is an async iterator of runtime events. run({ onEvent }) is the callback form. Same events: text, tools, handoffs, guardrails, memory, graph, completion.

Use when: You are driving a UI, a terminal spinner, or logging.

for await (const event of agent.runStream("Hello")) {
  if (event.type === "text.streamed") {
    process.stdout.write(String(event.data?.delta ?? ""));
  }
  if (event.type === "tool.started") {
    console.log("calling", event.data?.name);
  }
}

Event types: run.started, run.completed, run.failed, text.streamed, tool.started, tool.completed, handoff.started, guardrail.triggered, memory.updated, graph.updated, model.called.

10. Tracing, retries, timeouts

What: Every run has a runId. Traces record model calls, tools, handoffs, memory/graph I/O, timing, and errors. Retries and timeouts wrap provider calls.

Use when: You need to debug “why did this cost 12 turns?” or survive flaky APIs.

.retry({ maxAttempts: 3, baseDelayMs: 200 })
.timeout(60_000)
.tracing({ enabled: true, onEvent: (e) => logger.debug(e) })

result.traces;
result.usage;       // { inputTokens, outputTokens, totalTokens }
result.durationMs;

Cancel with abortSignal. Errors are typed: ConfigError, GuardrailTripwireError, MaxTurnsError, TimeoutError, ToolError, ModelError, MemoryError, McpError, OutputValidationError, AbortError.

11. MCP (Model Context Protocol)

What: Attach external MCP servers (GitHub, Linear) as tools, and serve your internal tools so Cursor or Claude Desktop can call them. Configure once; every agent inherits unless .mcp(false).

Use when: The agent should use existing MCP servers, or you want one catalog of company tools for both Shirube agents and IDEs.

import { mcp, tool } from "shirube-ai";

mcp.configure({
  internal: [refund],
  external: [
    {
      name: "github",
      command: "npx",
      args: ["-y", "@modelcontextprotocol/server-github"],
      env: { GITHUB_TOKEN: process.env.GITHUB_TOKEN! },
    },
    { name: "linear", url: "https://mcp.linear.app/mcp" },
  ],
});

Agent.builder().mcp() // or .mcp({ external: ["github"], include: ["github__*"] })

await mcp.serve({ transport: "stdio" });
await mcp.serve({ transport: "http", port: 3333, path: "/mcp" });

External tools are namespaced (github__create_issue). agent.run() connects lazily; await agent.connect() fails fast; await agent.close() stops stdio servers. Local .tools() win on name clashes.


Builder reference

Agent.builder()
  .name("ops")
  .instructions("You operate internal tools.") // string or (ctx) => string
  .apiKey(process.env.OPENAI_API_KEY!)
  .baseURL(process.env.OPENAI_BASE_URL)
  .model("gpt-4.1")
  .modelRouter({ mode: "hybrid" })
  .provider(openai)
  .fallback(anthropic)
  .tools([search])
  .handoffs([billing])
  .maxHandoffs(3)
  .output(schema)
  .graph(true)
  .mcp()
  .memory({ provider: "in-memory" })
  .session(session)
  .security({ pii: { redact: true } })
  .inputGuardrails([]).outputGuardrails([]).toolGuardrails([])
  .maxTurns(8)
  .timeout(60_000)
  .retry({ maxAttempts: 3 })
  .temperature(0.2)
  .maxTokens(2048)
  .tracing({ enabled: true })
  .use(metricsPlugin)
  .feature("tenant", "acme")
  .build();

Plugins hook onBuild, onBeforeRun, onAfterModel, onAfterTool, onAfterRun, onError.

Run result

const result = await agent.run("Summarize the latest invoice", {
  userId: "cus_123",
  sessionId: "chat_9",
  metadata: { ticket: "T-204" },
  abortSignal: controller.signal,
  onEvent: (e) => {},
  approval: async () => true,
});

result.output;
result.outputParsed; // if .output(schema)
result.agentName;
result.handoffs;
result.model;
result.complexity;   // when routing ran
result.usage;
result.turns;
result.durationMs;
result.runId;
result.guardrails;
result.traces;
result.events;
result.messages;

Examples in this repo

File Shows
examples/basic.ts Tool + in-memory memory
examples/graph.ts Graph workers + retrieval
npx tsx examples/basic.ts

License

MIT

Keywords