Skip to main content

Rules server for agents with REST and MCP interfaces

Project description

Daimyo - Rules Server for Agents

Daimyo (大名) is an extensible Python server providing rules to AI agents through REST and MCP interfaces. Supports scope-based rules with inheritance, categories for filtering, and server federation for distributed rule management.

Features

  • Multiple Interfaces: REST API, MCP (Model Context Protocol), and CLI
  • Scope Inheritance: Single and multiple parent inheritance with priority-based conflict resolution
  • Rule Types: Commandments (MUST) and Suggestions (SHOULD)
  • Categories: Organize rules into hierarchical categories for selective retrieval
  • Server Federation: Distribute scopes across multiple servers with automatic merging
  • Multiple Formats: Output as YAML, JSON, or Markdown
  • Clean Architecture: Domain-driven design with clear separation of concerns
  • Templating System: Rules can use Jinja2 templates to be defined as generic rules that change their form depending on the context
  • Extensibility via Plugins: Plugins can extend the features of daimyo instances

Installation

pip install daimyo

Or install from source:

git clone https://gitlab.com/Kencho1/daimyo.git
cd daimyo
pip install -e .

Quick Start

1. Set Up Your Rules

cp -r example-daimyo-rules daimyo-rules

2. Start the Server

daimyo serve

3. Access the API

Visit http://localhost:8000/docs for interactive API documentation.

curl http://localhost:8000/api/v1/scopes/python-general/rules

Core Concepts

Scopes

Scopes represent organizational contexts (company, team, project). Each scope is a directory containing:

  • metadata.yml - Scope configuration and parent references
  • commandments.yml - Mandatory rules (MUST)
  • suggestions.yml - Recommended rules (SHOULD)
.daimyo/rules/
├── python-general/
│   ├── metadata.yml
│   ├── commandments.yml
│   └── suggestions.yml
└── team-backend/
    ├── metadata.yml
    ├── commandments.yml
    └── suggestions.yml

Metadata Format

name: scope-name
description: Human-readable description
parents:
  - parent-scope-1
  - parent-scope-2
tags:
  team: backend
  language: python

Fields:

  • name: Scope identifier (must match directory name)
  • description: Human-readable description
  • parents: List of parent scopes (first = highest priority)
  • tags: Key-value pairs for categorization

Categories

Categories are hierarchical subdivisions within rules:

python.web.testing:
  when: When testing web interfaces
  ruleset:
    - Use playwright for acceptance tests
    - Use pytest fixtures for test setup

Optional "when" Descriptions

The "when" field is optional. When omitted or empty, the system uses intelligent fallback:

python.testing:
  when: When writing tests for this project
  ruleset:
    - Use our custom test fixtures

python.web:
  ruleset:
    - Follow team web standards

Fallback priority (highest to lowest):

  1. Local child category "when" description (e.g., python.web.testing)
  2. Remote (master server) same category "when" description
  3. Parent scope same category "when" description
  4. Parent categories in the hierarchy (e.g., python.webpython)
  5. Default: "These rules apply at all times"

This allows:

  • Parent/remote scopes to define general descriptions
  • Child scopes to override only when needed
  • Hierarchical inheritance from broader to specific categories
  • Simplified child scopes that inherit descriptions

Rule Types

Commandments (MUST): Mandatory rules that accumulate through inheritance

Suggestions (SHOULD): Recommended rules that can be overridden or appended with + prefix

Why not nesting the categories?

While it seems more intuitive, it proved to be confusing and harder to maintain in certain cases, e.g.:

  • Appending suggestions: it's confusing to know whether the + must be prepended to the innermost category, to the root category, or to a category in between.
  • Sharding categories: should it combine the innermost category or every category and subcategory defined?

For that reason it was decided to keep the categories at the root level, using the explicit path notation and nesting them logically using the dot path splitting.

Usage

REST API

Start the server:

daimyo serve
daimyo serve --host 0.0.0.0 --port 8080

Get rules:

curl http://localhost:8000/api/v1/scopes/python-general/rules

curl -H "Accept: application/json" \
  http://localhost:8000/api/v1/scopes/python-general/rules

curl -H "Accept: text/markdown" \
  http://localhost:8000/api/v1/scopes/python-general/rules

