UNPKG

@sema-agent/core

Version:

Stateless, task-oriented AI agent core

92 lines (91 loc) • 5.7 kB
/** * A background agent's CURRENT state — the one reading every read face renders. * * A parent model answers "how far along is it?" from what its read faces show. Three faces read a * background agent: AgentTranscript (its recent steps), ListAgents (the roster) and TaskOutput (the poll). * Each one projects {@link readAgentState}'s result and renders it with {@link agentStateSummary} / * {@link agentActivityClause} / {@link agentApprovalClause}; none derives status, activity time or approval * facts on its own, so the three cannot disagree about one agent at one instant. * * Sources: * - the registry row — the live handle this engine process runs ({@link agentStateSourceOfHandle}), or the * durable record when no live handle answers ({@link agentStateSourceOfRecord}); * - for a row whose status is `parked` only, the approval checkpoint the row names, read through the run's * checkpoint seat (the same seat the Agent tool parks children into). The checkpoint token is the resume * capability and never leaves this module: a face renders the checkpoint's non-secret `checkpointId`. * * Absence is stated, never left silent: a missing activity time renders as "last activity unknown (…why)", * and a parked row whose card cannot be read carries `approval.state` `missing` / `unreadable`. A face that * goes quiet on a fact invites the reader to fill the gap with a guess. */ import type { SemaTaskStatus } from "./task-registry-shared.js"; import { type CheckpointSeat } from "./checkpoint-seat.js"; /** The approval card's state: the checkpoint row's own `status`, plus `missing` (the row names no card, or * the store holds none under its token) and `unreadable` (no checkpoint seat on this face, or the store * threw). The same five answers the parked-row reconciliation sweep gives the same question. */ export type AgentApprovalState = "pending" | "resolved" | "expired" | "missing" | "unreadable"; /** The approval checkpoint a parked agent waits on. */ export interface AgentApproval { state: AgentApprovalState; /** The checkpoint's non-secret identity (the card id a person sees in the approval inbox). Present when * the card was read and carries one. */ checkpointId?: string; /** The card's SLA / abandonment deadline, epoch ms. Present when the card was read and carries one. */ deadline?: number; } /** Where the row was read from: the live handle this engine process runs, or the durable record (the * process that wrote it may be another one, or gone). */ export type AgentStateRow = "live_row" | "durable_row"; /** One agent's state at {@link readAt}. Times are epoch ms; relative ages appear only in rendered text. */ export interface AgentState { /** The registry row's own status word. A word outside {@link SemaTaskStatus} reads `unknown`. */ status: SemaTaskStatus | "unknown"; /** The instant this state was read (epoch ms) — the base every rendered age is computed from. */ readAt: number; row: AgentStateRow; /** The row's last recorded change (epoch ms). Live row: every state transition and every tool start / * end of the direct child moves it. Durable row: when it parked (`parked`) or settled (terminal). * ABSENT when the source holds no such time — a durable row still marked running (activity in another * process is not observable here), or a row that never recorded one. */ lastActivityAt?: number; /** Present exactly when {@link status} is `parked`. */ approval?: AgentApproval; } /** The row facts {@link readAgentState} reads. Built by the two projections below — never by a face. */ export interface AgentStateSource { status: string; row: AgentStateRow; lastActivityAt?: number; parkedCheckpointToken?: string; } /** The live handle's facts: `updatedAt` is moved by every state transition and by each direct-child tool * lifecycle beat, so it is the row's last activity. */ export declare function agentStateSourceOfHandle(h: { status: string; updatedAt: number; parkedCheckpointToken?: string; }): AgentStateSource; /** The durable record's facts. Its `updatedAt` is the writer's liveness lease renewal, not activity, so it * is never read as such: a parked record answers with `parkedAt`, a terminal one with `settledAt`, and a * record still marked running answers nothing. */ export declare function agentStateSourceOfRecord(r: { status: string; parkedAt?: number; settledAt?: number; parkedCheckpointToken?: string; }): AgentStateSource; /** * THE state of one background agent at `now`. Synchronous unless the row is parked on a card the seat can * read — only that row takes an async frame (the store read); every other row is answered in place. * The caller must already have passed the row through its face's access check: this reads the card the * row names and authorizes nothing. */ export declare function readAgentState(src: AgentStateSource, seat: CheckpointSeat | undefined, now: number): AgentState | Promise<AgentState>; /** `Ns` / `Nm` / `Nh` / `Nd` — a bounded age word; a negative span reads as `0s`. */ export declare function formatAgo(ms: number): string; /** `last activity <age> ago`, or the reason the time is unknown. */ export declare function agentActivityClause(s: AgentState): string; /** `approval <card id> <state>[ (deadline in <age> | deadline passed <age> ago)]` — facts only. */ export declare function agentApprovalClause(a: AgentApproval, readAt: number): string; /** The one-line state sentence: `<status> · <activity clause>[ · <approval clause>]`. */ export declare function agentStateSummary(s: AgentState): string;