Skip to main content

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:

  1. Labels — cardinal.pr_url and/or cardinal.pr_number (number is derived from the URL when only the URL is set).

  2. Observed gh pr create — a shell tool_result is linked only when every gate passes (a missed PR beats a wrong one):

    • the originating command (request_data.arguments.command, or cmd; an argv list like ["bash", "-lc", "<script>"] is unwrapped), tokenized shell-aware, runs exactly one gh pr create (optionally behind VAR=value / env), and every other && / || / ; segment is a plain cd or git command. Pipes, redirects, subshells and $(…) are rejected, so grep "gh pr create" or gh 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: the cardinal.repo label, else the single remote in this session's git push (To <url>) or git remote -v output. No known repo means no link, so gh pr create --repo someone/fork is never trusted blind.

    Emits a boundary cardinal.git_state and rides later records only while the branch is unchanged; a sniffed git checkout -b / git switch clears it, even when a (now stale) cardinal.branch label keeps winning branch resolution.

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.py L401, reason=None in the composed ALLOW result).
    • Native harness hooks (omnigent/native/native_policy_hook.py, evaluation_response_to_hook_output L364) emit nothing for ALLOW on UserPromptSubmit (L418), and a PostToolUse result 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.py L2834-2835), whose payload is metadata (system_prompt_preview, counts), not the prompt.
    • ALLOW data does 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 reaches mcp__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.
  • No tool surface. FunctionPolicySpec (omnigent/spec/types.py L1381) has no tool field; policy_modules are scanned only for POLICY_REGISTRY callables. 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)

Source distribution for cardinal-omnigent-policy 0.4.0
File Size Uploaded
cardinal_omnigent_policy-0.4.0.tar.gz 47.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for cardinal-omnigent-policy 0.4.0
File Interpreter ABI Platform
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 log

Release 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

Release history Release notifications | RSS feed

This release

0.4.0 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page