Skip to content

Getting Started with OpenMandate

Welcome! This guide will take you from npm init to running a fully governed AI agent in your terminal. We'll build a simple "Tenant Rights" agent based on the provided uk-law skill pack, but the concepts apply universally to any domain.

By the end of this tutorial, you will:

  1. Initialise a Mastra + OpenMandate project
  2. Write a declarative Mandate (YAML)
  3. Create a Tool and a Skill Pack
  4. Run an interactive chat loop with your agent

1. Prerequisites & Installation

OpenMandate extends Mastra.ai, meaning you get all of Mastra's features (Model Routing, Workflows, RAG) with OpenMandate's strict, declarative governance layered on top.

bash
# 1. Clone the repository
git clone https://github.com/ryanmcdonough/openmandate.git
cd openmandate

# 2. Install dependencies
npm install

# 3. Configure API Keys
cp .env.example .env
# Open .env and add either ANTHROPIC_API_KEY or OPENAI_API_KEY

Note on Mastra: The OpenMandate codebase already includes @mastra/core. If you are completely new to Mastra, you can read about their core primitives in the Mastra Documentation.


2. Writing Your First Mandate (YAML)

Everything an OpenMandate agent can or cannot do is defined in a YAML file. Create a file called tenant-rights.mandate.yaml. (In this repo, one is already located at packages/skill-uk-law/mandates/tenant-rights-eaw.mandate.yaml).

Here is a simplified example:

yaml
version: "1.0"
metadata:
  name: "tenant-rights-eaw"
  skill_packs: ["uk-law"]

capabilities:
  tools: ["legislation-lookup", "deadline-calculator"]

prohibitions:
  actions: ["provide_legal_advice", "represent_as_solicitor"]

requirements:
  disclaimers:
    - trigger: "always"
      text: "This is legal information, not legal advice."
      placement: "end"

scope:
  allowed: ["GB-EAW"]
  behavior_on_unsupported: "escalate"

You can validate that your mandate is syntactically and logically correct using the built-in CLI:

bash
npx tsx src/cli/index.ts validate packages/skill-uk-law/mandates/tenant-rights-eaw.mandate.yaml

3. Building Tools & Skill Packs

Agents need tools, but in OpenMandate, tools aren't passed directly to the agent. They belong to Skill Packs, which the Mandate imports.

Building a Tool

Tools are built using Mastra's standard createTool method.

Mastra Docs Signpost: How to build Tools in Mastra

typescript
// packages/skill-uk-law/tools/deadline-calculator.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";

export const deadlineCalculatorTool = createTool({
    id: "deadline-calculator",
    description: "Calculates statutory response deadlines for legal notices.",
    inputSchema: z.object({
        notice_type: z.enum(["section_21", "section_8"]),
        service_date: z.string()
    }),
    outputSchema: z.object({
        deadline_date: z.string(),
        notes: z.string()
    }),
    execute: async ({ context }) => {
        // ... calculation logic ...
        return { deadline_date: "2026-04-20", notes: "Minimum 2 months." };
    }
});

Wrapping it in a Skill Pack

A Skill Pack groups your tools together and registers them under a single id that the Mandate can require.

OpenMandate Docs Signpost: Building Skill Packs Deep-Dive

typescript
// packages/skill-uk-law/index.ts
import type { SkillPack } from "@openmandate/core/skills/types.js";
import { deadlineCalculatorTool } from "./tools/deadline-calculator.js";

export const ukLawSkillPack: SkillPack = {
    id: "uk-law",
    name: "UK Law",
    description: "Legal tools for UK jurisdictions.",
    version: "0.1.0",
    tools: {
        "deadline-calculator": deadlineCalculatorTool // Key matches capabilities.tools!
    },
    systemPromptFragment: "You are a legal assistant for England and Wales.",
};

4. Running the Agent

Now that you have a Mandate and a Skill Pack, it's time to run it. OpenMandate's createMandatedAgent handles reading the YAML, picking the allowed tools out of the Skill Pack, and building the Mastra Agent instance.

Create a script run.ts:

typescript
import "dotenv/config";
import { createAnthropic } from "@ai-sdk/anthropic";
import { SkillPackRegistry } from "./src/skills/registry.js";
import { createMandatedAgent } from "./src/mastra/agents/factory.js";
import { ukLawSkillPack } from "./packages/skill-uk-law/index.js";

const anthropic = createAnthropic();
const model = anthropic("claude-sonnet-4-5-20250929");

// 1. Register Skill Pack
const skillRegistry = new SkillPackRegistry();
skillRegistry.register(ukLawSkillPack);
await skillRegistry.setupAll();

// 2. Create the Agent
const tenantRightsAgent = createMandatedAgent({
    mandatePath: "./packages/skill-uk-law/mandates/tenant-rights-eaw.mandate.yaml",
    model,
    skillRegistry,
    description: "Helps tenants understand their rights.",
});

// 3. Run a generation
async function main() {
    console.log("Thinking...");
    const result = await tenantRightsAgent.generate("My landlord gave me a Section 21 notice yesterday.");
    
    // The processor chain validates the output (e.g. adding the required disclaimer)
    console.log("Agent:", result.text);
}

main();

Run your code!

bash
npx tsx run.ts

5. Trying the Interactive Chat

The codebase comes with a pre-configured, interactive chat.ts script that boots up a "Triage Agent" which dynamically routes your questions to the correct governed agent.

To try it out immediately:

bash
npm run chat
# or npx tsx examples/chat.ts
text
🤖 OpenMandate — Interactive Chat
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Talk to the triage agent. It will route you to
the right specialist based on your needs.

Available specialists:
  • tenant-rights-eaw: Helps tenants in England & Wales with housing rights...

You: Can you help me write a letter to my landlord?
⏳ Thinking...
Agent: I can help you draft a formal letter. What is the issue?

Next Steps

  • Deepen your framework knowledge: Read the Architecture Guide to understand exactly how the Processor Chain intercepts, validates, and logs LLM inputs/outputs.
  • Audit your agents: Learn how to use the npx tsx src/cli/index.ts audit command to inspect the SQLite database of stopped tool calls and blocked responses.
  • Connect to chat apps: Check out examples/whatsapp.ts to see how you can bridge these agents into real-world communication platforms.