Skip to content

API contract

Every public symbol of Keelgate, generated from the code. The public API is exactly what the modules below export; every other module is private (underscored) and every name not listed here is private whatever it is called. The narrative, the semantics and an end-to-end example are in the integration contract; the policy for changing anything on this page is in versioning.

stable: covered by semantic versioning; a breaking change needs a major bump (a minor bump while Keelgate is 0.x) and a migration note. provisional: works and is tested, but may change in a minor release with a changelog entry.

A change to this file in a pull request is a change to the public API.

Module Stable Provisional
keelgate.tools 21 1
keelgate.capabilities 23 2
keelgate.policy 14 0
keelgate.audit 11 1
keelgate.approvals 16 0
keelgate.loop 32 21
keelgate.context 25 0
keelgate.memory 13 8
keelgate.llm 14 0
keelgate.telemetry 12 9
keelgate.evals 6 21
keelgate.testing 4 4
keelgate.adapters 2 0
keelgate.llm.providers 0 5
keelgate.adapters.mcp 0 8
keelgate.adapters.langgraph 0 4
keelgate.adapters.openai_agents 0 3
keelgate.adapters.claude_agent_sdk 0 4
keelgate.adapters.a2a 0 10
keelgate.adapters.temporal 0 7
keelgate 1 0
keelgate.cli 0 2
keelgate.approvals.cli 0 1
keelgate.approvals.rest 0 1
keelgate.policy.cedar 0 1
keelgate.loop.langgraph_store 0 1
keelgate.testing.plugin 0 4
keelgate.telemetry.attributes 0 54
total 194 172

keelgate.tools

Tool registry and declaration, with each tool tagged by side effect.

