Add the structured agent-session host

One place where the lease, the journal, and the provider adapter meet: every
mutating call recomputes the fingerprint, admits through the durable operation
ledger, checks the lease, then acts, so no method can grow its own admission
rules. Journal recovery rehydrates an unreadable journal from provider history
into a fresh epoch and tells subscribers which reset they are seeing.

Attach adjudicates the leases it loaded from disk before granting any writer.
Without that step every lease stays unreconciled for the life of the process and
a restarted host refuses every persisted session forever.
This commit is contained in:
Brennan Benson
2026-08-09 13:35:19 -07:00
parent a0d829194e
commit fc36d58bbb
17 changed files with 2685 additions and 0 deletions
@@ -0,0 +1,33 @@
// Ledger admission for mutations that are not reservations — send, cancel, an
// approval answer. Split from the store so the store keeps only the transaction.
import {
agentSessionOperationKey,
evaluateAgentSessionOperation,
pruneAgentSessionOperationRows,
type AgentSessionOperationDecision,
type AgentSessionOperationRow
} from '../../shared/agent-session-operation-ledger'
export type AgentSessionOperationAdmission = {
callerKey: string
operationId: string
fingerprint: string
now: number
}
type OperationRows = Map<string, AgentSessionOperationRow>
/** Prune, evaluate, and (on admit) place the row. The caller runs this inside one
* transaction, so two concurrent copies of an operation id cannot both admit. */
export function admitAgentSessionOperationRow(
rows: OperationRows,
args: AgentSessionOperationAdmission
): { rows: OperationRows; decision: AgentSessionOperationDecision } {
const pruned = pruneAgentSessionOperationRows(rows, args.now)
const decision = evaluateAgentSessionOperation({ rows: pruned, ...args })
if (decision.decision === 'admit') {
pruned.set(agentSessionOperationKey(args.callerKey, args.operationId), decision.row)
}
return { rows: pruned, decision }
}
@@ -12,9 +12,14 @@
import {
pruneAgentSessionOperationRows,
agentSessionOperationKey,
type AgentSessionOperationDecision,
type AgentSessionOperationOutcome,
type AgentSessionOperationRow
} from '../../shared/agent-session-operation-ledger'
import {
admitAgentSessionOperationRow,
type AgentSessionOperationAdmission
} from './agent-session-operation-admission'
import type { AgentSessionOwnerProbe } from '../../shared/agent-session-lease-adjudication'
import { classifyObservedAgentSessionSpawnToken } from '../../shared/agent-session-lease-adjudication'
import type { AgentSessionProviderHandleLink } from '../../shared/agent-session-provider-handle'
@@ -276,6 +281,17 @@ export class AgentSessionRecordStore {
})
}
/** Admits one non-reservation mutation through the durable ledger. */
async admitOperation(
args: AgentSessionOperationAdmission
): Promise<AgentSessionOperationDecision> {
return this.transact(() => {
const admitted = admitAgentSessionOperationRow(this.state.operations, args)
this.state.operations = admitted.rows
return admitted.decision
})
}
async recordOperationOutcome(args: {
callerKey: string
operationId: string