Skip to main content

oiax — semantic policy routing for agent fleets. Delivers the right governance context to the right agent at the right turn, by meaning.

Project description

oiax

oiax — semantic policy routing for agent fleets. The tiller that delivers the right governance context to the right agent at the right turn, by meaning.

CI

oiax routes a free-text prompt against a governance corpus and delivers the policies that bear on that turn — by meaning, before the agent decides, at ~6ms with no network call.

The retrieval design is for normative text (policies, coding standards, ADRs, compliance rules), not general knowledge. Four decisions make it correct:

  1. Whole-document delivery, never chunks. A rule and its carve-out are semantically distant but logically inseparable.
  2. Precision over recall, asymmetric errors. A miss degrades to the status quo; a false positive actively degrades the layer.
  3. Surface names, never rules. Matched terms make a bad match dismissible at a glance.
  4. Runtime-agnostic core, harness-specific adapters. The router returns structured hits; each harness gets its own thin delivery layer.

Installation

pip install oiax

Requires Python ≥ 3.11. On first use, a ~90MB ONNX embedding model downloads and caches locally. Subsequent routes are ~6ms.

Quick start

Route a prompt

from oiax import build_index, route
from oiax.corpus import PolicyDirCorpus

# Load from a directory of markdown files with **Agent-trigger:** headers
# (the nous-ergon-ops policy directory convention)
corpus = PolicyDirCorpus("./policies/")
index = build_index(corpus)

# Route a prompt — returns hits sorted by score descending
hits = route("How do I deploy to production?", index)
for hit in hits:
    print(f"{hit.name} ({hit.score:.2f}): {', '.join(hit.why)}")

Route with query expansions

import json

expansions = json.load(open("./routing-expansions.json"))
index = build_index(corpus, expansions=expansions)
hits = route("help me merge my PR", index)

Use a custom corpus

from oiax.corpus import Document

class MyCorpus:
    def documents(self):
        yield Document(
            name="deploy-policy",
            trigger_line="deploying to production",
            body="Always run the test suite before deploying...",
        )

hits = route("deploy to prod", build_index(MyCorpus()))

Corpus format

Policy files are markdown with an **Agent-trigger:** header — a one-line statement of what the document governs. This is used for both lexical matching (TF-IDF) and semantic matching (embeddings).

# My deploy policy

**Agent-trigger:** deploying the application to production, CI/CD configuration

Always run the test suite before deploying. Never deploy on Friday.

The PolicyDirCorpus loader reads all *.md files in a directory. The filename (without .md) becomes the document name returned in route hits.

Claude Code integration

oiax.adapters.claude_code is a UserPromptSubmit hook adapter. Register it in ~/.claude/settings.json:

{
  "hooks": {
    "UserPromptSubmit": [
      {
        "matcher": "",
        "hooks": [{
          "type": "command",
          "command": "python3 -m oiax.adapters.claude_code /path/to/policies/ --expansions /path/to/expansions.json",
          "timeout": 8
        }]
      }
    ]
  }
}

On every prompt, the adapter routes the prompt text against the policy corpus and injects a context paragraph naming the policies that may apply — with the matched terms, so a bad match is dismissible at a glance. Never blocks: any error exits 0 silently.

Evaluation harness

Measure recall and precision against labelled ground truth:

python -m oiax.eval.route_eval score ./policies/ < labelled.jsonl

The labelled file is JSONL — one JSON object per line:

{"prompt": "How do I deploy to production?", "expected": ["deploy-policy"]}
{"prompt": "What's for lunch?", "expected": []}

A synthetic labelled corpus ships at oiax/eval/corpora/. Judge labels are evidence, not proof — hand-check a slice before treating the rate as authoritative.

API

oiax.router

Callable Signature Returns
route `route(prompt: str, index: Index None = None) -> list[RouteHit]`
build_index build_index(corpus: Corpus, *, expansions, lex_threshold, sem_threshold) -> Index Built index

RouteHit

@dataclass(frozen=True)
class RouteHit:
    name: str       # document name (surface name only, never body text)
    score: float    # [0, 1]
    why: list[str]  # matched terms/segments

oiax.corpus

Class Purpose
Document(name, trigger_line, body) One document in the routing corpus
Corpus (Protocol) Any object with .documents() -> Iterator[Document]
PolicyDirCorpus(path) Reads *.md files with **Agent-trigger:** headers

oiax.adapters

Module Purpose
claude_code.py UserPromptSubmit hook adapter
stdout.py Debug adapter — prints hits as text

When you need oiax

You need oiax when your rule corpus is too large to inject into every context (context-window pressure, attention dilution) and too important to leave to the agent's judgment (silent policy violations).

You do not need oiax when your corpus fits in a single CLAUDE.md — static injection is free and optimal for that case.

Development

git clone https://github.com/nousergon/oiax.git
cd oiax
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"

pytest                          # test suite
ruff check src/ tests/          # lint
mypy src/oiax                   # type check

All three run in CI on Python 3.11, 3.12 and 3.13 and are required to merge. See CONTRIBUTING.md.

License

AGPL-3.0 — see LICENSE.

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

oiax-0.1.2.tar.gz (31.6 kB view details)

Uploaded Source

Built Distribution

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

oiax-0.1.2-py3-none-any.whl (27.5 kB view details)

Uploaded Python 3

File details

Details for the file oiax-0.1.2.tar.gz.

File metadata

  • Download URL: oiax-0.1.2.tar.gz
  • Upload date:
  • Size: 31.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for oiax-0.1.2.tar.gz
Algorithm Hash digest
SHA256 9b2d6e819d1dc3cdcd30edec96946f8db21e42ed4c4992adfcc0898d25a5da22
MD5 2cc27524e018c0d1747ade1d113eb746
BLAKE2b-256 43850f16604fad471dfddb353ef4958ac5cc080fdb7b936c08a5b610c38ce075

See more details on using hashes here.

Provenance

The following attestation bundles were made for oiax-0.1.2.tar.gz:

Publisher: publish.yml on nousergon/oiax

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oiax-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: oiax-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 27.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for oiax-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 ae853577b18d5cd48cd21375973275d784bc1846411fe64bc554312880d320b7
MD5 bd7ef600571d418fcb66539ea874d9a1
BLAKE2b-256 33138a0e1239825496ee24a94fe02ebc59b2963bf488765694be236d98229c14

See more details on using hashes here.

Provenance

The following attestation bundles were made for oiax-0.1.2-py3-none-any.whl:

Publisher: publish.yml on nousergon/oiax

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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