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.
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:
- Whole-document delivery, never chunks. A rule and its carve-out are semantically distant but logically inseparable.
- Precision over recall, asymmetric errors. A miss degrades to the status quo; a false positive actively degrades the layer.
- Surface names, never rules. Matched terms make a bad match dismissible at a glance.
- 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.
For the full positioning, design rationale, and competitive landscape, see the positioning doc.
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file oiax-0.1.1.tar.gz.
File metadata
- Download URL: oiax-0.1.1.tar.gz
- Upload date:
- Size: 29.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
00624b530a16577552d5effc9ba9b3680c854310305822af82999a94da3e3a92
|
|
| MD5 |
02e7f3c9e22259eb425ca436e58e554d
|
|
| BLAKE2b-256 |
73b9154885aa0365dd41d0a81807428951f64ed7887399c79a284d3420d36210
|
Provenance
The following attestation bundles were made for oiax-0.1.1.tar.gz:
Publisher:
publish.yml on nousergon/oiax
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oiax-0.1.1.tar.gz -
Subject digest:
00624b530a16577552d5effc9ba9b3680c854310305822af82999a94da3e3a92 - Sigstore transparency entry: 2330179036
- Sigstore integration time:
-
Permalink:
nousergon/oiax@8b32d80d093d8a577f3540612e8c5b0143bcff46 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nousergon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8b32d80d093d8a577f3540612e8c5b0143bcff46 -
Trigger Event:
push
-
Statement type:
File details
Details for the file oiax-0.1.1-py3-none-any.whl.
File metadata
- Download URL: oiax-0.1.1-py3-none-any.whl
- Upload date:
- Size: 26.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
985d344a82019c35a84e00e4ea6713e2e939ac0551892d1b34ccca2f85ca7500
|
|
| MD5 |
5674f3aa9640c9905e17383f29e94a4c
|
|
| BLAKE2b-256 |
984fae44f2e9d9417361c66b769ae7b6ea26db5c9b253930a85ac8d9adede194
|
Provenance
The following attestation bundles were made for oiax-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on nousergon/oiax
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
oiax-0.1.1-py3-none-any.whl -
Subject digest:
985d344a82019c35a84e00e4ea6713e2e939ac0551892d1b34ccca2f85ca7500 - Sigstore transparency entry: 2330179294
- Sigstore integration time:
-
Permalink:
nousergon/oiax@8b32d80d093d8a577f3540612e8c5b0143bcff46 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/nousergon
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8b32d80d093d8a577f3540612e8c5b0143bcff46 -
Trigger Event:
push
-
Statement type: