diff --git a/components/home/CodeTabs.tsx b/components/home/CodeTabs.tsx
index b70c839..4e3debc 100644
--- a/components/home/CodeTabs.tsx
+++ b/components/home/CodeTabs.tsx
@@ -50,16 +50,16 @@ 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.startLoop({
+await engine.start({
loopId: 'expense.approval',
aggregateId: 'EXP-001',
actor: { type: 'system', id: 'intake' }
@@ -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/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/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/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/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..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).
` | `{}` | 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/integrations/perplexity.mdx b/content/docs/integrations/perplexity.mdx
index 7454a75..da76e6c 100644
--- a/content/docs/integrations/perplexity.mdx
+++ b/content/docs/integrations/perplexity.mdx
@@ -18,7 +18,7 @@ section: Integrations
## What it does
-`@loop-engine/adapter-perplexity` implements `LLMAdapter.invoke()` against the Sonar chat API. You get answer text plus structured citations for audit and evidence attachment. Use it where provenance matters more than open-ended generation.
+`@loop-engine/adapter-perplexity` implements `ToolAdapter.invoke()` against the Sonar chat API. You get answer text plus structured citations for audit and evidence attachment. Use it where provenance matters more than open-ended generation.
## When to use it
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
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/adapter-perplexity.mdx b/content/docs/packages/adapter-perplexity.mdx
index ed58ca8..dff1b70 100644
--- a/content/docs/packages/adapter-perplexity.mdx
+++ b/content/docs/packages/adapter-perplexity.mdx
@@ -10,7 +10,7 @@ section: "packages"
## Overview
-`@loop-engine/adapter-perplexity` wraps the Perplexity Sonar chat API as a Loop Engine `LLMAdapter`. Sonar adds grounded web retrieval with cited sources. You use it for Loop steps that need real-time, verifiable information — regulatory lookups, compliance research, supplier or market news. It is not a general-purpose generation adapter; for broad LLM actor flows, use [Anthropic](/docs/packages/adapter-anthropic) or [OpenAI](/docs/packages/adapter-openai) actor adapters.
+`@loop-engine/adapter-perplexity` wraps the Perplexity Sonar chat API as a Loop Engine `ToolAdapter`. Sonar adds grounded web retrieval with cited sources. You use it for Loop steps that need real-time, verifiable information — regulatory lookups, compliance research, supplier or market news. It is not a general-purpose generation adapter; for broad LLM actor flows, use [Anthropic](/docs/packages/adapter-anthropic) or [OpenAI](/docs/packages/adapter-openai) actor adapters.
## Installation
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/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`
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`)
}
})
```