/** * Type surface of Krikos, the identity registry and lifecycle manager for fleets of Massive * Intelligence (IM) agents and non-human entities (NHE). * * Everything here is data: agents, capabilities, lifecycle states, audit events, the persistence * contract, and the registry interface. No behavior lives in this module; the engine in * `core/createKrikos.ts` and the helpers in `lifecycle/`, `capability/`, and `identity/` * implement it. * * @packageDocumentation */ /** * Opaque agent identifier. A branded string so a raw string cannot be passed where an issued id * is expected without an explicit cast, catching id-versus-name confusion at compile time. */ type AgentId = string & { readonly __brand: 'KrikosAgentId'; }; /** * The lifecycle state of an agent identity. * * - `provisioned`: issued but not yet activated; cannot authorize anything. * - `active`: live; the only state in which {@link Krikos.can} can return true. * - `suspended`: temporarily disabled, reversibly, by an operator; cannot authorize. * - `revoked`: permanently invalidated; terminal except for archival. * - `expired`: past its expiration instant; terminal except for renewal or archival. * - `archived`: retired record kept for audit; terminal. */ type AgentStatus = 'provisioned' | 'active' | 'suspended' | 'revoked' | 'expired' | 'archived'; /** * A declared capability: a named permission with the actions it covers and, optionally, the * resources it is scoped to. * * Resource scoping is opt-in. A capability with no `resources` grants its actions over any * resource; a capability with `resources` grants its actions only over a resource that matches * one of the patterns (exact, or a trailing `*` prefix wildcard, see `capability/match.ts`). */ interface Capability { /** Capability name, for example `filesystem`, `http`, `payments`. Non-empty. */ readonly name: string; /** Actions this capability permits, for example `['read', 'write']`. Non-empty. */ readonly actions: readonly string[]; /** * Optional resource patterns this capability is scoped to, for example `['/data/*']`. When * omitted, the capability applies to any resource. */ readonly resources?: readonly string[]; } /** * The principal that issued an agent identity: a platform team, a parent orchestrator, or * another agent. Provenance of who created the identity, mandatory on every agent. */ interface Issuer { /** Stable issuer identifier, for example `platform-team` or a parent agent id. Non-empty. */ readonly id: string; /** Optional human-readable issuer name. */ readonly name?: string; } /** * Detached cryptographic signature over an agent's signable passport. * * Produced by the Ed25519 signer in `@takk/krikos/a2a`. The core registry never requires a * signature; it stores one when the host attaches it via {@link Krikos.attachSignature}, and it * is cleared on {@link Krikos.rotate} because rotation changes the fingerprint and the old * signature no longer attests to it. */ interface AgentSignature { /** Signature algorithm. Ed25519 is the only algorithm shipped in 1.0. */ readonly algorithm: 'ed25519'; /** Public key as a base64url-encoded raw 32-byte Ed25519 key. */ readonly publicKey: string; /** Detached signature as base64url, over the canonical signable passport. */ readonly signature: string; /** The fingerprint that was signed, so verification can detect a fingerprint change. */ readonly signedFingerprint: string; /** Credential version that was signed, so a rotated credential invalidates the signature. */ readonly signedCredentialVersion: number; } /** * A registered agent identity and its full lifecycle state. The central record of Krikos. * * All fields are readonly; the engine never mutates a record in place, it replaces it, so a * snapshot taken at any instant is a stable, structurally-shared view. */ interface Agent { /** Opaque, registry-unique identifier. */ readonly id: AgentId; /** Human-readable agent name, for example `billing-reconciler`. Non-empty. */ readonly name: string; /** Current lifecycle state. */ readonly status: AgentStatus; /** Declared capabilities. May be empty: an agent with no capabilities can authorize nothing. */ readonly capabilities: readonly Capability[]; /** The principal that issued this identity. */ readonly issuer: Issuer; /** * Optional framework tag, for example `hermes`, `mastra`, `vercel-ai-sdk`, `langchain`. Free * text; Krikos does not enumerate frameworks. */ readonly framework?: string; /** * Deterministic content fingerprint of the identity-defining fields at the current credential * version. Stable across runs and runtimes; recomputed on rotate and on capability changes. */ readonly fingerprint: string; /** Credential epoch, starting at 1 and incremented by {@link Krikos.rotate}. */ readonly credentialVersion: number; /** Arbitrary string-to-string metadata, for example team, environment, cost center. */ readonly metadata: Readonly>; /** Transaction instant (epoch ms) at which the identity was issued. */ readonly issuedAt: number; /** Transaction instant at which the agent was first activated, or null if never activated. */ readonly activatedAt: number | null; /** Expiration instant (epoch ms), or null for an identity that never expires. */ readonly expiresAt: number | null; /** Transaction instant at which the agent was revoked, or null if not revoked. */ readonly revokedAt: number | null; /** Transaction instant at which the agent was archived, or null if not archived. */ readonly archivedAt: number | null; /** Transaction instant of the last state change to this record. */ readonly updatedAt: number; /** Optional detached cryptographic signature attesting to the fingerprint. */ readonly signature?: AgentSignature; } /** Input to {@link Krikos.issue}. Defaults are applied by the engine. */ interface AgentInput { /** Human-readable agent name. Required, non-empty. */ readonly name: string; /** The issuing principal. Required. */ readonly issuer: Issuer; /** Declared capabilities. Defaults to an empty list. */ readonly capabilities?: readonly Capability[]; /** Optional framework tag. */ readonly framework?: string; /** Optional metadata. Defaults to an empty object. */ readonly metadata?: Readonly>; /** * Optional explicit id. When omitted, the engine derives a deterministic id from its seeded * counter. Supplying an id that already exists throws `ERR_CONFLICT`. */ readonly id?: string; /** * Optional expiration instant (epoch ms). When omitted and the registry has a `defaultTtlMs`, * the engine sets `expiresAt = issuedAt + defaultTtlMs`; otherwise the identity never expires. */ readonly expiresAt?: number | null; /** * When true, the agent is issued directly into the `active` state instead of `provisioned`, * recording both the issue and the activation. Defaults to false. */ readonly activate?: boolean; } /** Filter for {@link Krikos.list}. All clauses are conjunctive; omitted clauses match anything. */ interface AgentFilter { /** Restrict to a single lifecycle state, or any of several. */ readonly status?: AgentStatus | readonly AgentStatus[]; /** Restrict to agents that declare a capability with this name. */ readonly capability?: string; /** Restrict to agents issued by this issuer id. */ readonly issuer?: string; /** Restrict to agents tagged with this framework. */ readonly framework?: string; /** * When provided, restrict to agents whose `expiresAt` is at or before this instant (epoch ms). * Useful to find identities due to expire within a window. */ readonly expiringBefore?: number; } /** The kinds of operation recorded in the audit trail. The space is minor-extensible. */ type AuditKind = 'agent.issued' | 'agent.activated' | 'agent.suspended' | 'agent.resumed' | 'agent.revoked' | 'agent.rotated' | 'agent.renewed' | 'agent.expired' | 'agent.archived' | 'agent.signed' | 'capability.granted' | 'capability.revoked' | 'access.allowed' | 'access.denied'; /** * One immutable, hash-chained entry in the audit trail. * * Each event is frozen on creation and on load. `hash` is the FNV-1a digest of the previous * event's `hash` fused with this event's own fields, so any retroactive edit to an earlier event * breaks every subsequent `hash`. {@link Krikos.verifyAuditChain} recomputes the chain to detect * tampering. This is integrity bookkeeping, not a cryptographic guarantee; a host that needs * cryptographic non-repudiation signs the snapshot or individual events out of band. */ interface AuditEvent { /** Monotonic sequence number, starting at 1. */ readonly sequence: number; /** Transaction instant (epoch ms) at which the event was recorded. */ readonly at: number; /** What happened. */ readonly kind: AuditKind; /** The agent the event concerns. */ readonly agentId: AgentId; /** Who performed the operation: an operator id, a service name, or `system` for sweeps. */ readonly actor: string; /** Human-readable detail, for example a suspension reason or a capability name. */ readonly detail: string; /** `hash` of the preceding event, or the genesis sentinel for the first event. */ readonly prevHash: string; /** Digest of this event chained onto `prevHash`. */ readonly hash: string; } /** Options of {@link Krikos.auditTrail}. */ interface AuditOptions { /** Return only the most recent `limit` events, in chronological order. */ readonly limit?: number; /** Restrict to events about a single agent. */ readonly agentId?: string; /** Restrict to events of a single kind. */ readonly kind?: AuditKind; } /** Aggregate counts over the registry, from {@link Krikos.stats}. */ interface RegistryStats { /** Total agents in the registry, in any state. */ readonly total: number; /** Count per lifecycle state. Every {@link AgentStatus} key is present, possibly zero. */ readonly byStatus: Readonly>; /** Number of agents currently authorizable (active and not past expiry). */ readonly authorizable: number; /** Number of distinct issuers. */ readonly issuers: number; /** Number of audit events recorded. */ readonly auditEvents: number; } /** A telemetry event emitted by the engine to registered listeners. Best-effort, never awaited. */ interface TelemetryEvent { /** Event kind, mirroring the audit kinds plus engine-internal signals. */ readonly kind: AuditKind | 'registry.pruned' | 'registry.swept'; /** The agent the event concerns, when applicable. */ readonly agentId?: AgentId; /** Transaction instant (epoch ms). */ readonly at: number; /** Optional structured detail. */ readonly detail?: Readonly>; } /** A telemetry listener. Exceptions thrown by a listener are swallowed and never reach a caller. */ type TelemetryListener = (event: TelemetryEvent) => void; /** * Serialized registry state. One object, JSON-round-trippable, persisted and restored by a * {@link StateBackend}. `version` gates forward compatibility: an engine refuses to load a * snapshot whose version it does not understand rather than half-interpreting it. */ interface StateSnapshot { /** Snapshot layout version. The only supported value in 1.0 is the literal 1. */ readonly version: 1; /** Every agent record, in issue order. */ readonly agents: readonly Agent[]; /** The full audit trail, in sequence order. */ readonly audit: readonly AuditEvent[]; /** The id-generator counter, so restored engines never re-mint a used id. */ readonly counter: number; /** The audit sequence counter, so restored engines continue the chain. */ readonly sequence: number; } /** * Persistence contract. A backend is two async methods over one snapshot; the engine owns all * structure and validation, the backend only moves bytes. */ interface StateBackend { /** Resolves to the last saved snapshot, or `undefined` before the first save. */ load(): Promise; /** Persists `snapshot`. Resolves when durable to whatever degree the backend guarantees. */ save(snapshot: StateSnapshot): Promise; } /** * Minimal structural key-value client, satisfied by Redis, Upstash, Cloudflare KV, and most * embedded stores in one line. Krikos never constructs the client and never sees credentials. */ interface KvLike { /** Returns the string at `key`, or `null`/`undefined` when absent. */ get(key: string): Promise | string | null | undefined; /** Stores `value` at `key`. */ set(key: string, value: string): Promise | unknown; } /** Options of {@link createKrikos}. */ interface KrikosOptions { /** * Seed for the deterministic id generator. Two registries with the same seed mint the same id * sequence. Defaults to a fixed constant so behavior is reproducible out of the box. */ readonly seed?: number; /** * Clock returning the current transaction instant in epoch milliseconds. Inject a fixed clock * in tests; the engine never reads the wall clock itself. Defaults to `() => Date.now()`. */ readonly clock?: () => number; /** Persistence backend. Defaults to an in-memory backend that does not survive the process. */ readonly state?: StateBackend; /** Prefix for generated agent ids. Defaults to `agt`. Ids look like `agt_`. */ readonly idPrefix?: string; /** Hard cap on the number of agents. Issuing past it throws `ERR_LIMIT_REACHED`. */ readonly maxAgents?: number; /** Default time-to-live in milliseconds applied to issued agents without an explicit expiry. */ readonly defaultTtlMs?: number; /** * When true, read paths ({@link Krikos.get}, {@link Krikos.list}, {@link Krikos.can}) treat an * agent whose `expiresAt` is at or before now as expired even before an explicit sweep, so * authorization is fail-closed. The persisted `status` is only changed by an explicit * {@link Krikos.sweepExpired}. Defaults to true. */ readonly autoExpire?: boolean; /** * When true, least-privilege is enforced: an authorization request that names a resource is * admitted only by a capability that declares explicit resource patterns. An unscoped capability * (no `resources`) then authorizes only resource-less requests. Defaults to false, where an * unscoped capability admits any resource. Turn this on for a fail-safe default in * security-sensitive fleets. */ readonly requireResourceScope?: boolean; /** Optional telemetry listener, registered before any operation runs. */ readonly telemetry?: TelemetryListener; } /** * The Krikos registry: issue cryptographically-fingerprinted agent identities, declare and * enforce capabilities, drive each identity through an audited lifecycle, and reconstruct who * could do what at any point from an append-only trail. */ interface Krikos { /** * Issues a new agent identity into the `provisioned` state (or `active` when * `input.activate` is true), computes its fingerprint, and records the issue in the audit * trail. * * @throws KrikosError `ERR_INVALID_INPUT` for a missing name or issuer, or a malformed * capability. * @throws KrikosError `ERR_CONFLICT` when `input.id` is already registered. * @throws KrikosError `ERR_LIMIT_REACHED` when `maxAgents` would be exceeded. */ issue(input: AgentInput): Agent; /** Returns the agent with `id`, or `undefined`. Reflects auto-expiry when enabled. */ get(id: string): Agent | undefined; /** Returns all agents matching `filter`, in issue order. Reflects auto-expiry when enabled. */ list(filter?: AgentFilter): readonly Agent[]; /** Activates a provisioned or suspended agent. @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ activate(id: string, actor?: string): Agent; /** Suspends an active agent, reversibly. @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ suspend(id: string, reason?: string, actor?: string): Agent; /** Resumes a suspended agent back to active. @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ resume(id: string, actor?: string): Agent; /** Permanently revokes an agent. @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ revoke(id: string, reason?: string, actor?: string): Agent; /** * Rotates the agent's credential: bumps `credentialVersion`, recomputes the fingerprint, and * clears any attached signature (which no longer attests to the new fingerprint). The * lifecycle state is preserved. @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ rotate(id: string, actor?: string): Agent; /** Changes an agent's expiration instant (null for never). @throws ERR_NOT_FOUND. */ renew(id: string, expiresAt: number | null, actor?: string): Agent; /** Archives a terminal agent (revoked or expired). @throws ERR_NOT_FOUND, ERR_INVALID_TRANSITION. */ archive(id: string, actor?: string): Agent; /** Adds a capability to an agent and recomputes the fingerprint. @throws ERR_NOT_FOUND, ERR_INVALID_INPUT. */ grant(id: string, capability: Capability, actor?: string): Agent; /** Removes a named capability and recomputes the fingerprint. @throws ERR_NOT_FOUND. */ ungrant(id: string, capabilityName: string, actor?: string): Agent; /** * Pure, side-effect-free authorization check: true only when the agent exists, is `active`, * is not past expiry, and declares a capability whose name matches, whose actions include * `action`, and whose resource scope admits `resource` (when given). Records nothing. */ can(id: string, capability: string, action: string, resource?: string): boolean; /** * Enforcement-point authorization: the same decision as {@link Krikos.can}, additionally * recorded in the audit trail as `access.allowed` or `access.denied`. Use this at the boundary * where an action is actually permitted or refused. */ authorize(id: string, capability: string, action: string, resource?: string, actor?: string): boolean; /** Attaches a detached cryptographic signature to an agent. @throws ERR_NOT_FOUND, ERR_INVALID_INPUT. */ attachSignature(id: string, signature: AgentSignature, actor?: string): Agent; /** * Transitions every agent whose `expiresAt` is at or before `now` and whose state is * provisioned, active, or suspended into `expired`, recording each. Returns the agents that * were newly expired. `now` defaults to the registry clock. */ sweepExpired(now?: number): readonly Agent[]; /** Returns audit events matching `options`, in chronological order. */ auditTrail(options?: AuditOptions): readonly AuditEvent[]; /** Recomputes the audit hash chain and returns true when it is intact. */ verifyAuditChain(): boolean; /** Returns aggregate registry statistics. */ stats(): RegistryStats; /** * Drops archived agent records archived at or before `beforeArchivedAt`, bounding the live * registry footprint while keeping every non-archived identity. The append-only audit trail is * never pruned: the history of what happened to a retired identity is retained for compliance, * and the hash chain stays intact. Returns the number of agent records pruned. */ prune(beforeArchivedAt: number): number; /** Persists the current snapshot, awaiting any pending restore first. */ flush(): Promise; /** Returns a frozen snapshot of the current state. */ snapshot(): StateSnapshot; /** Registers a telemetry listener and returns an unsubscribe function. */ on(listener: TelemetryListener): () => void; } export type { Agent as A, Capability as C, Issuer as I, Krikos as K, RegistryStats as R, StateBackend as S, TelemetryEvent as T, AgentFilter as a, AgentId as b, AgentInput as c, AgentSignature as d, AgentStatus as e, AuditEvent as f, AuditKind as g, AuditOptions as h, KrikosOptions as i, KvLike as j, StateSnapshot as k, TelemetryListener as l };