Skip to main content

Cardinal policy modules for omnigent's policy engine: agent-session telemetry + enforced spend limits.

Project description

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 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.

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.

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

cardinal_omnigent_policy-0.2.0.tar.gz (29.1 kB view details)

Uploaded Source

Built Distribution

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

cardinal_omnigent_policy-0.2.0-py3-none-any.whl (23.4 kB view details)

Uploaded Python 3

File details

Details for the file cardinal_omnigent_policy-0.2.0.tar.gz.

File metadata

  • Download URL: cardinal_omnigent_policy-0.2.0.tar.gz
  • Upload date:
  • Size: 29.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cardinal_omnigent_policy-0.2.0.tar.gz
Algorithm Hash digest
SHA256 08f78e81ae0e321683374e73a05870436686ab3378fa85d9ee01f60f3d667399
MD5 50b95eaed228d83130d66fbc36ba797b
BLAKE2b-256 ce1e599d9677edb4898e10c1a2c927f56727474a00939a89856362f9aeb3e1aa

See more details on using hashes here.

Provenance

The following attestation bundles were made for cardinal_omnigent_policy-0.2.0.tar.gz:

Publisher: release.yml on cardinalhq/cardinal-agent-plugins

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

File details

Details for the file cardinal_omnigent_policy-0.2.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cardinal_omnigent_policy-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5ba657ca3a8ce4a8d8a6086d38060f50ec291b1491dc6aebd99f0ac2ada2ebbe
MD5 867a7c8bbe4cba4af8eb4dc0aae2f4a7
BLAKE2b-256 c2048d0bb691a10a4b34eea52a587325a1923c6f7cf8d2419e4d88831f1590b6

See more details on using hashes here.

Provenance

The following attestation bundles were made for cardinal_omnigent_policy-0.2.0-py3-none-any.whl:

Publisher: release.yml on cardinalhq/cardinal-agent-plugins

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