From 63820f6dc6646416da6644aa8ee7173cb2a0464b Mon Sep 17 00:00:00 2001 From: Todd Palmer Date: Wed, 22 Apr 2026 14:48:41 -0700 Subject: [PATCH 1/4] docs: reconcile package taxonomy and source-canonical signatures (Pass A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Groups 4/5/6/7 of MECHANICAL_RESOLUTION_SUMMARY.md. Each edit aligns the docs to a surface that already exists in @loop-engine/* source today, or to a decision already locked in API_SURFACE_DECISIONS_RESOLVED.md. Taxonomy + nav (Groups 5/7): - packages/index.mdx: drop @loop-engine/playground from taxonomy and remove the false "internal, not published" claim for core primitives (R-181, R-182). - getting-started/installation.mdx: list all 22 currently published packages, grouped by layer (R-180). Experimental boundaries + error surface (Group 6): - packages/dsl.mdx: mark LoopBuilder.guard / .signal / .priorityRule as 1.1.0+ experimental (R-112); type ValidationResult/ValidationError explicitly (R-115). - packages/guards.mdx: document GuardRegistry.registerBuiltIns() and drop fictional createEvaluator() (R-090); fix the built-in guards list to match registry source. - packages/registry-client.mdx: document RegistryNotFoundError, RegistryConflictError, RegistryNetworkError (R-143). Source-canonical rewrites (Group 4): - packages/events.mdx: LOOP_EVENT_TYPES content matches source; correct extractLearningSignal signature (R-103, plus parameter list). - running-loops/event-subscriptions.mdx: align event type list and loop.completed example payload to actual source. - packages/observability.mdx: complete LoopMetrics field list (R-115 type-shape adjacent). - packages/actors.mdx: actor field shapes match @loop-engine/actors source — drop fictional WebhookActor / SystemActor and gatewaySessionId (D-05 → A). - packages/core.mdx and packages/runtime.mdx: drop orgId from LoopInstance and LoopStore.listOpen* (D-06 → A). - getting-started/quick-start.mdx: drop orgId and the invalid "system" actor type from the runnable example. - packages/signals.mdx: rewrite to current SignalRegistry surface; scope SignalEngine + built-in rule factories as 1.1.0+ roadmap (R-114 group; honors D-20 → C deferral). Refs: oss-repackaging--02a-mechanical-pass-a.md --- content/docs/getting-started/installation.mdx | 22 ++++- content/docs/getting-started/quick-start.mdx | 9 +- content/docs/packages/actors.mdx | 22 ++--- content/docs/packages/core.mdx | 3 +- content/docs/packages/dsl.mdx | 29 +++++-- content/docs/packages/events.mdx | 20 ++--- content/docs/packages/guards.mdx | 39 ++++----- content/docs/packages/index.mdx | 8 +- content/docs/packages/observability.mdx | 2 +- content/docs/packages/registry-client.mdx | 36 ++++++++ content/docs/packages/runtime.mdx | 4 +- content/docs/packages/signals.mdx | 86 ++++++++++++------- .../running-loops/event-subscriptions.mdx | 9 +- 13 files changed, 189 insertions(+), 100 deletions(-) diff --git a/content/docs/getting-started/installation.mdx b/content/docs/getting-started/installation.mdx index 82b91a0..1e37bed 100644 --- a/content/docs/getting-started/installation.mdx +++ b/content/docs/getting-started/installation.mdx @@ -38,7 +38,9 @@ Use at least: ## Install packages individually -If you do not want the SDK aggregate package, install only what you need: +If you do not want the SDK aggregate package, install only what you need. + +Core primitives: - `@loop-engine/core` - domain model types and branded IDs - `@loop-engine/dsl` - `LoopBuilder`, parser, schema validation @@ -50,11 +52,29 @@ If you do not want the SDK aggregate package, install only what you need: - `@loop-engine/observability` - metrics, timelines, replay - `@loop-engine/registry-client` - remote/local **loop catalog** client (package name retains `registry-client`) - `@loop-engine/ui-devtools` - React devtools components + +Stores: + - `@loop-engine/adapter-memory` - in-memory `LoopStore` - `@loop-engine/adapter-postgres` - PostgreSQL `LoopStore` adapter - `@loop-engine/adapter-kafka` - Kafka `EventBus` adapter - `@loop-engine/adapter-http` - HTTP webhook `EventBus` adapter +AI adapters: + +- `@loop-engine/adapter-anthropic` - Claude actor adapter +- `@loop-engine/adapter-openai` - GPT actor adapter +- `@loop-engine/adapter-gemini` - Gemini actor adapter +- `@loop-engine/adapter-grok` - Grok actor adapter +- `@loop-engine/adapter-perplexity` - Sonar grounded-search adapter + +Routing and framework adapters: + +- `@loop-engine/adapter-pagerduty` - PagerDuty approval routing +- `@loop-engine/adapter-openclaw` - OpenClaw skill / approval bridge +- `@loop-engine/adapter-vercel-ai` - Vercel AI SDK tool-call governance +- `@loop-engine/adapter-commerce-gateway` - Commerce Gateway tool routing + ## Runtime requirements - Node.js 18+ is required. diff --git a/content/docs/getting-started/quick-start.mdx b/content/docs/getting-started/quick-start.mdx index 246a975..8b45b4c 100644 --- a/content/docs/getting-started/quick-start.mdx +++ b/content/docs/getting-started/quick-start.mdx @@ -65,20 +65,19 @@ eventBus.subscribe(async (event) => console.log(event.type)) await engine.start({ loopId: 'expense.approval', aggregateId: aggregateId('EXP-2026-001'), - orgId: 'acme', - actor: { type: 'system', id: 'system:intake' } + actor: { type: 'automation', id: 'system:intake', serviceId: 'intake' } }) await engine.transition({ aggregateId: aggregateId('EXP-2026-001'), transitionId: transitionId('start_review'), - actor: { type: 'automation', id: 'system:router' } + actor: { type: 'automation', id: 'system:router', serviceId: 'router' } }) await engine.transition({ aggregateId: aggregateId('EXP-2026-001'), transitionId: transitionId('approve'), - actor: { type: 'human', id: 'manager@acme.com' }, + actor: { type: 'human', id: 'manager@acme.com', userId: 'manager-1', displayName: 'Manager' }, evidence: { comment: 'Approved for Q1 budget' } }) @@ -91,7 +90,7 @@ console.log(state?.status) // CLOSED - The loop moved through 3 states (`SUBMITTED -> UNDER_REVIEW -> APPROVED`) -- 3 actor types were used (`system`, `automation`, `human`) +- 2 actor types were used (`automation`, `human`) - Each successful transition emitted `loop.transition.executed` - Reaching a terminal state sets loop status to `CLOSED` diff --git a/content/docs/packages/actors.mdx b/content/docs/packages/actors.mdx index 797f908..ddcd26f 100644 --- a/content/docs/packages/actors.mdx +++ b/content/docs/packages/actors.mdx @@ -17,28 +17,24 @@ npm install @loop-engine/actors ```ts interface HumanActor extends ActorRef { type: "human" - sessionId: string + userId: string + displayName: string + roles?: string[] } interface AutomationActor extends ActorRef { type: "automation" serviceId: string + version?: string } interface AIAgentActor extends ActorRef { type: "ai-agent" - agentId: string - gatewaySessionId: string - recommendedBy?: string -} - -interface WebhookActor extends ActorRef { - type: "webhook" - source: string -} - -interface SystemActor extends ActorRef { - type: "system" + modelId: string + provider: string + confidence?: number + promptHash?: string + toolsUsed?: string[] } ``` diff --git a/content/docs/packages/core.mdx b/content/docs/packages/core.mdx index e9418f3..a98cc38 100644 --- a/content/docs/packages/core.mdx +++ b/content/docs/packages/core.mdx @@ -99,12 +99,11 @@ export interface ActorRef { export interface LoopInstance { loopId: LoopId aggregateId: AggregateId - orgId: string currentState: StateId status: LoopStatus startedAt: string closedAt?: string - correlationId: CorrelationId + correlationId?: CorrelationId metadata?: Record } diff --git a/content/docs/packages/dsl.mdx b/content/docs/packages/dsl.mdx index f18820f..df5a6d2 100644 --- a/content/docs/packages/dsl.mdx +++ b/content/docs/packages/dsl.mdx @@ -29,7 +29,7 @@ Builder methods implemented in source: - `.build(): LoopDefinition` -`guard()`, `signal()`, and `actor()` are not LoopBuilder methods in `v0.1.0`. Guards are added inside `.transition({ guards: [...] })`. +`guard()`, `signal()`, and `actor()` are slated for `1.1.0+` as experimental builder methods and are not implemented in the current release. Guards are added today inside `.transition({ guards: [...] })`. ## Fluent example @@ -128,9 +128,9 @@ See `/docs/defining-loops/yaml-format` for the full format reference. ```ts import { parseLoopJson, - parseLoopFile, - serializeToJson, - serializeToYaml, + parseLoopYaml, + serializeLoopJson, + serializeLoopYaml, validateLoopDefinition } from "@loop-engine/dsl" ``` @@ -138,7 +138,18 @@ import { Validation signature: ```ts -validateLoopDefinition(input: unknown): { valid: boolean; errors: string[]; definition?: LoopDefinition } +validateLoopDefinition(definition: LoopDefinition): ValidationResult + +interface ValidationResult { + valid: boolean + errors: ValidationError[] +} + +interface ValidationError { + code: string + message: string + path?: string +} ``` Validation failure example: @@ -146,6 +157,12 @@ Validation failure example: ```ts { valid: false, - errors: ["initialState: initialState must exist in states"] + errors: [ + { + code: "INVALID_INITIAL_STATE", + message: 'initialState "DRAFT" does not exist in states', + path: "initialState" + } + ] } ``` diff --git a/content/docs/packages/events.mdx b/content/docs/packages/events.mdx index 59543ae..f150fc7 100644 --- a/content/docs/packages/events.mdx +++ b/content/docs/packages/events.mdx @@ -14,29 +14,28 @@ npm install @loop-engine/events ## Event catalog -The exported `LOOP_EVENT_TYPES` constants map to: +The exported `LOOP_EVENT_TYPES` constant enumerates the nine canonical lifecycle events: - `loop.started` +- `loop.completed` +- `loop.cancelled` +- `loop.failed` - `loop.transition.requested` - `loop.transition.executed` - `loop.transition.blocked` - `loop.guard.failed` -- `loop.completed` -- `loop.error` -- `loop.spawned` - `loop.signal.received` -- `loop.outcome.recorded` Every event extends: ```ts interface LoopEventBase { eventId: string + type: string loopId: LoopId aggregateId: AggregateId - orgId: string occurredAt: string - correlationId: CorrelationId + correlationId?: string causationId?: string } ``` @@ -70,9 +69,10 @@ unsubscribe() ```ts extractLearningSignal( completed: LoopCompletedEvent, - history: TransitionRecord[], - predicted?: Record + history: LoopTransitionExecutedEvent[], + definition: LoopDefinitionLike, + predicted?: Record ): LearningSignal ``` -This helper derives `actual`, `predicted`, and numeric `delta` fields from completion history. +The helper derives `actual`, `predicted`, and numeric `delta` fields from the completed event, the executed-transition history, and the loop definition's declared business metrics. `predicted` keys not declared in `definition.outcome.businessMetrics` are dropped with a warning. diff --git a/content/docs/packages/guards.mdx b/content/docs/packages/guards.mdx index 3e111a6..66b1b23 100644 --- a/content/docs/packages/guards.mdx +++ b/content/docs/packages/guards.mdx @@ -31,40 +31,37 @@ interface GuardResult { ```ts class GuardRegistry { - register(guardId: GuardId, fn: GuardFunction): void - get(guardId: GuardId): GuardFunction | undefined - createEvaluator(): GuardEvaluator + register(guardId: string, evaluator: GuardEvaluator): void + get(guardId: string): GuardEvaluator | undefined + registerBuiltIns(): void } ``` +`registerBuiltIns()` populates the registry with every guard exported from `@loop-engine/guards` (see "Built-in guards" below). Call it once on a fresh registry, or use the pre-populated `defaultRegistry` constant. + ```ts import { createGuardRegistry } from "@loop-engine/guards" import { guardId } from "@loop-engine/core" const registry = createGuardRegistry() -registry.register(guardId("budget_available"), async (context) => ({ - passed: context.evidence.budget_ok === true, - message: "Budget check failed" -})) +registry.register(guardId("budget_available"), { + async evaluate(context) { + return { + passed: context.evidence?.budget_ok === true, + message: "Budget check failed" + } + } +}) ``` ## Built-in guards -`defaultRegistry` pre-registers: - -- `actor_has_permission` (`actorPermissionGuard`) -- `approval_obtained` (`approvalObtainedGuard`) -- `deadline_not_exceeded` (`deadlineNotExceededGuard`) -- `duplicate_check_passed` (`duplicateCheckPassedGuard`) -- `field_value_constraint` (`fieldValueConstraintGuard`) - -Built-in evidence keys: +`defaultRegistry` (and `GuardRegistry.registerBuiltIns()`) pre-registers: -- `approval_obtained` reads `evidence.approved === true` -- `actor_has_permission` reads `required_role` and `roles` -- `deadline_not_exceeded` reads `deadline_iso` -- `duplicate_check_passed` reads `duplicate_found` -- `field_value_constraint` reads `constraint` with operators `eq | gt | lt | in` +- `confidence-threshold` (`ConfidenceThresholdGuard`) +- `human-only` (`HumanOnlyGuard`) +- `evidence-required` (`EvidenceRequiredGuard`) +- `cooldown` (`CooldownGuard`) ## Hard and soft behavior diff --git a/content/docs/packages/index.mdx b/content/docs/packages/index.mdx index 4b5f303..0a9b643 100644 --- a/content/docs/packages/index.mdx +++ b/content/docs/packages/index.mdx @@ -11,12 +11,12 @@ Every additional package is opt-in based on your stack. | Layer | What it is | Required | Current packages | Planned packages | | --- | --- | --- | --- | --- | -| Core | Canonical types and runtime execution. Everything else depends on these. | always | sdk, runtime | Internal (not published): core, dsl, guards, actors, events, signals | +| Core | Canonical types and runtime execution. Everything else depends on these. | always | sdk, runtime, core, dsl, guards, actors, events, signals | — | | Stores | Pluggable persistence for loop state. Swap without changing loop logic. Default to adapter-memory in dev. | pick one | adapter-memory, adapter-postgres, adapter-kafka | adapter-redis, adapter-sqlite, adapter-dynamodb | | AI adapters | Governed LLM actors with confidence scoring, prompt attribution, and hard guard enforcement at runtime; Sonar for grounded retrieval with citations. | pick one per LLM / use-case | adapter-anthropic, adapter-openai, adapter-gemini, adapter-grok, adapter-perplexity | adapter-ollama, adapter-cohere, adapter-mistral | | Routing adapters | Consume PENDING_HUMAN_APPROVAL events and route them to where the human already lives. | pick one for human approval delivery | adapter-pagerduty, adapter-openclaw, adapter-vercel-ai | adapter-slack, adapter-teams, adapter-discord, adapter-webhook | -| Framework adapters | Drop Loop Engine into your existing tool-call layer with zero rebuild. requiresApproval() gates wrap any tool call structurally. | optional | adapter-vercel-ai, adapter-openclaw | adapter-n8n, adapter-temporal, adapter-langchain | -| Observability | Metrics, event timelines, replay, and devtools. Consumes the event bus — no changes to loop definitions needed. | optional | observability, ui-devtools, playground, registry-client (loop catalog) | adapter-datadog, adapter-grafana | +| Framework adapters | Drop Loop Engine into your existing tool-call layer with zero rebuild. requiresApproval() gates wrap any tool call structurally. | optional | adapter-vercel-ai, adapter-openclaw, adapter-commerce-gateway | adapter-n8n, adapter-temporal, adapter-langchain | +| Observability | Metrics, event timelines, replay, and devtools. Consumes the event bus — no changes to loop definitions needed. | optional | observability, ui-devtools, registry-client (loop catalog) | adapter-datadog, adapter-grafana | | Verticals | Pre-assembled, domain-specific loop definitions for regulated industries. An enterprise installs one package instead of assembling manually. | optional | — (in development) | loops-healthcare, loops-fintech, loops-supply-chain, loops-hr-ops, loops-legaltech, loops-construction | ## Minimum install @@ -27,7 +27,7 @@ npm install @loop-engine/sdk This is always the floor. Add layers as your stack demands them. -Core primitives are bundled in `@loop-engine/sdk`. The internal packages are available in the monorepo for contributors. +Core primitives are bundled in `@loop-engine/sdk`. They are also published as standalone `@loop-engine/*` packages for consumers that want to install only the layers they need. ## Common install recipes diff --git a/content/docs/packages/observability.mdx b/content/docs/packages/observability.mdx index ba42f2e..05434c0 100644 --- a/content/docs/packages/observability.mdx +++ b/content/docs/packages/observability.mdx @@ -22,7 +22,7 @@ computeMetrics( ): LoopMetrics ``` -`LoopMetrics` includes `completionRate`, `avgDurationMs`, `medianDurationMs`, `p95DurationMs`, `aiActorRate`, `humanActorRate`, and `avgTransitionCount`. +`LoopMetrics` includes `loopId`, `period`, `totalInstances`, `openInstances`, `closedInstances`, `errorInstances`, `avgDurationMs`, `medianDurationMs`, `p95DurationMs`, `completionRate`, `guardFailureRate`, `aiActorRate`, `humanActorRate`, and `avgTransitionCount`. ```ts import { computeMetrics } from "@loop-engine/observability" diff --git a/content/docs/packages/registry-client.mdx b/content/docs/packages/registry-client.mdx index 8daf8f8..5f2c9e4 100644 --- a/content/docs/packages/registry-client.mdx +++ b/content/docs/packages/registry-client.mdx @@ -86,3 +86,39 @@ interface LoopRegistry { ## SDK integration `createLoopSystem({ loops, registry })` merges **catalog** results with local loops. Local `loops[]` definitions override matching catalog IDs, and catalog load failures fall back to local-only startup. + +## Error classes + +The package exports three typed errors. Catch by class to distinguish missing definitions from conflicts and from network/IO failures. + +```ts +import { + RegistryNotFoundError, + RegistryConflictError, + RegistryNetworkError +} from "@loop-engine/registry-client" +``` + +### RegistryNotFoundError + +Thrown by `get()` and `getVersion()` when a loop (or specific version) is not found. Exposes `loopId` and optional `version`. + +### RegistryConflictError + +Thrown by `register()` when a loop with the same `loopId@version` already exists. Pass `{ force: true }` (development only) to overwrite. Exposes `loopId` and `version`. + +### RegistryNetworkError + +Thrown by `httpRegistry` and the Better Data adapter on network or HTTP failures. Exposes `url`, optional `statusCode`, and the underlying `cause` when available. + +```ts +try { + await registry.register(definition) +} catch (error) { + if (error instanceof RegistryConflictError) { + console.warn(`Already registered: ${error.loopId}@${error.version}`) + return + } + throw error +} +``` diff --git a/content/docs/packages/runtime.mdx b/content/docs/packages/runtime.mdx index 6b432d1..98bf090 100644 --- a/content/docs/packages/runtime.mdx +++ b/content/docs/packages/runtime.mdx @@ -39,7 +39,7 @@ start(options: StartOptions): Promise transition(options: TransitionOptions): Promise getState(aggregateId: AggregateId): Promise getHistory(aggregateId: AggregateId): Promise -listOpen(loopId: string, orgId: string): Promise +listOpen(loopId: string): Promise registerSideEffectHandler(sideEffectId: string, handler: SideEffectHandler): void ``` @@ -71,7 +71,7 @@ interface LoopStore { saveInstance(instance: LoopInstance): Promise getTransitionHistory(aggregateId: AggregateId): Promise saveTransitionRecord(record: TransitionRecord): Promise - listOpenInstances(loopId: LoopId, orgId: string): Promise + listOpenInstances(loopId: LoopId): Promise } interface EventBus { diff --git a/content/docs/packages/signals.mdx b/content/docs/packages/signals.mdx index 923dbfc..234feaa 100644 --- a/content/docs/packages/signals.mdx +++ b/content/docs/packages/signals.mdx @@ -1,10 +1,10 @@ --- title: "@loop-engine/signals" -description: SignalEngine evaluates event streams with rule functions and emits Signal records for detection workflows. +description: SignalRegistry is the current signal-spec surface; SignalEngine pattern detection lands in 1.1.0 as experimental. section: Packages --- -`@loop-engine/signals` detects patterns from `LoopEvent[]` and emits typed `Signal` objects. +`@loop-engine/signals` ships `SignalRegistry` for declaring and validating signal specs. The pattern-detection engine (`SignalEngine`) is on the roadmap for `1.1.0+`. ## Install @@ -12,47 +12,73 @@ section: Packages npm install @loop-engine/signals ``` -## Engine API +## SignalRegistry + +The current public surface is a registry that declares each signal's identity, optional Zod schema, and human-readable metadata. Runtime code uses it to look up and validate signal payloads. ```ts -interface SignalEngine { - registerRule(rule: SignalRule): void - process(events: LoopEvent[]): Signal[] - subscribe(handler: (signal: Signal) => void): () => void +class SignalRegistry { + register(spec: SignalSpec): void + get(signalId: SignalId): SignalSpec | undefined + validatePayload(signalId: SignalId, payload: unknown): { valid: boolean; error?: string } + list(): SignalSpec[] } -``` -Factory: +interface SignalSpec { + signalId: SignalId + name: string + description?: string + schema?: ZodType + tags?: string[] +} +``` ```ts -createSignalEngine(): SignalEngine -``` +import { z } from "zod" +import { SignalRegistry } from "@loop-engine/signals" +import { signalId } from "@loop-engine/core" + +const registry = new SignalRegistry() +registry.register({ + signalId: signalId("expense.submitted"), + name: "Expense submitted", + description: "A user has submitted an expense report for review.", + schema: z.object({ + amount: z.number().positive(), + currency: z.string().length(3) + }) +}) -`createSignalEngine()` pre-registers: +const check = registry.validatePayload(signalId("expense.submitted"), { + amount: 1200, + currency: "USD" +}) +// => { valid: true } +``` -- `threshold-breach` -- `state-dwell` -- `repeated-guard-failure` -- `loop-not-started` +## SignalEngine (experimental, 1.1.0+) -## Built-in rule factories + +`SignalEngine`, `createSignalEngine()`, and the built-in rule factories listed below are slated for `1.1.0+` as experimental APIs and are **not** present in the current release. The shapes shown here are roadmap intent, not a contract — expect them to change before they ship. + ```ts -thresholdBreachRule(config: ThresholdRuleConfig): SignalRule -stateDwellRule(config: StateDwellRuleConfig): SignalRule -repeatedGuardFailureRule(config: RepeatedGuardFailureConfig): SignalRule -loopNotStartedRule(config: LoopNotStartedConfig): SignalRule +interface SignalEngine { + registerRule(rule: SignalRule): void + process(events: LoopEvent[]): Signal[] + subscribe(handler: (signal: Signal) => void): () => void +} + +createSignalEngine(): SignalEngine ``` -```ts -import { createSignalEngine } from "@loop-engine/signals" +Planned built-in rule factories: -const signalEngine = createSignalEngine() -signalEngine.subscribe((signal) => { - console.log(signal.type, signal.subject, signal.confidence) -}) -``` +- `thresholdBreachRule(config: ThresholdRuleConfig): SignalRule` +- `stateDwellRule(config: StateDwellRuleConfig): SignalRule` +- `repeatedGuardFailureRule(config: RepeatedGuardFailureConfig): SignalRule` +- `loopNotStartedRule(config: LoopNotStartedConfig): SignalRule` -## Detection flow +## Detection model -Signals are detections, not actors. Application code decides what to do with them, including whether to start or transition loops. +Signals are detections, not actors. Application code decides what to do with them — including whether to start or transition loops in response. diff --git a/content/docs/running-loops/event-subscriptions.mdx b/content/docs/running-loops/event-subscriptions.mdx index 01d0fbd..8e6a661 100644 --- a/content/docs/running-loops/event-subscriptions.mdx +++ b/content/docs/running-loops/event-subscriptions.mdx @@ -16,15 +16,14 @@ subscribe(handler: (event: LoopEvent) => Promise): () => void ## Event types - `loop.started` +- `loop.completed` +- `loop.cancelled` +- `loop.failed` - `loop.transition.requested` - `loop.transition.executed` - `loop.transition.blocked` - `loop.guard.failed` -- `loop.completed` -- `loop.error` -- `loop.spawned` - `loop.signal.received` -- `loop.outcome.recorded` ## Subscribe patterns @@ -37,7 +36,7 @@ eventBus.subscribe(async (event) => { ```ts eventBus.subscribe(async (event) => { if (event.type === 'loop.completed') { - console.log('Closed:', event.outcomeId, `${event.durationMs}ms`) + console.log('Closed:', event.finalState, `${event.durationMs}ms`) } }) ``` From a1667ca7732ab05f6ff8ca8e40a4554609d82338 Mon Sep 17 00:00:00 2001 From: Todd Palmer Date: Wed, 22 Apr 2026 18:23:22 -0700 Subject: [PATCH 2/4] docs(site): align with LoopEngine rename (D-07) Co-commit pair to loop-engine SR-001. Per resolution log D-07 the runtime engine class is now LoopEngine and the engine method startLoop is now start; update the docs site references that still used the old names. Changes: - components/home/CodeTabs.tsx: engine.startLoop -> engine.start in the homepage runnable snippet - content/docs/integrations/openclaw.mdx: configuration table now types loopSystem as LoopEngine (was LoopSystem) - content/docs/packages/sdk.mdx: re-export bullet now states that createLoopSystem is the SDK auto-wired aggregate and that the runtime factory createLoopEngine lives in @loop-engine/runtime, matching the resolution log explicitly (the SDK does not re-export createLoopEngine) What is intentionally left alone: - All createLoopSystem references that import from @loop-engine/sdk (the SDK retains createLoopSystem per D-07; this is intentional product naming, not an alias) - The page running-loops/create-loop-system.mdx, which documents the SDK factory and is correct as written Verification: - npx tsc --noEmit: green Cross-repo pair: loop-engine commit 6241eac. Surface-Reconciliation-Id: SR-001 --- components/home/CodeTabs.tsx | 2 +- content/docs/integrations/openclaw.mdx | 2 +- content/docs/packages/sdk.mdx | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/components/home/CodeTabs.tsx b/components/home/CodeTabs.tsx index b70c839..e31a3bc 100644 --- a/components/home/CodeTabs.tsx +++ b/components/home/CodeTabs.tsx @@ -59,7 +59,7 @@ const { engine } = await createLoopSystem({ storage: createMemoryLoopStorageAdapter() }) -await engine.startLoop({ +await engine.start({ loopId: 'expense.approval', aggregateId: 'EXP-001', actor: { type: 'system', id: 'intake' } diff --git a/content/docs/integrations/openclaw.mdx b/content/docs/integrations/openclaw.mdx index 903f2f0..ae1372f 100644 --- a/content/docs/integrations/openclaw.mdx +++ b/content/docs/integrations/openclaw.mdx @@ -76,7 +76,7 @@ This is the Sense → Decide → Govern pattern in practice: | Option | Type | Default | Description | |---|---|---|---| -| `loopSystem` | `LoopSystem` | required | Runtime instance used for governed transitions | +| `loopSystem` | `LoopEngine` | required | Runtime instance used for governed transitions | | `signalMap` | `Record` | `{}` | Optional action-to-signal mapping override | | `defaultActor` | `Actor` | `ai-agent/openclaw` | Fallback actor attribution metadata | | `onProposal` | `(proposal) => Promise` | — | Optional hook before transition submission | diff --git a/content/docs/packages/sdk.mdx b/content/docs/packages/sdk.mdx index 85fc43e..9401e11 100644 --- a/content/docs/packages/sdk.mdx +++ b/content/docs/packages/sdk.mdx @@ -17,7 +17,7 @@ npm install @loop-engine/sdk Source-verified exports include: - `LoopBuilder` -- `createLoopSystem` and `createLoopEngine` +- `createLoopSystem` (auto-wired aggregate; the runtime factory `createLoopEngine` lives in `@loop-engine/runtime`) - `guardRegistry` and `createSignalEngine` - `InMemoryEventBus` - `computeMetrics` and `buildTimeline` From 41d6f1aa8fab1c5113bcad47decdcb3c3aec3017 Mon Sep 17 00:00:00 2001 From: Todd Palmer Date: Wed, 22 Apr 2026 20:31:53 -0700 Subject: [PATCH 3/4] docs(site): align with LoopStore collapse and rename (D-11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Updates loopengine.dev pages that referenced the pre-rename LoopStorageAdapter / createMemoryLoopStorageAdapter / storage option key. Pages already in post-rename state (packages/runtime.mdx, packages/sdk.mdx, packages/adapter-memory.mdx, packages/ adapter-postgres.mdx, running-loops/adapters.mdx, running-loops/ create-loop-system.mdx, getting-started/installation.mdx, llms.txt) become correct without edits via the source rename — no churn. Updates in this commit: - components/home/CodeTabs.tsx: homepage Run + Events snippets switch to memoryStore() and store option key; syntax highlighter symbol list updated - content/docs/integrations/index.mdx: prose reference LoopStorageAdapter -> LoopStore - content/docs/integrations/http.mdx: two prose references LoopStorageAdapter -> LoopStore - content/docs/integrations/postgres.mdx: prose reference LoopStorageAdapter -> LoopStore - content/docs/examples/postgres-persistence.mdx: code sample updated (memoryStore factory, postgresStore factory, store option key); trailing prose reference LoopStorageAdapter -> LoopStore Verification - npx tsc --noEmit: clean - pnpm lint: clean - pnpm build: 12/12 pages prerendered, no MDX parse errors Surface-Reconciliation-Id: SR-002 --- components/home/CodeTabs.tsx | 8 ++++---- content/docs/examples/postgres-persistence.mdx | 12 ++++++------ content/docs/integrations/http.mdx | 4 ++-- content/docs/integrations/index.mdx | 2 +- content/docs/integrations/postgres.mdx | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/components/home/CodeTabs.tsx b/components/home/CodeTabs.tsx index e31a3bc..4e3debc 100644 --- a/components/home/CodeTabs.tsx +++ b/components/home/CodeTabs.tsx @@ -50,13 +50,13 @@ const checked = validateLoopDefinition(definition) if (!checked.valid) { throw new Error(checked.errors.map((e) => e.message).join('; ')) }`, - run: `import { createMemoryLoopStorageAdapter } from '@loop-engine/adapter-memory' + run: `import { memoryStore } from '@loop-engine/adapter-memory' import { createLoopSystem } from '@loop-engine/sdk' // assumes \`definition\` from the Define tab const { engine } = await createLoopSystem({ loops: [definition], - storage: createMemoryLoopStorageAdapter() + store: memoryStore() }) await engine.start({ @@ -74,7 +74,7 @@ await engine.transition({ events: `// assumes \`createLoopSystem\` returned \`eventBus\` alongside \`engine\` const { engine, eventBus } = await createLoopSystem({ loops: [definition], - storage: createMemoryLoopStorageAdapter() + store: memoryStore() }) eventBus.subscribe(async (event) => { @@ -99,7 +99,7 @@ function renderHighlighted(code: string) { '$1' ); return withKeywords.replace( - /\b(parseLoopYaml|validateLoopDefinition|createLoopSystem|createMemoryLoopStorageAdapter)\b/g, + /\b(parseLoopYaml|validateLoopDefinition|createLoopSystem|memoryStore)\b/g, '$1' ); } diff --git a/content/docs/examples/postgres-persistence.mdx b/content/docs/examples/postgres-persistence.mdx index 4d61fdf..1678245 100644 --- a/content/docs/examples/postgres-persistence.mdx +++ b/content/docs/examples/postgres-persistence.mdx @@ -38,18 +38,18 @@ QUERY STATE ---> READ INSTANCE + READ HISTORY ```ts import { createLoopSystem } from "@loop-engine/sdk"; -import { createPostgresStore, createSchema } from "@loop-engine/adapter-postgres"; -import { createMemoryLoopStorageAdapter } from "@loop-engine/adapter-memory"; +import { postgresStore, createSchema } from "@loop-engine/adapter-postgres"; +import { memoryStore } from "@loop-engine/adapter-memory"; import { Pool } from "pg"; // Version A: in-memory (default local dev) -const memoryStore = createMemoryLoopStorageAdapter(); -const memoryRuntime = await createLoopSystem({ loops: [definition], store: memoryStore }); +const memStore = memoryStore(); +const memoryRuntime = await createLoopSystem({ loops: [definition], store: memStore }); // Version B: postgres (durable) const pool = new Pool({ connectionString: process.env.DATABASE_URL }); await createSchema(pool); -const pgStore = createPostgresStore(pool); +const pgStore = postgresStore(pool); const postgresRuntime = await createLoopSystem({ loops: [definition], store: pgStore }); // Loop definitions + transition calls are unchanged across adapters. @@ -79,5 +79,5 @@ pnpm dev ``` -The same contract pattern extends to `adapter-kafka` and `adapter-http`. Any backend implementing `LoopStorageAdapter` can be used. +The same contract pattern extends to `adapter-kafka` and `adapter-http`. Any backend implementing `LoopStore` can be used. diff --git a/content/docs/integrations/http.mdx b/content/docs/integrations/http.mdx index 8ff5422..e4a307c 100644 --- a/content/docs/integrations/http.mdx +++ b/content/docs/integrations/http.mdx @@ -11,7 +11,7 @@ section: Integrations ## What it does -Implements `LoopStorageAdapter` over HTTP. Any REST service that speaks the adapter protocol can back loop state storage. This is useful for integrating with existing services or remote storage planes. +Implements `LoopStore` over HTTP. Any REST service that speaks the adapter protocol can back loop state storage. This is useful for integrating with existing services or remote storage planes. ## Adapter protocol @@ -51,7 +51,7 @@ Expected backend endpoints: ## Note -If your backend is in the same process, implementing `LoopStorageAdapter` directly is usually simpler than standing up the HTTP protocol. +If your backend is in the same process, implementing `LoopStore` directly is usually simpler than standing up the HTTP protocol. ## Configuration reference diff --git a/content/docs/integrations/index.mdx b/content/docs/integrations/index.mdx index 88fa42a..6b4cd04 100644 --- a/content/docs/integrations/index.mdx +++ b/content/docs/integrations/index.mdx @@ -136,7 +136,7 @@ Connect Loop Engine to commerce platforms and LLM commerce data layers. ## Building an integration? -The Loop Engine adapter interface is open and documented. Any backend that implements `LoopStorageAdapter` is valid. +The Loop Engine adapter interface is open and documented. Any backend that implements `LoopStore` is valid. - [Adapter interface docs →](/docs/running-loops/adapters) - [Apply for certification →](mailto:oss@betterdata.co) diff --git a/content/docs/integrations/postgres.mdx b/content/docs/integrations/postgres.mdx index 373f4a9..73829c6 100644 --- a/content/docs/integrations/postgres.mdx +++ b/content/docs/integrations/postgres.mdx @@ -11,7 +11,7 @@ section: Integrations ## What it does -Swaps the in-memory adapter for PostgreSQL persistence. Loop state, transitions, and events are stored in Postgres through the same `LoopStorageAdapter` interface, so loop definitions and business logic remain unchanged. +Swaps the in-memory adapter for PostgreSQL persistence. Loop state, transitions, and events are stored in Postgres through the same `LoopStore` interface, so loop definitions and business logic remain unchanged. ## Quick setup From 45592915aece2bfc1968a466b266422a560e784d Mon Sep 17 00:00:00 2001 From: Todd Palmer Date: Thu, 23 Apr 2026 08:03:31 -0700 Subject: [PATCH 4/4] docs: update Perplexity adapter pages to ToolAdapter (D-13) Class 4B paired docs commit for SR-003 (loop-engine `6843194`). Updates the five MDX pages that referenced the `LLMAdapter` interface to instead reference `ToolAdapter`, matching the post-rename source state in `@loop-engine/core`. The five pages: - `content/docs/integrations/index.mdx` - `content/docs/changelog.mdx` (Unreleased section only; becomes the 1.0.0-rc.0 entry) - `content/docs/ai-and-automation/ai-as-actor.mdx` (two references) - `content/docs/integrations/perplexity.mdx` - `content/docs/packages/adapter-perplexity.mdx` The four other AI provider pages (Anthropic / OpenAI / Gemini / Grok) are untouched here. Their re-homing onto `ActorAdapter` is a separate Phase A.3 work item (D-13 secondary; not part of SR-003's narrow scope). Verification: `npx tsc --noEmit` green; `pnpm lint` green; `pnpm build` green (12+ routes prerendered, no MDX parse errors). Surface-Reconciliation-Id: SR-003 --- content/docs/ai-and-automation/ai-as-actor.mdx | 4 ++-- content/docs/changelog.mdx | 4 ++-- content/docs/integrations/index.mdx | 2 +- content/docs/integrations/perplexity.mdx | 2 +- content/docs/packages/adapter-perplexity.mdx | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/content/docs/ai-and-automation/ai-as-actor.mdx b/content/docs/ai-and-automation/ai-as-actor.mdx index 69183d8..5b86475 100644 --- a/content/docs/ai-and-automation/ai-as-actor.mdx +++ b/content/docs/ai-and-automation/ai-as-actor.mdx @@ -112,9 +112,9 @@ The full dual-provider walkthrough is in `/docs/examples/ai-replenishment`, with | [`@loop-engine/adapter-openai`](/docs/packages/adapter-openai) | OpenAI (`gpt-4o`, o-series) | | [`@loop-engine/adapter-grok`](/docs/packages/adapter-grok) | Grok (`grok-3`, `grok-2`) via xAI | | [`@loop-engine/adapter-gemini`](/docs/packages/adapter-gemini) | Gemini (`gemini-1.5-pro`, `gemini-2.0-flash`) | -| [`@loop-engine/adapter-perplexity`](/docs/packages/adapter-perplexity) | Perplexity Sonar (`sonar`, `sonar-pro`, …) — `LLMAdapter` with citations for research steps | +| [`@loop-engine/adapter-perplexity`](/docs/packages/adapter-perplexity) | Perplexity Sonar (`sonar`, `sonar-pro`, …) — `ToolAdapter` with citations for research steps | -The Perplexity package implements `LLMAdapter.invoke()` (text + citations), not the actor `createSubmission` flow. Use it where you need grounded retrieval inside a loop; use the actor adapters above for structured signal decisions. +The Perplexity package implements `ToolAdapter.invoke()` (text + citations), not the actor `createSubmission` flow. Use it where you need grounded retrieval inside a loop; use the actor adapters above for structured signal decisions. ## Related diff --git a/content/docs/changelog.mdx b/content/docs/changelog.mdx index 81bb8fb..8c82814 100644 --- a/content/docs/changelog.mdx +++ b/content/docs/changelog.mdx @@ -14,8 +14,8 @@ Versioning: [Semantic Versioning](https://semver.org/) ## Unreleased ### Added -- `@loop-engine/adapter-perplexity` — Perplexity Sonar `LLMAdapter` with citations, retries, and `guardEvidence` integration -- `@loop-engine/core` — `LLMAdapter`, `AdapterInput` / `AdapterOutput`, and deep `guardEvidence()` for adapter audit redaction +- `@loop-engine/adapter-perplexity` — Perplexity Sonar `ToolAdapter` with citations, retries, and `guardEvidence` integration +- `@loop-engine/core` — `ToolAdapter`, `AdapterInput` / `AdapterOutput`, and deep `guardEvidence()` for adapter audit redaction - Docs: `docs/integrations-perplexity.md` (Sonar vs [Perplexity Computer skills](https://www.perplexity.ai/computer/skills)) - Release contract stub: `.rc/adapter-perplexity.json` (RC **DRAFT** until promoted **LOCKED**) diff --git a/content/docs/integrations/index.mdx b/content/docs/integrations/index.mdx index 6b4cd04..b8fa13a 100644 --- a/content/docs/integrations/index.mdx +++ b/content/docs/integrations/index.mdx @@ -8,7 +8,7 @@ Connect Loop Engine to any AI provider, agentic platform, storage backend, or ob ## ⟩ AI Providers -Use any major LLM as a governed actor. Provider actor adapters produce the same `AIAgentActor` shape — switching providers requires changing one import. Perplexity Sonar covers **grounded retrieval with citations** as an `LLMAdapter` for research steps (see the package reference). +Use any major LLM as a governed actor. Provider actor adapters produce the same `AIAgentActor` shape — switching providers requires changing one import. Perplexity Sonar covers **grounded retrieval with citations** as a `ToolAdapter` for research steps (see the package reference).