cardinal-omnigent-policy
Cardinal policy modules for omnigent's policy engine. One
pip-installable package (cardinal-omnigent-policy) covers every harness
omnigent runs (Claude, Codex, Cursor, Hermes, Pi, OpenCode, custom YAML
agents) — unlike the four CLI adapters in this repo, cardinal-agent-core
is a real dependency here, not vendored, and the policies run inside the
omnigent server process.
Verified against omnigent commit 2b3b54a4
(cardinal_omnigent.OMNIGENT_VERIFIED_COMMIT); CI re-diffs the upstream
contract surface against that pin (build/omnigent_drift.py, the
"Omnigent contract drift" workflow). omnigent is alpha: every event-field
read goes through defensive accessors (_events.py), and the contract
must be re-verified on omnigent upgrades.
What it ships
| Module | Kind | Phases | Emits / enforces |
|---|---|---|---|
cardinal_omnigent.telemetry |
observe-only (never blocks; ALLOWs only to carry state_updates) |
request, llm_response, tool_call, tool_result, response | cardinal.git_state (labels convention + branch sniffing), api_request + cardinal.turn_usage (per-turn on the claude_sdk executor, usage_granularity="turn"), cardinal.turn_tool (core bashclass on shell tools), tool_result, cardinal.subagent_usage (sub-agent child conversations — see below) |
cardinal_omnigent.spend_limits |
enforcing | request | Cardinal server verdicts → DENY (block) / ALLOW + standing message with ack_band hysteresis (warn/notify); plus an omnigent-native per-session cap (session_cost_limit_usd) checked against context.usage.total_cost_usd |
Because request is in omnigent's FAIL_CLOSED_PHASES, spend limits here
are enforced, not suggested — if policy evaluation fails, the request
does not proceed. Our own internal errors deliberately abstain
(return None), never self-DENY: only a genuine server verdict or the
configured session cap denies (see the spend_limits module docstring).
Session identity (minted)
omnigent's policy contract carries no session or conversation id —
the event a callable receives is {type, target, data, context{actor, usage, user_daily_cost, model, harness, labels, subtree_usage}, session_state, llm_client, request_data}. Cardinal telemetry is
session-keyed, so the policies mint a cardinal.session_id into the
engine's per-conversation session_state via state_updates (durable
server-side; an in-process memo keyed by the engine-shared llm_client
keeps both policies on one id until it persists). See _identity.py.
Subagent mapping
cardinal.subagent_usage is emitted on the response phase of
conversations whose labels mark them as sub-agent children:
- claude-native / codex-native UI children carry
omnigent.wrapper(*-native-ui-subagent) plus id labels (omnigent.claude_native.subagent_id,omnigent.codex_native.subagent_thread_id/parent_thread_id/agent_nickname/agent_role) — stamped by the omnigent server. - native workflow / custom-YAML sub-agents get no upstream label:
stamp
cardinal.subagent: <name>in the sub-agent spec's guardrails labels (the Cardinal convention).
Emitted usage is the child conversation's cumulative context.usage
(usage_scope="session_cumulative" — downstream takes last-per-session,
not a sum) with model from the engine-injected context.model.
Cost attribution: cost_usd on usage events is the delta of the
server-maintained cumulative context.usage.total_cost_usd (anchored via
omnigent state_updates with an in-process mirror), falling back to
core pricing tables (anthropic / openai / gemini, core 0.3.0) keyed
on the model — platform-prefixed SKUs like databricks-claude-opus-4-8
re-resolve from the embedded claude- name. Unpriced models emit no
cost_usd rather than a misleading $0.
Registering with an omnigent server
The easy way — device-flow connect (mints the ingest credential, writes
the state dir, merges a cardinalManaged-tagged block into your server
config). Install into the interpreter that runs your omnigent server:
# venv
pip install cardinal-omnigent-policy
# pipx-installed omnigent (use --include-apps so the CLI lands on PATH)
pipx inject omnigent cardinal-omnigent-policy --include-apps
# then, from anywhere:
cardinal-omnigent-connect --config /etc/omnigent/config.yaml
cardinal-agent-core is resolved as a dependency. python3 -m cardinal_omnigent.connect still works as an alternative to the
cardinal-omnigent-connect console script.
Or by hand: policy_modules: names the module; omnigent scans it for
POLICY_REGISTRY (both policies take config by arity). The
cardinal: block is what the policies receive as config:
policy_modules:
- cardinal_omnigent
cardinal:
ingest_endpoint: "https://intake.us-east-2.aws.cardinalhq.io"
ingest_api_key: "..." # minted by connect / org admin
ingest_api_header: "x-cardinalhq-api-key"
deployment_environment: "prod"
org: "your-org-slug"
state_dir: "~/.omnigent" # server-writable; verdict/ack files
session_cost_limit_usd: 25.0 # optional per-session hard cap
service_name: "omnigent" # optional resource override
Factory registration (closure state) is also supported via omnigent's
function: {path, arguments} form with
cardinal_omnigent.make_telemetry_policy /
cardinal_omnigent.make_spend_limits_policy.
The labels convention (initiative attribution)
Policies never see the workspace — omnigent's policy contract carries no cwd, repo, or branch. Initiative attribution therefore rides session labels: have your agent specs / operator config stamp
labels:
cardinal.repo: "your-org/your-repo" # 'org/name'
cardinal.branch: "feat/outcomes-observability"
cardinal.branch runs through core resolve_initiative (one branch =
one initiative, <type-prefix>/<kebab-name>). Sessions without labels
attribute to initiative=None, type=research — same as protected-branch
sessions on the CLI adapters, so rollups stay honest. Consequently
cardinal.git_state here carries repo/branch/initiative facts, and PR
facts where a reliable signal exists (see PR linkage below), but not
head_sha/cwd/remote_url (structurally unavailable server-side).
Branch sniffing (enrichment): creation-time labels can't see a
mid-session git checkout -b. Shell tool_call events are watched for
branch creation/switch commands (checkout -b/-B, switch/switch -c);
a detected branch emits a fresh cardinal.git_state at the boundary
(marked cardinal_branch_source="tool_sniff") and is used at request
time when labels carry no branch. Labels always win when present.
Identity is per-event: user.email comes from
event.context.actor.run_as; cardinal.omnigent_harness (resource
attribute) from event.context.harness so downstream can slice by
underlying harness.
PR linkage (cardinal_pr_number / cardinal_pr_url)
The CLI adapters resolve the branch's PR with
cardinal_core.decisions.resolve_pr (gh pr view in the checkout). That
cannot work here: the policy event carries no cwd
(omnigent/policies/function.py::FunctionPolicy._build_event, unchanged
between the pin and upstream main cc83145e), and a session's
Conversation.workspace is a path on the runner's host — the host,
not the server, runs git (omnigent/server/routes/_host_worktree.py,
omnigent/host/git_worktree.py). Running gh in the server's own cwd
would attribute the wrong repo, so this package never shells out. PR
fields come only from:
-
Labels —
cardinal.pr_urland/orcardinal.pr_number(number is derived from the URL when only the URL is set). -
Observed
gh pr create— a shelltool_resultis linked only when every gate passes (a missed PR beats a wrong one):- the originating command (
request_data.arguments.command, orcmd; an argv list like["bash", "-lc", "<script>"]is unwrapped), tokenized shell-aware, runs exactly onegh pr create(optionally behindVAR=value/env), and every other&&/||/;segment is a plaincdorgitcommand. Pipes, redirects, subshells and$(…)are rejected, sogrep "gh pr create"orgh pr create; gh pr view <url>never link; - the output holds exactly one distinct
https://<host>/<owner>/<repo>/pull/<n>(gh prints it on success and on the "already exists" failure); <owner>/<repo>equals a known repo: thecardinal.repolabel, else the single remote in this session'sgit push(To <url>) orgit remote -voutput. No known repo means no link, sogh pr create --repo someone/forkis never trusted blind.
Emits a boundary
cardinal.git_stateand rides later records only while the branch is unchanged; a sniffedgit checkout -b/git switchclears it, even when a (now stale)cardinal.branchlabel keeps winning branch resolution. - the originating command (
Not covered: PRs opened outside the session (web UI, another machine);
unlabeled sessions that never ran git push / git remote -v; fork
workflows where the push remote differs from the PR's base repo; quoted
multi-line gh pr create bodies (rejected by the line splitter); gh
output the harness does not return in the tool result; and PR state after
an omnigent server restart (cached in-process only, like the sniffed
branch). The argv-list cmd shape is handled defensively but was not
confirmed against a captured Codex exec_command payload.
Known mis-attribution: the sniffed remote isn't scoped to a directory. In an
unlabeled session, cd /elsewhere && git remote -v (or
git -C /elsewhere push) followed by a gh pr create for that other repo
links the PR to this session's branch. On a labeled session, a PR opened
after a sniffed branch switch is recorded under the cardinal.branch label,
because the label wins branch resolution.
cardinal_head_sha is still not emitted — nothing in the policy contract
reports it reliably. Only cardinal_pr_number / cardinal_pr_url are
emitted, matching the other adapters.
Decision capture: unsupported
Decision capture (cardinal.decision, docs/specs/decision-telemetry.md)
is not supported on omnigent, because a policy has no channel to put
the capture prompt and ledger into the model's context, and cannot give
the agent a tool to record with. Line references are to upstream main
d25b9a23:
- No usable context channel.
- On ALLOW the engine discards
reason(omnigent/runtime/policies/engine.pyL401,reason=Nonein the composed ALLOW result). - Native harness hooks (
omnigent/native/native_policy_hook.py,evaluation_response_to_hook_outputL364) emit nothing for ALLOW onUserPromptSubmit(L418), and aPostToolUseresult reaches the model only as[Policy violation] <reason>on DENY (L461). - SDK executors act only on DENY from
PHASE_LLM_REQUEST(omnigent/inner/claude_sdk_executor.pyL2834-2835), whose payload is metadata (system_prompt_preview, counts), not the prompt. - ALLOW
datadoes replace content in two places: on tool-call arguments, and on TOOL_RESULT output the model sees — the server MCP relay (omnigent/server/routes/_sessions/orchestration.py,_handle_mcp_tools_call, L9837-9850) and sub-agent inbox output (omnigent/runner/tool_dispatch.py,_apply_subagent_policy_verdict, L7698-7711). That channel only reachesmcp__omnigent__*tool results and sub-agent inbox results — not native Bash output, user prompts, or the system prompt — so it cannot deliver a per-turn instruction reliably. And rewriting tool output would break this policy's observe-only rule (it must never alter or block the user's work); DENY-smuggling is ruled out for the same reason.
- On ALLOW the engine discards
- No tool surface.
FunctionPolicySpec(omnigent/spec/types.pyL1381) has no tool field;policy_modulesare scanned only forPOLICY_REGISTRYcallables. Tools come from the agent spec (ToolsConfig, L844), which this package does not own.
cardinal-decision is therefore not shipped here, and no
decisions.is_enabled toggle is read. Future path: open upstream PRs
omnigent-ai/omnigent#3353 and #2375 add per-turn context_providers to
the agent spec (additive system-instruction augmentation). That would
be an agent-owned integration configured per agent spec, not a policy
channel, so it would need a separate Cardinal component rather than a
change to this package.
Tests
cd adapters/omnigent
PYTHONPATH=../../core:. python3 -m unittest discover tests
(tests/fixtures.py also bootstraps sys.path for core and the adapter,
so a bare python3 -m unittest discover tests from this directory works
too.) The suite drives both policies against the core StubIngest harness
and asserts attribute coverage against the repo-root contract test's
REQUIRED_KEYS.
This adapter has no hooks/ directory on purpose: build/vendor.py
discovers vendoring targets by the presence of hooks/, and this package
must never be vendored — core is a pip dependency.
Metadata
Release files for cardinal-omnigent-policy 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| cardinal_omnigent_policy-0.4.0.tar.gz | 47.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| cardinal_omnigent_policy-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 83.6 kB
Release files / cardinal_omnigent_policy-0.4.0.tar.gz
| Download URL | cardinal_omnigent_policy-0.4.0.tar.gz |
|---|---|
| Size | 47.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d90242de63af33f59229cc7b5e590ff589db2cc6254696453f9814b4f4344855
|
|
BLAKE2b-256 checksum How to use checksums |
11b2c781cde7c7e7142d8cf16a475c0419c68303b618d5dafadc805d5851b672
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency logRelease files / cardinal_omnigent_policy-0.4.0-py3-none-any.whl
| Download URL | cardinal_omnigent_policy-0.4.0-py3-none-any.whl |
|---|---|
| Size | 35.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
fd61e963ac2580b46e93204a2f466b85dc45d7f130477666cff04a43ea48806d
|
|
BLAKE2b-256 checksum How to use checksums |
c0386c42625418231ab141058dffc74f797735eedadf6f8be48b8cb224fff076
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Sep 14, 2026.
Transparency log