Symbol Kind Stability Summary
CallContext class stable Per-call facts, split by trust.
Claim class stable The outcome of claiming an idempotency key.
ClaimState enum stable Where an idempotency key stands when a caller tries to claim it.
DirectInvocationError exception stable A tool was called without going through the gateway.
ErrorCode enum stable Why the gateway refused or failed a call.
IdempotencyStore protocol stable Where idempotency keys and stored results live.
InMemoryIdempotencyStore class stable Process-local idempotency store.
OutcomeStatus enum stable The four things a gateway call can end as.
PAPER_MODES constant stable
RegistryFrozenError exception stable A tool was registered after the registry was frozen.
SideEffect enum stable What a tool can do to the world.
SqliteIdempotencyStore class provisional Durable idempotency, safe to share between processes on one host.
Tool class stable A declared tool.
ToolDefinitionError exception stable The tool is declared unsafely or inconsistently.
ToolError model stable A refusal or failure a model can act on: a code, fixed-wording message and hint, and whether a retry could help.
ToolGateway class stable The single choke point every tool call passes through.
ToolOutcome class stable The structured result of one gateway call.
ToolRefusedError exception stable Raised by a tool body to say: I did not do anything, so a retry is safe.
ToolRegistry class stable The set of tools an agent may be offered.
ToolSpec class stable Everything the gateway knows about a tool.
Untrusted class stable Marks data that came from outside the harness.
tool function stable Declare a tool.
Signatures
class CallContext(tenant_id: str, policy_context: PolicyContext, rationale: str = '', sources: tuple[EvidenceSource, ...] = (), confidence: float | None = None, verifier_flags: tuple[str, ...] = (), approval_id: str | None = None, approval_ttl: timedelta | None = None, allowed_side_effects: frozenset[SideEffect] | None = None, call_id: str = ...) -> None
class Claim(state: ClaimState, output: dict[str, Any] | None = None) -> None
enum ClaimState: NEW='NEW', DONE='DONE', CONFLICT='CONFLICT', UNKNOWN='UNKNOWN', IN_FLIGHT='IN_FLIGHT'
exception DirectInvocationError(RuntimeError)(...)
enum ErrorCode: NOT_AUTHORISED='not_authorised', CAPABILITY_DENIED='capability_denied', UNKNOWN_TOOL='unknown_tool', INVALID_ARGUMENTS='invalid_arguments', EXECUTION_MODE_FORBIDDEN='execution_mode_forbidden', SIDE_EFFECT_NOT_PERMITTED='side_effect_not_permitted', BUDGET_EXCEEDED='budget_exceeded', POLICY_DENIED='policy_denied', POLICY_UNAVAILABLE='policy_unavailable', APPROVAL_UNAVAILABLE='approval_unavailable', APPROVAL_INVALID='approval_invalid', IDEMPOTENCY_CONFLICT='idempotency_conflict', OUTCOME_UNKNOWN='outcome_unknown', TOOL_TIMEOUT='tool_timeout', TOOL_FAILED='tool_failed', INVALID_TOOL_OUTPUT='invalid_tool_output', AUDIT_UNAVAILABLE='audit_unavailable', AUDIT_FAILED_AFTER_EXECUTION='audit_failed_after_execution', INTERNAL='internal_error'
protocol IdempotencyStore
    claim(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    complete(tenant_id: str, tool: str, key: str, output: dict[str, Any]) -> None
    mark_unknown(tenant_id: str, tool: str, key: str) -> None
    peek(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    release(tenant_id: str, tool: str, key: str) -> None
class InMemoryIdempotencyStore() -> None
    claim(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    complete(tenant_id: str, tool: str, key: str, output: dict[str, Any]) -> None
    mark_unknown(tenant_id: str, tool: str, key: str) -> None
    peek(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    release(tenant_id: str, tool: str, key: str) -> None
enum OutcomeStatus: OK='OK', DENIED='DENIED', APPROVAL_REQUIRED='APPROVAL_REQUIRED', ERROR='ERROR'
PAPER_MODES = frozenset({'paper', 'simulation'})
exception RegistryFrozenError(RuntimeError)(...)
enum SideEffect: READ='READ', PROPOSE='PROPOSE', WRITE='WRITE'
class SqliteIdempotencyStore(path: str | Path = ':memory:') -> None
    claim(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    close() -> None
    complete(tenant_id: str, tool: str, key: str, output: dict[str, Any]) -> None
    mark_unknown(tenant_id: str, tool: str, key: str) -> None
    peek(tenant_id: str, tool: str, key: str, args_hash: str) -> Claim
    release(tenant_id: str, tool: str, key: str) -> None
class Tool(spec: ToolSpec, fn: Callable[..., Any]) -> None
    property name
exception ToolDefinitionError(ValueError)(...)
model ToolError(*, code: ErrorCode, message: str, retryable: bool = False, hint: str = '', details: dict[str, Any] = ...) -> None
class ToolGateway(*, registry: ToolRegistry, verifier: GrantVerifier, engine: PolicyEngine, audit: AuditLog, approvals: ApprovalQueue | None = None, ledger: BudgetLedger | None = None, idempotency: IdempotencyStore | None = None, allowed_modes: frozenset[str] = frozenset({'paper', 'simulation'})) -> None
    async call(*, tool_name: str, arguments: Mapping[str, Any], grant_token: str, context: CallContext) -> ToolOutcome
class ToolOutcome(status: OutcomeStatus, tool: str, call_id: str, output: Untrusted[BaseModel] | None = None, error: ToolError | None = None, approval_id: str | None = None, approval_tier: ApprovalTier | None = None, policy: PolicyDecision | None = None, replayed: bool = False, audit_seq: int | None = None) -> None
    for_model() -> dict[str, Any]
    property ok
exception ToolRefusedError(Exception)(...)
class ToolRegistry() -> None
    describe() -> list[dict[str, Any]]
    freeze() -> None
    get(name: str) -> Tool | None
    names() -> list[str]
    register(tool_: Tool) -> Tool
class ToolSpec(name: str, description: str, input_model: type[BaseModel], output_model: type[BaseModel], capability: Capability, side_effect: SideEffect, timeout_s: float, cost_estimate: float, idempotency_key: IdempotencyKeyFn | None, resource: ResourceFn | None, input_schema: dict[str, Any] | None = None) -> None
class Untrusted(value: T) -> None
    unwrap_untrusted() -> T
def tool(*, capability: str, side_effect: SideEffect, name: str | None = None, description: str | None = None, timeout_s: float = 10.0, cost_estimate: float = 0.0, idempotency_key: IdempotencyKeyFn | None = None, resource: ResourceFn | None = None) -> Callable[[Callable[..., Any]], Tool]

keelgate.capabilities

Capability definitions and scoped, explicitly issued grants.

Symbol Kind Stability Summary
Budget model stable A spending ceiling, in whatever cost units the tools declare.
BudgetLedger protocol stable Where a grant's remaining budget is tracked.
Capability class stable A validated capability string.
CapabilityGrant model stable The verified claims of a grant.
DEFAULT_MAX_TTL constant stable
GrantError exception stable Base class: the presented grant cannot be used.
GrantExpiredError exception stable The grant's lifetime has ended.
GrantInvalidError exception stable Malformed, wrongly signed, wrong purpose, or otherwise untrustworthy.
GrantNotYetValidError exception stable The grant's not-before time has not arrived.
GrantRevokedError exception stable The grant was revoked after it was issued.
GrantSigner class stable Holds the Ed25519 private key and signs grants.
GrantVerifier class stable Verifies grant tokens against a set of trusted public keys.
InMemoryBudgetLedger class stable Atomic per-grant spend tracking.
InMemoryRevocationList class stable Process-local revocations.
InvalidCapabilityError exception stable Raised for a malformed capability string, including any wildcard.
MARKET_DATA_READ constant stable
REPORT_WRITE constant stable
RevocationList protocol stable Where revoked grant ids are recorded.
SignedGrant class stable A freshly issued grant: the bearer token and its parsed grant.
SqliteBudgetLedger class provisional Atomic per-grant spend, persisted.
SqliteRevocationList class provisional Persisted revocations.
TRADE_PAPER_EXECUTE constant stable
TRADE_PROPOSE constant stable
UnknownKeyError exception stable Signed by a key the verifier does not trust.
issue_grant function stable Mint a grant.
Signatures
model Budget(*, max_cost: Annotated[float, Ge(ge=0), _PydanticGeneralMetadata(allow_inf_nan=False)]) -> None
protocol BudgetLedger
    release(grant_id: str, amount: float) -> None
    spent(grant_id: str) -> float
    try_reserve(grant_id: str, amount: float, limit: float) -> bool
class Capability(str)(value: object) -> Capability
    property action
    property resource
model CapabilityGrant(*, grant_id: str, issuer: str, key_id: str, agent_id: str, tenant_id: str, capabilities: frozenset[Capability], budget: Budget, issued_at: datetime.datetime, not_before: datetime.datetime, expires_at: datetime.datetime) -> None
    allows(capability: str) -> bool
DEFAULT_MAX_TTL = datetime.timedelta(days=1)
exception GrantError(Exception)(...)
exception GrantExpiredError(GrantError)(...)
exception GrantInvalidError(GrantError)(...)
exception GrantNotYetValidError(GrantError)(...)
exception GrantRevokedError(GrantError)(...)
class GrantSigner(private_key_pem: bytes, *, key_id: str, issuer: str = 'keelgate') -> None
    classmethod generate(*, key_id: str = 'k1', issuer: str = 'keelgate') -> GrantSigner
    public_key_pem() -> bytes
    sign(claims: Mapping[str, Any]) -> str
class GrantVerifier(trusted_keys: Mapping[str, bytes], *, clock: Clock = ..., revocations: RevocationList | None = None, max_ttl: timedelta = ..., leeway: timedelta = ...) -> None
    verify(token: str) -> CapabilityGrant
class InMemoryBudgetLedger() -> None
    release(grant_id: str, amount: float) -> None
    spent(grant_id: str) -> float
    try_reserve(grant_id: str, amount: float, limit: float) -> bool
class InMemoryRevocationList() -> None
    is_revoked(grant_id: str) -> bool
    revoke(grant_id: str) -> None
exception InvalidCapabilityError(ValueError)(...)
MARKET_DATA_READ = 'market_data:read'
REPORT_WRITE = 'report:write'
protocol RevocationList
    is_revoked(grant_id: str) -> bool
class SignedGrant(grant: CapabilityGrant, token: str) -> None
class SqliteBudgetLedger(path: str | Path = ':memory:') -> None
    close() -> None
    release(grant_id: str, amount: float) -> None
    spent(grant_id: str) -> float
    try_reserve(grant_id: str, amount: float, limit: float) -> bool
class SqliteRevocationList(path: str | Path = ':memory:') -> None
    close() -> None
    is_revoked(grant_id: str) -> bool
    revoke(grant_id: str) -> None
TRADE_PAPER_EXECUTE = 'trade:paper_execute'
TRADE_PROPOSE = 'trade:propose'
exception UnknownKeyError(GrantError)(...)
def issue_grant(signer: GrantSigner, *, agent_id: str, tenant_id: str, capabilities: Iterable[str], max_cost: float, ttl: timedelta, clock: Clock = ..., not_before: datetime | None = None, max_ttl: timedelta = ...) -> SignedGrant

keelgate.policy

Policy-as-code decision point.

Symbol Kind Stability Summary
Decision enum stable What a policy engine decides: ALLOW, DENY or REQUIRE_APPROVAL.
OpaHttpEngine class stable Policy engine that asks an OPA server over HTTP (the production default).
PolicyAction model stable The proposed action a policy judges: the tool, its side effect and capability, and its arguments.
PolicyActor model stable Who is acting, as verified from the grant: the agent, the tenant and the grant id.
PolicyContext model stable Trusted facts about the world at as_of.
PolicyDecision model stable What the policy decided, and exactly which policy decided it.
PolicyEngine protocol stable Decides ALLOW / DENY / REQUIRE_APPROVAL for one proposed action.
PolicyInput model stable Everything a policy engine is given: the action, the actor, the resource facts derived from validated arguments, and the trusted context.
RegoEngine class stable Policy engine that evaluates the same Rego packs in-process.
decision_from_result function stable Turn an engine's raw result into a decision, failing closed.
deny function stable A fail-closed denial.
hash_sources function stable A stable version hash over named policy sources.
load_pack_sources function stable Read a pack's .rego sources, excluding *_test.rego.
pack_path function stable Locate a shipped policy pack.
Signatures
enum Decision: ALLOW='ALLOW', DENY='DENY', REQUIRE_APPROVAL='REQUIRE_APPROVAL'
class OpaHttpEngine(base_url: str, *, decision_path: str = 'keelgate/finance_basic/decision', pack_name: str = 'finance_basic', timeout: float = 2.0, bearer_token: str | None = None, client: httpx.AsyncClient | None = None, version_ttl: float = 5.0, monotonic: Callable[[], float] = ...) -> None
    async aclose() -> None
    async decide(policy_input: PolicyInput) -> PolicyDecision
    async decide_document(document: Mapping[str, Any]) -> PolicyDecision
    async policy_version() -> str
model PolicyAction(*, tool: str, side_effect: Literal['READ', 'PROPOSE', 'WRITE'], capability: str, args: dict[str, Any] = ...) -> None
model PolicyActor(*, agent_id: str, tenant_id: str, grant_id: str) -> None
model PolicyContext(*, as_of: datetime.datetime, execution_mode: str = 'paper', positions: dict[str, float] = ..., limits: dict[str, Any] = ..., exposure: dict[str, float] = ...) -> None
model PolicyDecision(*, effect: Decision, reasons: tuple[str, ...] = (), approval_tier: ApprovalTier | None = None, policy_version: str, engine: str) -> None
    property allowed
protocol PolicyEngine
    async decide(policy_input: PolicyInput) -> PolicyDecision
model PolicyInput(*, action: PolicyAction, actor: PolicyActor, resource: dict[str, Any] = ..., context: PolicyContext) -> None
    to_document() -> dict[str, Any]
class RegoEngine(pack_dir: Path | None = None, *, query: str = 'data.keelgate.finance_basic.decision') -> None
    async decide(policy_input: PolicyInput) -> PolicyDecision
    async decide_document(document: Mapping[str, Any]) -> PolicyDecision
def decision_from_result(raw: object, *, policy_version: str, engine: str) -> PolicyDecision
def deny(reason: str, *, engine: str, policy_version: str = 'unavailable') -> PolicyDecision
def hash_sources(sources: Mapping[str, str]) -> str
def load_pack_sources(directory: Path) -> dict[str, str]
def pack_path(name: str) -> Path

keelgate.audit

Tamper-evident, hash-chained audit log and its verifier.

Symbol Kind Stability Summary
AuditError exception stable The audit log could not be written or read correctly.
AuditLog class stable Append-only, hash-chained, per-tenant audit log.
AuditRecord model stable One hash-chained entry in a tenant's audit log: sequence, time, event type, actor, payload and the hashes that link it to the one before.
AuditStore protocol stable Where audit records are persisted (SQLite and Postgres ship).
ChainHead class stable The newest record of a tenant's chain.
ChainVerification class stable The result of verifying one tenant's audit chain.
EventType enum stable Event kinds Keelgate itself emits.
PostgresAuditStore class provisional Shared audit store for multi-process deployments.
SqliteAuditStore class stable Local, single-file audit store.
canonical_json function stable Deterministic JSON: sorted keys, no whitespace, ASCII, no NaN or infinity.
genesis_hash function stable The prev_hash of a tenant's first record.
verify_chain function stable Verify one tenant's records, in order, from genesis.
Signatures
exception AuditError(Exception)(...)
class AuditLog(store: AuditStore | None = None, *, clock: Clock = ...) -> None
    append(*, tenant_id: str, event_type: EventType | str, actor: str, payload: object) -> AuditRecord
    close() -> None
    head(tenant_id: str) -> ChainHead | None
    records(tenant_id: str, *, after_seq: int = 0) -> Iterator[AuditRecord]
    verify_chain(tenant_id: str, *, expected_head: ChainHead | None = None) -> ChainVerification
model AuditRecord(*, tenant_id: Annotated[str, MinLen(min_length=1)], seq: Annotated[int, Ge(ge=1)], timestamp: str, event_type: Annotated[str, MinLen(min_length=1)], actor: str, payload_json: str, prev_hash: str, hash: str) -> None
    property payload
protocol AuditStore
    append(tenant_id: str, build: BuildRecord) -> AuditRecord
    close() -> None
    head(tenant_id: str) -> ChainHead | None
    iter_records(tenant_id: str, *, after_seq: int = 0) -> Iterator[AuditRecord]
class ChainHead(tenant_id: str, seq: int, hash: str) -> None
class ChainVerification(ok: bool, tenant_id: str, records_checked: int, head: ChainHead | None, bad_seq: int | None = None, error: str | None = None) -> None
enum EventType: TOOL_CALL='tool.call', GRANT_REJECTED='grant.rejected', POLICY_DECISION='policy.decision', APPROVAL_REQUESTED='approval.requested', APPROVAL_DECIDED='approval.decided', APPROVAL_CONSUMED='approval.consumed', TOOL_RESULT='tool.result', TOOL_ERROR='tool.error', TOOL_REPLAY='tool.replay', LOOP_TRANSITION='loop.transition', MEMORY_WRITE='memory.write', CONTEXT_REJECTED='context.rejected', OUTCOME_RECONCILED='loop.reconciled'
class PostgresAuditStore(dsn: str) -> None
    append(tenant_id: str, build: BuildRecord) -> AuditRecord
    close() -> None
    head(tenant_id: str) -> ChainHead | None
    iter_records(tenant_id: str, *, after_seq: int = 0) -> Iterator[AuditRecord]
class SqliteAuditStore(path: str | Path = ':memory:') -> None
    append(tenant_id: str, build: BuildRecord) -> AuditRecord
    close() -> None
    head(tenant_id: str) -> ChainHead | None
    iter_records(tenant_id: str, *, after_seq: int = 0) -> Iterator[AuditRecord]
def canonical_json(value: object) -> str
def genesis_hash(tenant_id: str) -> str
def verify_chain(records: Iterable[AuditRecord], *, tenant_id: str, expected_head: ChainHead | None = None) -> ChainVerification

keelgate.approvals

Human-in-the-loop approval requests and the queue that serves them.

Symbol Kind Stability Summary
ApprovalError exception stable Base class.
ApprovalExpiredError exception stable The approval request expired before it was decided or used.
ApprovalNotAuthorisedError exception stable The approver may not decide this request: wrong tenant, tier too low, or the requester itself.
ApprovalNotFoundError exception stable No such request for this tenant.
ApprovalNotPendingError exception stable The request was already decided.
ApprovalNotUsableError exception stable The request exists but cannot back this execution (wrong state or action).
ApprovalQueue class stable The human-in-the-loop approval queue (SQLite-backed).
ApprovalRequest model stable A call parked for a human: the exact arguments' hash, the tier required, the evidence bundle, and the request's status and decision.
ApprovalSignoffError exception stable An explicit sign-off was required and the code supplied was missing or wrong.
ApprovalStatus enum stable Where an approval request is: pending, approved, rejected, expired or consumed.
ApprovalTier enum stable How much human attention an action needs.
Approver model stable A human cleared to decide requests in one tenant up to max_tier.
EvidenceBundle model stable What an approver needs to decide, and what their decision is bound to.
EvidenceSource model stable A source cited in an approval's evidence: where it came from, when it was retrieved, and a hash of its content.
action_hash function stable Hash of exactly what will run.
sanitize_for_display function stable Strip control characters so model-derived text cannot drive a terminal.
Signatures
exception ApprovalError(Exception)(...)
exception ApprovalExpiredError(ApprovalError)(...)
exception ApprovalNotAuthorisedError(ApprovalError)(...)
exception ApprovalNotFoundError(ApprovalError)(...)
exception ApprovalNotPendingError(ApprovalError)(...)
exception ApprovalNotUsableError(ApprovalError)(...)
class ApprovalQueue(path: str | Path = ':memory:', *, audit: AuditLog | None = None, clock: Clock = ..., default_ttl: timedelta = ...) -> None
    approve(tenant_id: str, request_id: str, approver: Approver, *, signoff_code: str | None = None, note: str | None = None) -> ApprovalRequest
    close() -> None
    consume(*, tenant_id: str, request_id: str, agent_id: str, tool_name: str, args_hash: str) -> ApprovalRequest
    get(tenant_id: str, request_id: str) -> ApprovalRequest
    list_pending(tenant_id: str) -> list[ApprovalRequest]
    reject(tenant_id: str, request_id: str, approver: Approver, *, note: str | None = None) -> ApprovalRequest
    submit(*, tenant_id: str, agent_id: str, tool_name: str, args_hash: str, tier: ApprovalTier, evidence: EvidenceBundle, ttl: timedelta | None = None) -> ApprovalRequest
model ApprovalRequest(*, request_id: str, tenant_id: str, agent_id: str, tool_name: str, args_hash: str, tier: ApprovalTier, evidence: EvidenceBundle, evidence_hash: str, status: ApprovalStatus, created_at: datetime.datetime, expires_at: datetime.datetime, decided_by: str | None = None, decided_at: datetime.datetime | None = None, decision_note: str | None = None) -> None
    property signoff_code
exception ApprovalSignoffError(ApprovalError)(...)
enum ApprovalStatus: PENDING='PENDING', APPROVED='APPROVED', REJECTED='REJECTED', EXPIRED='EXPIRED', CONSUMED='CONSUMED'
enum ApprovalTier: AUTO='AUTO', ONE_CLICK='ONE_CLICK', EXPLICIT_SIGNOFF='EXPLICIT_SIGNOFF'
model Approver(*, approver_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=128)], tenant_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=128)], max_tier: ApprovalTier = <ApprovalTier.ONE_CLICK: 'ONE_CLICK'>) -> None
model EvidenceBundle(*, tool: str, args: dict[str, Any], rationale: Annotated[str, MaxLen(max_length=4000)], sources: tuple[EvidenceSource, ...] = (), confidence: Annotated[float | None, Ge(ge=0), Le(le=1), _PydanticGeneralMetadata(allow_inf_nan=False)] = None, verifier_flags: tuple[str, ...] = (), policy_reasons: tuple[str, ...] = (), policy_version: str = '') -> None
    digest() -> str
model EvidenceSource(*, uri: Annotated[str, MaxLen(max_length=2048)], title: Annotated[str | None, MaxLen(max_length=300)] = None, retrieved_at: datetime.datetime | None = None, sha256: Annotated[str | None, _PydanticGeneralMetadata(pattern='^[0-9a-f]{64}$')] = None) -> None
def action_hash(tool: str, args: dict[str, Any]) -> str
def sanitize_for_display(text: str) -> str

keelgate.loop

Durable, budgeted agent loop with explicit stop conditions and resume.

Symbol Kind Stability Summary
AcceptAllVerifier class stable Accepts everything.
ActionOutcome model stable What happened to one planned action: status, error code and fixed-wording detail, and the tool output as untrusted JSON text.
ActionStatus enum stable Where a planned action stands: pending, done, denied, error, awaiting approval, unknown (may or may not have happened) or abandoned.
CallableVerifier class stable Wraps a plain function (sync or async) returning a Verdict, a decision or a bool.
CheckpointStore protocol stable Where loop state is saved after every step.
ContextSource protocol stable Supplies extra context for a plan step, such as retrieved memory.
DEFAULT_SYSTEM_PROMPT constant stable
Divergence class provisional One difference between a recorded run and its replay: where, what was expected and what was seen.
GoalPredicate type alias stable
GroundedAnswerVerifier class provisional A deterministic verifier: is the final answer supported by what the tools returned?
HistoryCheckpointStore protocol provisional A store that keeps every checkpoint, oldest first.
InMemoryCheckpointStore class stable Process-local.
InProcessRunner class provisional Runs the loop in the calling process.
LLMPlanner class stable Plans by function calling: tool calls are actions, plain text is a final answer.
LLMVerifier class stable Asks a model for a JSON verdict.
Loop class stable The durable agent loop: plan, act, observe, verify, then revise or stop.
LoopOutcome class provisional The serialisable result of a run.
LoopResult class stable What a Loop run or resume returns.
LoopRunner protocol provisional Starts a run, or resumes it if a checkpoint exists.
LoopSpec class provisional Everything needed to start or resume one run, as plain data.
LoopState model stable Everything a run needs to resume: the goal, the phase, the planned actions and their outcomes, usage, and the trace identity.
LoopType enum stable The kind of run: a TASK, a read-only VERIFICATION, or a MONITOR tick.
MonitorLoop class provisional Runs a bounded loop on a schedule, finishing a half-done tick after a restart.
MonitorSummary class provisional The outcome of a bounded monitoring session: how many ticks ran and each tick's result.
NotReplayableError exception provisional The recording lacks a recorded result for some action, or the run cannot be found.
OutcomeConfirmer protocol provisional Settles an action whose outcome is unknown by asking the downstream system of record.
Phase enum stable Where a run is in its cycle: plan, act, observe, verify, or done.
Plan class stable What the planner proposed: actions to run, or a final answer, never both.
PlanRequest class stable What the planner is given for one planning call.
PlannedAction model stable One tool call the planner chose, written down before it runs so a resume re-submits the same arguments.
Planner protocol stable Proposes the next step: tool calls to run, or a final answer.
ProposedAction class stable One tool call the planner proposes: a tool name, untrusted arguments and a rationale.
READ_ONLY constant provisional
RecordedAction class provisional One tool call as recorded in a run: name, arguments, final status and outcome.
RecordedStep class provisional One iteration of a recorded run: the actions proposed, the final answer if any and the verdict.
Recording class provisional A run rebuilt from its checkpoint history, ready to replay.
ReplayReport class provisional The result of a replay: the original and replayed recordings and every divergence.
RunExistsError exception stable run was asked to start a run id that already has a checkpoint.
RunNotFoundError exception stable resume found no checkpoint for that tenant and run id.
Schedule class provisional A fixed interval between monitoring ticks.
SqliteCheckpointStore class stable Durable, dependency-free.
StaleCheckpointError exception stable A newer checkpoint already exists: another writer got there first.
StopConditions class stable Limits for one run.
StopReason enum stable Why a run stopped: the goal was reached, a limit was hit (iterations, tokens, dollars, time), the verifier rejected too often, an outcome is unknown, an approval is pending, or an error.
Verdict class stable A verifier's judgement of a proposed final answer.
VerdictDecision enum stable A verifier's answer: ACCEPT, REVISE or REJECT.
VerificationLoop class provisional A loop that can only read.
Verifier protocol stable Judges a proposed final answer.
VerifyRequest class stable What the verifier is given.
default_checkpointer function stable A durable store: LangGraph's SQLite saver if installed, else the standard-library one.
diff function provisional Compare two recordings step by step and return every divergence (empty when they match).
find_run function provisional The run id whose trace id is trace_id within one tenant (never across tenants).
replay async function provisional Replay a recording through a fresh loop and report any divergence.
Signatures
class AcceptAllVerifier()
    async verify(request: VerifyRequest) -> Verdict
model ActionOutcome(*, status: str, error_code: str | None = None, message: str = '', hint: str = '', details: dict[str, Any] = ..., output_json: str | None = None, published_at: datetime.datetime | None = None, replayed: bool = False) -> None
enum ActionStatus: PENDING='pending', DONE='done', DENIED='denied', ERROR='error', AWAITING_APPROVAL='awaiting_approval', UNKNOWN='unknown', ABANDONED='abandoned'
class CallableVerifier(fn: VerifierFn) -> None
    async verify(request: VerifyRequest) -> Verdict
protocol CheckpointStore
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None
protocol ContextSource
    retrieve(state: LoopState) -> Sequence[ContextItem]
DEFAULT_SYSTEM_PROMPT = str(...)
class Divergence(where: str, expected: Any, actual: Any) -> None
type GoalPredicate = collections.abc.Callable[['LoopState'], bool]
class GroundedAnswerVerifier(*, write_tools: Iterable[str] | None = None) -> None
    async verify(request: VerifyRequest) -> Verdict
protocol HistoryCheckpointStore
    history(tenant_id: str, run_id: str) -> list[LoopState]
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None
class InMemoryCheckpointStore() -> None
    history(tenant_id: str, run_id: str) -> list[LoopState]
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None
class InProcessRunner(loop: Loop) -> None
    async run(spec: LoopSpec) -> LoopOutcome
class LLMPlanner(llm: LLMClient, model: str, *, max_tokens: int = 1024, max_actions: int = 8, temperature: float | None = None) -> None
    async plan(request: PlanRequest) -> Plan
class LLMVerifier(llm: LLMClient, model: str, *, max_tokens: int = 300) -> None
    async verify(request: VerifyRequest) -> Verdict
class Loop(*, gateway: ToolCaller, registry: ToolRegistry, planner: Planner, checkpoints: CheckpointStore, grant_token: str | Callable[[], str], policy_context: Callable[[datetime], PolicyContext], stop: StopConditions | None = None, verifier: Verifier | None = None, audit: AuditLog | None = None, approvals: ApprovalQueue | None = None, episodic: EpisodicMemory | None = None, context_sources: Sequence[ContextSource] = (), context_budget: int = 8000, system_prompt: str = 'You are an agent working toward the goal below. Use the offered tools to gather information and to act. When the goal is met, reply with a final answer in plain text and no tool call. Some actions need human approval or may be refused; if one is, do not try another route to the same effect.', counter: TokenCounter | None = None, pricing: PricingTable | None = None, price_model: str = '', confirmer: OutcomeConfirmer | None = None, summarizer: Summarizer | None = None, clock: Callable[[], datetime] = ..., allowed_side_effects: frozenset[SideEffect] | None = None, loop_type: LoopType = <LoopType.TASK: 'task'>, failpoint: Callable[[str], None] | None = None) -> None
    async reconcile(tenant_id: str, run_id: str, action_id: str, *, executed: bool, by: str, note: str = '') -> LoopState
    async resume(tenant_id: str, run_id: str, *, stop: StopConditions | None = None) -> LoopResult
    async run(*, goal: str, tenant_id: str, agent_id: str, as_of: datetime, run_id: str | None = None, trace_id: str | None = None, stop: StopConditions | None = None) -> LoopResult
    async run_or_resume(*, goal: str, tenant_id: str, agent_id: str, as_of: datetime, run_id: str, trace_id: str | None = None, stop: StopConditions | None = None) -> LoopResult
class LoopOutcome(run_id: str, stop_reason: str | None, final_answer: str | None, iterations: int, ok: bool, resumable: bool) -> None
    classmethod of(run_id: str, result: LoopResult) -> LoopOutcome
class LoopResult(state: LoopState) -> None
    property final_answer
    property ok
    property resumable
    property stop_reason
protocol LoopRunner
    async run(spec: LoopSpec) -> LoopOutcome
class LoopSpec(goal: str, tenant_id: str, agent_id: str, as_of: datetime, run_id: str, trace_id: str | None = None, max_iterations: int | None = 10, max_tokens: int | None = None, max_dollars: float | None = None, timeout_seconds: float | None = None, max_verifier_rejections: int | None = 3) -> None
    stop_conditions() -> StopConditions
model LoopState(*, state_version: int = 1, run_id: str, tenant_id: str, agent_id: str, trace_id: str, root_span_id: str = '', loop_type: LoopType = <LoopType.TASK: 'task'>, goal: str, as_of: datetime.datetime, phase: Phase = <Phase.PLAN: 'plan'>, iteration: int = 0, actions: list[PlannedAction] = ..., pending_final_answer: str | None = None, observations: list[ContextItem] = ..., verdicts: list[VerdictRecord] = ..., llm_calls: int = 0, plan_calls: int = 0, tokens_used: int = 0, dollars_used: float = 0.0, cost_unknown: bool = False, rejections: int = 0, context_rejections: int = 0, context_rejected_ids: list[str] = ..., active_seconds: float = 0.0, stop_reason: StopReason | None = None, final_answer: str | None = None, error: str | None = None, created_at: datetime.datetime, updated_at: datetime.datetime, checkpoint_seq: int = 0) -> None
    action(action_id: str) -> PlannedAction
    property finished
enum LoopType: TASK='task', VERIFICATION='verification', MONITOR='monitor'
class MonitorLoop(*, monitor_id: str, goal: str, loop_factory: Callable[..., Loop], schedule: Schedule, tenant_id: str, agent_id: str, as_of_for: Callable[[datetime], datetime] | None = None, tick_stop: StopConditions | None = None, allow_writes: bool = False, clock: Callable[[], datetime] | None = None, sleep: Callable[[float], Awaitable[None]] = ...) -> None
    async run_ticks(count: int) -> MonitorSummary
    async tick() -> LoopResult
    property read_only
class MonitorSummary(ticks_run: int, results: tuple[LoopResult, ...]) -> None
exception NotReplayableError(Exception)(...)
protocol OutcomeConfirmer
    async confirm(action: PlannedAction, state: LoopState) -> bool | None
enum Phase: PLAN='plan', ACT='act', OBSERVE='observe', VERIFY='verify', DONE='done'
class Plan(actions: tuple[ProposedAction, ...] = (), final_answer: str | None = None, usage: Usage = ..., model: str = '', text: str = '') -> None
class PlanRequest(run_id: str, tenant_id: str, iteration: int, call_index: int, messages: tuple[Message, ...], tools: tuple[ToolSchema, ...]) -> None
model PlannedAction(*, action_id: str, tool: str, arguments: dict[str, Any] = ..., rationale: str = '', status: ActionStatus = <ActionStatus.PENDING: 'pending'>, approval_id: str | None = None, outcome: ActionOutcome | None = None) -> None
protocol Planner
    async plan(request: PlanRequest) -> Plan
class ProposedAction(tool: str, arguments: dict[str, Any] = ..., rationale: str = '') -> None
READ_ONLY = frozenset({<SideEffect.READ: 'READ'>})
class RecordedAction(tool: str, arguments: dict[str, Any], rationale: str, status: str, outcome: ActionOutcome | None) -> None
class RecordedStep(iteration: int, actions: tuple[RecordedAction, ...], final_answer: str | None, verdict: tuple[str, tuple[str, ...], tuple[str, ...]] | None) -> None
class Recording(tenant_id: str, run_id: str, trace_id: str, agent_id: str, goal: str, as_of: datetime, loop_type: LoopType, steps: tuple[RecordedStep, ...], stop_reason: StopReason | None, final_answer: str | None, checkpoints: int = 0) -> None
    check_replayable() -> None
    classmethod from_history(history: Sequence[LoopState]) -> Recording
    classmethod from_store(store: CheckpointStore, tenant_id: str, *, trace_id: str | None = None, run_id: str | None = None) -> Recording
    to_dict() -> dict[str, Any]
class ReplayReport(original: Recording, replayed: Recording, divergences: tuple[Divergence, ...] = ..., mismatched_calls: tuple[str, ...] = ()) -> None
    property identical
exception RunExistsError(Exception)(...)
exception RunNotFoundError(Exception)(...)
class Schedule(interval: timedelta) -> None
    next_after(moment: datetime) -> datetime
class SqliteCheckpointStore(path: str | Path = ':memory:') -> None
    close() -> None
    history(tenant_id: str, run_id: str) -> list[LoopState]
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None
exception StaleCheckpointError(Exception)(...)
class StopConditions(max_iterations: int | None = 10, max_tokens: int | None = None, max_dollars: float | None = None, timeout: timedelta | None = None, max_verifier_rejections: int | None = 3, goal: GoalPredicate | None = None) -> None
enum StopReason: GOAL_REACHED='goal_reached', MAX_ITERATIONS='max_iterations', TOKEN_BUDGET='token_budget', DOLLAR_BUDGET='dollar_budget', TIMEOUT='timeout', VERIFIER_REJECTIONS='verifier_rejections', COST_UNKNOWN='cost_unknown', OUTCOME_UNKNOWN='outcome_unknown', APPROVAL_PENDING='approval_pending', ERROR='error'
class Verdict(decision: VerdictDecision, reasons: tuple[str, ...] = (), flags: tuple[str, ...] = (), usage: Usage | None = None) -> None
    classmethod accept(*reasons) -> Verdict
    classmethod reject(*reasons, flags: tuple[str, ...] = ()) -> Verdict
    classmethod revise(*reasons) -> Verdict
enum VerdictDecision: ACCEPT='ACCEPT', REVISE='REVISE', REJECT='REJECT'
class VerificationLoop(Loop)(**kwargs) -> None
    async reconcile(tenant_id: str, run_id: str, action_id: str, *, executed: bool, by: str, note: str = '') -> LoopState
    async resume(tenant_id: str, run_id: str, *, stop: StopConditions | None = None) -> LoopResult
    async run(*, goal: str, tenant_id: str, agent_id: str, as_of: datetime, run_id: str | None = None, trace_id: str | None = None, stop: StopConditions | None = None) -> LoopResult
    async run_or_resume(*, goal: str, tenant_id: str, agent_id: str, as_of: datetime, run_id: str, trace_id: str | None = None, stop: StopConditions | None = None) -> LoopResult
    async verify_claim(claim: str, *, tenant_id: str, agent_id: str, as_of: datetime, run_id: str | None = None) -> LoopResult
protocol Verifier
    async verify(request: VerifyRequest) -> Verdict
class VerifyRequest(run_id: str, tenant_id: str, iteration: int, goal: str, final_answer: str | None, actions: tuple[tuple[str, str], ...], observations: tuple[ContextItem, ...]) -> None
def default_checkpointer(path: str | Path = ':memory:') -> CheckpointStore
def diff(original: Recording, replayed: Recording) -> list[Divergence]
def find_run(store: CheckpointStore, tenant_id: str, trace_id: str) -> str | None
async def replay(recording: Recording) -> ReplayReport

keelgate.context

As-of-time context assembly.

Symbol Kind Stability Summary
ApproxTokenCounter class stable Roughly four characters a token, rounded up.
AsOfViolationError exception stable An item was published after as_of.
BuiltContext class stable The result of building a context.
Compaction class stable A record that several context items were replaced by one summary item.
ContextBudgetError exception stable Trusted content alone does not fit the token budget.
ContextBuilder class stable Assembles a prompt for a given as_of.
ContextError exception stable Base class for context assembly failures.
ContextItem model stable One piece of context: its content, trust level, publication time and provenance.
DuplicateItemError exception stable Two items share an id.
ExtractiveSummarizer class stable Deterministic and model-free: the first sentence of each item, with a pointer.
FENCE_TOKENS constant stable
HARNESS_KINDS constant stable
InMemoryRecordStore class stable Process-local store of full context records, so a compacted summary can always be traced back to what it replaced.
ItemKind enum stable What a context item is: system text, an instruction, the task, an observation, memory, a document or a summary.
LLMSummarizer class stable Asks a model for a structured summary, and falls back to extraction if it misbehaves.
Provenance model stable Where an item came from.
RecordStore protocol stable Keeps the full text behind a summary pointer.
Rejection class stable A refused item.
RejectionReason enum stable Why an item was kept out of the context: published after as_of, undated, or a duplicate id.
StructuredSummary model stable A compacted summary: facts, decisions and open questions, plus pointers back to the full records it replaced.
Summarizer protocol stable Compacts items into a structured summary.
TokenCounter protocol stable Counts the tokens in a piece of text, so the builder can honour a budget.
Trust enum stable Whether a context item may be treated as an instruction (TRUSTED) or only as data (UNTRUSTED).
UNTRUSTED_NOTICE constant stable
UndatedItemError exception stable An outside item carries no publication time, so it cannot be shown to be earlier.
Signatures
class ApproxTokenCounter(chars_per_token: float = 4.0) -> None
    count(text: str) -> int
exception AsOfViolationError(ContextError)(...)
class BuiltContext(messages: tuple[Message, ...], items: tuple[ContextItem, ...], tokens: int, token_budget: int, compactions: tuple[Compaction, ...], dropped: tuple[str, ...], rejections: tuple[Rejection, ...]) -> None
class Compaction(summary_id: str, source_ids: tuple[str, ...]) -> None
exception ContextBudgetError(ContextError)(...)
class ContextBuilder(*, as_of: datetime, token_budget: int, counter: TokenCounter | None = None, summarizer: Summarizer | None = None, records: RecordStore | None = None, on_rejection: Callable[[Rejection], None] | None = None) -> None
    add(item: ContextItem) -> None
    async build() -> BuiltContext
    property items
    property rejections
    try_add(item: ContextItem) -> bool
exception ContextError(Exception)(...)
model ContextItem(*, item_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=200)], kind: ItemKind, content: str, trust: Trust = <Trust.UNTRUSTED: 'untrusted'>, published_at: datetime.datetime | None = None, provenance: Provenance, priority: int = 0, pointers: tuple[str, ...] = ()) -> None
    classmethod harness(kind: ItemKind, content: str, *, item_id: str, priority: int = 1000, origin: str = 'harness') -> ContextItem
    classmethod outside(kind: ItemKind, content: str, *, item_id: str, published_at: datetime | None, origin: str, ref: str = '', uri: str | None = None, priority: int = 0) -> ContextItem
    property content_sha256
    property is_trusted