Filter by categories:

curl "http://localhost:8000/api/v1/scopes/team-backend/rules?categories=python.web,python.testing"

MCP Server

Start the MCP server:

# Using stdio transport (default)
daimyo mcp

# Using HTTP transport
daimyo mcp --transport http

# Using HTTP with custom host and port
daimyo mcp --transport http --host 127.0.0.1 --port 8002

Available tools:

  • get_rules(scope_name, categories?) - Get formatted rules for a scope
  • get_category_index(scope_name) - Get a hierarchical list of all available categories with their descriptions
  • list_scopes() - List available scopes
  • apply_scope_rules(scope_name, categories?) - Get prompt template with rules

Connecting to the MCP server

Add the running daimyo MCP server instance to your configuration (replace the server name and the URL with your own):

{
  "mcpServers": {
    "daimyo-rules": {
      "type": "http",
      "url": "http://daimyo-mcp-instance/mcp"
    }
  }
}

Instruct your agents how to use the tools:

  • State the project scope to use.
  • Tell it to read the categories index and fetch the rules of the relevant categories before anything else.

For instance, in CLAUDE.md:

- The current scope name of this project is `project-api`.
- First and foremost, use the `daimyo-rules` MCP server tools.
  - Use `list_scopes()` to see available scopes.
  - Use `get_category_index` passing the current scope name to list available categories and their descriptions in the given scope.
  - Depending on the categories that apply to the current task, use `get_rules` with the current scope name and a comma-separated list of all the categories that apply, to fetch the specific rules for the related categories.

Note some less capable models (like local models via Ollama) may need additional or more detailed instructions.

To make the instructions reusable, the scope name can be read from a file (for instance .project-scope).

CLI Commands

# List all available scopes
daimyo list-scopes

# Show details of a specific scope
daimyo show python-general

# View template context for debugging
daimyo context python-general
daimyo context python-general --category python.testing
daimyo context python-general --format json
daimyo context python-general --sources

# Version information
daimyo --version

Template Context Command

The context command displays the Jinja2 template context available when rendering rules for a scope. This is useful for debugging template issues and understanding what variables are available in templates.

Basic usage:

daimyo context <scope_name>

Options:

  • --category, -c: Show context for a specific category (includes category key and when description)
  • --format, -f: Output format - yaml (default), json, or table
  • --sources, -s: Annotate each variable with its source (config, scope, category, or plugins)

Examples:

# View context in YAML format (default)
daimyo context python-general

# View context for a specific category
daimyo context python-general --category python.testing

# JSON format for programmatic use
daimyo context python-general --format json

# Table format for quick scanning
daimyo context python-general --format table

# Show variable sources
daimyo context python-general --sources

Output includes:

  • Configuration variables: All DAIMYO_* settings from environment or config files
  • Scope metadata: name, description, tags, sources
  • Category info: key and when description (if --category specified)
  • Plugin context: Variables provided by enabled plugins
  • Plugin metadata: Available Jinja2 filters and tests from plugins

Configuration

Configuration is managed via .daimyo/config/settings.toml or environment variables.

Configuration Parameters

All configuration parameters with their defaults and descriptions:

Rules Directory

  • rules_path (default: ".daimyo/rules")
    • Path to the directory containing scope definitions
    • Environment variable: DAIMYO_RULES_PATH

Logging

  • console_log_level (default: "WARNING")

    • Log level for console output: DEBUG, INFO, WARNING, ERROR, CRITICAL
    • Environment variable: DAIMYO_CONSOLE_LOG_LEVEL
  • file_log_level (default: "INFO")

    • Log level for file output: DEBUG, INFO, WARNING, ERROR, CRITICAL
    • Environment variable: DAIMYO_FILE_LOG_LEVEL
  • log_file (default: "logs/daimyo.log")

    • Path to the main log file
    • Environment variable: DAIMYO_LOG_FILE
  • log_json_file (default: "logs/daimyo.jsonl")

    • Path to the JSON-formatted log file
    • Environment variable: DAIMYO_LOG_JSON_FILE

