blob: 369efa0fbdfe8e5297ac43118e24a9556985468d [file]
import {
requireCount,
requireEncodedByteLimit,
requireEntityId,
requireExactRecord,
requireShapedRecord,
requireUtf8String,
} from './codec.js';
import { invalidProtocolFrame } from './errors.js';
import { defineOperation } from './operation-spec.js';
export const AGENT_GRAPH_CLIENT_SCHEMA_VERSION = 1 as const;
export const AGENT_GRAPH_RESULT_MAX_BYTES = 48 * 1024;
export const AGENT_GRAPH_TERMINAL_CURSOR_MAX_BYTES = 2 * 1024;
export const AGENT_GRAPH_MAX_OPERATORS = 32;
export const AGENT_GRAPH_MAX_EDGES = 64;
export const AGENT_GRAPH_MAX_WORK = 32;
export const AGENT_GRAPH_MAX_RECONCILIATION_FAILURES = 32;
export const AGENT_GRAPH_MAX_STOPPED_TARGETS = 16;
export const AGENT_GRAPH_MAX_CLAIMS = 32;
export const AGENT_GRAPH_MAX_CONTROL_DECISIONS = 16;
export const AGENT_GRAPH_MAX_ACTIVITY = 32;
export const AGENT_GRAPH_MAX_TERMINAL_ACTIVITY = 32;
export const AGENT_GRAPH_MAX_OPERATOR_REFS = 16;
export const AGENT_GRAPH_MAX_OPERATOR_READINESS = 2;
export const AGENT_GRAPH_MAX_READINESS_WAITS = 4;
export const AGENT_GRAPH_MAX_INPUT_ROUTE_OPERATORS = 4;
export const AGENT_GRAPH_MAX_WORK_INPUTS = 64;
export const AGENT_GRAPH_MAX_CONTROL_REFS = 64;
export const AGENT_GRAPH_MAX_INSPECTION_EDGES = 64;
export const AGENT_GRAPH_MAX_INSPECTION_WORK = 32;
export const AGENT_GRAPH_MAX_INSPECTION_CLAIMS = 32;
export const AGENT_GRAPH_MAX_INSPECTION_ACTIVATIONS = 32;
export const AGENT_GRAPH_MAX_INSPECTION_RECORDS = 32;
export const AGENT_GRAPH_EPOCH_PAGE_SIZE = 32;
const AGENT_GRAPH_INSTRUCTION_PREVIEW_MAX_BYTES = 2 * 1024;
const AGENT_GRAPH_REASON_MAX_BYTES = 12 * 1024;
const AGENT_GRAPH_RECONCILIATION_FAILURE_REASON_MAX_BYTES = 4 * 1024;
const QUERY_ERRORS = [
'host_not_ready',
'host_draining',
'operation_unavailable',
'not_found',
'operation_conflict',
'invalid_request',
'persistence_failed',
'internal_failure',
] as const;
const STOP_ERRORS = [
'host_not_ready',
'host_draining',
'operation_unavailable',
'not_found',
'session_archived',
'operation_conflict',
'internal_failure',
] as const;
export type AgentGraphActivationStatus =
| 'running'
| 'completed'
| 'failed'
| 'aborted'
| 'cancelled';
export type AgentGraphClientOperatorStatus =
| 'not_started'
| 'waiting'
| 'runnable'
| 'blocked'
| AgentGraphActivationStatus;
export type AgentGraphClientStatus =
| 'empty'
| 'active'
| 'closing'
| 'waiting'
| 'stopped'
| 'failed'
| 'completed';
export type AgentGraphRecordFacet =
| 'message'
| 'thinking'
| 'error'
| 'tool_call'
| 'tool_dispatch'
| 'tool_result'
| 'artifact_update'
| 'permission_request'
| 'permission_decision'
| 'user_question_request'
| 'transfer'
| 'usage'
| 'completed'
| 'failed'
| 'aborted'
| 'cancelled'
| 'runtime_fact';
export type AgentGraphSupervisorSignal =
| { readonly kind: 'attention'; readonly reason: 'permission_request' | 'user_question_request' }
| {
readonly kind: 'terminal';
readonly status: 'completed' | 'failed' | 'aborted' | 'cancelled';
};
export type AgentGraphReadinessWait =
| { readonly kind: 'input_route'; readonly upstreamOperatorIds: readonly string[] }
| {
readonly kind: 'activation_missing' | 'activation_running';
readonly operatorId: string;
readonly activationId: string;
};
export interface AgentGraphClientRunRef {
readonly sessionId: string;
readonly agentRunId: string;
readonly turnId?: string;
}
export interface AgentGraphClientOperator {
readonly operatorId: string;
readonly childSessionId: string;
readonly provisionId: string;
readonly agentId: string;
readonly provisionedAt: number;
readonly status: AgentGraphClientOperatorStatus;
readonly inboundEdgeIds: readonly string[];
readonly outboundEdgeIds: readonly string[];
readonly scheduledWorkIds: readonly string[];
readonly readiness: readonly {
readonly readinessId: string;
readonly status: 'waiting' | 'runnable';
readonly waitingFor: readonly AgentGraphReadinessWait[];
readonly omittedWaitingFor: number;
}[];
readonly omitted: {
readonly inboundEdgeIds: number;
readonly outboundEdgeIds: number;
readonly scheduledWorkIds: number;
readonly readiness: number;
readonly readinessWaits: number;
};
readonly currentActivation?: {
readonly activationId: string;
readonly status: AgentGraphActivationStatus;
readonly recordCount: number;
readonly firstEventTime: number;
readonly lastEventTime: number;
readonly terminalRecordId?: string;
readonly run: AgentGraphClientRunRef;
};
}
export interface AgentGraphClientEdge {
readonly edgeId: string;
readonly fromOperatorId: string;
readonly toOperatorId: string;
}
export interface AgentGraphClientScheduledWork {
readonly workId: string;
readonly target:
| { readonly kind: 'agent'; readonly agentId: string }
| { readonly kind: 'preset'; readonly presetId: string }
| { readonly kind: 'operator'; readonly operatorId: string };
readonly inputIds: readonly string[];
readonly selectedResultInputs?: readonly {
readonly sourceGraphId: string;
readonly resultId: string;
}[];
readonly replaces?: string;
readonly status: 'requested' | 'stopped' | 'superseded';
readonly instructionPreview: string;
readonly instructionTruncated: boolean;
readonly revision: number;
readonly committedAt: number;
}
export interface AgentGraphClientReconciliationFailure {
readonly workId: string;
readonly phase: 'schedule' | 'topology' | 'stop' | 'render' | 'dispatch';
readonly reason: string;
}
export interface AgentGraphClientStoppedTarget {
readonly targetId: string;
readonly reason: string;
readonly revision: number;
readonly committedAt: number;
}
export interface AgentGraphClientFinish {
readonly resultIds: readonly string[];
readonly reason: string;
readonly revision: number;
readonly committedAt: number;
}
export interface AgentGraphClientControlDecision {
readonly updateId: string;
readonly revision: number;
readonly committedAt: number;
readonly source: {
readonly sessionId: string;
readonly agentRunId: string;
readonly turnId: string;
readonly toolCallId: string;
};
readonly addedWorkIds: readonly string[];
readonly stoppedTargetIds: readonly string[];
readonly selectedResultIds: readonly string[];
}
export interface AgentGraphClientClaimRef {
readonly claimId: string;
readonly intentId: string;
readonly operatorId: string;
readonly childSessionId: string;
readonly run: AgentGraphClientRunRef;
readonly admissionState: 'claimed' | 'executing' | 'cancelled';
readonly claimedAt: number;
}
export interface AgentGraphClientActivity {
readonly recordId: string;
readonly operatorId: string;
readonly activationId: string;
readonly eventTime: number;
readonly facets: readonly AgentGraphRecordFacet[];
readonly signals: readonly AgentGraphSupervisorSignal[];
readonly run: AgentGraphClientRunRef;
}
export interface AgentGraphClientSnapshot {
readonly schemaVersion: typeof AGENT_GRAPH_CLIENT_SCHEMA_VERSION;
readonly rootSessionId: string;
readonly graphId: string;
readonly orchestrationMode: 'graph' | 'swarm';
readonly snapshotVersion: `sha256:${string}`;
readonly status: AgentGraphClientStatus;
readonly scheduleRevision: number;
readonly topologyFingerprint: `sha256:${string}`;
readonly closed: boolean;
readonly latestEventTime?: number;
readonly operators: readonly AgentGraphClientOperator[];
readonly edges: readonly AgentGraphClientEdge[];
readonly work: readonly AgentGraphClientScheduledWork[];
readonly reconciliationFailures: readonly AgentGraphClientReconciliationFailure[];
readonly stoppedTargets: readonly AgentGraphClientStoppedTarget[];
readonly finish?: AgentGraphClientFinish;
readonly claims: readonly AgentGraphClientClaimRef[];
readonly recentControlDecisions: readonly AgentGraphClientControlDecision[];
readonly recentActivity: readonly AgentGraphClientActivity[];
readonly terminalHistory: {
readonly records: readonly AgentGraphClientActivity[];
readonly nextCursor?: string;
};
readonly omitted: {
readonly operators: number;
readonly edges: number;
readonly work: number;
readonly reconciliationFailures: number;
readonly stoppedTargets: number;
readonly claims: number;
readonly controlDecisions: number;
readonly recentActivity: number;
};
}
export interface AgentGraphOperatorInspection {
readonly schemaVersion: typeof AGENT_GRAPH_CLIENT_SCHEMA_VERSION;
readonly rootSessionId: string;
readonly graphId: string;
readonly snapshotVersion: `sha256:${string}`;
readonly operator: AgentGraphClientOperator;
readonly inboundEdges: readonly AgentGraphClientEdge[];
readonly outboundEdges: readonly AgentGraphClientEdge[];
readonly work: readonly AgentGraphClientScheduledWork[];
readonly claims: readonly AgentGraphClientClaimRef[];
readonly activations: readonly {
readonly activationId: string;
readonly status: AgentGraphActivationStatus;
readonly recordCount: number;
readonly firstEventTime: number;
readonly lastEventTime: number;
readonly lastRecordId: string;
readonly terminalRecordId?: string;
readonly run: AgentGraphClientRunRef;
}[];
readonly recentRecords: readonly AgentGraphClientActivity[];
readonly omitted: {
readonly inboundEdges: number;
readonly outboundEdges: number;
readonly work: number;
readonly claims: number;
readonly activations: number;
readonly records: number;
};
}
export interface AgentGraphQueryInput {
readonly rootSessionId: string;
readonly graphId?: string;
readonly terminalCursor?: string;
}
export interface AgentGraphEpochListInput {
readonly rootSessionId: string;
readonly beforeEpoch?: number;
}
export interface AgentGraphEpochSummary {
readonly epoch: number;
readonly graphId: string;
readonly createdAt: number;
readonly current: boolean;
}
export interface AgentGraphEpochListResult {
readonly rootSessionId: string;
readonly epochs: readonly AgentGraphEpochSummary[];
readonly nextBeforeEpoch: number | null;
}
export interface AgentGraphOperatorQueryInput {
readonly rootSessionId: string;
readonly graphId?: string;
readonly operatorId: string;
}
export interface AgentGraphStopInput {
readonly rootSessionId: string;
readonly expectedGraphId?: string;
}
export interface AgentGraphStopResult {
readonly rootSessionId: string;
readonly graphId: string;
}
export const AGENT_GRAPH_OPERATION_SPECS = {
'agent.graph.epochs.query': defineOperation<
AgentGraphEpochListInput,
AgentGraphEpochListResult,
(typeof QUERY_ERRORS)[number]
>({
mode: 'query',
availability: 'ready',
errors: QUERY_ERRORS,
decodeInput: decodeAgentGraphEpochListInput,
decodeOutput: decodeAgentGraphEpochListResult,
assertOutputForInput: (input, output) => {
assertRootIdentity(input.rootSessionId, output);
if (input.beforeEpoch === undefined) {
if (output.epochs.filter((entry) => entry.current).length !== 1) {
throw invalidProtocolFrame(
'Agent graph first epoch page must identify the current epoch',
);
}
} else {
if (output.epochs.some((entry) => entry.current)) {
throw invalidProtocolFrame('Agent graph historical epoch pages cannot be current');
}
if (output.epochs.some((entry) => entry.epoch >= input.beforeEpoch!)) {
throw invalidProtocolFrame('Agent graph epoch page did not advance its cursor');
}
}
},
}),
'agent.graph.query': defineOperation<
AgentGraphQueryInput,
AgentGraphClientSnapshot,
(typeof QUERY_ERRORS)[number]
>({
mode: 'query',
availability: 'ready',
errors: QUERY_ERRORS,
decodeInput: decodeAgentGraphQueryInput,
decodeOutput: decodeAgentGraphClientSnapshot,
assertOutputForInput: (input, output) => {
assertRootIdentity(input.rootSessionId, output);
if (input.graphId !== undefined && input.graphId !== output.graphId) {
throw invalidProtocolFrame('Agent graph result changed request graph identity');
}
},
}),
'agent.graph.operator.query': defineOperation<
AgentGraphOperatorQueryInput,
AgentGraphOperatorInspection,
(typeof QUERY_ERRORS)[number]
>({
mode: 'query',
availability: 'ready',
errors: QUERY_ERRORS,
decodeInput: decodeAgentGraphOperatorQueryInput,
decodeOutput: decodeAgentGraphOperatorInspection,
assertOutputForInput: (input, output) => {
assertRootIdentity(input.rootSessionId, output);
if (input.graphId !== undefined && input.graphId !== output.graphId) {
throw invalidProtocolFrame('Agent graph operator result changed request graph identity');
}
if (output.operator.operatorId !== input.operatorId) {
throw invalidProtocolFrame('Agent graph operator result changed request identity');
}
},
}),
'agent.graph.stop': defineOperation<
AgentGraphStopInput,
AgentGraphStopResult,
(typeof STOP_ERRORS)[number]
>({
mode: 'control',
availability: 'ready',
errors: STOP_ERRORS,
decodeInput: decodeAgentGraphStopInput,
decodeOutput: decodeAgentGraphStopResult,
assertOutputForInput: (input, output) => {
assertRootIdentity(input.rootSessionId, output);
if (input.expectedGraphId !== undefined && output.graphId !== input.expectedGraphId) {
throw invalidProtocolFrame('Agent graph stop changed request graph identity');
}
},
}),
} as const;
export function decodeAgentGraphQueryInput(value: unknown): AgentGraphQueryInput {
const record = requireShapedRecord(
value,
'agent.graph.query input',
['rootSessionId'],
['graphId', 'terminalCursor'],
);
return {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
...(record.graphId === undefined
? {}
: { graphId: requireOpaqueIdentity(record.graphId, 'graphId') }),
...(record.terminalCursor === undefined
? {}
: { terminalCursor: requireCursor(record.terminalCursor) }),
};
}
export function decodeAgentGraphOperatorQueryInput(value: unknown): AgentGraphOperatorQueryInput {
const record = requireShapedRecord(
value,
'agent.graph.operator.query input',
['rootSessionId', 'operatorId'],
['graphId'],
);
return {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
...(record.graphId === undefined
? {}
: { graphId: requireOpaqueIdentity(record.graphId, 'graphId') }),
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
};
}
export function decodeAgentGraphEpochListInput(value: unknown): AgentGraphEpochListInput {
const record = requireShapedRecord(
value,
'agent.graph.epochs.query input',
['rootSessionId'],
['beforeEpoch'],
);
const beforeEpoch =
record.beforeEpoch === undefined ? undefined : requireCount(record.beforeEpoch, 'beforeEpoch');
if (beforeEpoch === 0) throw invalidProtocolFrame('Invalid beforeEpoch');
return {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
...(beforeEpoch === undefined ? {} : { beforeEpoch }),
};
}
export function decodeAgentGraphEpochListResult(value: unknown): AgentGraphEpochListResult {
requireEncodedByteLimit(value, 'agent.graph.epochs.query result', AGENT_GRAPH_RESULT_MAX_BYTES);
const record = requireExactRecord(value, 'agent graph epoch list', [
'rootSessionId',
'epochs',
'nextBeforeEpoch',
]);
const result = {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
epochs: decodeArray(
record.epochs,
'agent graph epochs',
AGENT_GRAPH_EPOCH_PAGE_SIZE,
(entry) => {
const epoch = requireExactRecord(entry, 'agent graph epoch', [
'epoch',
'graphId',
'createdAt',
'current',
]);
const epochNumber = requireCount(epoch.epoch, 'epoch');
if (epochNumber === 0) throw invalidProtocolFrame('Invalid epoch');
return {
epoch: epochNumber,
graphId: requireOpaqueIdentity(epoch.graphId, 'graphId'),
createdAt: requireCount(epoch.createdAt, 'createdAt'),
current: requireBoolean(epoch.current, 'current'),
};
},
),
nextBeforeEpoch: decodeOptionalEpochCursor(record.nextBeforeEpoch),
};
assertUnique(result.epochs, (entry) => String(entry.epoch), 'agent graph epoch');
assertUnique(result.epochs, (entry) => entry.graphId, 'agent graph identity');
if (result.epochs.filter((entry) => entry.current).length > 1) {
throw invalidProtocolFrame('Agent graph epoch list identifies multiple current epochs');
}
for (let index = 1; index < result.epochs.length; index += 1) {
if (result.epochs[index - 1]!.epoch - 1 !== result.epochs[index]!.epoch) {
throw invalidProtocolFrame('Agent graph epoch page must be contiguous and newest-first');
}
}
if (result.epochs.some((entry) => entry.current) && !result.epochs[0]?.current) {
throw invalidProtocolFrame('Agent graph current epoch must be first');
}
if (result.nextBeforeEpoch !== null && result.nextBeforeEpoch !== result.epochs.at(-1)?.epoch) {
throw invalidProtocolFrame('Agent graph epoch cursor must continue after the oldest result');
}
return result;
}
function decodeOptionalEpochCursor(value: unknown): number | null {
if (value === null) return null;
const cursor = requireCount(value, 'nextBeforeEpoch');
if (cursor === 0) throw invalidProtocolFrame('Invalid nextBeforeEpoch');
return cursor;
}
export function decodeAgentGraphStopInput(value: unknown): AgentGraphStopInput {
const record = requireShapedRecord(
value,
'agent.graph.stop input',
['rootSessionId'],
['expectedGraphId'],
);
return {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
...(record.expectedGraphId === undefined
? {}
: { expectedGraphId: requireOpaqueIdentity(record.expectedGraphId, 'expectedGraphId') }),
};
}
export function decodeAgentGraphStopResult(value: unknown): AgentGraphStopResult {
const record = requireExactRecord(value, 'agent.graph.stop result', ['rootSessionId', 'graphId']);
return {
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
graphId: requireOpaqueIdentity(record.graphId, 'graphId'),
};
}
export function decodeAgentGraphClientSnapshot(value: unknown): AgentGraphClientSnapshot {
requireEncodedByteLimit(value, 'agent.graph.query result', AGENT_GRAPH_RESULT_MAX_BYTES);
const record = requireShapedRecord(
value,
'agent graph snapshot',
[
'schemaVersion',
'rootSessionId',
'graphId',
'orchestrationMode',
'snapshotVersion',
'status',
'scheduleRevision',
'topologyFingerprint',
'closed',
'operators',
'edges',
'work',
'reconciliationFailures',
'stoppedTargets',
'claims',
'recentControlDecisions',
'recentActivity',
'terminalHistory',
'omitted',
],
['latestEventTime', 'finish'],
);
requireSchemaVersion(record.schemaVersion);
const snapshot: AgentGraphClientSnapshot = {
schemaVersion: AGENT_GRAPH_CLIENT_SCHEMA_VERSION,
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
graphId: requireOpaqueIdentity(record.graphId, 'graphId'),
orchestrationMode: requireGraphOrchestrationMode(record.orchestrationMode),
snapshotVersion: requireFingerprint(record.snapshotVersion, 'snapshotVersion'),
status: requireGraphStatus(record.status),
scheduleRevision: requireCount(record.scheduleRevision, 'scheduleRevision'),
topologyFingerprint: requireFingerprint(record.topologyFingerprint, 'topologyFingerprint'),
closed: requireBoolean(record.closed, 'closed'),
...(record.latestEventTime === undefined
? {}
: { latestEventTime: requireCount(record.latestEventTime, 'latestEventTime') }),
operators: decodeArray(
record.operators,
'agent graph operators',
AGENT_GRAPH_MAX_OPERATORS,
decodeOperator,
),
edges: decodeArray(record.edges, 'agent graph edges', AGENT_GRAPH_MAX_EDGES, decodeEdge),
work: decodeArray(record.work, 'agent graph work', AGENT_GRAPH_MAX_WORK, decodeWork),
reconciliationFailures: decodeArray(
record.reconciliationFailures,
'agent graph reconciliation failures',
AGENT_GRAPH_MAX_RECONCILIATION_FAILURES,
decodeReconciliationFailure,
),
stoppedTargets: decodeArray(
record.stoppedTargets,
'agent graph stopped targets',
AGENT_GRAPH_MAX_STOPPED_TARGETS,
decodeStoppedTarget,
),
...(record.finish === undefined ? {} : { finish: decodeFinish(record.finish) }),
claims: decodeArray(record.claims, 'agent graph claims', AGENT_GRAPH_MAX_CLAIMS, decodeClaim),
recentControlDecisions: decodeArray(
record.recentControlDecisions,
'agent graph control decisions',
AGENT_GRAPH_MAX_CONTROL_DECISIONS,
decodeControlDecision,
),
recentActivity: decodeArray(
record.recentActivity,
'agent graph recent activity',
AGENT_GRAPH_MAX_ACTIVITY,
decodeActivity,
),
terminalHistory: decodeTerminalHistory(record.terminalHistory),
omitted: decodeSnapshotOmitted(record.omitted),
};
assertUnique(snapshot.operators, (item) => item.operatorId, 'agent graph operator');
assertUnique(snapshot.edges, (item) => item.edgeId, 'agent graph edge');
assertUnique(snapshot.work, (item) => item.workId, 'agent graph work');
assertUnique(
snapshot.reconciliationFailures,
(item) => item.workId,
'agent graph reconciliation failure',
);
assertUnique(snapshot.claims, (item) => item.claimId, 'agent graph claim');
return snapshot;
}
export function decodeAgentGraphOperatorInspection(value: unknown): AgentGraphOperatorInspection {
requireEncodedByteLimit(value, 'agent.graph.operator.query result', AGENT_GRAPH_RESULT_MAX_BYTES);
const record = requireExactRecord(value, 'agent graph operator inspection', [
'schemaVersion',
'rootSessionId',
'graphId',
'snapshotVersion',
'operator',
'inboundEdges',
'outboundEdges',
'work',
'claims',
'activations',
'recentRecords',
'omitted',
]);
requireSchemaVersion(record.schemaVersion);
const inspection: AgentGraphOperatorInspection = {
schemaVersion: AGENT_GRAPH_CLIENT_SCHEMA_VERSION,
rootSessionId: requireEntityId(record.rootSessionId, 'rootSessionId'),
graphId: requireOpaqueIdentity(record.graphId, 'graphId'),
snapshotVersion: requireFingerprint(record.snapshotVersion, 'snapshotVersion'),
operator: decodeOperator(record.operator),
inboundEdges: decodeArray(
record.inboundEdges,
'agent graph inbound edges',
AGENT_GRAPH_MAX_INSPECTION_EDGES,
decodeEdge,
),
outboundEdges: decodeArray(
record.outboundEdges,
'agent graph outbound edges',
AGENT_GRAPH_MAX_INSPECTION_EDGES,
decodeEdge,
),
work: decodeArray(
record.work,
'agent graph inspection work',
AGENT_GRAPH_MAX_INSPECTION_WORK,
decodeWork,
),
claims: decodeArray(
record.claims,
'agent graph inspection claims',
AGENT_GRAPH_MAX_INSPECTION_CLAIMS,
decodeClaim,
),
activations: decodeArray(
record.activations,
'agent graph activations',
AGENT_GRAPH_MAX_INSPECTION_ACTIVATIONS,
decodeActivation,
),
recentRecords: decodeArray(
record.recentRecords,
'agent graph inspection records',
AGENT_GRAPH_MAX_INSPECTION_RECORDS,
decodeActivity,
),
omitted: decodeInspectionOmitted(record.omitted),
};
assertUnique(inspection.inboundEdges, (item) => item.edgeId, 'agent graph inbound edge');
assertUnique(inspection.outboundEdges, (item) => item.edgeId, 'agent graph outbound edge');
assertUnique(inspection.work, (item) => item.workId, 'agent graph inspection work');
assertUnique(inspection.claims, (item) => item.claimId, 'agent graph inspection claim');
assertUnique(inspection.activations, (item) => item.activationId, 'agent graph activation');
return inspection;
}
function decodeOperator(value: unknown): AgentGraphClientOperator {
const record = requireShapedRecord(
value,
'agent graph operator',
[
'operatorId',
'childSessionId',
'provisionId',
'agentId',
'provisionedAt',
'status',
'inboundEdgeIds',
'outboundEdgeIds',
'scheduledWorkIds',
'readiness',
'omitted',
],
['currentActivation'],
);
return {
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
childSessionId: requireEntityId(record.childSessionId, 'childSessionId'),
provisionId: requireOpaqueIdentity(record.provisionId, 'provisionId'),
agentId: requireOpaqueIdentity(record.agentId, 'agentId'),
provisionedAt: requireCount(record.provisionedAt, 'provisionedAt'),
status: requireOperatorStatus(record.status),
inboundEdgeIds: decodeIdentityArray(
record.inboundEdgeIds,
'inboundEdgeIds',
AGENT_GRAPH_MAX_OPERATOR_REFS,
),
outboundEdgeIds: decodeIdentityArray(
record.outboundEdgeIds,
'outboundEdgeIds',
AGENT_GRAPH_MAX_OPERATOR_REFS,
),
scheduledWorkIds: decodeIdentityArray(
record.scheduledWorkIds,
'scheduledWorkIds',
AGENT_GRAPH_MAX_OPERATOR_REFS,
),
readiness: decodeArray(
record.readiness,
'agent graph operator readiness',
AGENT_GRAPH_MAX_OPERATOR_READINESS,
decodeReadiness,
),
omitted: decodeOperatorOmitted(record.omitted),
...(record.currentActivation === undefined
? {}
: { currentActivation: decodeCurrentActivation(record.currentActivation) }),
};
}
function decodeReadiness(value: unknown): AgentGraphClientOperator['readiness'][number] {
const record = requireExactRecord(value, 'agent graph readiness', [
'readinessId',
'status',
'waitingFor',
'omittedWaitingFor',
]);
if (record.status !== 'waiting' && record.status !== 'runnable') {
throw invalidProtocolFrame('Invalid agent graph readiness status');
}
return {
readinessId: requireOpaqueIdentity(record.readinessId, 'readinessId'),
status: record.status,
waitingFor: decodeArray(
record.waitingFor,
'agent graph readiness waits',
AGENT_GRAPH_MAX_READINESS_WAITS,
decodeReadinessWait,
),
omittedWaitingFor: requireCount(record.omittedWaitingFor, 'omittedWaitingFor'),
};
}
function decodeReadinessWait(value: unknown): AgentGraphReadinessWait {
const record = requireShapedRecord(
value,
'agent graph readiness wait',
['kind'],
['upstreamOperatorIds', 'operatorId', 'activationId'],
);
if (record.kind === 'input_route') {
requireExactRecord(record, 'agent graph input route wait', ['kind', 'upstreamOperatorIds']);
return {
kind: record.kind,
upstreamOperatorIds: decodeIdentityArray(
record.upstreamOperatorIds,
'upstreamOperatorIds',
AGENT_GRAPH_MAX_INPUT_ROUTE_OPERATORS,
),
};
}
if (record.kind === 'activation_missing' || record.kind === 'activation_running') {
requireExactRecord(record, 'agent graph activation wait', [
'kind',
'operatorId',
'activationId',
]);
return {
kind: record.kind,
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
activationId: requireOpaqueIdentity(record.activationId, 'activationId'),
};
}
throw invalidProtocolFrame('Invalid agent graph readiness wait kind');
}
function decodeCurrentActivation(
value: unknown,
): NonNullable<AgentGraphClientOperator['currentActivation']> {
const record = requireShapedRecord(
value,
'agent graph current activation',
['activationId', 'status', 'recordCount', 'firstEventTime', 'lastEventTime', 'run'],
['terminalRecordId'],
);
return {
activationId: requireOpaqueIdentity(record.activationId, 'activationId'),
status: requireActivationStatus(record.status),
recordCount: requireCount(record.recordCount, 'recordCount'),
firstEventTime: requireCount(record.firstEventTime, 'firstEventTime'),
lastEventTime: requireCount(record.lastEventTime, 'lastEventTime'),
...(record.terminalRecordId === undefined
? {}
: {
terminalRecordId: requireOpaqueIdentity(record.terminalRecordId, 'terminalRecordId'),
}),
run: decodeRunRef(record.run),
};
}
function decodeEdge(value: unknown): AgentGraphClientEdge {
const record = requireExactRecord(value, 'agent graph edge', [
'edgeId',
'fromOperatorId',
'toOperatorId',
]);
return {
edgeId: requireOpaqueIdentity(record.edgeId, 'edgeId'),
fromOperatorId: requireOpaqueIdentity(record.fromOperatorId, 'fromOperatorId'),
toOperatorId: requireOpaqueIdentity(record.toOperatorId, 'toOperatorId'),
};
}
function decodeWork(value: unknown): AgentGraphClientScheduledWork {
const record = requireShapedRecord(
value,
'agent graph work',
[
'workId',
'target',
'inputIds',
'status',
'instructionPreview',
'instructionTruncated',
'revision',
'committedAt',
],
['replaces', 'selectedResultInputs'],
);
if (
record.status !== 'requested' &&
record.status !== 'stopped' &&
record.status !== 'superseded'
) {
throw invalidProtocolFrame('Invalid agent graph work status');
}
const inputIds = decodeIdentityArray(record.inputIds, 'inputIds', AGENT_GRAPH_MAX_WORK_INPUTS);
return {
workId: requireOpaqueIdentity(record.workId, 'workId'),
target: decodeWorkTarget(record.target),
inputIds,
...(record.selectedResultInputs === undefined
? {}
: {
selectedResultInputs: decodeSelectedResultInputs(
record.selectedResultInputs,
new Set(inputIds),
),
}),
...(record.replaces === undefined
? {}
: { replaces: requireOpaqueIdentity(record.replaces, 'replaces') }),
status: record.status,
instructionPreview: requireUtf8String(
record.instructionPreview,
'instructionPreview',
AGENT_GRAPH_INSTRUCTION_PREVIEW_MAX_BYTES,
),
instructionTruncated: requireBoolean(record.instructionTruncated, 'instructionTruncated'),
revision: requireCount(record.revision, 'revision'),
committedAt: requireCount(record.committedAt, 'committedAt'),
};
}
function decodeSelectedResultInputs(
value: unknown,
currentInputIds: ReadonlySet<string>,
): Array<{ sourceGraphId: string; resultId: string }> {
if (!Array.isArray(value) || value.length === 0 || value.length > AGENT_GRAPH_MAX_WORK_INPUTS) {
throw invalidProtocolFrame('Invalid selected graph result inputs');
}
// The durable schedule contract caps current and selected historical inputs
// COMBINED at AGENT_GRAPH_MAX_WORK_INPUTS; the decoder must not admit a
// wider frame than the contract it projects.
if (currentInputIds.size + value.length > AGENT_GRAPH_MAX_WORK_INPUTS) {
throw invalidProtocolFrame('Graph input ids exceed the combined current and historical cap');
}
const selected = value.map((item) => {
const record = requireExactRecord(item, 'selected graph result input', [
'sourceGraphId',
'resultId',
]);
return {
sourceGraphId: requireOpaqueIdentity(record.sourceGraphId, 'sourceGraphId'),
resultId: requireOpaqueIdentity(record.resultId, 'resultId'),
};
});
assertUnique(selected, (item) => item.resultId, 'selected graph result');
if (selected.some((item) => currentInputIds.has(item.resultId))) {
throw invalidProtocolFrame('Graph input ids are ambiguous across current and historical data');
}
return selected;
}
function decodeReconciliationFailure(value: unknown): AgentGraphClientReconciliationFailure {
const record = requireExactRecord(value, 'agent graph reconciliation failure', [
'workId',
'phase',
'reason',
]);
if (
record.phase !== 'schedule' &&
record.phase !== 'topology' &&
record.phase !== 'stop' &&
record.phase !== 'render' &&
record.phase !== 'dispatch'
) {
throw invalidProtocolFrame('Invalid agent graph reconciliation failure phase');
}
return {
workId: requireOpaqueIdentity(record.workId, 'workId'),
phase: record.phase,
reason: requireUtf8String(
record.reason,
'reason',
AGENT_GRAPH_RECONCILIATION_FAILURE_REASON_MAX_BYTES,
),
};
}
function decodeWorkTarget(value: unknown): AgentGraphClientScheduledWork['target'] {
const record = requireShapedRecord(
value,
'agent graph work target',
['kind'],
['agentId', 'presetId', 'operatorId'],
);
if (record.kind === 'agent') {
requireExactRecord(record, 'agent graph agent target', ['kind', 'agentId']);
return { kind: record.kind, agentId: requireOpaqueIdentity(record.agentId, 'agentId') };
}
if (record.kind === 'operator') {
requireExactRecord(record, 'agent graph operator target', ['kind', 'operatorId']);
return {
kind: record.kind,
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
};
}
if (record.kind === 'preset') {
requireExactRecord(record, 'agent graph preset target', ['kind', 'presetId']);
return {
kind: record.kind,
presetId: requireOpaqueIdentity(record.presetId, 'presetId'),
};
}
throw invalidProtocolFrame('Invalid agent graph work target');
}
function decodeStoppedTarget(value: unknown): AgentGraphClientStoppedTarget {
const record = requireExactRecord(value, 'agent graph stopped target', [
'targetId',
'reason',
'revision',
'committedAt',
]);
return {
targetId: requireOpaqueIdentity(record.targetId, 'targetId'),
reason: requireUtf8String(record.reason, 'stop reason', AGENT_GRAPH_REASON_MAX_BYTES),
revision: requireCount(record.revision, 'revision'),
committedAt: requireCount(record.committedAt, 'committedAt'),
};
}
function decodeFinish(value: unknown): AgentGraphClientFinish {
const record = requireExactRecord(value, 'agent graph finish', [
'resultIds',
'reason',
'revision',
'committedAt',
]);
return {
resultIds: decodeIdentityArray(record.resultIds, 'resultIds', AGENT_GRAPH_MAX_CONTROL_REFS),
reason: requireUtf8String(record.reason, 'finish reason', AGENT_GRAPH_REASON_MAX_BYTES),
revision: requireCount(record.revision, 'revision'),
committedAt: requireCount(record.committedAt, 'committedAt'),
};
}
function decodeControlDecision(value: unknown): AgentGraphClientControlDecision {
const record = requireExactRecord(value, 'agent graph control decision', [
'updateId',
'revision',
'committedAt',
'source',
'addedWorkIds',
'stoppedTargetIds',
'selectedResultIds',
]);
const source = requireExactRecord(record.source, 'agent graph control source', [
'sessionId',
'agentRunId',
'turnId',
'toolCallId',
]);
return {
updateId: requireOpaqueIdentity(record.updateId, 'updateId'),
revision: requireCount(record.revision, 'revision'),
committedAt: requireCount(record.committedAt, 'committedAt'),
source: {
sessionId: requireEntityId(source.sessionId, 'sessionId'),
agentRunId: requireOpaqueIdentity(source.agentRunId, 'agentRunId'),
turnId: requireOpaqueIdentity(source.turnId, 'turnId'),
toolCallId: requireOpaqueIdentity(source.toolCallId, 'toolCallId'),
},
addedWorkIds: decodeIdentityArray(
record.addedWorkIds,
'addedWorkIds',
AGENT_GRAPH_MAX_CONTROL_REFS,
),
stoppedTargetIds: decodeIdentityArray(
record.stoppedTargetIds,
'stoppedTargetIds',
AGENT_GRAPH_MAX_CONTROL_REFS,
),
selectedResultIds: decodeIdentityArray(
record.selectedResultIds,
'selectedResultIds',
AGENT_GRAPH_MAX_CONTROL_REFS,
),
};
}
function decodeClaim(value: unknown): AgentGraphClientClaimRef {
const record = requireExactRecord(value, 'agent graph claim', [
'claimId',
'intentId',
'operatorId',
'childSessionId',
'run',
'admissionState',
'claimedAt',
]);
if (
record.admissionState !== 'claimed' &&
record.admissionState !== 'executing' &&
record.admissionState !== 'cancelled'
) {
throw invalidProtocolFrame('Invalid agent graph claim admission state');
}
return {
claimId: requireOpaqueIdentity(record.claimId, 'claimId'),
intentId: requireOpaqueIdentity(record.intentId, 'intentId'),
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
childSessionId: requireEntityId(record.childSessionId, 'childSessionId'),
run: decodeRunRef(record.run),
admissionState: record.admissionState,
claimedAt: requireCount(record.claimedAt, 'claimedAt'),
};
}
function decodeActivity(value: unknown): AgentGraphClientActivity {
const record = requireExactRecord(value, 'agent graph activity', [
'recordId',
'operatorId',
'activationId',
'eventTime',
'facets',
'signals',
'run',
]);
return {
recordId: requireOpaqueIdentity(record.recordId, 'recordId'),
operatorId: requireOpaqueIdentity(record.operatorId, 'operatorId'),
activationId: requireOpaqueIdentity(record.activationId, 'activationId'),
eventTime: requireCount(record.eventTime, 'eventTime'),
facets: decodeArray(record.facets, 'agent graph activity facets', 18, requireFacet),
signals: decodeArray(record.signals, 'agent graph activity signals', 2, decodeSignal),
run: decodeRunRef(record.run),
};
}
function decodeActivation(value: unknown): AgentGraphOperatorInspection['activations'][number] {
const record = requireShapedRecord(
value,
'agent graph activation',
[
'activationId',
'status',
'recordCount',
'firstEventTime',
'lastEventTime',
'lastRecordId',
'run',
],
['terminalRecordId'],
);
return {
activationId: requireOpaqueIdentity(record.activationId, 'activationId'),
status: requireActivationStatus(record.status),
recordCount: requireCount(record.recordCount, 'recordCount'),
firstEventTime: requireCount(record.firstEventTime, 'firstEventTime'),
lastEventTime: requireCount(record.lastEventTime, 'lastEventTime'),
lastRecordId: requireOpaqueIdentity(record.lastRecordId, 'lastRecordId'),
...(record.terminalRecordId === undefined
? {}
: {
terminalRecordId: requireOpaqueIdentity(record.terminalRecordId, 'terminalRecordId'),
}),
run: decodeRunRef(record.run),
};
}
function decodeRunRef(value: unknown): AgentGraphClientRunRef {
const record = requireShapedRecord(
value,
'agent graph run reference',
['sessionId', 'agentRunId'],
['turnId'],
);
return {
sessionId: requireEntityId(record.sessionId, 'sessionId'),
agentRunId: requireOpaqueIdentity(record.agentRunId, 'agentRunId'),
...(record.turnId === undefined
? {}
: { turnId: requireOpaqueIdentity(record.turnId, 'turnId') }),
};
}
function decodeSignal(value: unknown): AgentGraphSupervisorSignal {
const record = requireShapedRecord(value, 'agent graph signal', ['kind'], ['reason', 'status']);
if (record.kind === 'attention') {
requireExactRecord(record, 'agent graph attention signal', ['kind', 'reason']);
if (record.reason !== 'permission_request' && record.reason !== 'user_question_request') {
throw invalidProtocolFrame('Invalid agent graph attention reason');
}
return { kind: record.kind, reason: record.reason };
}
if (record.kind === 'terminal') {
requireExactRecord(record, 'agent graph terminal signal', ['kind', 'status']);
if (
record.status !== 'completed' &&
record.status !== 'failed' &&
record.status !== 'aborted' &&
record.status !== 'cancelled'
) {
throw invalidProtocolFrame('Invalid agent graph terminal status');
}
return { kind: record.kind, status: record.status };
}
throw invalidProtocolFrame('Invalid agent graph signal kind');
}
function decodeTerminalHistory(value: unknown): AgentGraphClientSnapshot['terminalHistory'] {
const record = requireShapedRecord(
value,
'agent graph terminal history',
['records'],
['nextCursor'],
);
return {
records: decodeArray(
record.records,
'agent graph terminal activity',
AGENT_GRAPH_MAX_TERMINAL_ACTIVITY,
decodeActivity,
),
...(record.nextCursor === undefined ? {} : { nextCursor: requireCursor(record.nextCursor) }),
};
}
function decodeOperatorOmitted(value: unknown): AgentGraphClientOperator['omitted'] {
const record = requireExactRecord(value, 'agent graph operator omitted counts', [
'inboundEdgeIds',
'outboundEdgeIds',
'scheduledWorkIds',
'readiness',
'readinessWaits',
]);
return {
inboundEdgeIds: requireCount(record.inboundEdgeIds, 'omitted inboundEdgeIds'),
outboundEdgeIds: requireCount(record.outboundEdgeIds, 'omitted outboundEdgeIds'),
scheduledWorkIds: requireCount(record.scheduledWorkIds, 'omitted scheduledWorkIds'),
readiness: requireCount(record.readiness, 'omitted readiness'),
readinessWaits: requireCount(record.readinessWaits, 'omitted readinessWaits'),
};
}
function decodeSnapshotOmitted(value: unknown): AgentGraphClientSnapshot['omitted'] {
const record = requireExactRecord(value, 'agent graph snapshot omitted counts', [
'operators',
'edges',
'work',
'reconciliationFailures',
'stoppedTargets',
'claims',
'controlDecisions',
'recentActivity',
]);
return {
operators: requireCount(record.operators, 'omitted operators'),
edges: requireCount(record.edges, 'omitted edges'),
work: requireCount(record.work, 'omitted work'),
reconciliationFailures: requireCount(
record.reconciliationFailures,
'omitted reconciliationFailures',
),
stoppedTargets: requireCount(record.stoppedTargets, 'omitted stoppedTargets'),
claims: requireCount(record.claims, 'omitted claims'),
controlDecisions: requireCount(record.controlDecisions, 'omitted controlDecisions'),
recentActivity: requireCount(record.recentActivity, 'omitted recentActivity'),
};
}
function decodeInspectionOmitted(value: unknown): AgentGraphOperatorInspection['omitted'] {
const record = requireExactRecord(value, 'agent graph inspection omitted counts', [
'inboundEdges',
'outboundEdges',
'work',
'claims',
'activations',
'records',
]);
return {
inboundEdges: requireCount(record.inboundEdges, 'omitted inboundEdges'),
outboundEdges: requireCount(record.outboundEdges, 'omitted outboundEdges'),
work: requireCount(record.work, 'omitted work'),
claims: requireCount(record.claims, 'omitted claims'),
activations: requireCount(record.activations, 'omitted activations'),
records: requireCount(record.records, 'omitted records'),
};
}
function decodeIdentityArray(value: unknown, label: string, maxItems: number): string[] {
const identities = decodeArray(value, label, maxItems, (item) =>
requireOpaqueIdentity(item, label),
);
assertUnique(identities, (item) => item, label);
return identities;
}
function decodeArray<T>(
value: unknown,
label: string,
maxItems: number,
decode: (item: unknown) => T,
): T[] {
if (!Array.isArray(value) || value.length > maxItems) {
throw invalidProtocolFrame(`Invalid ${label}`);
}
return value.map(decode);
}
function assertUnique<T>(items: readonly T[], key: (item: T) => string, label: string): void {
if (new Set(items.map(key)).size !== items.length) {
throw invalidProtocolFrame(`Duplicate ${label} identity`);
}
}
function assertRootIdentity(
rootSessionId: string,
output: { readonly rootSessionId: string },
): void {
if (output.rootSessionId !== rootSessionId) {
throw invalidProtocolFrame('Agent graph result changed request identity');
}
}
function requireSchemaVersion(value: unknown): void {
if (value !== AGENT_GRAPH_CLIENT_SCHEMA_VERSION) {
throw invalidProtocolFrame('Unsupported agent graph client schema');
}
}
function requireBoolean(value: unknown, label: string): boolean {
if (typeof value !== 'boolean') throw invalidProtocolFrame(`Invalid ${label}`);
return value;
}
function requireOpaqueIdentity(value: unknown, label: string): string {
if (
typeof value !== 'string' ||
value.length === 0 ||
value.length > 256 ||
value.trim() !== value ||
/[\u0000-\u001f\u007f]/.test(value)
) {
throw invalidProtocolFrame(`Invalid ${label}`);
}
return value;
}
function requireFingerprint(value: unknown, label: string): `sha256:${string}` {
if (typeof value !== 'string' || !/^sha256:[a-f0-9]{64}$/.test(value)) {
throw invalidProtocolFrame(`Invalid ${label}`);
}
return value as `sha256:${string}`;
}
function requireCursor(value: unknown): string {
const cursor = requireUtf8String(
value,
'agent graph terminal cursor',
AGENT_GRAPH_TERMINAL_CURSOR_MAX_BYTES,
);
if (!/^[A-Za-z0-9_-]+$/.test(cursor)) {
throw invalidProtocolFrame('Invalid agent graph terminal cursor');
}
return cursor;
}
function requireActivationStatus(value: unknown): AgentGraphActivationStatus {
if (
value === 'running' ||
value === 'completed' ||
value === 'failed' ||
value === 'aborted' ||
value === 'cancelled'
) {
return value;
}
throw invalidProtocolFrame('Invalid agent graph activation status');
}
function requireOperatorStatus(value: unknown): AgentGraphClientOperatorStatus {
if (
value === 'not_started' ||
value === 'waiting' ||
value === 'runnable' ||
value === 'blocked'
) {
return value;
}
return requireActivationStatus(value);
}
function requireGraphStatus(value: unknown): AgentGraphClientStatus {
if (
value === 'empty' ||
value === 'active' ||
value === 'closing' ||
value === 'waiting' ||
value === 'stopped' ||
value === 'failed' ||
value === 'completed'
) {
return value;
}
throw invalidProtocolFrame('Invalid agent graph status');
}
function requireGraphOrchestrationMode(value: unknown): 'graph' | 'swarm' {
if (value === 'graph' || value === 'swarm') return value;
throw invalidProtocolFrame('Invalid agent graph orchestration mode');
}
function requireFacet(value: unknown): AgentGraphRecordFacet {
if (
value === 'message' ||
value === 'thinking' ||
value === 'error' ||
value === 'tool_call' ||
value === 'tool_dispatch' ||
value === 'tool_result' ||
value === 'artifact_update' ||
value === 'permission_request' ||
value === 'permission_decision' ||
value === 'user_question_request' ||
value === 'transfer' ||
value === 'usage' ||
value === 'completed' ||
value === 'failed' ||
value === 'aborted' ||
value === 'cancelled' ||
value === 'runtime_fact'
) {
return value;
}
throw invalidProtocolFrame('Invalid agent graph record facet');
}