exception DuplicateItemError(ContextError)(...)
class ExtractiveSummarizer()
    async summarize(items: Sequence[ContextItem]) -> StructuredSummary
FENCE_TOKENS = 12
HARNESS_KINDS = frozenset(...)
class InMemoryRecordStore() -> None
    get(item_id: str) -> ContextItem | None
    put(item: ContextItem) -> None
enum ItemKind: SYSTEM='system', INSTRUCTION='instruction', TASK='task', OBSERVATION='observation', MEMORY='memory', DOCUMENT='document', SUMMARY='summary'
class LLMSummarizer(llm: LLMClient, model: str, *, max_tokens: int = 400) -> None
    async summarize(items: Sequence[ContextItem]) -> StructuredSummary
model Provenance(*, origin: Annotated[str, MinLen(min_length=1), MaxLen(max_length=200)], ref: Annotated[str, MaxLen(max_length=200)] = '', uri: Annotated[str | None, MaxLen(max_length=2048)] = None) -> None
protocol RecordStore
    get(item_id: str) -> ContextItem | None
    put(item: ContextItem) -> None
class Rejection(item_id: str, reason: RejectionReason, origin: str, published_at: datetime | None, as_of: datetime, content_sha256: str) -> None
enum RejectionReason: AS_OF_VIOLATION='as_of_violation', UNDATED='undated', DUPLICATE='duplicate'
model StructuredSummary(*, facts: tuple[str, ...] = (), decisions: tuple[str, ...] = (), open_questions: tuple[str, ...] = (), pointers: tuple[str, ...] = ()) -> None
    render() -> str
