agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
95 lines (94 loc) • 5.36 kB
TypeScript
/**
* callerIdentity — WHO a resumed run is for, read back from the run it resumes.
*
* Seed (`stages/seed.ts · seedFrom`) writes the run's memory namespace on one of
* three rungs: the caller's EXPLICIT identity; `{ conversationId: sessionId }`
* for a session-bound run that named none (recorded as
* `runIdentitySource: 'session'`); or the per-run default
* `{ conversationId: '<runId>' }`. Only the first rung is a caller's identity —
* the other two are derivations this library made, and `Agent.lastRunIdentity`
* (what `checkpoint()`, `EventMeta.principal` and a tool's `ctx.identity` read)
* must stay absent for them, so "absent" keeps meaning "nobody named one".
*
* `Agent.resume` does not re-seed: the resumed run keeps the paused run's
* `runIdentity`, restored from the flowchart checkpoint. So the checkpoint is
* the one carrier of "who the paused run was for" that survives a shared
* instance, a pooled instance rebuilt after eviction, and a restart. This
* module is the inverse of seed's rungs over that carrier.
*
* THE LAW: never drop an identity the caller named, never promote a derived
* one. The session rung is recorded, so it is certain. The per-run default is
* NOT recorded (writing a marker would change the committed keys of every
* run that names nobody), and a flowchart checkpoint does not carry the paused
* run's id — so it is recognised by the strongest evidence available, in order:
*
* 1. **The caller named exactly this identity on this instance** (`named`, the
* instance's last caller identity) → it is the caller's. This is what keeps
* a single-user app's `{ conversationId: 'run-1-1' }` across a bare resume.
* 2. **This instance minted that conversation id as a run id** (`minted`) →
* derived, for certain.
* 3. **The checkpoint's own receipt names that id as its run** (the paused run's
* `receipt.basis.runId`, present once it called a model) → derived, for
* certain.
* 4. **LAST RESORT — the SHAPE `makeRunId` mints** (`isMintedRunId`), for a
* checkpoint from another instance with no receipt to match → read as
* derived. The one stated edge: a caller who names exactly
* `{ conversationId: 'run-<digits>-<digits>' }` and resumes it BARE loses
* it whenever step 1 cannot see it — on another instance, or on the same
* instance once anyone else has run in between — and no receipt matches.
* The fail-closed direction: a derived namespace is never published as a
* person. Pass `identity` on `resume` to keep it.
*
* A checkpoint names who it is for; it is not proof. A host that lets
* checkpoints leave its trust boundary signs them or keeps them server-side —
* see `Agent.resume`.
*/
import type { MemoryIdentity } from '../../memory/identity/types.js';
/** What the resuming instance knows about identities and run ids. */
export interface CallerIdentityEvidence {
/** The identity a caller last NAMED on this instance, if any. */
readonly named?: MemoryIdentity;
/** Run ids this instance minted (bounded). */
readonly minted?: ReadonlySet<string>;
}
/**
* The identity the CALLER of the run whose state this is named, as a COPY, or
* undefined when that run's identity was derived (session rung, per-run
* default), is malformed, or the state carries none.
*
* @example
* ```ts
* // Agent.resume — a resuming call that names a DIFFERENT identity is refused:
* const named = callerIdentityOf(checkpoint.sharedState, { named: last, minted });
* ```
*/
export declare function callerIdentityOf(state: unknown, evidence?: CallerIdentityEvidence): MemoryIdentity | undefined;
/**
* The identity the paused run's memory namespace and credentials are restored
* from — `scope.runIdentity` as the checkpoint carries it, whatever its source —
* as a validated copy; `undefined` when absent; `'malformed'` when the
* checkpoint's own fields disagree: a value that is not an identity, or a
* session-rung marker (`runIdentitySource: 'session'`) on anything but exactly
* `{ conversationId }` — the only shape seed's session rung writes.
*/
export declare function restoredIdentityOf(state: unknown): MemoryIdentity | 'malformed' | undefined;
/**
* The session the paused run ran under, as its own state records it: the
* `runSessionId` seed writes on every session-bound run, else — for a
* checkpoint written before that key existed — the session rung's
* `runIdentity.conversationId`. `undefined` when neither says.
*/
export declare function pausedSessionOf(state: unknown): string | undefined;
/**
* Refuse, when a run BEGINS, an identity that is not one: not an object, a
* field that is present and not a string, or no field at all. TypeScript
* already forbids these; a JavaScript caller can still pass them, and without
* this door the run would start, pause, and then every resume of it — bare ones
* included — would refuse as a malformed checkpoint (recheck NIT 2). The
* refusal lands where the mistake was made.
*
* @throws TypeError naming the door and the field.
*/
export declare function assertIdentityShape(identity: unknown, door: string): void;
/** Do two identities name the same tenant, principal and conversation? */
export declare function sameIdentity(a: MemoryIdentity, b: MemoryIdentity): boolean;