Scope Resolution

  • max_inheritance_depth (default: 10, range: 1-100)
    • Maximum depth for scope inheritance chain to prevent infinite loops
    • Environment variable: DAIMYO_MAX_INHERITANCE_DEPTH

Remote Server (Federation)

  • master_server_url (default: "")

    • URL of master server for scope federation (e.g., "http://master.example.com:8000")
    • Leave empty to disable federation
    • Environment variable: DAIMYO_MASTER_SERVER_URL
  • remote_timeout_seconds (default: 5, range: 1-60)

    • Timeout in seconds for remote server requests
    • Environment variable: DAIMYO_REMOTE_TIMEOUT_SECONDS
  • remote_max_retries (default: 3, range: 0-10)

    • Maximum number of retry attempts for failed remote requests
    • Environment variable: DAIMYO_REMOTE_MAX_RETRIES

REST API Server

  • rest_host (default: "0.0.0.0")

    • Host address to bind the REST API server
    • Environment variable: DAIMYO_REST_HOST
  • rest_port (default: 8000, range: 1-65535)

    • Port number for the REST API server
    • Environment variable: DAIMYO_REST_PORT

MCP Server

  • mcp_transport (default: "stdio", options: "stdio", "http")

    • Transport type for MCP server
    • stdio: Standard input/output (for CLI integrations)
    • http: HTTP server (for HTTP-based integrations)
    • Environment variable: DAIMYO_MCP_TRANSPORT
  • mcp_host (default: "0.0.0.0")

    • Host address to bind the MCP server when using HTTP transport
    • Only applies when mcp_transport="http"
    • Environment variable: DAIMYO_MCP_HOST
  • mcp_port (default: 8001, range: 1-65535)

    • Port number for the MCP server when using HTTP transport
    • Only applies when mcp_transport="http"
    • Environment variable: DAIMYO_MCP_PORT

Configuration File Example

[default]
# Rules directory configuration
rules_path = ".daimyo/rules"

# Logging configuration
console_log_level = "WARNING"
file_log_level = "INFO"
log_file = "logs/daimyo.log"
log_json_file = "logs/daimyo.jsonl"

# Scope resolution configuration
max_inheritance_depth = 10

# Remote server configuration
master_server_url = ""
remote_timeout_seconds = 5
remote_max_retries = 3

# REST API configuration
rest_host = "0.0.0.0"
rest_port = 8000

# MCP configuration
mcp_transport = "stdio"
mcp_host = "0.0.0.0"
mcp_port = 8001

[development]
console_log_level = "DEBUG"
rest_port = 8001

[production]
console_log_level = "WARNING"

Environment Variables

Override any configuration parameter using environment variables with the DAIMYO_ prefix:

# Rules path
export DAIMYO_RULES_PATH="/custom/rules/path"

# Logging
export DAIMYO_CONSOLE_LOG_LEVEL="DEBUG"
export DAIMYO_FILE_LOG_LEVEL="INFO"

# Server federation
export DAIMYO_MASTER_SERVER_URL="http://master.example.com:8000"

# REST API
export DAIMYO_REST_HOST="127.0.0.1"
export DAIMYO_REST_PORT="9000"

# MCP Server
export DAIMYO_MCP_TRANSPORT="http"
export DAIMYO_MCP_HOST="0.0.0.0"
export DAIMYO_MCP_PORT="8001"

Examples

The example-daimyo-rules/ directory contains working examples:

python-general

Base Python development rules with categories for core practices, testing, security, and documentation.

python-fastapi

FastAPI framework rules extending python-general with routing, async patterns, and performance optimization.

team-backend

Backend team rules extending python-general with REST API patterns, database access, and deployment considerations.

project-api

Demonstrates multiple parent inheritance with parents: [team-backend, python-fastapi]:

  • Combines team-specific and technology-specific rules
  • Shows priority-based conflict resolution
  • Uses + prefix to append to parent rules

Advanced Topics

Multiple Parent Inheritance

parents:
  - high-priority
  - low-priority

Commandments: All rules from all parents are combined (additive)

Suggestions: First parent wins in conflicts; use + prefix to append instead of replace

Server Federation

Configure a master server for distributed scope management:

export DAIMYO_MASTER_SERVER_URL="http://master.example.com:8000"

