Skip to content

Public modules

Plain modules that are public on purpose: entry points, plugin targets, and optional-dependency modules whose symbols are intentionally not importable from the package root.

keelgate.cli

The keelgate command line: quickstart, eval run, eval list and replay.

Exit codes: 0 everything held, 1 a case failed or a regression was found, 2 bad usage or a configuration problem (so CI can tell "the code is worse" from "the job is misconfigured").

build_parser

build_parser() -> argparse.ArgumentParser

Build the keelgate argument parser.

main

main(argv: Sequence[str] | None = None) -> int

Run the keelgate command line and return the exit code (0 passed, 1 failed, 2 usage error).

keelgate.approvals.cli

Command-line approval client: keelgate-approvals.

Trust model: the CLI runs as a local operator with filesystem access to the queue database, and the operator names themself with --approver. That is the right model for a developer machine and a dev stack, and nothing more; remote or multi-user approval goes through the REST surface, which authenticates callers.

Evidence text comes from the model. It is printed with control characters stripped so it cannot drive the operator's terminal.

main

main(argv: Sequence[str] | None = None) -> int

Run the keelgate-approvals command line (list, show, approve, reject).

keelgate.approvals.rest

REST surface for the approval queue (pip install 'keelgate[server]').

Authentication is a static bearer-token table mapping each token to an :class:~keelgate.approvals._models.Approver. The approver's tenant comes from that table, never from the request, so a caller cannot name another tenant. This is deliberately minimal: real identity (OIDC, SSO) belongs to the Cloud control plane and is out of scope for K1.

The signoff_code is friction for human UIs, so an approver reads the evidence before typing it. It is not a cryptographic proof of reading. The real controls are the tier clearance, tenant scoping, separation of duties, binding to the exact action, single use, and the audit trail.

create_app

create_app(
    queue: ApprovalQueue, tokens: Mapping[str, Approver]
) -> FastAPI

Build the approvals API over queue, authenticating with tokens.

keelgate.policy.cedar

Cedar backend (pip install 'keelgate[cedar]'), behind the same PolicyEngine.

Cedar answers permit or deny; Keelgate needs ALLOW, DENY or REQUIRE_APPROVAL. The adapter maps three Cedar actions onto that:

  • Action::"invoke": must be permitted for the call to be allowed at all. Cedar is deny-by-default, and a matching forbid always wins.
  • Action::"explicit_signoff" / Action::"one_click": consulted only once invoke is permitted. A permit here turns ALLOW into REQUIRE_APPROVAL at that tier, strictest first.

Two Cedar behaviours the adapter exists to neutralise:

  • An erroring policy is skipped, not fatal. If a forbid references an attribute that is not there, Cedar ignores that policy and may still return Allow. The adapter treats any evaluation error as DENY.
  • Cedar has integers, not floats. Money amounts are rounded up to whole units, so a cap can only ever be hit early, never missed.

Reasons come from each policy's @id("...") annotation, which is author controlled; they never echo model-supplied text.

CedarEngine

Cedar policy engine (extra keelgate[cedar]) implementing a documented subset of finance_basic.

Fails closed, including on any evaluation error.

keelgate.loop.langgraph_store

A checkpoint store backed by a LangGraph checkpointer (pip install 'keelgate[langgraph]').

LangGraph's savers are usable on their own, outside a graph: each loop checkpoint becomes one LangGraph checkpoint on the thread <tenant>::<run>, with the loop state as the value of a single channel. That gives Keelgate LangGraph's persistence backends (SQLite, Postgres) without making the loop itself a LangGraph graph, so the core stays free of the dependency.

LangGraphCheckpointStore

Loop checkpoints on a LangGraph saver.

The stale-writer guard (save refuses a sequence that is not newer) is a read followed by a write, which LangGraph's saver API cannot make atomic by itself. Two locks make it atomic: a thread lock for threads of one process, and an optional process_lock for other processes. :meth:sqlite on a real file supplies one (a separate <path>.lock SQLite file held under BEGIN IMMEDIATE). With a saver you build yourself (for example Postgres) and no process_lock, the guard is thread-safe only; pass a lock that suits your backend, or use :class:~keelgate.loop._checkpoint.SqliteCheckpointStore.

history

history(tenant_id: str, run_id: str) -> list[LoopState]

Every checkpoint of a run, oldest first.

keelgate.testing.plugin

Pytest fixtures for projects built on Keelgate.

Installed automatically through the pytest11 entry point (pip install keelgate then run pytest); nothing to import or register. To opt out, run pytest -p no:keelgate.

Fixtures:

fake_llm A factory: fake_llm([Reply.call(...), Reply.say("done")], indexed=True) -> FakeLLM. keelgate_clock A :class:ManualClock to share with grants, audit and approvals. governed_harness A factory: governed_harness(tools=[...], engine=...) -> :class:GovernedHarness. static_policy A factory: static_policy({"my_tool": Decision.ALLOW}) -> :class:StaticPolicyEngine.

keelgate.telemetry.attributes

Span and metric attribute names.

The gen_ai.* names come from the OpenTelemetry GenAI semantic conventions (the incubating module of opentelemetry-semantic-conventions), so they cannot drift from the spec by typo. Those conventions are still marked development upstream; this package pins a minor version of the semantic-conventions distribution through opentelemetry-sdk.

The keelgate.* names are ours: tenancy, runs, policy, approvals and cost, none of which the GenAI conventions cover.

Nothing here ever names an attribute that carries tool arguments, tool output, prompts, or model text. Those are untrusted and may be sensitive; see Telemetry.capture_content for the one opt-in way they leave the process.