From 34e3048f473404cdbc0292d0075b7bae40738184 Mon Sep 17 00:00:00 2001 From: Jason Doyle Date: Wed, 2 Sep 2026 15:28:37 -0700 Subject: [PATCH 1/2] Formalize core operation layers --- CHANGELOG.md | 5 + README.md | 20 +- docs/API.md | 63 ++++-- scripts/test-package.mjs | 26 ++- src/index.ts | 25 +++ src/ir.ts | 39 ++-- src/kernel.ts | 52 +++-- src/layers.ts | 389 ++++++++++++++++++++++++++++++++++++++ src/production/catalog.ts | 30 +-- src/production/index.ts | 22 +++ src/production/kernel.ts | 93 +++++++-- src/test/layers.test.ts | 189 ++++++++++++++++++ src/types.ts | 17 ++ 13 files changed, 859 insertions(+), 111 deletions(-) create mode 100644 src/layers.ts create mode 100644 src/test/layers.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index d044dcc..f7eb5e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,11 @@ ## Unreleased +- Added typed Knowledge and Agency operation layers to the embedded API and + operation catalogs. +- Moved retail Agent Intent dispatch behind a compatibility adapter without + removing or renaming existing operations. + ## 0.3.0-alpha.3 - Added a reproducible SRE comparison between a competent conventional diff --git a/README.md b/README.md index d811494..af930df 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,9 @@ single governed system. It is designed for applications that need to answer: - What conflicts with it? - Which workflow or external action depended on it? +The public API separates these responsibilities into a Knowledge layer, an +Agency layer, and a compatibility adapter for the original retail workflow. + ## Capabilities - Bitemporal assertions with evidence, epistemic kind, perspective, and typed @@ -130,6 +133,8 @@ import { AgenticKernel, SqliteStore } from "agentic-data-kernel"; const store = new SqliteStore(".data/app.db"); const kernel = new AgenticKernel(store); + +const { knowledge, agency, retail } = kernel; ``` The npm package provides: @@ -342,12 +347,15 @@ HTTP / MCP / TypeScript / CLI | identity, scope, purpose | - knowledge + workflow kernel - | assertions and evidence - | conflict resolution - | hybrid retrieval - | timers and state machines - | effects and receipts + Knowledge layer + | assertions, evidence, retrieval + | resolution, lineage, explanation + + + Agency layer + | workflows, effects, receipts + + + retail compatibility adapter + | inventory, orders, payment timers | SQLite development adapter or diff --git a/docs/API.md b/docs/API.md index 5714263..cc8ed9d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -37,26 +37,48 @@ separate idempotency namespace. In the PostgreSQL profile, the supplied principal must exactly match the authenticated API key. +## Operation layers + +Agent Intent keeps one backward-compatible operation union and one execution +endpoint. Operations are now grouped into three explicit layers: + +| Layer | Responsibility | +| --- | --- | +| Knowledge | Entities, artifacts, temporal assertions, resolution, retrieval, lineage, and explanation | +| Agency | Generic workflows, controlled effects, workflow reads, and effect reads | +| Retail compatibility | Inventory reservation, payment, order expiry, and existing retail workflow behavior | + +The embedded TypeScript API exposes these as `kernel.knowledge`, +`kernel.agency`, and `kernel.retail`. Existing methods such as +`kernel.assert(...)`, `kernel.createWorkflow(...)`, and +`kernel.reserveInventory(...)` remain available with unchanged behavior. +`kernel.agency.getMachine(...)` returns either a generic workflow or retail +order record, while `kernel.retail.getOrder(...)` requires a retail order. + +The PostgreSQL, HTTP, MCP, and CLI profiles continue to use the same Agent +Intent envelope. `GET /v1/catalog` now reports the layer for each available +operation. No operation name was removed or renamed. + ## Operations -| Operation | Scope | Description | -| --- | --- | --- | -| `put_entity` | `data:write` | Create or update an entity identity | -| `put_artifact` | `data:write` | Store immutable source evidence | -| `assert` | `data:write` | Add or supersede a typed temporal assertion | -| `resolve` | `data:read` | Return known, unknown, conflicting, or policy-selected values | -| `search` | `data:read` | Run hybrid retrieval with optional graph filters | -| `create_workflow` | `workflows:run` | Create a non-retail durable workflow | -| `advance_workflow` | `workflows:run` | Commit a guarded workflow transition | -| `request_effect` | `effects:write` | Request an authorized generic external effect | -| `add_lineage` | `data:write` | Add a typed causal link between durable records | -| `explain` | `data:read` | Traverse bounded typed causal lineage | -| `seed_inventory` | `inventory:admin` | Create initial inventory for a SKU and location | -| `reserve_inventory` | `orders:write` | Reserve stock and start an order workflow | -| `request_payment` | `effects:write` | Reserve effect budget and create a payment intent | -| `get_machine` | `data:read` | Read current workflow state | -| `list_effects` | `data:read` | Read effect state | -| `process_timers` | `workflows:run` | Process timers using database server time | +| Operation | Layer | Scope | Description | +| --- | --- | --- | --- | +| `put_entity` | Knowledge | `data:write` | Create or update an entity identity | +| `put_artifact` | Knowledge | `data:write` | Store immutable source evidence | +| `assert` | Knowledge | `data:write` | Add or supersede a typed temporal assertion | +| `resolve` | Knowledge | `data:read` | Return known, unknown, conflicting, or policy-selected values | +| `search` | Knowledge | `data:read` | Run hybrid retrieval with optional graph filters | +| `add_lineage` | Knowledge | `data:write` | Add a typed causal link between durable records | +| `explain` | Knowledge | `data:read` | Traverse bounded typed causal lineage | +| `create_workflow` | Agency | `workflows:run` | Create a non-retail durable workflow | +| `advance_workflow` | Agency | `workflows:run` | Commit a guarded workflow transition | +| `request_effect` | Agency | `effects:write` | Request an authorized generic external effect | +| `get_machine` | Agency | `data:read` | Read current workflow state | +| `list_effects` | Agency | `data:read` | Read effect state | +| `seed_inventory` | Retail compatibility | `inventory:admin` | Create initial inventory for a SKU and location | +| `reserve_inventory` | Retail compatibility | `orders:write` | Reserve stock and start an order workflow | +| `request_payment` | Retail compatibility | `effects:write` | Reserve effect budget and create a payment intent | +| `process_timers` | Retail compatibility | `workflows:run` | Process retail reservation timers using database server time | `record_payment_outcome` exists only in the development profile. The production profile accepts terminal effect state only from the effect worker. @@ -221,7 +243,10 @@ Temporal, status, tenant, optional field, and graph constraints remain inside candidate selection so bounded retrieval does not reintroduce stale or out-of-scope rows. -## Retail workflow +## Retail compatibility workflow + +Retail operations remain supported behind the compatibility adapter while the +generic Agency layer stays independent of retail order invariants. ```text new -> reserved -> payment_pending -> confirmed diff --git a/scripts/test-package.mjs b/scripts/test-package.mjs index f58d4ac..fafb6d0 100644 --- a/scripts/test-package.mjs +++ b/scripts/test-package.mjs @@ -102,9 +102,11 @@ try { smokeModule, `import { AgenticKernel, - SqliteStore, - formatTraceExplanation, -} from "agentic-data-kernel"; + KNOWLEDGE_OPERATION_NAMES, + KnowledgeLayer, + SqliteStore, + formatTraceExplanation, + } from "agentic-data-kernel"; import { OpenAiCompatibleEmbeddingProvider, postgresMigrationDirectory, @@ -124,6 +126,12 @@ try { if (typeof formatTraceExplanation !== "function") { throw new Error("Trace formatter export is unavailable"); } + if ( + !(kernel.knowledge instanceof KnowledgeLayer) || + !KNOWLEDGE_OPERATION_NAMES.includes("assert") + ) { + throw new Error("Layered API exports are unavailable"); + } if ( !existsSync(join(postgresMigrationDirectory, "001_core.sql")) || !existsSync(join(postgresMigrationDirectory, "002_embedding_space.sql")) || @@ -146,9 +154,11 @@ try { typeSmokeModule, `import { AgenticKernel, - SqliteStore, - formatTraceExplanation, -} from "agentic-data-kernel"; + type KnowledgeOperationName, + KnowledgeLayer, + SqliteStore, + formatTraceExplanation, + } from "agentic-data-kernel"; import { type EmbeddingSpace, ProductionDatabase, @@ -157,6 +167,8 @@ import { const store = new SqliteStore(":memory:"); const kernel: AgenticKernel = new AgenticKernel(store); +const knowledgeLayer: KnowledgeLayer = kernel.knowledge; +const knowledgeOperation: KnowledgeOperationName = "assert"; const formatter: typeof formatTraceExplanation = formatTraceExplanation; const databaseType: typeof ProductionDatabase = ProductionDatabase; const migrationPath: string = postgresMigrationDirectory; @@ -166,6 +178,8 @@ const embeddingSpace: EmbeddingSpace = { dimensions: 768, }; void kernel; +void knowledgeLayer; +void knowledgeOperation; void formatter; void databaseType; void migrationPath; diff --git a/src/index.ts b/src/index.ts index 7856c2b..d3d0e6b 100644 --- a/src/index.ts +++ b/src/index.ts @@ -10,6 +10,31 @@ export type { IntentEnvelope, IntentExecutionResult, } from "./ir.js"; +export { + AGENCY_OPERATION_NAMES, + AgencyLayer, + DEVELOPMENT_OPERATION_NAMES, + KNOWLEDGE_OPERATION_NAMES, + KnowledgeLayer, + isAgencyOperation, + isKnowledgeOperation, + isRetailCompatibilityOperation, + operationLayer, + operationLayerCatalog, + PRODUCTION_OPERATION_NAMES, + RETAIL_COMPATIBILITY_OPERATION_NAMES, + RetailCompatibilityAdapter, +} from "./layers.js"; +export type { + AgencyOperation, + AgencyOperationName, + AgentOperationName, + KnowledgeOperation, + KnowledgeOperationName, + OperationLayer, + RetailCompatibilityOperation, + RetailCompatibilityOperationName, +} from "./layers.js"; export { SqliteStore } from "./store.js"; export { formatTraceExplanation, diff --git a/src/ir.ts b/src/ir.ts index 11564ad..263ea54 100644 --- a/src/ir.ts +++ b/src/ir.ts @@ -425,13 +425,13 @@ function executeOperation( const { operation, principal } = envelope; switch (operation.op) { case "put_entity": - return kernel.putEntity(principal, operation.entity); + return kernel.knowledge.putEntity(principal, operation.entity); case "put_artifact": - return kernel.putArtifact(principal, operation.artifact); + return kernel.knowledge.putArtifact(principal, operation.artifact); case "assert": - return kernel.assert(principal, operation.assertion); + return kernel.knowledge.assert(principal, operation.assertion); case "resolve": - return kernel.resolve( + return kernel.knowledge.resolve( principal.tenantId, operation.subjectEntityId, operation.predicate, @@ -443,7 +443,7 @@ function executeOperation( }, ); case "search": - return kernel.search(principal.tenantId, { + return kernel.knowledge.search(principal.tenantId, { text: operation.text, predicate: operation.predicate, kind: operation.kind, @@ -455,43 +455,46 @@ function executeOperation( limit: operation.limit, }); case "create_workflow": - return kernel.createWorkflow(principal, operation); + return kernel.agency.createWorkflow(principal, operation); case "advance_workflow": - return kernel.advanceWorkflow(principal, operation); + return kernel.agency.advanceWorkflow(principal, operation); case "request_effect": - return kernel.requestEffect(principal, operation); + return kernel.agency.requestEffect(principal, operation); case "add_lineage": - return kernel.addLineage(principal, operation); + return kernel.knowledge.addLineage(principal, operation); case "explain": - return kernel.explain( + return kernel.knowledge.explain( principal.tenantId, operation.target, operation.maxDepth, ); case "record_effect_outcome": - return kernel.recordEffectOutcome(principal, operation); + return kernel.agency.recordEffectOutcome(principal, operation); case "seed_inventory": - return kernel.seedInventory( + return kernel.retail.seedInventory( principal, operation.sku, operation.location, operation.quantityOnHand, ); case "reserve_inventory": - return kernel.reserveInventory(principal, operation); + return kernel.retail.reserveInventory(principal, operation); case "request_payment": - return kernel.requestPayment(principal, operation); + return kernel.retail.requestPayment(principal, operation); case "record_payment_outcome": - return kernel.recordPaymentOutcome(principal, operation); + return kernel.retail.recordPaymentOutcome(principal, operation); case "get_machine": - return kernel.getMachineRecord( + return kernel.agency.getMachine( principal.tenantId, operation.instanceId, ); case "list_effects": - return kernel.listEffects(principal.tenantId, operation.instanceId); + return kernel.agency.listEffects( + principal.tenantId, + operation.instanceId, + ); case "process_timers": - return kernel.processDueTimers(principal, operation.asOf); + return kernel.retail.processTimers(principal, operation.asOf); default: return assertNever(operation); } diff --git a/src/kernel.ts b/src/kernel.ts index 17ddbab..cf4767e 100644 --- a/src/kernel.ts +++ b/src/kernel.ts @@ -4,6 +4,13 @@ import { summarizeTraceJson, traceEndpointKey, } from "./explain.js"; +import { + AgencyLayer, + DEVELOPMENT_OPERATION_NAMES, + KnowledgeLayer, + operationLayerCatalog, + RetailCompatibilityAdapter, +} from "./layers.js"; import type { AdvanceWorkflowInput, ArtifactInput, @@ -12,7 +19,6 @@ import type { AssertionQuery, AssertionRecord, AssertionStatus, - CatalogDescription, CreateWorkflowInput, EffectRecord, EffectOutcomeInput, @@ -26,7 +32,9 @@ import type { JsonValue, LineageEdgeRecord, LineageEndpoint, + LineageInput, LineageRelation, + LayeredCatalogDescription, MachineRecord, MachineState, OrderData, @@ -227,10 +235,18 @@ export class KernelError extends Error { } export class AgenticKernel { + public readonly knowledge: KnowledgeLayer; + public readonly agency: AgencyLayer; + public readonly retail: RetailCompatibilityAdapter; + public constructor( private readonly store: SqliteStore, private readonly clock: () => Date = () => new Date(), - ) {} + ) { + this.knowledge = new KnowledgeLayer(this); + this.agency = new AgencyLayer(this); + this.retail = new RetailCompatibilityAdapter(this); + } public transaction(operation: () => T): T { return this.store.transaction(operation); @@ -1064,11 +1080,7 @@ export class AgenticKernel { public addLineage( principal: PrincipalContext, - input: { - relation: LineageRelation; - from: LineageEndpoint; - to: LineageEndpoint; - }, + input: LineageInput, ): LineageEdgeRecord { return this.store.transaction(() => this.insertLineage(principal, input), @@ -1861,30 +1873,14 @@ export class AgenticKernel { } } - public catalog(): CatalogDescription { + public catalog(): LayeredCatalogDescription { return { protocolVersion: "0.1", storage: "Node.js embedded SQLite with replaceable storage boundary", - operations: [ - "put_entity", - "put_artifact", - "assert", - "resolve", - "search", - "create_workflow", - "advance_workflow", - "request_effect", - "add_lineage", - "explain", - "record_effect_outcome", - "seed_inventory", - "reserve_inventory", - "request_payment", - "record_payment_outcome", - "get_machine", - "list_effects", - "process_timers", - ], + operations: [...DEVELOPMENT_OPERATION_NAMES], + operationLayers: operationLayerCatalog( + DEVELOPMENT_OPERATION_NAMES, + ), epistemicKinds: [ "observation", "reported_fact", diff --git a/src/layers.ts b/src/layers.ts new file mode 100644 index 0000000..9cd3c84 --- /dev/null +++ b/src/layers.ts @@ -0,0 +1,389 @@ +import type { AgentOperation } from "./ir.js"; +import type { AgenticKernel } from "./kernel.js"; +import type { + AdvanceWorkflowInput, + ArtifactInput, + ArtifactRecord, + AssertionInput, + AssertionQuery, + AssertionRecord, + CreateWorkflowInput, + EffectOutcomeInput, + EffectRecord, + EntityInput, + EntityRecord, + GenericEffectRequestInput, + InventoryRecord, + LineageEdgeRecord, + LineageEndpoint, + LineageInput, + MachineRecord, + OperationLayerCatalog, + PaymentOutcomeInput, + PaymentRequestInput, + PrincipalContext, + ReservationResult, + ReserveInventoryInput, + ResolutionPolicy, + ResolutionResult, + SearchHit, + SearchQuery, + TraceExplanation, + WorkflowRecord, +} from "./types.js"; + +export const KNOWLEDGE_OPERATION_NAMES = [ + "put_entity", + "put_artifact", + "assert", + "resolve", + "search", + "add_lineage", + "explain", +] as const; + +export const AGENCY_OPERATION_NAMES = [ + "create_workflow", + "advance_workflow", + "request_effect", + "record_effect_outcome", + "get_machine", + "list_effects", +] as const; + +export const RETAIL_COMPATIBILITY_OPERATION_NAMES = [ + "seed_inventory", + "reserve_inventory", + "request_payment", + "record_payment_outcome", + "process_timers", +] as const; + +export type KnowledgeOperationName = + (typeof KNOWLEDGE_OPERATION_NAMES)[number]; +export type AgencyOperationName = + (typeof AGENCY_OPERATION_NAMES)[number]; +export type RetailCompatibilityOperationName = + (typeof RETAIL_COMPATIBILITY_OPERATION_NAMES)[number]; +export type AgentOperationName = + | KnowledgeOperationName + | AgencyOperationName + | RetailCompatibilityOperationName; +export type OperationLayer = + | "knowledge" + | "agency" + | "retail_compatibility"; + +export type KnowledgeOperation = Extract< + AgentOperation, + { op: KnowledgeOperationName } +>; +export type AgencyOperation = Extract< + AgentOperation, + { op: AgencyOperationName } +>; +export type RetailCompatibilityOperation = Extract< + AgentOperation, + { op: RetailCompatibilityOperationName } +>; + +type MissingOperationNames = Exclude< + AgentOperation["op"], + AgentOperationName +>; +type UnknownOperationNames = Exclude< + AgentOperationName, + AgentOperation["op"] +>; +const operationNamesMatchSchema: [ + MissingOperationNames, + UnknownOperationNames, +] extends [never, never] + ? true + : never = true; +void operationNamesMatchSchema; + +const knowledgeOperationSet = new Set( + KNOWLEDGE_OPERATION_NAMES, +); +const agencyOperationSet = new Set(AGENCY_OPERATION_NAMES); +const retailCompatibilityOperationSet = new Set( + RETAIL_COMPATIBILITY_OPERATION_NAMES, +); + +export const DEVELOPMENT_OPERATION_NAMES: readonly AgentOperationName[] = [ + "put_entity", + "put_artifact", + "assert", + "resolve", + "search", + "create_workflow", + "advance_workflow", + "request_effect", + "add_lineage", + "explain", + "record_effect_outcome", + "seed_inventory", + "reserve_inventory", + "request_payment", + "record_payment_outcome", + "get_machine", + "list_effects", + "process_timers", +]; + +export const PRODUCTION_OPERATION_NAMES: readonly AgentOperationName[] = + DEVELOPMENT_OPERATION_NAMES.filter( + (name) => + name !== "record_effect_outcome" && + name !== "record_payment_outcome", + ); + +export function operationLayer( + operationName: AgentOperationName, +): OperationLayer { + if (knowledgeOperationSet.has(operationName)) { + return "knowledge"; + } + if (agencyOperationSet.has(operationName)) { + return "agency"; + } + if (retailCompatibilityOperationSet.has(operationName)) { + return "retail_compatibility"; + } + throw new Error(`Unknown Agent Intent operation ${operationName}`); +} + +export function isKnowledgeOperation( + operation: AgentOperation, +): operation is KnowledgeOperation { + return knowledgeOperationSet.has(operation.op); +} + +export function isAgencyOperation( + operation: AgentOperation, +): operation is AgencyOperation { + return agencyOperationSet.has(operation.op); +} + +export function isRetailCompatibilityOperation( + operation: AgentOperation, +): operation is RetailCompatibilityOperation { + return retailCompatibilityOperationSet.has(operation.op); +} + +export function operationLayerCatalog( + availableOperations: readonly AgentOperationName[], +): OperationLayerCatalog { + const available = new Set(availableOperations); + return { + knowledge: KNOWLEDGE_OPERATION_NAMES.filter((name) => + available.has(name), + ), + agency: AGENCY_OPERATION_NAMES.filter((name) => + available.has(name), + ), + retailCompatibility: RETAIL_COMPATIBILITY_OPERATION_NAMES.filter( + (name) => available.has(name), + ), + }; +} + +export class KnowledgeLayer { + public constructor(private readonly kernel: AgenticKernel) {} + + public putEntity( + principal: PrincipalContext, + input: EntityInput, + ): EntityRecord { + return this.kernel.putEntity(principal, input); + } + + public putArtifact( + principal: PrincipalContext, + input: ArtifactInput, + ): ArtifactRecord { + return this.kernel.putArtifact(principal, input); + } + + public getArtifact( + tenantId: string, + artifactId: string, + ): ArtifactRecord { + return this.kernel.getArtifact(tenantId, artifactId); + } + + public assert( + principal: PrincipalContext, + input: AssertionInput, + ): AssertionRecord { + return this.kernel.assert(principal, input); + } + + public getEntity(tenantId: string, entityId: string): EntityRecord { + return this.kernel.getEntity(tenantId, entityId); + } + + public getAssertion( + tenantId: string, + assertionId: string, + ): AssertionRecord { + return this.kernel.getAssertion(tenantId, assertionId); + } + + public queryAssertions( + tenantId: string, + query: AssertionQuery = {}, + ): AssertionRecord[] { + return this.kernel.queryAssertions(tenantId, query); + } + + public resolve( + tenantId: string, + subjectEntityId: string, + predicate: string, + policy: ResolutionPolicy = "none", + options: Pick< + AssertionQuery, + "perspective" | "validAt" | "systemAt" + > = {}, + ): ResolutionResult { + return this.kernel.resolve( + tenantId, + subjectEntityId, + predicate, + policy, + options, + ); + } + + public search(tenantId: string, query: SearchQuery): SearchHit[] { + return this.kernel.search(tenantId, query); + } + + public addLineage( + principal: PrincipalContext, + input: LineageInput, + ): LineageEdgeRecord { + return this.kernel.addLineage(principal, input); + } + + public explain( + tenantId: string, + root: LineageEndpoint, + maxDepth = 4, + ): TraceExplanation { + return this.kernel.explain(tenantId, root, maxDepth); + } +} + +export class AgencyLayer { + public constructor(private readonly kernel: AgenticKernel) {} + + public createWorkflow( + principal: PrincipalContext, + input: CreateWorkflowInput, + ): WorkflowRecord { + return this.kernel.createWorkflow(principal, input); + } + + public advanceWorkflow( + principal: PrincipalContext, + input: AdvanceWorkflowInput, + ): WorkflowRecord { + return this.kernel.advanceWorkflow(principal, input); + } + + public requestEffect( + principal: PrincipalContext, + input: GenericEffectRequestInput, + ): EffectRecord { + return this.kernel.requestEffect(principal, input); + } + + public recordEffectOutcome( + principal: PrincipalContext, + input: EffectOutcomeInput, + ): EffectRecord { + return this.kernel.recordEffectOutcome(principal, input); + } + + public getMachine( + tenantId: string, + instanceId: string, + ): MachineRecord | WorkflowRecord { + return this.kernel.getMachineRecord(tenantId, instanceId); + } + + public getWorkflow( + tenantId: string, + instanceId: string, + ): WorkflowRecord { + return this.kernel.getWorkflow(tenantId, instanceId); + } + + public listEffects( + tenantId: string, + instanceId?: string, + ): EffectRecord[] { + return this.kernel.listEffects(tenantId, instanceId); + } +} + +export class RetailCompatibilityAdapter { + public constructor(private readonly kernel: AgenticKernel) {} + + public seedInventory( + principal: PrincipalContext, + sku: string, + location: string, + quantityOnHand: number, + ): InventoryRecord { + return this.kernel.seedInventory( + principal, + sku, + location, + quantityOnHand, + ); + } + + public getInventory( + tenantId: string, + sku: string, + location: string, + ): InventoryRecord { + return this.kernel.getInventory(tenantId, sku, location); + } + + public reserveInventory( + principal: PrincipalContext, + input: ReserveInventoryInput, + ): ReservationResult { + return this.kernel.reserveInventory(principal, input); + } + + public requestPayment( + principal: PrincipalContext, + input: PaymentRequestInput, + ): EffectRecord { + return this.kernel.requestPayment(principal, input); + } + + public recordPaymentOutcome( + principal: PrincipalContext, + input: PaymentOutcomeInput, + ): MachineRecord { + return this.kernel.recordPaymentOutcome(principal, input); + } + + public processTimers( + principal: PrincipalContext, + asOf?: string, + ): MachineRecord[] { + return this.kernel.processDueTimers(principal, asOf); + } + + public getOrder(tenantId: string, instanceId: string): MachineRecord { + return this.kernel.getMachine(tenantId, instanceId); + } +} diff --git a/src/production/catalog.ts b/src/production/catalog.ts index 42ea126..714a246 100644 --- a/src/production/catalog.ts +++ b/src/production/catalog.ts @@ -1,9 +1,13 @@ -import type { CatalogDescription } from "../types.js"; +import type { LayeredCatalogDescription } from "../types.js"; +import { + operationLayerCatalog, + PRODUCTION_OPERATION_NAMES, +} from "../layers.js"; import type { EmbeddingSpace } from "./embeddings.js"; export function productionCatalog( embeddingSpace?: EmbeddingSpace, -): CatalogDescription & { +): LayeredCatalogDescription & { profile: "postgres-production"; security: string[]; embeddingSpace?: EmbeddingSpace; @@ -12,24 +16,10 @@ export function productionCatalog( protocolVersion: "0.1", profile: "postgres-production", storage: "PostgreSQL 18 with pgvector and forced tenant row-level security", - operations: [ - "put_entity", - "put_artifact", - "assert", - "resolve", - "search", - "create_workflow", - "advance_workflow", - "request_effect", - "add_lineage", - "explain", - "seed_inventory", - "reserve_inventory", - "request_payment", - "get_machine", - "list_effects", - "process_timers", - ], + operations: [...PRODUCTION_OPERATION_NAMES], + operationLayers: operationLayerCatalog( + PRODUCTION_OPERATION_NAMES, + ), epistemicKinds: [ "observation", "reported_fact", diff --git a/src/production/index.ts b/src/production/index.ts index 6c0c37a..ff3b87a 100644 --- a/src/production/index.ts +++ b/src/production/index.ts @@ -73,3 +73,25 @@ export { summarizeTraceJson, traceEndpointKey, } from "../explain.js"; +export { + AGENCY_OPERATION_NAMES, + DEVELOPMENT_OPERATION_NAMES, + KNOWLEDGE_OPERATION_NAMES, + isAgencyOperation, + isKnowledgeOperation, + isRetailCompatibilityOperation, + operationLayer, + operationLayerCatalog, + PRODUCTION_OPERATION_NAMES, + RETAIL_COMPATIBILITY_OPERATION_NAMES, +} from "../layers.js"; +export type { + AgencyOperation, + AgencyOperationName, + AgentOperationName, + KnowledgeOperation, + KnowledgeOperationName, + OperationLayer, + RetailCompatibilityOperation, + RetailCompatibilityOperationName, +} from "../layers.js"; diff --git a/src/production/kernel.ts b/src/production/kernel.ts index 866a0a1..91e115e 100644 --- a/src/production/kernel.ts +++ b/src/production/kernel.ts @@ -12,6 +12,14 @@ import { summarizeTraceJson, traceEndpointKey, } from "../explain.js"; +import { + isAgencyOperation, + isKnowledgeOperation, + isRetailCompatibilityOperation, + type AgencyOperation, + type KnowledgeOperation, + type RetailCompatibilityOperation, +} from "../layers.js"; import { parseIntentEnvelope, type AgentOperation, @@ -583,6 +591,37 @@ export class ProductionKernel { preparedArtifact: PreparedArtifact | null, preparedAssertion: PreparedAssertion | null, searchEmbedding: number[] | null, + ): Promise { + if (isKnowledgeOperation(operation)) { + return this.executeKnowledgeOperation( + client, + principal, + operation, + preparedArtifact, + preparedAssertion, + searchEmbedding, + ); + } + if (isAgencyOperation(operation)) { + return this.executeAgencyOperation(client, principal, operation); + } + if (isRetailCompatibilityOperation(operation)) { + return this.executeRetailCompatibilityOperation( + client, + principal, + operation, + ); + } + return assertNever(operation); + } + + private async executeKnowledgeOperation( + client: PoolClient, + principal: AuthenticatedPrincipal, + operation: KnowledgeOperation, + preparedArtifact: PreparedArtifact | null, + preparedAssertion: PreparedAssertion | null, + searchEmbedding: number[] | null, ): Promise { switch (operation.op) { case "put_entity": @@ -609,12 +648,6 @@ export class ProductionKernel { throw new Error("Search embedding was not prepared"); } return this.search(client, operation, searchEmbedding); - case "create_workflow": - return this.createWorkflow(client, principal, operation); - case "advance_workflow": - return this.advanceWorkflow(client, principal, operation); - case "request_effect": - return this.requestEffect(client, principal, operation); case "add_lineage": return this.addLineage(client, principal, operation); case "explain": @@ -624,11 +657,51 @@ export class ProductionKernel { operation.target, operation.maxDepth ?? 4, ); + default: + return assertNever(operation); + } + } + + private async executeAgencyOperation( + client: PoolClient, + principal: AuthenticatedPrincipal, + operation: AgencyOperation, + ): Promise { + switch (operation.op) { + case "create_workflow": + return this.createWorkflow(client, principal, operation); + case "advance_workflow": + return this.advanceWorkflow(client, principal, operation); + case "request_effect": + return this.requestEffect(client, principal, operation); case "record_effect_outcome": throw new KernelError( "unauthorized", "Effect outcomes are accepted only from the effect worker", ); + case "get_machine": + return this.getMachineRecord( + client, + principal.tenantId, + operation.instanceId, + ); + case "list_effects": + return this.listEffects( + client, + principal.tenantId, + operation.instanceId, + ); + default: + return assertNever(operation); + } + } + + private async executeRetailCompatibilityOperation( + client: PoolClient, + principal: AuthenticatedPrincipal, + operation: RetailCompatibilityOperation, + ): Promise { + switch (operation.op) { case "seed_inventory": return this.seedInventory(client, principal, operation); case "reserve_inventory": @@ -637,14 +710,6 @@ export class ProductionKernel { return this.requestPayment(client, principal, operation); case "record_payment_outcome": return this.recordPaymentOutcome(client, principal, operation); - case "get_machine": - return this.getMachineRecord( - client, - principal.tenantId, - operation.instanceId, - ); - case "list_effects": - return this.listEffects(client, principal.tenantId, operation.instanceId); case "process_timers": return this.processTimers(client, principal, operation.asOf); default: diff --git a/src/test/layers.test.ts b/src/test/layers.test.ts new file mode 100644 index 0000000..ecfe91e --- /dev/null +++ b/src/test/layers.test.ts @@ -0,0 +1,189 @@ +import assert from "node:assert/strict"; +import test from "node:test"; +import { + DEVELOPMENT_OPERATION_NAMES, + operationLayer, + operationLayerCatalog, + PRODUCTION_OPERATION_NAMES, + RetailCompatibilityAdapter, +} from "../layers.js"; +import { AgenticKernel } from "../kernel.js"; +import { productionCatalog } from "../production/catalog.js"; +import { SqliteStore } from "../store.js"; +import type { PrincipalContext } from "../types.js"; + +const principal: PrincipalContext = { + tenantId: "layer-tenant", + principalId: "layer-agent", + purpose: "layer-test", +}; + +test("operation layers classify every compatible Agent Intent operation", () => { + assert.deepEqual(DEVELOPMENT_OPERATION_NAMES, [ + "put_entity", + "put_artifact", + "assert", + "resolve", + "search", + "create_workflow", + "advance_workflow", + "request_effect", + "add_lineage", + "explain", + "record_effect_outcome", + "seed_inventory", + "reserve_inventory", + "request_payment", + "record_payment_outcome", + "get_machine", + "list_effects", + "process_timers", + ]); + assert.equal( + new Set(DEVELOPMENT_OPERATION_NAMES).size, + DEVELOPMENT_OPERATION_NAMES.length, + ); + assert.equal(operationLayer("assert"), "knowledge"); + assert.equal(operationLayer("request_effect"), "agency"); + assert.equal(operationLayer("process_timers"), "retail_compatibility"); + assert.throws( + () => + operationLayer( + "unknown_operation" as (typeof DEVELOPMENT_OPERATION_NAMES)[number], + ), + /Unknown Agent Intent operation/, + ); + assert.deepEqual( + operationLayerCatalog(DEVELOPMENT_OPERATION_NAMES), + { + knowledge: [ + "put_entity", + "put_artifact", + "assert", + "resolve", + "search", + "add_lineage", + "explain", + ], + agency: [ + "create_workflow", + "advance_workflow", + "request_effect", + "record_effect_outcome", + "get_machine", + "list_effects", + ], + retailCompatibility: [ + "seed_inventory", + "reserve_inventory", + "request_payment", + "record_payment_outcome", + "process_timers", + ], + }, + ); +}); + +test("embedded layers provide preferred APIs without removing legacy methods", () => { + const store = new SqliteStore(":memory:"); + const kernel = new AgenticKernel(store); + try { + assert.ok(kernel.retail instanceof RetailCompatibilityAdapter); + kernel.knowledge.putEntity(principal, { + entityId: "service:checkout", + entityType: "service", + canonicalName: "Checkout", + }); + kernel.knowledge.assert(principal, { + assertionId: "assertion:healthy", + subjectEntityId: "service:checkout", + predicate: "healthy", + object: { type: "boolean", value: true }, + kind: "observation", + }); + assert.equal( + kernel.knowledge.resolve( + principal.tenantId, + "service:checkout", + "healthy", + ).status, + "known", + ); + + const workflow = kernel.agency.createWorkflow(principal, { + instanceId: "incident:layers", + workflowType: "incident_response", + initialState: "open", + data: { severity: 3 }, + }); + assert.deepEqual( + kernel.getWorkflow(principal.tenantId, workflow.instanceId), + workflow, + ); + + const inventory = kernel.retail.seedInventory( + principal, + "camera", + "store", + 2, + ); + assert.deepEqual( + kernel.getInventory(principal.tenantId, "camera", "store"), + inventory, + ); + const reservation = kernel.retail.reserveInventory(principal, { + orderId: "layer-order", + sku: "camera", + location: "store", + quantity: 1, + holdSeconds: 600, + idempotencyKey: "layer-order", + }); + assert.equal( + kernel.getMachine( + principal.tenantId, + reservation.machine.instanceId, + ).state, + "reserved", + ); + } finally { + store.close(); + } +}); + +test("catalogs expose profile-specific operation layers", () => { + const store = new SqliteStore(":memory:"); + const kernel = new AgenticKernel(store); + try { + const development = kernel.catalog(); + assert.deepEqual( + development.operations, + DEVELOPMENT_OPERATION_NAMES, + ); + assert.deepEqual( + development.operationLayers, + operationLayerCatalog(DEVELOPMENT_OPERATION_NAMES), + ); + } finally { + store.close(); + } + + const production = productionCatalog(); + assert.deepEqual(production.operations, PRODUCTION_OPERATION_NAMES); + assert.deepEqual( + production.operationLayers, + operationLayerCatalog(PRODUCTION_OPERATION_NAMES), + ); + assert.equal( + production.operationLayers.agency.includes( + "record_effect_outcome", + ), + false, + ); + assert.equal( + production.operationLayers.retailCompatibility.includes( + "record_payment_outcome", + ), + false, + ); +}); diff --git a/src/types.ts b/src/types.ts index 45283c3..81591f3 100644 --- a/src/types.ts +++ b/src/types.ts @@ -276,6 +276,12 @@ export interface LineageEdgeRecord { createdAt: string; } +export interface LineageInput { + relation: LineageRelation; + from: LineageEndpoint; + to: LineageEndpoint; +} + export interface TraceNode { ref: LineageEndpoint; depth: number; @@ -375,13 +381,24 @@ export interface PaymentOutcomeInput { outcome?: JsonValue; } +export interface OperationLayerCatalog { + knowledge: string[]; + agency: string[]; + retailCompatibility: string[]; +} + export interface CatalogDescription { protocolVersion: "0.1"; storage: string; operations: string[]; + operationLayers?: OperationLayerCatalog; epistemicKinds: EpistemicKind[]; strengthTypes: Strength["type"][]; machineStates: MachineState[]; guarantees: string[]; limitations: string[]; } + +export interface LayeredCatalogDescription extends CatalogDescription { + operationLayers: OperationLayerCatalog; +} From a08e862d549c8117262d55915606d84fde385536 Mon Sep 17 00:00:00 2001 From: Jason Doyle Date: Wed, 2 Sep 2026 15:29:34 -0700 Subject: [PATCH 2/2] Refresh core layer benchmark evidence --- benchmarks/sre/results/report.md | 10 +++++----- benchmarks/sre/results/summary.json | 22 +++++++++++----------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/benchmarks/sre/results/report.md b/benchmarks/sre/results/report.md index 467c2d9..1a1a8ce 100644 --- a/benchmarks/sre/results/report.md +++ b/benchmarks/sre/results/report.md @@ -2,9 +2,9 @@ Generated from `summary.json`. -Source revision: `8f550182ed7441b42ee475f5d4de372949c2622f` +Source revision: `34e3048f473404cdbc0292d0075b7bae40738184` -Source hash: `1922383ad9b79b244ae960c12943e44af727cb08daf084c6a5dda5cd5a77021e` +Source hash: `170c987cc84217860ce29feabceacd863684ed5abd73397c700947635d23755a` ## Correctness @@ -24,7 +24,7 @@ Both variants must resolve every run with one delivery and one reconciliation. The adapter delegates to the shipped SRE scenario, which contains 929 nonblank TypeScript source lines inside the -dependency. The full kernel dependency contains 13292 +dependency. The full kernel dependency contains 13750 nonblank TypeScript source lines. The benchmark runner and engine-specific audit verification contain @@ -44,8 +44,8 @@ operated, or upgraded. | Variant | Median milliseconds | | --- | ---: | -| Conventional PostgreSQL | 73.64 | -| Agentic Data Kernel | 1119.30 | +| Conventional PostgreSQL | 61.04 | +| Agentic Data Kernel | 869.38 | Runtime is not a headline metric. The variants perform different work and this deterministic smoke benchmark is not a latency study. diff --git a/benchmarks/sre/results/summary.json b/benchmarks/sre/results/summary.json index 0172c93..f7d96dd 100644 --- a/benchmarks/sre/results/summary.json +++ b/benchmarks/sre/results/summary.json @@ -4,8 +4,8 @@ "environment": { "node": "v22.22.2", "postgres": "18.6 (Debian 18.6-1.pgdg12+2)", - "commit": "8f550182ed7441b42ee475f5d4de372949c2622f", - "sourceHash": "1922383ad9b79b244ae960c12943e44af727cb08daf084c6a5dda5cd5a77021e" + "commit": "34e3048f473404cdbc0292d0075b7bae40738184", + "sourceHash": "170c987cc84217860ce29feabceacd863684ed5abd73397c700947635d23755a" }, "runs": [ { @@ -31,7 +31,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 70.12690000000009 + "durationMs": 85.97399999999993 }, "operatedTables": 8, "databaseBytes": 540672 @@ -59,7 +59,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 917.8879 + "durationMs": 967.3033000000001 }, "operatedTables": 18, "databaseBytes": 1572864 @@ -87,7 +87,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 92.02959999999985 + "durationMs": 61.03850000000011 }, "operatedTables": 8, "databaseBytes": 540672 @@ -115,7 +115,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 1119.3008 + "durationMs": 864.3142000000003 }, "operatedTables": 18, "databaseBytes": 1572864 @@ -143,7 +143,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 73.63929999999982 + "durationMs": 59.98480000000018 }, "operatedTables": 8, "databaseBytes": 540672 @@ -171,7 +171,7 @@ "provider reconciliation": true, "verification and terminal state": true }, - "durationMs": 1167.6057 + "durationMs": 869.3758999999995 }, "operatedTables": 18, "databaseBytes": 1572864 @@ -266,7 +266,7 @@ "authoredTables": 0, "operatedTables": 18, "scenarioSourceLines": 929, - "dependencySourceLines": 13292 + "dependencySourceLines": 13750 } }, "benchmarkHarness": { @@ -277,8 +277,8 @@ "agenticDataKernelMedian": 1572864 }, "runtimeMillisecondsInformational": { - "conventionalPostgresMedian": 73.63929999999982, - "agenticDataKernelMedian": 1119.3008 + "conventionalPostgresMedian": 61.03850000000011, + "agenticDataKernelMedian": 869.3758999999995 }, "explanationQuestions": 9, "claims": {