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").
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
¶
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
¶
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 matchingforbidalways wins.Action::"explicit_signoff"/Action::"one_click": consulted only onceinvokeis 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
forbidreferences 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
¶
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.