blob: cec3e23720e31d3720678d076def2e633eed688a [file]
import {
decodeAgentGraphIntentClaim,
type AgentGraphIntentClaim,
type AgentGraphIntentClaimStore,
} from '@maka/core';
import type { AgentRunStore } from '@maka/core';
import type { SessionEvent } from '@maka/core';
import type { RuntimeEventStore } from '@maka/core';
import type {
ClaimedAgentGraphIntentResult,
RunClaimedAgentGraphIntentInput,
} from './session-manager.js';
import { claimAgentGraphRunnableIntent } from './stream-graph-admission.js';
import { compareAgentGraphIdentity } from './stream-graph-identity.js';
import {
readCommittedAgentGraphProjection,
type AgentGraphProjection,
type AgentGraphRecord,
} from './stream-graph-projection.js';
import {
buildAgentGraphReadinessSnapshot,
type AgentGraphReadinessPolicy,
type AgentGraphReadinessSnapshot,
type AgentGraphRunnableIntent,
} from './stream-graph-readiness.js';
import type { AgentGraphTraceTopology } from './stream-graph-trace.js';
export interface AgentGraphIntentExecutor {
runClaimedAgentGraphIntent(
input: RunClaimedAgentGraphIntentInput,
): Promise<ClaimedAgentGraphIntentResult>;
}
export interface ResolveAgentGraphPoliciesInput {
projection: AgentGraphProjection;
claims: readonly AgentGraphIntentClaim[];
}
export interface RenderAgentGraphIntentPromptInput {
intent: AgentGraphRunnableIntent;
triggerRecords: readonly AgentGraphRecord[];
}
export interface AgentGraphSupervisorObservation {
projection: AgentGraphProjection;
readiness: AgentGraphReadinessSnapshot;
claims: readonly AgentGraphIntentClaim[];
}
export interface AgentGraphSupervisorActivationReady {
intent: AgentGraphRunnableIntent;
claim: AgentGraphIntentClaim;
runtime: Parameters<NonNullable<RunClaimedAgentGraphIntentInput['onReady']>>[0];
}
export interface AgentGraphSupervisorRuntimeEvent {
intent: AgentGraphRunnableIntent;
claim: AgentGraphIntentClaim;
event: SessionEvent;
}
/**
* Presentation-only observer for the main-agent supervisor.
*
* The driver never awaits these callbacks and ignores observer failures, so
* supervision stays beside the graph instead of becoming a data-path gate.
*/
export interface AgentGraphSupervisorObserver {
onObservation?(observation: AgentGraphSupervisorObservation): void | Promise<void>;
onActivationReady?(activation: AgentGraphSupervisorActivationReady): void | Promise<void>;
onRuntimeEvent?(event: AgentGraphSupervisorRuntimeEvent): void | Promise<void>;
onReconciliationFailure?(
failure: import('./stream-graph-schedule-reconcile.js').AgentGraphScheduleReconciliationFailure,
): void | Promise<void>;
}
export interface RunAgentGraphToQuiescenceInput {
topology: AgentGraphTraceTopology;
runStore: Pick<AgentRunStore, 'listSessionRuns'>;
runtimeEventStore: Pick<RuntimeEventStore, 'readImmutableRuntimeEvents'>;
claimStore: AgentGraphIntentClaimStore;
executor: AgentGraphIntentExecutor;
newId: () => string;
/**
* Hard bound on claims first created by this invocation.
*
* Existing durable claims remain recoverable even when the budget is zero.
* Resource permits and fairness are deliberately outside this structural
* scheduling boundary.
*/
maxNewActivations: number;
resolvePolicies(
input: ResolveAgentGraphPoliciesInput,
): readonly AgentGraphReadinessPolicy[] | Promise<readonly AgentGraphReadinessPolicy[]>;
renderPrompt(input: RenderAgentGraphIntentPromptInput): string | Promise<string>;
abortSignal?: AbortSignal;
supervisor?: AgentGraphSupervisorObserver;
}
export interface AgentGraphDispatchedActivation {
intent: AgentGraphRunnableIntent;
claim: AgentGraphIntentClaim;
claimCreated: boolean;
result: ClaimedAgentGraphIntentResult;
}
export interface AgentGraphDispatchFailure {
intent: AgentGraphRunnableIntent;
error: unknown;
/**
* Present when durable admission completed before execution failed.
*/
claim?: AgentGraphIntentClaim;
claimCreated?: boolean;
}
export interface AgentGraphQuiescenceResult {
/**
* `quiescent` is not graph-wide completion. Topology/admission closure remains
* a separate future protocol.
*/
status: 'quiescent' | 'limit_reached' | 'failed' | 'cancelled';
newActivationCount: number;
observedExistingActivationCount: number;
dispatches: AgentGraphDispatchedActivation[];
failures: AgentGraphDispatchFailure[];
projection: AgentGraphProjection;
readiness: AgentGraphReadinessSnapshot;
}
interface GraphObservation {
projection: AgentGraphProjection;
readiness: AgentGraphReadinessSnapshot;
claims: AgentGraphIntentClaim[];
}
interface PreparedDispatch {
intent: AgentGraphRunnableIntent;
prompt: string;
}
type DispatchOutcome =
| {
status: 'fulfilled';
dispatch: AgentGraphDispatchedActivation;
}
| {
status: 'rejected';
failure: AgentGraphDispatchFailure;
};
/**
* Repeatedly reconstructs graph state from durable Runtime facts and executes
* newly runnable work until no unobserved intent remains.
*
* The driver owns no execution ledger. Intent claims are durable admission
* authority; AgentRun/RuntimeEvent stores remain execution authority.
*/
export async function runAgentGraphToQuiescence(
input: RunAgentGraphToQuiescenceInput,
): Promise<AgentGraphQuiescenceResult> {
if (!Number.isSafeInteger(input.maxNewActivations) || input.maxNewActivations < 0) {
throw new Error('Agent graph maxNewActivations must be a non-negative safe integer');
}
const processedIntentIds = new Set<string>();
const dispatches: AgentGraphDispatchedActivation[] = [];
const failures: AgentGraphDispatchFailure[] = [];
let newActivationCount = 0;
let observedExistingActivationCount = 0;
let observation = await observeGraph(input);
while (true) {
if (input.abortSignal?.aborted) {
return graphResult(
'cancelled',
newActivationCount,
observedExistingActivationCount,
dispatches,
failures,
observation,
);
}
const existingIntentIds = new Set(observation.claims.map((claim) => claim.intentId));
const candidates = orderedRunnableIntents(observation.readiness).filter(
(intent) => !processedIntentIds.has(intent.intentId),
);
const selected: AgentGraphRunnableIntent[] = [];
let deferredByLimit = false;
for (const intent of candidates) {
if (
existingIntentIds.has(intent.intentId) ||
newActivationCount + selectedNewIntentCount(selected, existingIntentIds) <
input.maxNewActivations
) {
selected.push(intent);
} else {
deferredByLimit = true;
}
}
if (selected.length === 0) {
return graphResult(
deferredByLimit ? 'limit_reached' : 'quiescent',
newActivationCount,
observedExistingActivationCount,
dispatches,
failures,
observation,
);
}
const rendered = await Promise.allSettled(
selected.map(async (intent): Promise<PreparedDispatch> => {
const prompt = await input.renderPrompt(
clonePlain({
intent,
triggerRecords: resolveTriggerRecords(observation.projection, intent),
}),
);
if (!prompt.trim()) {
throw new Error(`Agent graph intent ${intent.intentId} rendered an empty prompt`);
}
return { intent, prompt };
}),
);
const prepared: PreparedDispatch[] = [];
rendered.forEach((result, index) => {
if (result.status === 'fulfilled') {
prepared.push(result.value);
} else {
failures.push({ intent: selected[index]!, error: result.reason });
}
});
if (failures.length > 0) {
observation = await observeGraph(input);
return graphResult(
input.abortSignal?.aborted ? 'cancelled' : 'failed',
newActivationCount,
observedExistingActivationCount,
dispatches,
failures,
observation,
);
}
const outcomes = await Promise.all(prepared.map((dispatch) => dispatchIntent(input, dispatch)));
for (const outcome of outcomes) {
if (outcome.status === 'fulfilled') {
dispatches.push(outcome.dispatch);
processedIntentIds.add(outcome.dispatch.intent.intentId);
if (outcome.dispatch.claimCreated) newActivationCount += 1;
else observedExistingActivationCount += 1;
} else {
failures.push(outcome.failure);
if (outcome.failure.claim) {
processedIntentIds.add(outcome.failure.intent.intentId);
if (outcome.failure.claimCreated) newActivationCount += 1;
else observedExistingActivationCount += 1;
}
}
}
observation = await observeGraph(input);
if (failures.length > 0) {
return graphResult(
input.abortSignal?.aborted ? 'cancelled' : 'failed',
newActivationCount,
observedExistingActivationCount,
dispatches,
failures,
observation,
);
}
if (deferredByLimit && newActivationCount >= input.maxNewActivations) {
return graphResult(
'limit_reached',
newActivationCount,
observedExistingActivationCount,
dispatches,
failures,
observation,
);
}
}
}
async function observeGraph(input: RunAgentGraphToQuiescenceInput): Promise<GraphObservation> {
const [projection, listedClaims] = await Promise.all([
readCommittedAgentGraphProjection({
graphId: input.topology.graphId,
operators: input.topology.operators,
runStore: input.runStore,
runtimeEventStore: input.runtimeEventStore,
}),
input.claimStore.listAgentGraphIntentClaims(input.topology.graphId),
]);
const claims = listedClaims
.map((claim) => {
const decoded = decodeAgentGraphIntentClaim(claim);
if (decoded.graphId !== input.topology.graphId) {
throw new Error(
`Graph claim ${decoded.claimId} belongs to ${decoded.graphId}, expected ${input.topology.graphId}`,
);
}
return decoded;
})
.sort(
(a, b) =>
compareAgentGraphIdentity(a.intentId, b.intentId) ||
compareAgentGraphIdentity(a.claimId, b.claimId),
);
assertUniqueClaimIntents(claims);
const policies = await input.resolvePolicies(clonePlain({ projection, claims }));
const readiness = buildAgentGraphReadinessSnapshot({
topology: input.topology,
records: projection.records,
policies,
});
notifySupervisor(input.supervisor?.onObservation, {
projection,
readiness,
claims,
});
return { projection, readiness, claims };
}
async function dispatchIntent(
input: RunAgentGraphToQuiescenceInput,
prepared: PreparedDispatch,
): Promise<DispatchOutcome> {
let admission:
| {
claim: AgentGraphIntentClaim;
created: boolean;
}
| undefined;
try {
if (input.abortSignal?.aborted) {
throw new Error('Agent graph dispatch was cancelled before admission');
}
const claimed = await claimAgentGraphRunnableIntent({
intent: prepared.intent,
store: input.claimStore,
newId: input.newId,
executionInput: { prompt: prepared.prompt },
});
admission = claimed;
const result = await input.executor.runClaimedAgentGraphIntent({
claimStore: input.claimStore,
intent: prepared.intent,
graphId: prepared.intent.graphId,
intentId: prepared.intent.intentId,
prompt: prepared.prompt,
...(input.abortSignal ? { abortSignal: input.abortSignal } : {}),
onReady(runtime) {
notifySupervisor(input.supervisor?.onActivationReady, {
intent: prepared.intent,
claim: claimed.claim,
runtime,
});
},
onEvent(event) {
notifySupervisor(input.supervisor?.onRuntimeEvent, {
intent: prepared.intent,
claim: claimed.claim,
event,
});
},
});
return {
status: 'fulfilled',
dispatch: {
intent: prepared.intent,
claim: claimed.claim,
claimCreated: claimed.created,
result,
},
};
} catch (error) {
return {
status: 'rejected',
failure: admission
? {
intent: prepared.intent,
error,
claim: admission.claim,
claimCreated: admission.created,
}
: {
intent: prepared.intent,
error,
},
};
}
}
function orderedRunnableIntents(
readiness: AgentGraphReadinessSnapshot,
): AgentGraphRunnableIntent[] {
const topologicalIndex = new Map(
readiness.trace.topologicalOrder.map((operatorId, index) => [operatorId, index]),
);
const intents = Object.values(readiness.readiness).flatMap((state) => state.intents);
return intents.sort(
(a, b) =>
topologicalIndex.get(a.operatorId)! - topologicalIndex.get(b.operatorId)! ||
compareAgentGraphIdentity(a.readinessId, b.readinessId) ||
compareAgentGraphIdentity(a.intentId, b.intentId),
);
}
function selectedNewIntentCount(
selected: readonly AgentGraphRunnableIntent[],
existingIntentIds: ReadonlySet<string>,
): number {
return selected.filter((intent) => !existingIntentIds.has(intent.intentId)).length;
}
function resolveTriggerRecords(
projection: AgentGraphProjection,
intent: AgentGraphRunnableIntent,
): AgentGraphRecord[] {
const recordsById = new Map(projection.records.map((record) => [record.recordId, record]));
return intent.triggerRecordIds.map((recordId) => {
const record = recordsById.get(recordId);
if (!record) {
throw new Error(
`Agent graph intent ${intent.intentId} references missing record ${recordId}`,
);
}
return record;
});
}
function assertUniqueClaimIntents(claims: readonly AgentGraphIntentClaim[]): void {
const seen = new Set<string>();
for (const claim of claims) {
if (seen.has(claim.intentId)) {
throw new Error(`Graph intent ${claim.graphId}/${claim.intentId} has multiple claims`);
}
seen.add(claim.intentId);
}
}
function graphResult(
status: AgentGraphQuiescenceResult['status'],
newActivationCount: number,
observedExistingActivationCount: number,
dispatches: readonly AgentGraphDispatchedActivation[],
failures: readonly AgentGraphDispatchFailure[],
observation: GraphObservation,
): AgentGraphQuiescenceResult {
return {
status,
newActivationCount,
observedExistingActivationCount,
dispatches: [...dispatches],
failures: [...failures],
projection: observation.projection,
readiness: observation.readiness,
};
}
function notifySupervisor<T>(
observer: ((input: T) => void | Promise<void>) | undefined,
value: T,
): void {
if (!observer) return;
try {
void Promise.resolve(observer(clonePlain(value))).catch(() => {
// Presentation-only supervision must not gate graph execution.
});
} catch {
// Presentation-only supervision must not gate graph execution.
}
}
function clonePlain<T>(value: T): T {
return structuredClone(value);
}