The system will:

  1. Look for scopes locally
  2. Look for scopes on the master server
  3. Merge both if found in both locations (local extends remote)

Scope Sharding

The same scope name can exist on both master server and locally. When both exist, they are merged with the remote version as the base and the local version extending it.

Markdown formatting

Rules are typically rendered in Markdown format. LLMs may take advantage of certain formatting features such as emphasis or code fragments, so feel free to use these when writing rules.

Jinja2 Templates

Rules and category descriptions support Jinja2 templates for dynamic content based on configuration and scope metadata.

Available Template Variables

Templates can access:

  • Configuration: All DAIMYO_* environment variables and settings from config/settings.toml
  • Scope metadata: scope.name, scope.description, scope.tags, scope.sources
  • Category info: category.key, category.when (in rule text only)

Basic Example

Configuration (config/settings.toml):

[default]
TEAM_NAME = "Backend Team"
SLACK_CHANNEL = "#backend"

Rules with templates (commandments.yml):

python.monitoring:
  when: "When monitoring {{ scope.name }} in {{ scope.tags.env | default('dev') }}"
  ruleset:
    - "Alert {{ TEAM_NAME }} via {{ SLACK_CHANNEL }}"
    - "Log level: {{ LOG_LEVEL }}"

Rendered output (assuming scope.tags.env = "production"):

## python.monitoring
*When monitoring my-service in production*
- **MUST**: Alert Backend Team via #backend
- **MUST**: Log level: INFO

Best Practices

Always use the default filter for optional variables:

- "Use {{ MY_VAR | default('fallback_value') }} for configuration"

Conditionals:

- "{% if scope.tags.env == 'prod' %}Use strict security{% else %}Use standard security{% endif %}"

Multiple variables:

- "Team {{ scope.tags.team }} deploys to {{ scope.tags.region }}"

Error Handling

If a template references an undefined variable without a default:

REST API: Returns 422 Unprocessable Entity

{
  "detail": "Template variable 'UNDEFINED_VAR' is undefined in scope 'my-scope', category 'python.web'\n\nTemplate: Use {{ UNDEFINED_VAR }} here\n\nTip: Use Jinja2 'default' filter: {{ UNDEFINED_VAR | default('fallback') }}"
}

MCP/CLI: Returns error string with same guidance

Use Cases

Environment-aware rules:

python.deployment:
  when: "When deploying to {{ scope.tags.region }}"
  ruleset:
    - "Deploy to {{ scope.tags.region }} region"
    - "{% if scope.tags.env == 'production' %}Require manual approval{% endif %}"
    - "Notification: {{ SLACK_DEPLOY_CHANNEL | default('#deployments') }}"

Team-specific rules:

code-review:
  when: "When reviewing code for {{ TEAM_NAME }}"
  ruleset:
    - "Review in {{ CODE_REVIEW_TOOL | default('SonarQube') }}"
    - "Require approval from {{ scope.tags.team }} lead"

Plugin System

Daimyo supports plugins (bugyo - 奉行) that extend functionality through callback hooks.

Using Plugins

1. Install a Plugin

Plugins are installed via pip:

pip install daimyo-example-plugin

2. Enable Plugins

Edit .daimyo/config/settings.toml:

enabled_plugins = [
    "git.*",         # Enable all git plugins
    "fs.*",          # Enable all filesystem plugins
    "example.*",     # Enable all plugins with 'example' prefix
    "git.context",   # Enable specific plugin only
]

Wildcard patterns supported:

  • "example.*" - Enable all plugins starting with "example."
  • "example.context" - Enable specific plugin only

Note: The "*" wildcard to enable all plugins is not supported. You must explicitly specify plugin patterns.

3. Running plugins

Once enabled, plugin callbacks are called on different events. For instance, when providing additional context to the templating system:

python.web:
  when: When writing Python web code
  ruleset:
    - Use custom variable: {{ custom_var }}

Official Plugins

Daimyo provides official plugins for common use cases. See the Plugin Catalog for details.

Creating Plugins

Each plugin has its own entry point and inherits from a specialized base class depending on its purpose.

Context Provider Plugins

Provide template variables for Jinja2 templates:

my_plugin.py:

from daimyo.domain import ContextProviderPlugin

