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