@manifesto-ai/sdk
Base runtime entry point for Manifesto apps.
Overview
@manifesto-ai/sdk owns the application-facing runtime path:
createManifesto(schema, effects) -> activate() -> ManifestoAppUse SDK when you want:
- the shortest path to a running base runtime
- typed actions through
action.<name> - typed effect authoring through
@manifesto-ai/sdk/effects - app-facing Snapshot reads through
snapshot() - typed field reads through
state.<name>andcomputed.<name> - observer and event subscriptions through
observe
It also exposes tooling reads when you need them:
- dynamic tooling action lookup through
getAction(name) - static/runtime inspection through
inspect - low-level post-activation simulation helpers through
@manifesto-ai/sdk/extensions
Raw Intent construction remains available as an advanced low-level escape hatch through BoundAction.intent(). It is not the primary app path.
Everyday SDK Surface
createManifesto()activate()- activated base runtime:
snapshot()action.<name>state.<name>.value()state.<name>.observe(listener)computed.<name>.value()computed.<name>.observe(listener)observe.state(selector, listener)observe.event(event, listener)dispose()
- SDK error types
@manifesto-ai/sdk/effectsfor typed effect authoring helpers
Tooling And Advanced Surface
context()injectContext(next)updateContext(updater)with(view)getAction(name)inspect.graph()inspect.canonicalSnapshot()for full internal snapshot readsinspect.action(name)inspect.availableActions()inspect.schemaHash()@manifesto-ai/sdk/extensionsfor low-level read-only simulation helpers@manifesto-ai/sdk/providerfor package-author runtime seams
Effect Authoring Helper
The root SDK story stays centered on createManifesto(). If you want typed top-level state refs when authoring effect handlers, import defineEffects() from the dedicated effects subpath.
import { defineEffects } from "@manifesto-ai/sdk/effects";defineEffects() is an authoring helper only. It still returns a plain Record<string, EffectHandler>, and handlers still return concrete Patch[].
Base Runtime Example
import { createManifesto } from "@manifesto-ai/sdk";
import TodoMel from "./domain/todo.mel";
import type { TodoDomain } from "./domain/todo.domain";
const app = createManifesto<TodoDomain>(TodoMel, {}).activate();
const admission = app.action.addTodo.check("Review docs");
const result = await app.action.addTodo.submit("Review docs");
if (result.ok && result.status === "settled" && result.outcome.kind === "ok") {
console.log(result.after.state.todos);
}
app.action.clearCompleted.available();
app.state.todos.value();
app.computed.activeCount.value();
app.state.todos.observe((next, prev) => {
console.log(prev, next);
});
app.snapshot();
console.log(admission.ok);Add inspection and report detail when building UI capability lists, model-facing tools, or debugging infrastructure:
const info = app.action.addTodo.info();
const dynamic = app.getAction("addTodo");
const preview = app.with({ diagnostics: "summary" }).action.addTodo.preview("Review docs");
const resultWithReport = await app.with({ report: "summary" }).action.addTodo.submit("Review docs");
app.inspect.availableActions();
app.inspect.action("addTodo");
app.inspect.graph();
console.log(info.name, dynamic?.info().name, preview.admitted, resultWithReport.ok);Action Binding Forms
Action handles keep argument shape typed from the domain.
app.action.clearCompleted.submit();
app.action.addTodo.submit("Review docs");
app.action.setFilter.submit("active");
app.action.configureProject.submit({ enabled: true, label: "Review" });Rules:
- zero-parameter actions use
submit()/bind() - single-parameter actions accept the parameter value directly
- multi-parameter actions preserve ordered tuple input
- hand-authored multi-field object inputs without positional metadata are object-only bindings
bind(...input) returns a reusable bound action:
const boundAddTodo = app.action.addTodo.bind("Review docs");
boundAddTodo.check();
boundAddTodo.preview();
await boundAddTodo.submit();
const rawIntent = boundAddTodo.intent();Treat rawIntent as a low-level record. App code should prefer the bound action methods above.
Action Metadata And Availability
Use info() or inspect.action() when a UI, adapter, or agent needs the runtime's public action contract without maintaining a parallel registry.
const addTodo = app.action.addTodo.info();
const same = app.inspect.action("addTodo");
const available = app.inspect.availableActions();
console.log(addTodo.name);
console.log(addTodo.parameters);
console.log(addTodo.description);
console.log(same.annotations);
console.log(available.map((action) => action.name));action.<name>.available() remains the coarse legality query. action.<name>.check(...input) is the fine input-specific legality surface.
Treat availability reads as current-snapshot observations, not durable capability grants. The runtime still revalidates legality at submit time.
Preview And Submit
Use the current-state action ladder when you want one structured answer to:
- is the action available right now?
- if available, is this input admissible?
- if admitted, what would the dry-run result look like?
- if submitted, what result did the active runtime mode produce?
const addTodo = app.action.addTodo.bind("Review docs");
const admission = addTodo.check();
if (!admission.ok) {
console.log(admission.code, admission.blockers);
}
const preview = app
.with({ diagnostics: "trace" })
.action.addTodo
.bind("Review docs")
.preview();
if (preview.admitted) {
console.log(preview.after.state);
console.log(preview.changes);
}
const result = await app
.with({ report: "full" })
.action.addTodo
.bind("Review docs")
.submit();Preview is non-mutating. Submit revalidates immediately before the write. The base runtime uses this ladder directly. Optional history and approval extensions keep the same action-handle shape and differ through result type:
- base returns a settled
BaseSubmissionResult - history returns a settled
LineageSubmissionResultwith sealed world data - approval returns a pending
GovernanceSubmissionResult; observe settlement withpending.waitForSettlement()orapp.waitForSettlement(ref)
Use diagnostics: "none" and report: "none" when an agent/tool path needs the smallest in-band payload. Use with({ report: "full" }) when the write result should carry execution diagnostics in addition to the summary report fields.
Observability
For top-level app-facing fields, use read handles when you want a typed current value or direct field observer:
const todos = app.state.todos.value();
const unsubscribeTodos = app.state.todos.observe((next, prev) => {
console.log(prev, next);
});Read handles are app-facing and read-only. Writes still go through action.<name>.submit(...).
Use observe.state() when you need a custom selector:
const unsubscribe = app.observe.state(
(snapshot) => snapshot.computed.activeCount,
(next, prev) => {
console.log(prev, next);
},
);
const unsubscribeEvents = app.observe.event("submission:settled", (event) => {
console.log(event.action, event.outcome.kind);
});Call the returned unsubscribe functions before disposing a long-lived runtime.
Decorator/provider authoring seam
Use @manifesto-ai/sdk/provider when you are composing runtime decorators on top of the SDK. That subpath is for package authors, not typical app code.
The current public seam includes:
RuntimeKernelRuntimeKernelFactory- activation helpers such as
attachRuntimeKernelFactory(),getRuntimeKernelFactory(),getActivationState(), andactivateComposable()
App-facing runtime work should stay on @manifesto-ai/sdk. snapshot() is the default app-facing read model for application code. inspect.canonicalSnapshot() is the full internal runtime snapshot for persistence, deep debugging, and infrastructure-aware tooling.
Extension Kernel
@manifesto-ai/sdk/extensions is the advanced read-only simulation surface.
Its purpose is to give helper and tool authors a safe read-only surface after activation without exposing the full provider seam.
import { getExtensionKernel } from "@manifesto-ai/sdk/extensions";
const ext = getExtensionKernel(app);
const root = ext.getCanonicalSnapshot();
const intent = app.action.addTodo.bind("Review docs").intent();
if (intent) {
const explanation = ext.explainIntentFor(root, intent);
const simulated = ext.simulateSync(root, intent);
const appSnapshot = ext.projectSnapshot(simulated.snapshot);
console.log(explanation.kind);
console.log(appSnapshot.state);
}The core analytical helpers on this seam are:
projectSnapshot()getAvailableActionsFor()isActionAvailableFor()isIntentDispatchableFor()explainIntentFor()simulateSync()
Branching hypothetical futures stay on the same surface through createSimulationSession(app).