class MyPlugin(ContextProviderPlugin):
    @property
    def name(self) -> str:
        return "myplugin.context"

    @property
    def description(self) -> str:
        return "Provides custom context variables"

    def is_available(self) -> bool:
        """Check if plugin can run in current environment."""
        return True

    def get_context(self, scope) -> dict:
        """Provide template variables."""
        return {
            "my_var": "my_value",
            "git_branch": "main",
        }

pyproject.toml:

[project.entry-points."daimyo.plugins"]
"myplugin.context" = "my_plugin:MyPlugin"

Install and enable:

pip install -e .

Then add to config/settings.toml:

enabled_plugins = ["myplugin.*"]

Filter Provider Plugins

Provide custom Jinja2 filters and tests:

my_filters.py:

from daimyo.domain import FilterProviderPlugin
import os.path

class MyFiltersPlugin(FilterProviderPlugin):
    @property
    def name(self) -> str:
        return "myplugin.filters"

    @property
    def description(self) -> str:
        return "Provides custom Jinja2 filters and tests"

    def is_available(self) -> bool:
        return True

    def get_filters(self) -> dict:
        """Provide custom Jinja2 filters."""
        return {
            "uppercase": lambda s: s.upper(),
            "quote": lambda s: f'"{s}"',
        }

    def get_tests(self) -> dict:
        """Provide custom Jinja2 tests."""
        return {
            "file_exists": lambda path: os.path.exists(path),
            "git_repo": lambda path: os.path.exists(os.path.join(path, ".git")),
        }

Use in templates:

python.web:
  when: When writing Python web code
  ruleset:
    - Name must be {{ package_name | uppercase }}
    - |
      {% if "." is file_exists %}
      Include tests
      {% endif %}

Plugin Entry Points

Register plugins in pyproject.toml:

[project.entry-points."daimyo.plugins"]
"myplugin.context" = "my_plugin:MyPlugin"
"myplugin.filters" = "my_filters:MyFiltersPlugin"

Each plugin has its own entry point for independent discovery and enablement.

Rusuiyaku - Workspace-local representative of a daimyo instance

Rusuiyaku (留守居役, "caretaker") describes a daimyo instance that:

  • Runs from a local directory without its own rules
  • References a master daimyo server for shared rules via MASTER_SERVER_URL
  • May have project-specific plugins installed and enabled

This is a conceptual description, not a distinct mode - all daimyo instances use the same plugin discovery and enablement mechanism.

Development

Running Tests

pip install -e ".[dev]"
pytest
pytest --cov=daimyo

Code Quality

mypy daimyo
ruff check daimyo
ruff format daimyo

License

MIT

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

daimyo-1.5.0.tar.gz (71.3 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

daimyo-1.5.0-py3-none-any.whl (69.0 kB view details)

Uploaded Python 3

File details

Details for the file daimyo-1.5.0.tar.gz.

File metadata

  • Download URL: daimyo-1.5.0.tar.gz
  • Upload date:
  • Size: 71.3 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Rocky Linux","version":"9.6","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for daimyo-1.5.0.tar.gz
Algorithm Hash digest
SHA256 f6c43cbb455c8c2ce22c49947cdb0137df5a5e95d2563f72e67c2c5330d4611a
MD5 91d4c1d7d23f8c747e2073cdf2c428e0
BLAKE2b-256 390952f304c449e4c983a1197b05af49d3e1e9e19629143c0d4a2abb629f16bf

See more details on using hashes here.

File details

Details for the file daimyo-1.5.0-py3-none-any.whl.

File metadata

  • Download URL: daimyo-1.5.0-py3-none-any.whl
  • Upload date:
  • Size: 69.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.9.10 {"installer":{"name":"uv","version":"0.9.10"},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Rocky Linux","version":"9.6","id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for daimyo-1.5.0-py3-none-any.whl
Algorithm Hash digest
SHA256 054da2a9bb6a11fa6c2c8df78e7517846168f7d5ec7855e6cc76a63db1946a05
MD5 3d4dffbaac369a08573ae074d5ed8ee5
BLAKE2b-256 e36567b01d6235582a359eea7e9fb890ec9a219b87b66f76854a581b093dc710

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page