protocol Summarizer
    async summarize(items: Sequence[ContextItem]) -> StructuredSummary
protocol TokenCounter
    count(text: str) -> int
enum Trust: TRUSTED='trusted', UNTRUSTED='untrusted'
UNTRUSTED_NOTICE = str(...)
exception UndatedItemError(ContextError)(...)

keelgate.memory

Layered memory: working, episodic, semantic and procedural.

Symbol Kind Stability Summary
Attribution model stable Who wrote a version, and under which trace.
ConcurrentWriteError exception stable Another writer created the next version first.
Embedder protocol stable Turns text into a vector of a fixed size (dim) for semantic search.
EmbeddingError exception provisional The embedding service failed or returned something unusable.
EpisodicMemory class stable What the agent decided and what came of it.
HashEmbedder class provisional A deterministic bag-of-words embedder with no model and no network.
InvalidMemoryWriteError exception stable The write is malformed for its tier.
Memory protocol stable The interface every tier implements.
MemoryBackend protocol provisional Storage for memory records.
MemoryRecord model stable One version of one memory: its content, who wrote it and under which trace, when it was recorded, and for semantic facts the window in which it is valid.
MemoryStoreError exception stable Base class for memory failures.
MemoryTier enum stable The four layers of memory: working, episodic, semantic and procedural.
OllamaEmbedder class provisional Embeds text with a local Ollama server (/api/embed).
OpenAICompatibleEmbedder class provisional Embeds text with OpenAI or any server speaking the OpenAI /embeddings format.
PostgresMemoryBackend class provisional Shared memory on Postgres with the pgvector extension.
ProceduralMemory class stable Versioned skills and playbooks, addressed by name.
RecordNotFoundError exception stable No such record.
SemanticMemory class stable Facts with validity windows, retrieved by embedding similarity.
SqliteMemoryBackend class provisional SQLite storage for memory: append-only, bitemporal, tenant-scoped, with exact vector search.
WorkingMemory class stable Scratch space for one run.
cosine function provisional Cosine similarity; 0.0 when either vector is all zeros.
Signatures
model Attribution(*, agent_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=128)], trace_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=128)]) -> None
exception ConcurrentWriteError(MemoryStoreError)(...)
protocol Embedder
    embed(texts: Sequence[str]) -> list[list[float]]
