Skip to content

Rule Client (Python SDK)

The Rule Client is a Python SDK that provides a typed, async wrapper over the Rule Repository REST API. It lives in packages/rule-client.

Installation

cd packages/rule-client
uv sync

Or add it as a dependency in your project:

uv add rulerepo --path /path/to/packages/rule-client

Initialization

from rulerepo import RuleClient

# As an async context manager (recommended)
async with RuleClient("http://localhost:8000", api_key="your-key") as client:
    status = await client.health()
    print(status)  # {"status": "ok"}

# Manual lifecycle
client = RuleClient("http://localhost:8000")
# ... use client ...
await client.close()

Constructor parameters:

Parameter Type Default Description
server_url str "http://localhost:8000" Base URL of the Rule Repository server.
api_key str or None None API key sent via X-API-Key header.
timeout float 30.0 Request timeout in seconds.

Resources

The client exposes seven resource groups: rules, search, intent, documents, contracts, transactions, and communications.

Rules (CRUD)

async with RuleClient("http://localhost:8000") as client:
    # Create a rule
    rule = await client.rules.create(
        statement="All API endpoints must return structured JSON errors.",
        modality="MUST",
        severity="HIGH",
        scope=["engineering/backend"],
        tags=["api", "error-handling"],
        rationale="Consistent error responses improve client integration.",
    )
    print(rule.id)

    # Get a rule by ID
    rule = await client.rules.get("a1b2c3d4-...")

    # List rules with filters
    result = await client.rules.list(page=1, page_size=10, severity="HIGH")
    for r in result.items:
        print(r.statement)

    # Update a rule
    updated = await client.rules.update(rule.id, statement="Updated statement...", revision_note="Clarified wording")

    # Retire a rule (soft-delete)
    retired = await client.rules.retire(rule.id)

Revisions and Relationships

    # Get revision history
    revisions = await client.rules.revisions(rule.id)
    for rev in revisions:
        print(rev.version, rev.changed_at)

    # Get relationships
    rels = await client.rules.relationships(rule.id)
    for rel in rels:
        print(rel.relationship_type, rel.target_id)

Five search modes are available:

    # Full-text search (BM25)
    result = await client.search.fulltext("overtime monthly limit")

    # Semantic vector search
    result = await client.search.vector("rules about working hours")

    # Hybrid search (BM25 + vector)
    result = await client.search.hybrid("overtime limit", scope="hr/attendance")

    # Category search (filter only, no free-text query)
    result = await client.search.category(modality="MUST", severity="CRITICAL")

All search methods accept optional keyword arguments for filtering (scope, modality, severity, tags) and pagination (page, page_size).

Intent

    # Ask a natural-language question
    result = await client.intent.ask("What are the rules for refunding orders over $500?")
    print(result.intent)       # e.g., "search_rules"
    print(result.result)       # intent-specific result data
    print(result.explanation)  # human-readable explanation

    # With optional context
    result = await client.intent.ask(
        "Can we deploy on Friday?",
        context={"team": "platform", "environment": "production"},
    )

Documents (Upload and Extraction)

    # Upload a document
    upload = await client.documents.upload("path/to/policy.pdf")
    print(upload.document_id)

    # Upload from bytes
    upload = await client.documents.upload(pdf_bytes, filename="policy.pdf")

    # Trigger rule extraction
    extraction = await client.documents.extract(upload.document_id)
    print(extraction.candidates)  # list of candidate rules

    # Get extraction results later
    extraction = await client.documents.get_extraction(extraction.extraction_id)

    # Review extraction: approve candidates by index
    result = await client.documents.review(
        extraction.extraction_id,
        approved_indices=[0, 2, 3],
    )
    print(result["rules_created"])

    # Review with edits
    result = await client.documents.review(
        extraction.extraction_id,
        edits={
            1: {
                "statement": "Edited rule statement...",
                "modality": "SHOULD",
                "severity": "MEDIUM",
            }
        },
    )

Contracts

    # List contract-applicable rules
    rules = await client.contracts.list_rules(contract_type="nda")

    # Search for contract rules
    results = await client.contracts.search("indemnity clause")

    # Evaluate a contract clause
    result = await client.contracts.evaluate(
        "The Receiving Party shall protect...",
        clause_type="confidentiality",
        parties=["Acme Corp", "Beta Inc"],
    )

Transactions

    # List transaction-applicable rules
    rules = await client.transactions.list_rules(transaction_type="expense")

    # Search for transaction rules
    results = await client.transactions.search("expense limit")

    # Evaluate a transaction
    result = await client.transactions.evaluate(
        {"amount_jpy": 30000, "category": "entertainment"},
        transaction_type="expense",
        actor_role="manager",
    )

Communications

    # List communication rules
    rules = await client.communications.list_rules(channel="email")

    # Search for communication rules
    results = await client.communications.search("PII disclosure")

    # Evaluate a message
    result = await client.communications.evaluate(
        "Dear Customer, please find attached...",
        channel="email",
        audience="external",
    )

Error Handling

The SDK raises typed exceptions mapped from HTTP status codes:

Exception HTTP Status When
NotFoundError 404 Rule, document, or extraction not found.
ValidationError 422 Request failed validation.
AuthenticationError 401 Missing or invalid API key.
AuthorizationError 403 Insufficient permissions.
ServerError 5xx Server-side error.
RuleRepoError other Base exception for any other non-2xx response.

All exceptions inherit from RuleRepoError and include message, status_code, and code attributes.

from rulerepo.errors import NotFoundError, RuleRepoError

try:
    rule = await client.rules.get("nonexistent-id")
except NotFoundError as e:
    print(f"Rule not found: {e.message}")
except RuleRepoError as e:
    print(f"API error ({e.status_code}): {e.message}")

Async Context Manager Pattern

The client uses httpx.AsyncClient internally. Always close the client when done, either via the context manager or by calling await client.close() explicitly:

# Recommended
async with RuleClient("http://localhost:8000") as client:
    rules = await client.rules.list()

# Also valid
client = RuleClient("http://localhost:8000")
try:
    rules = await client.rules.list()
finally:
    await client.close()