exception EmbeddingError(Exception)(...)
class EpisodicMemory(_TierMemory)(backend: MemoryBackend, *, tenant_id: str, audit: AuditLog | None = None, clock: Callable[[], datetime] = ...) -> None
    get(record_id: str, *, as_of: datetime) -> MemoryRecord | None
    history(record_id: str, *, as_of: datetime) -> tuple[MemoryRecord, ...]
    recall(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    record_episode(decision: str, *, attribution: Attribution, occurred_at: datetime | None = None, outcome: str | None = None, key: str = 'decision') -> MemoryRecord
    record_outcome(record_id: str, outcome: str, *, attribution: Attribution) -> MemoryRecord
    retire(record_id: str, *, attribution: Attribution) -> MemoryRecord
    revise(record_id: str, *, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, valid_to: datetime | None = None) -> MemoryRecord
    search(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    write(*, key: str, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, occurred_at: datetime | None = None, valid_from: datetime | None = None, valid_to: datetime | None = None) -> MemoryRecord
class HashEmbedder(dim: int = 64) -> None
    embed(texts: Sequence[str]) -> list[list[float]]
exception InvalidMemoryWriteError(MemoryStoreError)(...)
protocol Memory
    get(record_id: str, *, as_of: datetime) -> MemoryRecord | None
    history(record_id: str, *, as_of: datetime) -> tuple[MemoryRecord, ...]
    retire(record_id: str, *, attribution: Attribution) -> MemoryRecord
    revise(record_id: str, *, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, valid_to: datetime | None = None) -> MemoryRecord
    search(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    write(*, key: str, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, occurred_at: datetime | None = None, valid_from: datetime | None = None, valid_to: datetime | None = None) -> MemoryRecord
protocol MemoryBackend
    append(record: MemoryRecord, embedding: Sequence[float] | None) -> None
    close() -> None
    latest(tenant_id: str, record_id: str) -> MemoryRecord | None
    nearest(tenant_id: str, tier: MemoryTier, embedding: Sequence[float], *, as_of: datetime, limit: int) -> list[tuple[MemoryRecord, float]]
    versions(tenant_id: str, record_id: str, *, as_of: datetime) -> list[MemoryRecord]
    visible(tenant_id: str, tier: MemoryTier, *, as_of: datetime, run_id: str | None = None, key: str | None = None) -> list[MemoryRecord]
model MemoryRecord(*, record_id: str, version: Annotated[int, Ge(ge=1)], tenant_id: str, tier: MemoryTier, key: Annotated[str, MinLen(min_length=1), MaxLen(max_length=300)], content: str, data: dict[str, Any] = ..., recorded_at: datetime.datetime, attribution: Attribution, occurred_at: datetime.datetime | None = None, valid_from: datetime.datetime | None = None, valid_to: datetime.datetime | None = None, run_id: str | None = None, retired: bool = False) -> None
    property content_sha256
    to_context_item(*, priority: int = 0) -> ContextItem
exception MemoryStoreError(Exception)(...)
enum MemoryTier: WORKING='working', EPISODIC='episodic', SEMANTIC='semantic', PROCEDURAL='procedural'
class OllamaEmbedder(_HttpEmbedder)(*, model: str, dim: int, base_url: str = 'http://localhost:11434', client: httpx.Client | None = None, timeout: float = 60.0) -> None
    close() -> None
    embed(texts: Sequence[str]) -> list[list[float]]
class OpenAICompatibleEmbedder(_HttpEmbedder)(*, model: str, dim: int, base_url: str = 'https://api.openai.com/v1', api_key: str | None = None, send_dimensions: bool = False, client: httpx.Client | None = None, timeout: float = 60.0) -> None
    close() -> None
    embed(texts: Sequence[str]) -> list[list[float]]
class PostgresMemoryBackend(dsn: str, *, dim: int) -> None
    append(record: MemoryRecord, embedding: Sequence[float] | None) -> None
    close() -> None
    latest(tenant_id: str, record_id: str) -> MemoryRecord | None
    nearest(tenant_id: str, tier: MemoryTier, embedding: Sequence[float], *, as_of: datetime, limit: int) -> list[tuple[MemoryRecord, float]]
    versions(tenant_id: str, record_id: str, *, as_of: datetime) -> list[MemoryRecord]
    visible(tenant_id: str, tier: MemoryTier, *, as_of: datetime, run_id: str | None = None, key: str | None = None) -> list[MemoryRecord]
class ProceduralMemory(_TierMemory)(backend: MemoryBackend, *, tenant_id: str, audit: AuditLog | None = None, clock: Callable[[], datetime] = ...) -> None
    get(record_id: str, *, as_of: datetime) -> MemoryRecord | None
    history(record_id: str, *, as_of: datetime) -> tuple[MemoryRecord, ...]
    publish(name: str, body: str, *, attribution: Attribution) -> MemoryRecord
    retire(record_id: str, *, attribution: Attribution) -> MemoryRecord
    revise(record_id: str, *, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, valid_to: datetime | None = None) -> MemoryRecord
    search(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    skill(name: str, *, as_of: datetime) -> MemoryRecord | None
    skills(*, as_of: datetime) -> tuple[MemoryRecord, ...]
    write(*, key: str, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, occurred_at: datetime | None = None, valid_from: datetime | None = None, valid_to: datetime | None = None) -> MemoryRecord
exception RecordNotFoundError(MemoryStoreError)(...)
class SemanticMemory(_TierMemory)(backend: MemoryBackend, *, tenant_id: str, embedder: Embedder | None = None, **kwargs) -> None
    assert_fact(subject: str, text: str, *, valid_from: datetime, attribution: Attribution, valid_to: datetime | None = None, data: Mapping[str, Any] | None = None) -> MemoryRecord
    end_fact(record_id: str, valid_to: datetime, *, attribution: Attribution) -> MemoryRecord
    get(record_id: str, *, as_of: datetime) -> MemoryRecord | None
    history(record_id: str, *, as_of: datetime) -> tuple[MemoryRecord, ...]
    retire(record_id: str, *, attribution: Attribution) -> MemoryRecord
    revise(record_id: str, *, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, valid_to: datetime | None = None) -> MemoryRecord
    search(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    search_scored(query: str, *, as_of: datetime, limit: int = 5) -> tuple[tuple[MemoryRecord, float], ...]
    write(*, key: str, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, occurred_at: datetime | None = None, valid_from: datetime | None = None, valid_to: datetime | None = None) -> MemoryRecord
class SqliteMemoryBackend(path: str | Path = ':memory:') -> None
    append(record: MemoryRecord, embedding: Sequence[float] | None) -> None
    close() -> None
    latest(tenant_id: str, record_id: str) -> MemoryRecord | None
    nearest(tenant_id: str, tier: MemoryTier, embedding: Sequence[float], *, as_of: datetime, limit: int) -> list[tuple[MemoryRecord, float]]
    versions(tenant_id: str, record_id: str, *, as_of: datetime) -> list[MemoryRecord]
    visible(tenant_id: str, tier: MemoryTier, *, as_of: datetime, run_id: str | None = None, key: str | None = None) -> list[MemoryRecord]
class WorkingMemory(_TierMemory)(backend: MemoryBackend, *, tenant_id: str, run_id: str, **kwargs) -> None
    get(record_id: str, *, as_of: datetime) -> MemoryRecord | None
    history(record_id: str, *, as_of: datetime) -> tuple[MemoryRecord, ...]
    put(key: str, value: str, *, attribution: Attribution) -> MemoryRecord
    retire(record_id: str, *, attribution: Attribution) -> MemoryRecord
    revise(record_id: str, *, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, valid_to: datetime | None = None) -> MemoryRecord
    search(query: str, *, as_of: datetime, limit: int = 5) -> tuple[MemoryRecord, ...]
    value(key: str, *, as_of: datetime) -> str | None
    write(*, key: str, content: str, attribution: Attribution, data: Mapping[str, Any] | None = None, occurred_at: datetime | None = None, valid_from: datetime | None = None, valid_to: datetime | None = None) -> MemoryRecord
def cosine(a: Sequence[float], b: Sequence[float]) -> float

keelgate.llm

Model-agnostic LLM client boundary.

Symbol Kind Stability Summary
FinishReason enum stable Why a model stopped: a natural stop, tool calls, the length limit, or something else.
LLMAuthError exception stable Credentials are missing or rejected.
LLMClient protocol stable A model-agnostic chat client.
LLMError exception stable A provider call failed.
LLMRateLimitError exception stable The provider asked the caller to slow down.
LLMRequest model stable A provider-neutral request: model, messages, the tools offered, a token limit.
LLMResponse model stable A provider-neutral reply: one assistant message, usage with cost if known, and why it stopped.
Message model stable One message in a conversation: a role, text, any tool calls the assistant made, and for a tool result which call it answers.
ModelPrice class stable USD per million tokens.
PricingTable class stable Looks a model up by exact name, then by the longest matching prefix.
Role enum stable Who said a message: system, user, assistant or tool.
ToolCall model stable A tool invocation the model proposes.
ToolSchema model stable A tool offered to the model.
Usage model stable Tokens and money for one call.
Signatures
enum FinishReason: STOP='stop', TOOL_CALLS='tool_calls', LENGTH='length', OTHER='other'
exception LLMAuthError(LLMError)(message: str, *, provider: str = '') -> None
protocol LLMClient
    async complete(request: LLMRequest) -> LLMResponse
exception LLMError(Exception)(message: str, *, provider: str = '', retryable: bool = False) -> None
exception LLMRateLimitError(LLMError)(message: str, *, provider: str = '') -> None
model LLMRequest(*, model: str, messages: tuple[Message, ...], tools: tuple[ToolSchema, ...] = (), max_tokens: Annotated[int, Gt(gt=0)] = 1024, temperature: Annotated[float | None, Ge(ge=0)] = None, metadata: dict[str, Any] = ...) -> None
model LLMResponse(*, message: Message, usage: Usage = ..., finish_reason: FinishReason = <FinishReason.STOP: 'stop'>, model: str = '', response_id: str | None = None) -> None
    property text
    property tool_calls
model Message(*, role: Role, content: str = '', tool_calls: tuple[ToolCall, ...] = (), tool_call_id: str | None = None, name: str | None = None) -> None
class ModelPrice(input_per_mtok: float, output_per_mtok: float) -> None
class PricingTable(prices: Mapping[str, ModelPrice] | None = None) -> None
    price_for(model: str) -> ModelPrice | None
    usage(model: str, input_tokens: int, output_tokens: int) -> Usage
enum Role: SYSTEM='system', USER='user', ASSISTANT='assistant', TOOL='tool'
model ToolCall(*, id: str, name: str, arguments: dict[str, Any] = ...) -> None
model ToolSchema(*, name: str, description: str = '', input_schema: dict[str, Any] = ...) -> None
model Usage(*, input_tokens: Annotated[int, Ge(ge=0)] = 0, output_tokens: Annotated[int, Ge(ge=0)] = 0, cost_usd: Annotated[float | None, Ge(ge=0), _PydanticGeneralMetadata(allow_inf_nan=False)] = None) -> None
    property total_tokens

keelgate.telemetry

OpenTelemetry tracing, cost tracking and PII redaction for Keelgate.

Symbol Kind Stability Summary
CostTotals class provisional Calls, tokens and dollars for one tenant/agent selection.
CostTracker class provisional Per-tenant and per-agent token and dollar totals, also emitted as OpenTelemetry metrics.
InstrumentedLLM class stable An LLMClient that records a span and cost for every complete call.
RedactingSpanProcessor class provisional Wrap delegate so that it only ever receives redacted spans.
Redactor class provisional Rewrites text and attribute values.
RunContext class stable The tenant, agent and run that spans and costs created inside a block are attributed to.
RunSpan class provisional The root span of a loop run, with the ids that identify the run's trace.
Telemetry class stable A tracer, a meter, a cost tracker, and the content-capture switch.
UNATTRIBUTED constant provisional
activate function stable Make telemetry the active handle for the process (None restores the default).
active function stable The currently active Telemetry handle.
attributes module provisional Span and metric attribute names.
bind_run function stable Attribute spans, metrics and costs created inside the block to this run.
current_run function stable The RunContext bound by the surrounding loop, or None outside a run.
instrument function stable Configure tracing and make it the active telemetry for Keelgate.
redact_span function provisional A copy of a finished span with attribute values, event attributes and the name redacted.
run_span function provisional The root span for one loop run, bound so everything inside is attributed to the run.
set_attributes function stable Set several span attributes at once, dropping None values and truncating long strings.
span function stable A span that is current inside the block, attributed to the bound run.
traced function stable Run a synchronous method inside a span.
use function stable Temporarily activate telemetry (tests, and short scripts).
Signatures
class CostTotals(calls: int = 0, input_tokens: int = 0, output_tokens: int = 0, cost_usd: float = 0.0, unpriced_calls: int = 0) -> None
    property total_tokens
class CostTracker(meter: Meter | None = None) -> None
    by_agent(tenant_id: str) -> dict[str, CostTotals]
    by_tenant() -> dict[str, CostTotals]
    record(usage: Usage, *, tenant_id: str | None, agent_id: str | None, model: str) -> None
    total(*, tenant_id: str | None = None, agent_id: str | None = None) -> CostTotals
class InstrumentedLLM(client: LLMClient) -> None
    async complete(request: LLMRequest) -> LLMResponse
    property wrapped
class RedactingSpanProcessor(SpanProcessor)(delegate: SpanProcessor, redactor: Redactor | None = None) -> None
    force_flush(timeout_millis: int = 30000) -> bool
    on_end(span: ReadableSpan) -> None
    on_start(span: Span, parent_context: Context | None = None) -> None
    shutdown() -> None
class Redactor(patterns: tuple[tuple[str, re.Pattern[str]], ...] = (('email', re.compile('[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}')), ('ssn', re.compile('(?<!\\d)\\d{3}-\\d{2}-\\d{4}(?!\\d)')), ('phone', re.compile('(?<![\\w.])(?:\\+?\\d{1,3}[ .-]?)?(?:\\(\\d{3}\\)|\\d{3})[ .-]\\d{3}[ .-]\\d{4}(?!\\d)')), ('iban', re.compile('\\b[A-Z]{2}\\d{2}[A-Z0-9]{11,30}\\b')), ('api_key', re.compile('\\b(?:sk|pk|rk)-[A-Za-z0-9_-]{16,}\\b')), ('aws_key', re.compile('\\bAKIA[0-9A-Z]{16}\\b')), ('jwt', re.compile('\\beyJ[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\.[A-Za-z0-9_-]{8,}\\b')), ('bearer', re.compile('(?i)\\bbearer\\s+[A-Za-z0-9._~+/=-]{16,}', re.IGNORECASE))), replacement: str = '[REDACTED]', redact_cards: bool = True, drop_keys: frozenset[str] = ...) -> None
    attributes(attributes: Mapping[str, Any] | None) -> dict[str, Any]
    text(value: str) -> str
    value(value: Any) -> Any
class RunContext(tenant_id: str, agent_id: str, run_id: str) -> None
class RunSpan(span: Span, trace_id: str | None, span_id: str | None) -> None
class Telemetry(*, tracer_provider: TracerProvider | None = None, meter_provider: MeterProvider | None = None, capture_content: bool = False, costs: CostTracker | None = None) -> None
    force_flush(timeout_millis: int = 30000) -> None
    shutdown() -> None
UNATTRIBUTED = 'unattributed'
def activate(telemetry: Telemetry | None) -> None
def active() -> Telemetry
module attributes
def bind_run(tenant_id: str, agent_id: str, run_id: str) -> Iterator[RunContext]
def current_run() -> RunContext | None
def instrument(*, service_name: str | None = None, endpoint: str | None = None, headers: Mapping[str, str] | None = None, exporter: SpanExporter | None = None, redact: bool | Redactor = False, capture_content: bool | None = None, batch: bool = True, resource_attributes: Mapping[str, Any] | None = None, meter_provider: MeterProvider | None = None, set_global: bool = False) -> Telemetry
def redact_span(span: ReadableSpan, redactor: Redactor) -> ReadableSpan
def run_span(*, tenant_id: str, agent_id: str, run_id: str, loop_type: str, trace_id: str | None = None, root_span_id: str | None = None, resumed: bool = False, extra: Mapping[str, Any] | None = None) -> Iterator[RunSpan]
def set_attributes(span_: Span, attributes: Mapping[str, Any]) -> None
def span(name: str, *, kind: SpanKind = SpanKind.INTERNAL, attributes: Mapping[str, Any] | None = None, context: Context | None = None) -> Iterator[Span]
def traced(name: str, *, pre: Callable[[Mapping[str, Any]], Mapping[str, Any]] | None = None, post: Callable[[Any], Mapping[str, Any]] | None = None) -> Callable[[Callable[P, R]], Callable[P, R]]
def use(telemetry: Telemetry) -> Iterator[Telemetry]

keelgate.evals

Agent evals, red-team suites and outcome-metric plugins.

Symbol Kind Stability Summary
AccuracyMetric class provisional Fraction of records whose predicted["label"] equals realized["label"].
CaseResult class provisional One eval case.
ENTRY_POINT_GROUP constant stable
EvalContext class provisional fake mode replays each case's scripted model (deterministic, free, runs on every PR).
EvalReport class provisional Everything one eval run produced: the suites, any regressions against a baseline, and notes.
EvalStack class provisional One isolated stack.
LoadedMetric class provisional An outcome metric found through the entry-point group, or why it failed to load.
MetricResult class stable One metric's value over a batch of outcomes, with how many records it used and which direction is better.
OutcomeMetric protocol stable A named measure over a batch of outcomes.
OutcomeRecord class stable One decision and what came of it.
SUITES constant provisional
SuiteResult class provisional The cases and metrics of one eval suite.
baseline_of function provisional The facts a future run must not regress below: passing cases and metric values.
compare function provisional Every way report is worse than baseline.
discover_metrics function stable Every metric registered under the entry-point group, loaded defensively.
load_records function provisional Read OutcomeRecord rows from a JSON-lines file (one JSON object per line).
run_metrics function provisional Compute every loaded metric; return the results and one line per problem.
run_outcome async function provisional Run every registered OutcomeMetric (plus the built-in accuracy) over the records.
run_suite async function stable Run one suite by name and return its result.
run_suites async function provisional Run the named suites.
sample_records function provisional A small fixed dataset so metrics can be exercised without a project's own data.
to_dict function provisional The report as a JSON-serialisable dict.
to_html function provisional The report as one self-contained HTML page (no external resources; everything escaped).
to_markdown function provisional A short summary for a CI job page: verdict, per-suite counts, metrics, what failed.
write_html function provisional Write the report as a self-contained HTML page to path.
write_json function provisional Write the report as JSON to path.
write_markdown function provisional Write a short Markdown summary of the report to path (for a CI job page).
Signatures
class AccuracyMetric()
    compute(records: Sequence[OutcomeRecord]) -> MetricResult
class CaseResult(suite: str, case_id: str, title: str, passed: bool, detail: str = '', category: str = '', evidence: dict[str, Any] = ..., duration_ms: float = 0.0, skipped: bool = False) -> None
ENTRY_POINT_GROUP = 'keelgate.outcome_metrics'
class EvalContext(mode: str = 'fake', live_client: LLMClient | None = None, model: str = 'eval-model', options: dict[str, Any] = ...) -> None
    llm(script: Sequence[Reply] = ()) -> Any
    property is_live
class EvalReport(version: str, generated_at: str, mode: str, suites: list[SuiteResult] = ..., regressions: list[str] = ..., notes: list[str] = ...) -> None
    property failures
    property metrics
    property passed
    suite(name: str) -> SuiteResult | None
class EvalStack(engine: PolicyEngine | None = None, capabilities: tuple[str, ...] = ('market_data:read', 'document:read', 'trade:paper_execute'), tenant_id: str = 'tenant-1', agent_id: str = 'agent-1', max_cost: float = 1000.0, ttl: timedelta = ..., news_text: str = 'Markets were quiet today.', news_published: datetime = ..., documents: dict[str, tuple[str, datetime]] = ..., clock: ManualClock = ..., executions: list[dict[str, Any]] = ...) -> None
    async run(llm: LLMClient, goal: str, *, run_id: str = 'eval-run', tenant_id: str | None = None, **loop_kwargs) -> LoopResult
    audit_ok() -> bool
    grant(capabilities: Iterable[str] | None = None, *, tenant_id: str | None = None, agent_id: str | None = None, max_cost: float | None = None, ttl: timedelta | None = None) -> str
    grant_object(capabilities: Iterable[str] | None = None, *, tenant_id: str | None = None, agent_id: str | None = None, max_cost: float | None = None, ttl: timedelta | None = None) -> SignedGrant
    loop(llm: LLMClient, *, stop: StopConditions | None = None, verifier: Verifier | None = None, sources: Sequence[ContextSource] = (), allowed_side_effects: frozenset[SideEffect] | None = None, grant_token: str | None = None, model: str = 'eval-model', **kwargs) -> Loop
    staticmethod policy_context(as_of: datetime) -> PolicyContext
class LoadedMetric(entry_point: str, metric: OutcomeMetric | None, error: str = '', source: str = '') -> None
class MetricResult(name: str, value: float, n: int, higher_is_better: bool = True, details: dict[str, Any] = ..., source: str = 'builtin') -> None
protocol OutcomeMetric
    compute(records: Sequence[OutcomeRecord]) -> MetricResult
class OutcomeRecord(record_id: str, predicted: Mapping[str, Any], realized: Mapping[str, Any], tenant_id: str = '', run_id: str = '', metadata: Mapping[str, Any] = ...) -> None
SUITES = ('unit', 'trajectory', 'redteam', 'outcome')
class SuiteResult(name: str, description: str, cases: list[CaseResult] = ..., metrics: list[MetricResult] = ..., error: str = '') -> None
    property pass_rate
    property passed
    property ran
def baseline_of(report: EvalReport) -> dict[str, Any]
def compare(report: EvalReport, baseline: dict[str, Any], *, tolerance: float = 0.0) -> list[str]
def discover_metrics(group: str = 'keelgate.outcome_metrics') -> list[LoadedMetric]
def load_records(path: str) -> list[OutcomeRecord]
def run_metrics(records: Sequence[OutcomeRecord], metrics: Sequence[LoadedMetric]) -> tuple[list[MetricResult], list[str]]
async def run_outcome(records: Sequence[OutcomeRecord] | None = None, metrics: Sequence[LoadedMetric] | None = None) -> SuiteResult
async def run_suite(name: str, ctx: EvalContext | None = None, **kwargs) -> SuiteResult
async def run_suites(names: Sequence[str] = ('unit', 'trajectory', 'redteam', 'outcome'), ctx: EvalContext | None = None, *, records: Sequence[OutcomeRecord] | None = None, new_stack: Callable[[], EvalStack] | None = None) -> EvalReport
def sample_records() -> list[OutcomeRecord]
def to_dict(report: EvalReport) -> dict[str, Any]
def to_html(report: EvalReport) -> str
def to_markdown(report: EvalReport) -> str
def write_html(report: EvalReport, path: Path) -> None
def write_json(report: EvalReport, path: Path) -> None
def write_markdown(report: EvalReport, path: Path) -> None

keelgate.testing

Fakes and fixtures Keelgate ships for downstream projects' CI.

Symbol Kind Stability Summary
FakeLLM class stable A scripted, deterministic LLMClient for tests.
GovernedHarness class provisional A wired gateway plus the handles a test needs to drive and inspect it.
ManualClock class provisional A clock that only moves when told to.
Reply class stable One scripted model turn: text, tool calls, or both.
ScriptExhaustedError exception stable The code under test made more LLM calls than the script has replies for.
StaticPolicyEngine class provisional A scripted :class:~keelgate.policy.PolicyEngine.
UnofferedToolError exception stable The script asked for a tool that the request did not offer.
build_governed_harness function provisional Build the stack.
Signatures
class FakeLLM(script: Sequence[ScriptItem], *, indexed: bool = False, default: Reply | None = None, pricing: PricingTable | None = None, model: str = 'fake-model', strict_tools: bool = True) -> None
    async complete(request: LLMRequest) -> LLMResponse
    property calls_made
    property last_request
class GovernedHarness(clock: ManualClock, signer: GrantSigner, audit: AuditLog, approvals: ApprovalQueue, registry: ToolRegistry, gateway: ToolGateway, engine: PolicyEngine, revocations: InMemoryRevocationList, tenant_id: str = 'tenant-1', agent_id: str = 'agent-1', extra: dict[str, Any] = ...) -> None
    async call(tool: str, arguments: dict[str, Any], *, token: str | None = None, **ctx) -> ToolOutcome
    context(tenant: str | None = None, **overrides) -> CallContext
    grant(capabilities: Iterable[str] | None = None, *, tenant: str | None = None, max_cost: float = 100.0, ttl: timedelta = ...) -> str
class ManualClock(start: datetime = ...) -> None
    advance(delta: timedelta) -> None
class Reply(text: str = '', tool_calls: tuple[ToolCall, ...] = (), usage: Usage | None = None, finish_reason: FinishReason | None = None) -> None
    classmethod call(name: str, *, call_id: str = '', usage: Usage | None = None, **args) -> Reply
    classmethod calls(*calls, usage: Usage | None = None) -> Reply
    classmethod say(text: str, *, usage: Usage | None = None) -> Reply
exception ScriptExhaustedError(AssertionError)(...)
class StaticPolicyEngine(rules: Mapping[str, Decision] | None = None, *, default: Decision = <Decision.DENY: 'DENY'>, approval_tier: ApprovalTier = <ApprovalTier.ONE_CLICK: 'ONE_CLICK'>) -> None
    async decide(policy_input: PolicyInput) -> PolicyDecision
    classmethod allow_all() -> StaticPolicyEngine
exception UnofferedToolError(AssertionError)(...)
def build_governed_harness(tools: Iterable[Tool] = (), *, engine: PolicyEngine | None = None, clock: ManualClock | None = None, tenant_id: str = 'tenant-1', agent_id: str = 'agent-1') -> GovernedHarness

keelgate.adapters

Adapters that wrap existing agent frameworks.

Symbol Kind Stability Summary
GovernedToolset class stable A registry of tools bound to a gateway, a grant and a trusted context.
UNTRUSTED_KEY constant stable
Signatures
class GovernedToolset(gateway: ToolGateway, registry: ToolRegistry, grant_token: str | Callable[[], str], context_factory: Callable[[], CallContext], allowed_side_effects: frozenset[SideEffect] | None = None) -> None
    async invoke(name: str, arguments: Mapping[str, Any]) -> ToolOutcome
    render(outcome: ToolOutcome) -> str
    schemas() -> list[ToolSchema]
    staticmethod is_error(outcome: ToolOutcome) -> bool
    tools() -> list[Tool]
UNTRUSTED_KEY = 'untrusted_tool_output'

keelgate.llm.providers

Provider clients.

Symbol Kind Stability Summary
AnthropicClient class provisional An :class:~keelgate.llm._types.LLMClient for Claude models.
GoogleClient class provisional An :class:~keelgate.llm._types.LLMClient for Gemini models.
OllamaClient class provisional An :class:~keelgate.llm._types.LLMClient for a local Ollama server.
OpenAIClient class provisional An :class:~keelgate.llm._types.LLMClient for OpenAI and OpenAI-compatible servers.
VLLMClient class provisional A vLLM (or other OpenAI-compatible) server.
Signatures
class AnthropicClient(*, client: Any = None, api_key: str | None = None, pricing: PricingTable | None = None) -> None
    async complete(request: LLMRequest) -> LLMResponse
class GoogleClient(*, client: Any = None, api_key: str | None = None, pricing: PricingTable | None = None) -> None
    async complete(request: LLMRequest) -> LLMResponse
class OllamaClient(*, base_url: str = 'http://localhost:11434', client: httpx.AsyncClient | None = None, pricing: PricingTable | None = None, timeout: float = 120.0) -> None
    async aclose() -> None
    async complete(request: LLMRequest) -> LLMResponse
class OpenAIClient(*, client: Any = None, api_key: str | None = None, base_url: str | None = None, pricing: PricingTable | None = None, token_param: str = 'max_completion_tokens') -> None
    async complete(request: LLMRequest) -> LLMResponse
class VLLMClient(OpenAIClient)(*, base_url: str = 'http://localhost:8000/v1', api_key: str = 'EMPTY', client: Any = None, pricing: PricingTable | None = None) -> None
    async complete(request: LLMRequest) -> LLMResponse

keelgate.adapters.mcp

Model Context Protocol adapter.

Symbol Kind Stability Summary
Discovery class provisional What was found on the server, and what happened to each tool.
ExternalArgs model provisional Passthrough arguments for an external tool; the remote JSON schema is enforced separately.
ExternalOutput model provisional What a governed external MCP tool returns, wrapped so it stays labelled untrusted.
ExternalToolError exception provisional The external server reported an error, or returned something unusable.
ExternalToolMapping class provisional How one external tool is admitted: the capability and side effect you assign.
GovernedMCPClient class provisional Wraps an MCP client (anything with list_tools and call_tool).
GovernedMCPServer class provisional Serve governed tools as an MCP server over stdio or streamable HTTP.
schema_digest function provisional The pin for a remote tool: a hash over its name, description and input schema.
Signatures
class Discovery(registered: tuple[str, ...] = (), blocked: tuple[str, ...] = (), missing: tuple[str, ...] = (), mismatched: tuple[str, ...] = (), tools: tuple[Tool, ...] = ()) -> None
model ExternalArgs(**extra_data) -> None
model ExternalOutput(*, text: str = '', structured: dict[str, Any] | None = None) -> None
exception ExternalToolError(Exception)(...)
class ExternalToolMapping(remote_name: str, capability: str, side_effect: SideEffect, local_name: str | None = None, schema_sha256: str | None = None, idempotency_key: Callable[[Any], str] | None = None, resource: Callable[[Any], Mapping[str, Any]] | None = None, cost_estimate: float = 0.0, timeout_s: float = 30.0) -> None
class GovernedMCPClient(client: Any, *, server_name: str, allowlist: Sequence[ExternalToolMapping]) -> None
    async discover() -> Discovery
    async register(registry: ToolRegistry) -> Discovery
class GovernedMCPServer(toolset: GovernedToolset, *, name: str = 'keelgate', instructions: str | None = None, toolset_for_request: Callable[[types.CallToolRequestParams], GovernedToolset] | None = None) -> None
    async serve_stdio() -> None
    http_app(*, path: str = '/mcp', **kwargs) -> Starlette
def schema_digest(tool: types.Tool) -> str

keelgate.adapters.langgraph

LangGraph adapter: governed tools, a chat-model wrapper, and the durable checkpoint store.

Symbol Kind Stability Summary
KeelgateChatModel model provisional A LangChain chat model whose completions come from a Keelgate LLMClient.
LangGraphCheckpointStore class provisional Loop checkpoints on a LangGraph saver.
governed_langchain_tools function provisional One LangChain tool per governed tool.
to_keelgate_messages function provisional Convert LangChain messages into Keelgate Message objects.
Signatures
model KeelgateChatModel(BaseChatModel)(*args, name: str | None = None, cache: langchain_core.caches.BaseCache | bool | None = None, verbose: bool = ..., callbacks: list[langchain_core.callbacks.base.BaseCallbackHandler] | langchain_core.callbacks.base.BaseCallbackManager | None = None, tags: list[str] | None = None, metadata: dict[str, Any] | None = None, custom_get_token_ids: collections.abc.Callable[[str], list[int]] | None = None, rate_limiter: langchain_core.rate_limiters.BaseRateLimiter | None = None, disable_streaming: Union[bool, Literal['tool_calling']] = False, output_version: str | None = ..., profile: langchain_core.language_models.model_profile.ModelProfile | None = None, llm: Any, model_name: str = 'keelgate', max_tokens: int = 1024, tool_schemas: tuple[ToolSchema, ...] = ()) -> None
    bind_tools(tools: Any, **kwargs) -> KeelgateChatModel
class LangGraphCheckpointStore(saver: BaseCheckpointSaver[Any], *, process_lock: Callable[[], AbstractContextManager[None]] | None = None) -> None
    classmethod sqlite(path: str | Path = ':memory:') -> LangGraphCheckpointStore
    history(tenant_id: str, run_id: str) -> list[LoopState]
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None
def governed_langchain_tools(toolset: GovernedToolset) -> list[StructuredTool]
def to_keelgate_messages(messages: list[BaseMessage]) -> tuple[Message, ...]

keelgate.adapters.openai_agents

OpenAI Agents SDK adapter: governed function tools, and an LLMClient-backed model.

Symbol Kind Stability Summary
KeelgateModel class provisional An Agents SDK Model whose completions come from a Keelgate LLMClient.
governed_function_tools function provisional One FunctionTool per governed tool.
to_keelgate_messages function provisional Convert Agents SDK input items into Keelgate Message objects.
Signatures
class KeelgateModel(Model)(llm: LLMClient, *, model_name: str = 'keelgate', max_tokens: int = 1024) -> None
    async get_response(system_instructions: str | None, input: Any, model_settings: Any, tools: list[Any], output_schema: Any, handoffs: list[Any], tracing: Any, *, previous_response_id: str | None = None, conversation_id: str | None = None, prompt: Any = None) -> ModelResponse
    stream_response(*args, **kwargs) -> AsyncIterator[Any]
def governed_function_tools(toolset: GovernedToolset) -> list[FunctionTool]
def to_keelgate_messages(system_instructions: str | None, items: str | Sequence[Any]) -> tuple[Message, ...]

keelgate.adapters.claude_agent_sdk

Claude Agent SDK adapter: governed tools as an in-process MCP server, and locked-down options.

Symbol Kind Stability Summary
BUILTIN_TOOLS constant provisional
governed_claude_options function provisional ClaudeAgentOptions that confine an agent to the governed tools.
governed_sdk_mcp_server function provisional An in-process MCP server exposing the governed tools to the Claude Agent SDK.
governed_tool_names function provisional The SDK's fully qualified names for the governed tools: mcp__<server>__<tool>.
Signatures
BUILTIN_TOOLS = tuple(...)
def governed_claude_options(toolset: GovernedToolset, *, server_name: str = 'keelgate', **overrides) -> ClaudeAgentOptions
def governed_sdk_mcp_server(toolset: GovernedToolset, *, name: str = 'keelgate', version: str = '1.0.0') -> McpSdkServerConfig
def governed_tool_names(toolset: GovernedToolset, *, server_name: str = 'keelgate') -> list[str]

keelgate.adapters.a2a

Agent-to-Agent (A2A) adapter: an agent card and task intake under policy.

Symbol Kind Stability Summary
GovernedA2AExecutor class provisional Runs inbound A2A tasks through the intake gate, then the supplied runner.
INTAKE_TOOL constant provisional
IntakeIn model provisional The arguments of the governed a2a_task_intake tool: the remote task's text, id and context.
IntakeOut model provisional The result of accepting an A2A task: whether it was accepted and its id.
TaskRequest class provisional A task from a remote caller.
TaskResult class provisional What a task runner returns for an A2A task: the text to report and whether it succeeded.
build_a2a_app function provisional A Starlette app serving the agent card and the JSON-RPC task endpoint.
build_agent_card function provisional An agent card.
loop_task_runner function provisional A :data:TaskRunner that runs a loop, with the remote text as untrusted context.
make_intake_tool function provisional The governed tool that gates task intake.
Signatures
class GovernedA2AExecutor(AgentExecutor)(toolset: GovernedToolset, runner: TaskRunner, *, grant_resolver: GrantResolver | None = None) -> None
    async cancel(context: RequestContext, event_queue: EventQueue) -> None
    async execute(context: RequestContext, event_queue: EventQueue) -> None
INTAKE_TOOL = 'a2a_task_intake'
model IntakeIn(*, goal: Annotated[str, MinLen(min_length=1), MaxLen(max_length=4000)], task_id: Annotated[str, MinLen(min_length=1), MaxLen(max_length=200)], context_id: Annotated[str, MaxLen(max_length=200)] = '') -> None
model IntakeOut(*, accepted: bool, task_id: str) -> None
class TaskRequest(text: str, task_id: str, context_id: str) -> None
class TaskResult(text: str, ok: bool = True) -> None
def build_a2a_app(toolset: GovernedToolset, runner: TaskRunner, card: AgentCard, *, grant_resolver: GrantResolver | None = None, rpc_url: str = '/a2a') -> Starlette
def build_agent_card(*, name: str, description: str, url: str, version: str = '1.0.0', skills: Sequence[tuple[str, str, str]] = ()) -> AgentCard
def loop_task_runner(loop_factory: Callable[..., Loop], *, tenant_id: str, agent_id: str, as_of_for: Callable[[], datetime] | None = None) -> TaskRunner
def make_intake_tool(*, timeout_s: float = 5.0) -> Tool

keelgate.adapters.temporal

Temporal adapter: run a Keelgate loop as a Temporal workflow (keelgate[temporal]).

Symbol Kind Stability Summary
ACTIVITY_NAME constant provisional
DEFAULT_TASK_QUEUE constant provisional
KeelgateLoopWorkflow class provisional A Temporal workflow that runs one loop as an activity.
LoopActivities class provisional The activity implementation.
TemporalRunner class provisional A :class:~keelgate.loop._runner.LoopRunner that starts the loop as a Temporal workflow.
WorkflowOptions class provisional Timeouts and retries for the loop activity.
build_worker function provisional A worker serving the loop workflow and its activity.
Signatures
ACTIVITY_NAME = 'keelgate_run_loop'
DEFAULT_TASK_QUEUE = 'keelgate'
class KeelgateLoopWorkflow()
    async run(spec: LoopSpec) -> LoopOutcome
class LoopActivities(loop_factory: Callable[[], Loop]) -> None
    async run_loop(spec: LoopSpec) -> LoopOutcome
class TemporalRunner(client: Client, *, task_queue: str = 'keelgate') -> None
    async run(spec: LoopSpec) -> LoopOutcome
    staticmethod workflow_id(spec: LoopSpec) -> str
class WorkflowOptions(start_to_close: timedelta = ..., heartbeat_timeout: timedelta = ..., max_attempts: int = 5) -> None
def build_worker(client: Client, loop_factory: Callable[[], Loop], *, task_queue: str = 'keelgate', **worker_kwargs) -> Worker

keelgate

Keelgate — a safety-first agent harness for AI agents that touch money.

Symbol Kind Stability Summary
__version__ constant stable
Signatures
__version__ = '0.1.0'

keelgate.cli

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

Symbol Kind Stability Summary
main function provisional Run the keelgate command line and return the exit code (0 passed, 1 failed, 2 usage error).
build_parser function provisional Build the keelgate argument parser.
Signatures
def main(argv: Sequence[str] | None = None) -> int
def build_parser() -> argparse.ArgumentParser

keelgate.approvals.cli

Command-line approval client: keelgate-approvals.

Symbol Kind Stability Summary
main function provisional Run the keelgate-approvals command line (list, show, approve, reject).
Signatures
def main(argv: Sequence[str] | None = None) -> int

keelgate.approvals.rest

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

Symbol Kind Stability Summary
create_app function provisional Build the approvals API over queue, authenticating with tokens.
Signatures
def create_app(queue: ApprovalQueue, tokens: collections.abc.Mapping[str, Approver]) -> fastapi.applications.FastAPI

keelgate.policy.cedar

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

Symbol Kind Stability Summary
CedarEngine class provisional Cedar policy engine (extra keelgate[cedar]) implementing a documented subset of finance_basic.
Signatures
class CedarEngine(pack_dir: Path) -> None
    async decide(policy_input: PolicyInput) -> PolicyDecision
    async decide_document(document: Mapping[str, Any]) -> PolicyDecision

keelgate.loop.langgraph_store

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

Symbol Kind Stability Summary
LangGraphCheckpointStore class provisional Loop checkpoints on a LangGraph saver.
Signatures
class LangGraphCheckpointStore(saver: BaseCheckpointSaver[Any], *, process_lock: Callable[[], AbstractContextManager[None]] | None = None) -> None
    classmethod sqlite(path: str | Path = ':memory:') -> LangGraphCheckpointStore
    history(tenant_id: str, run_id: str) -> list[LoopState]
    load(tenant_id: str, run_id: str) -> LoopState | None
    runs(tenant_id: str, *, prefix: str = '') -> list[str]
    save(state: LoopState) -> None

keelgate.testing.plugin

Pytest fixtures for projects built on Keelgate.

Symbol Kind Stability Summary
fake_llm function provisional
governed_harness function provisional
keelgate_clock function provisional
static_policy function provisional
Signatures
def fake_llm() -> Callable[..., FakeLLM]
def governed_harness(keelgate_clock: ManualClock) -> Callable[..., GovernedHarness]
def keelgate_clock() -> ManualClock
def static_policy() -> Callable[..., StaticPolicyEngine]

keelgate.telemetry.attributes

Span and metric attribute names.

Symbol Kind Stability Summary
AGENT_ID constant provisional
APPROVAL_ACTION constant provisional
APPROVAL_ID constant provisional
APPROVAL_STATUS constant provisional
APPROVAL_TIER constant provisional
ARGS_HASH constant provisional
AS_OF constant provisional
COST_KNOWN constant provisional
COST_USD constant provisional
GEN_AI_AGENT_ID constant provisional
GEN_AI_AGENT_NAME constant provisional
GEN_AI_CONVERSATION_ID constant provisional
GEN_AI_INPUT_MESSAGES constant provisional
GEN_AI_OPERATION_NAME constant provisional
GEN_AI_OUTPUT_MESSAGES constant provisional
GEN_AI_PROVIDER_NAME constant provisional
GEN_AI_REQUEST_MAX_TOKENS constant provisional
GEN_AI_REQUEST_MODEL constant provisional
GEN_AI_REQUEST_TEMPERATURE constant provisional
GEN_AI_RESPONSE_FINISH_REASONS constant provisional
GEN_AI_RESPONSE_ID constant provisional
GEN_AI_RESPONSE_MODEL constant provisional
GEN_AI_TOKEN_TYPE constant provisional
GEN_AI_TOOL_CALL_ID constant provisional
GEN_AI_TOOL_NAME constant provisional
GEN_AI_TOOL_TYPE constant provisional
GEN_AI_USAGE_INPUT_TOKENS constant provisional
GEN_AI_USAGE_OUTPUT_TOKENS constant provisional
ITERATION constant provisional
LOOP_TYPE constant provisional
MAX_ATTRIBUTE_CHARS constant provisional
MEMORY_OP constant provisional
MEMORY_RESULTS constant provisional
MEMORY_TIER constant provisional
METRIC_COST constant provisional
METRIC_TOKEN_USAGE constant provisional
OP_CHAT constant provisional
OP_EXECUTE_TOOL constant provisional
OP_INVOKE_AGENT constant provisional
POLICY_EFFECT constant provisional
POLICY_ENGINE constant provisional
POLICY_REASONS constant provisional
POLICY_VERSION constant provisional
REPLAY constant provisional
REPLAY_OF constant provisional
RESUMED constant provisional
RUN_ID constant provisional
STOP_REASON constant provisional
TENANT_ID constant provisional
TOOL_CAPABILITY constant provisional
TOOL_ERROR_CODE constant provisional
TOOL_REPLAYED constant provisional
TOOL_SIDE_EFFECT constant provisional
TOOL_STATUS constant provisional
Signatures
AGENT_ID = 'keelgate.agent.id'
APPROVAL_ACTION = 'keelgate.approval.action'
APPROVAL_ID = 'keelgate.approval.id'
APPROVAL_STATUS = 'keelgate.approval.status'
APPROVAL_TIER = 'keelgate.approval.tier'
ARGS_HASH = 'keelgate.tool.args_sha256'
AS_OF = 'keelgate.as_of'
COST_KNOWN = 'keelgate.cost.known'
COST_USD = 'keelgate.cost.usd'
GEN_AI_AGENT_ID = 'gen_ai.agent.id'
GEN_AI_AGENT_NAME = 'gen_ai.agent.name'
GEN_AI_CONVERSATION_ID = 'gen_ai.conversation.id'
GEN_AI_INPUT_MESSAGES = 'gen_ai.input.messages'
GEN_AI_OPERATION_NAME = 'gen_ai.operation.name'
GEN_AI_OUTPUT_MESSAGES = 'gen_ai.output.messages'
GEN_AI_PROVIDER_NAME = 'gen_ai.provider.name'
GEN_AI_REQUEST_MAX_TOKENS = 'gen_ai.request.max_tokens'
GEN_AI_REQUEST_MODEL = 'gen_ai.request.model'
GEN_AI_REQUEST_TEMPERATURE = 'gen_ai.request.temperature'
GEN_AI_RESPONSE_FINISH_REASONS = 'gen_ai.response.finish_reasons'
GEN_AI_RESPONSE_ID = 'gen_ai.response.id'
GEN_AI_RESPONSE_MODEL = 'gen_ai.response.model'
GEN_AI_TOKEN_TYPE = 'gen_ai.token.type'
GEN_AI_TOOL_CALL_ID = 'gen_ai.tool.call.id'
GEN_AI_TOOL_NAME = 'gen_ai.tool.name'
GEN_AI_TOOL_TYPE = 'gen_ai.tool.type'
GEN_AI_USAGE_INPUT_TOKENS = 'gen_ai.usage.input_tokens'
GEN_AI_USAGE_OUTPUT_TOKENS = 'gen_ai.usage.output_tokens'
ITERATION = 'keelgate.loop.iteration'
LOOP_TYPE = 'keelgate.loop.type'
MAX_ATTRIBUTE_CHARS = 256
MEMORY_OP = 'keelgate.memory.operation'
MEMORY_RESULTS = 'keelgate.memory.results'
MEMORY_TIER = 'keelgate.memory.tier'
METRIC_COST = 'keelgate.llm.cost'
METRIC_TOKEN_USAGE = 'gen_ai.client.token.usage'
OP_CHAT = 'chat'
OP_EXECUTE_TOOL = 'execute_tool'
OP_INVOKE_AGENT = 'invoke_agent'
POLICY_EFFECT = 'keelgate.policy.effect'
POLICY_ENGINE = 'keelgate.policy._engine'
POLICY_REASONS = 'keelgate.policy.reasons'
POLICY_VERSION = 'keelgate.policy.version'
REPLAY = 'keelgate.replay'
REPLAY_OF = 'keelgate.replay.of_trace_id'
RESUMED = 'keelgate.loop.resumed'
RUN_ID = 'keelgate.run.id'
STOP_REASON = 'keelgate.loop.stop_reason'
TENANT_ID = 'keelgate.tenant.id'
TOOL_CAPABILITY = 'keelgate.tool.capability'
TOOL_ERROR_CODE = 'keelgate.tool.error_code'
TOOL_REPLAYED = 'keelgate.tool.idempotent_replay'
TOOL_SIDE_EFFECT = 'keelgate.tool.side_effect'
TOOL_STATUS = 'keelgate.tool.status'