# Migration: 0.x → 1.0

This is a clean-break rewrite. Every row below was checked against the public exports of the 1.0 packages.

## Status

Version 1.0.0. Requires Node 22+. The `openai` and `log-symbols` dependencies are gone from core; `sequelize`, `express`, `terminal-kit` and `i18next` are now peer dependencies of the packages that use them.

| 0.x API | 1.0 API | Notes |
|---|---|---|
| `new Intelligence({ service: 'openai', key, model })` | `new Intelligence({ provider, model })` | `Intelligence` no longer hard-wires an SDK. A `Provider` (see `provider.ts`) does one model step per call; `runAgentLoop` in `loop.ts` owns iteration. The `openai` dependency is fully removed from `intellibrix`. `@intellibrix/ai-sdk` supplies real provider adapters; `customProvider(fn)` replaces `service: 'custom'` for simple cases. |
| `intelligence.ask(question, context?)` → `{text, context, function_result, finish_reason, usage}` | `intelligence.ask(prompt, options?)` → `{text, messages, steps}` (`AskResult`) | Multi-step tool loops, parallel tool calls, and approvals are now built in; a `functions` schema/methods pair is replaced by `defineTool`/`ToolDefinition[]` passed via `tools`. |
| `intelligence.image(prompt, size?)` | `intelligence.image(prompt)` | Delegates to `Provider.image()`; only available when the configured provider implements it. |
| n/a | `intelligence.object(schema, prompt)` | New: forces a structured response via a synthetic tool call, returning parsed, schema-validated input. |
| n/a | `intelligence.stream(prompt, options?)` | New: streams `StreamEvent`s from the underlying provider. |
| n/a | `intelligence.embed(input)` | New: delegates to `Provider.embed()`, when supported. |
| `new Brick({ id, ... })` (bug: `id` was ignored; always randomUUID) | `new Brick({ id, ... })` | Fixed: a provided `id` is now honored. |
| `brick.schedule({ start: false, onComplete, ... })` (bug: `start` ignored, always true; `onComplete` invoked eagerly at schedule-time) | same shape | Fixed: `start: false` is honored; `onComplete` is passed as a callback reference and only runs when the job actually completes. `cron` bumped 2.x → 4.x alongside this fix. |
| Per-`Brick`/`Structure` pino-pretty transport (bug: one worker thread per instance) | Shared, lazily-created logger (`getSharedLogger()` in `logger.ts`) | Fixed: one process-wide transport (or none, in JSON/production/non-TTY mode) instead of N. |
| n/a | `brick.tools()` | New: exposes every registered `Program` as a `ToolDefinition`. |
| n/a | `brick.use(...toolsets)` | New: attaches external tool arrays (e.g. future MCP-sourced tools) to a Brick. |
| n/a | `brick.ask(prompt, options?)` | New: convenience wrapper running `this.ai.ask()` with `brick.tools()` + attached tools pre-wired in. |
| `Program` had no schema | `Program.input?` / `Program.output?` (Standard Schema) | New, optional: validated in `Brick.run()` when present; also used by `brick.tools()`. |
| `new Database({ service: 'memory' \| 'sql' })` (single class) | Split into interfaces: `KeyValueStore`, `ConversationMemory`, `VectorStore` (`database.ts`), with in-memory implementations (`MemoryKeyValueStore`, `MemoryConversationMemory`, `MemoryVectorStore`) shipped in core | SQL support moved to `@intellibrix/sql` (`SQLKeyValueStore implements KeyValueStore`; `PgVectorStore` for pgvector). `brick.db` now defaults to a `MemoryKeyValueStore` instance rather than being `undefined`. |
| `Structure` had no shared state | `structure.state: KeyValueStore` | New: shared key-value state across a Structure's Bricks, in-memory by default. |
| n/a | `Part.trust?: 'trusted' \| 'untrusted'` (`messages.ts`) | New: content-provenance tagging for indirect-prompt-injection defense. Defaults to `'trusted'` when omitted; threaded through `loop.ts`'s tool dispatch into `ToolContext.trust`, letting `needsApproval` policies key off it. |
| n/a | `Checkpointer` / `InMemoryCheckpointer` (`checkpoint.ts`) | New: a durable-execution seam. `runAgentLoop({ checkpointer?, resume? })` saves/loads run state. It is not a workflow engine: a production `Checkpointer` (Temporal/Inngest/etc.) is expected to be supplied by the application. |
| n/a | `intellibrix/testing` (`MockProvider`, `mockTool`) | New subpackage export: a scripted `Provider` for deterministic, network-free tests. |
| Bundled `src/bricks/{express,terminal,i18n}.ts` | Standalone `@intellibrix/{express,terminal,i18n}` packages | Import-path change only for Express/Terminal; I18n additionally fixes a global-singleton bug (see below). |
| `I18nBrick` used the global `i18next.init()` singleton (bug: multiple instances clobbered each other's language state) | `i18next.createInstance()` per `I18nBrick` | Behavior change. `new I18nBrick(...)` still initializes synchronously for inline `resources`; `I18nBrick.create(options)` additionally awaits init. `brick.t` is now bound to the instance. Only `defaultLanguage`, `debug` and `resources` are forwarded to i18next (previously the whole options object, brick options included). |
| n/a | `new Intelligence({ provider, model, memory })` + `ask(prompt, { threadId })` / `stream(prompt, { threadId })` | Opt-in conversation memory: prior turns of the thread are prepended, and the new user turn plus every generated message (including tool calls/results) are appended afterwards. `stream()` persists only once fully drained. Without `memory`/`threadId` behavior is unchanged. |
| n/a | `SQLiteConversationMemory`, `SQLiteVectorStore` (`@intellibrix/memory-sqlite`) | Built on Node's `node:sqlite` (zero native deps). `SQLiteVectorStore.query()` is a brute-force cosine scan, suited to small local datasets; use `PgVectorStore` for larger ones. |
| n/a | `PgVectorStore({ sequelize, dimensions })` (`@intellibrix/sql`) | Postgres + pgvector, HNSW cosine index. `dimensions` is required and every `upsert`/`query` vector is checked against it. |
| n/a | `keyValueStoreContract`, `conversationMemoryContract`, `vectorStoreContract` (`intellibrix/testing`) | Runner-agnostic conformance suites for custom store implementations. |
| n/a | `@intellibrix/mcp`, `@intellibrix/workspace`, `@intellibrix/github`, `@intellibrix/cli`, `@intellibrix/ai-sdk`, `@intellibrix/sandbox`, `@intellibrix/memory-sqlite` | Net-new packages with no 0.x equivalent. |
| n/a | `@intellibrix/ai-sdk`: `provider('anthropic:claude-sonnet-5')` | New: resolves an intellibrix `Provider` from a `"<vendor>:<model>"` string via AI SDK's `createProviderRegistry`, lazily requiring whichever `@ai-sdk/{anthropic,openai,google}` adapter is installed (all optional peers) and throwing an actionable error naming the missing package otherwise. |
| n/a | `@intellibrix/ai-sdk`: `compatibleProvider(model, {baseURL, apiKey})` | New: resolves a `Provider` against any OpenAI-compatible HTTP endpoint (Ollama, OpenRouter, vLLM, ...) via the optional `@ai-sdk/openai-compatible` peer. |
| n/a | `@intellibrix/ai-sdk`: `aiSdkProvider(languageModel, opts?)` | New: wraps any AI SDK `LanguageModel` directly as an intellibrix `Provider`. `generate`/`stream` each perform exactly one model step (`stepCountIs(1)`); tools are declared schema-only (no `execute`), so AI SDK always returns control to `loop.ts` on a tool-call finish reason instead of looping internally. `opts.embeddingModel`/`opts.imageModel` enable `embed()`/`image()`. |
| n/a | `RunEvent` / `LoopOptions.onRunEvent` (`events.ts`) | New: structural observability events layered onto `LoopEvent` (additive, so nothing consuming `LoopEvent`s breaks). Covers `run:start/finish`, `model:start/finish`, `tool:call/result/error`, `approval:requested/resolved`, emitted at the same call sites `loop.ts` already defines. `Intelligence`/`Brick.ask()` accept/forward `onRunEvent`; `AskOptions.brickName`/`LoopOptions.brickName` annotate `run:*` events. |
| n/a | `getTracer()` / `withSpan()` (`telemetry.ts`) | New: a structural `Span`/`Tracer` shape, never re-exported from `@opentelemetry/api` (so `tsc` never needs that package to typecheck core). `getTracer()` memoizes one guarded attempt to load the optional `@opentelemetry/api` peer, falling back permanently to a no-op tracer/span pair if it's absent. |
| n/a | `UsageAccumulator` / `defaultPriceTable` (`usage.ts`) | New: accumulates token usage and (via a `PriceTable`) cost across calls. `Intelligence.usage` is the per-instance accumulator; `Brick.usage` starts as a `.child()` of `this.ai.usage` (or standalone without one) and is rebound as a `.child()` of `Structure.usage` when added via `Structure.add()`, so totals roll up Brick → Structure. `AskResult` gained a `usage: Usage` field. |
| n/a | `recordProvider(inner, {onRecord})` / `replayProvider(fixtures)` (`record-replay.ts`) | New: `recordProvider` wraps any real `Provider`, reporting a serialized `{request, result}` per call (non-serializable fields like `abortSignal` stripped, tools reduced to name/description); persistence is the caller's job. `replayProvider` plays fixtures back in call order, throwing `replayProvider: fixtures exhausted` on drift rather than reusing stale data. |
| n/a | `textToolCalls(provider, {parsers?, knownToolsOnly?})`, `pythonicToolCalls`, `hermesToolCalls`, `xmlToolCalls`, `invokeToolCalls`, opt-in `jsonToolCalls`, `defaultToolCallParsers` (`text-tool-calls.ts`) | New: wraps a `Provider` so tool calls a model writes as text (pythonic `<|tool_call_start|>` Hermes `<tool_call>` JSON, Qwen3-Coder `<function=…>` XML, or `<invoke name=…>` blocks) become real `tool-call` parts, so `loop.ts` handles approvals, validation and unknown-tool errors as usual. Steps that already contain native tool calls are untouched; streaming holds markup back. Still one model step per call. |
| n/a | `@intellibrix/structures`: `asTool(brick, opts?)` | New: wraps a whole `Brick` as one opaque `ToolDefinition` (`execute` calls `brick.ask(input.prompt)`), additive to `brick.tools()` (which exposes individual programs). |
| n/a | `@intellibrix/structures`: `supervisor({supervisor, bricks, maxDepth?, toolFactory?, onEvent?})` | New: the parent `Intelligence`'s tool list is the sub-bricks wrapped via `asTool` (or a caller-supplied `toolFactory`); nested delegation happens through the existing agent-loop tool-dispatch machinery. Depth is threaded explicitly through each hop's closure and checked against `maxDepth` (default 3), throwing `DepthExceededError` (`limits.ts`) when exceeded. |
| n/a | `@intellibrix/structures`: `router({classifier, routes, fallback?, onEvent?})` | New: a single `Intelligence.object()` structured-output classification (one model step), then dispatch to the matched brick's `ask()`; falls back to `fallback` on an unmatched route, or throws if none is set. |
| n/a | `@intellibrix/structures`: `sequential(steps)` / `parallel(steps)` (`pipeline.ts`) | New: `sequential` chains each step's output into the next step's input; `parallel` fans the same input to every step via `Promise.all`. A step is a `Brick` (via `ask()`) or a `[Brick, Program]` tuple (via `run()`). |
| n/a | `@intellibrix/structures`: `handoffTool({to, mode?, getCurrentHistory?, onEvent?})` | New: a `ToolDefinition` transferring control to another brick. `'delegate'` mode (default) terminates the current turn and invokes the target's `ask()` fresh, seeded with prior history rendered via `getCurrentHistory()`. `'continue'` mode (advanced/opt-in) returns a `__handoffContinue` sentinel for a caller-supplied `loop.ts` hook to swap `system`/`tools` mid-run; this package makes no core changes itself. |
| n/a | `@intellibrix/structures`: `StructuresEvent` / `structure:hop` (`events.ts`) | New: additive to `RunEvent`; `supervisor`/`router`/`handoff` emit `structure:hop` at each delegation point via an `onEvent` callback, enough to reconstruct a multi-agent call graph for a given `runId`. |
| n/a | `fromJSONSchema(schema)` (`schema-adapter.ts`) | New: wraps a raw JSON Schema as a Standard Schema validated with Ajv (adds `ajv` to core's dependencies). Also implements the Standard JSON Schema interface, so it round-trips through `toolToJSONSchema()`. Lets MCP-sourced tools sit behind `~standard` like Zod tools, so `loop.ts` stays unaware of the distinction. |
| n/a | `toolToJSONSchema(tool)` / `ToolDefinition.resultTrust` (`tool.ts`) | New: `toolToJSONSchema` reads the tool's input JSON Schema (draft 2020-12) via the Standard JSON Schema interface (Zod 4 implements it; no Zod dependency in core), falling back to a permissive object schema. `resultTrust` stamps `trust` onto the tool's result `Part`s in `loop.ts`. |
| n/a | `brick.toolset()` | New: programs (`brick.tools()`) plus tools attached via `brick.use()`. `brick.ask()` now builds its tool list from it; `serveMcp` uses it. |
| n/a | `INTELLIBRIX_LOG_STDERR` env var (`logger.ts`) | New: when set, the shared logger writes JSON to stderr. Required for stdio MCP servers, where stdout carries the protocol and Brick logs would otherwise corrupt it. |
| n/a | `@intellibrix/mcp`: `mcpTools({transport, ...})` | New: connects over stdio, streamable HTTP, or a caller-supplied SDK `Transport`, and mounts each remote tool as a `ToolDefinition` (`input: fromJSONSchema(...)`, `resultTrust: 'untrusted'`). Returns `{tools, client, close()}`; callers must `close()` (idempotent). Tools annotated `destructiveHint: true` need approval by default; override with `needsApproval`. |
| n/a | `@intellibrix/mcp`: `serveMcp(brickOrStructure, {transport, ...})` | New: exposes `brick.toolset()` (or the merge across a Structure's bricks; duplicate names throw at startup) over stdio, streamable HTTP (loopback by default, one session per client), or a supplied `Transport`. There is no agent loop between a remote caller and a tool, so tools needing approval are **denied** unless `onToolApproval` is supplied; tool input is treated as `trust: 'untrusted'`. |
| n/a | `@intellibrix/mcp`: sampling + elicitation (`onSamplingRequest`, `onElicitation`, `McpToolContext`) | New: a tool running behind `serveMcp` receives `ctx.sample()` / `ctx.elicit()` to call back into the connected client's LLM or user. Clients advertise each capability only when they supply the matching callback, so both are declined by default. `samplingFromProvider(provider, model)` adapts any `Provider` as a sampling handler. |
| `ExpressBrick` with `port: 0` | Same option, now honored | Bug fix: `options.port \|\| 3000` turned `0` into `3000`, so "0 disables listening" never worked; it is now `??`. |
| n/a | `@intellibrix/express`: `askRoute(target)`, `askStreamRoute(target)`, `brick.askEndpoint()`, `brick.askStreamEndpoint()` | New: `POST /ask {prompt, threadId?}` replies `{reply}`; `GET /ask/stream?prompt=` streams `Intelligence.stream()` events as SSE (`event:`/`data:` frames, then `event: done`) and aborts on client disconnect. Named `askEndpoint` (not `ask`) so `Brick.ask()` is not shadowed. |
| n/a | `@intellibrix/terminal`: `chatRepl(brick, opts?)` | New: input → `brick.ask(line, {threadId})` → `printLine`, until an exit command, Ctrl+C at the prompt, closed stdin, or `opts.signal` aborts. Ctrl+C while a reply is generating cancels just that turn. Prints a one-line quit hint (`opts.hint`) and releases the terminal on exit; `TerminalBrick.input()` rejects with `InputClosedError` on Ctrl+C or EOF. Uses the brick's terminal-kit `input()`, not `node:readline`. |
| n/a | `@intellibrix/workspace`: `workspaceTools`, `fsTools`, `shellTool`, `gitTools`, `resolveJailed` | New: jailed `read_file`/`list_dir`/`glob`/`grep` (results `untrusted`), `write_file`/`edit_file`, allowlisted `shell` (`execFile`, no shell), and `git_status`/`git_diff`/`git_commit`/`git_branch`. Writes, shell and commits require approval by default. `resolveJailed` re-checks containment after `realpath`; do not remove that step. `read_file` also takes `offset`, `limit` and `lineNumbers` for reading part of a large file. An opt-in `FileTracker` (`workspaceTools({ tracker })`, shared with `agentTools`) makes `write_file`, `edit_file` and `apply_patch` refuse files that were never read (a `grep` match counts) or changed since. `write_file`, `edit_file` and `apply_patch` now write atomically (temp file + rename, permissions kept) under a per-file in-process lock, so parallel calls no longer lose updates; `withFileLock`, `withFileLocks` and `writeFileAtomic` are exported. |
| n/a | `@intellibrix/sandbox`: `sandboxTool(provider)`, `e2bProvider`, `daytonaProvider`, `cloudflareProvider`, `containerProvider` | New: one `run_code` tool (approval required, results `untrusted`) over a swappable `SandboxProvider`, one sandbox cached per `runId` (`closeRun`/`closeAll`, plus an idle TTL). Vendor SDKs are optional peers, imported lazily. The Cloudflare adapter wraps a caller-supplied sandbox handle because those are bound to a Worker environment. `containerProvider` runs code in a local Podman or Docker container (no network, dropped capabilities, resource limits; code travels over stdin). |
| n/a | `@intellibrix/agent-tools`: `agentTools`, `applyPatchTool`, `runChecksTool`, `detectChecks`, `todoTool`, `TodoStore`, `calculatorTool`, `datetimeTool`, `jsonQueryTool`, `diffTool`, `createUnifiedDiff`, `memoryTool`, `htmlToMarkdown`, `fetchUrlTool`, `webSearchTool`, `braveSearch`, `tavilySearch`, `searxngSearch`, `askUserTool`, `delegateTool` | New: `apply_patch` applies a unified diff across files all or nothing (hunks located by context, path jail, rollback on write failure); `run_checks` runs named test/typecheck/lint/build commands (detected from `package.json` or supplied) and returns parsed failures; `todo` keeps a per-run task list; `calculator` evaluates arithmetic with a parser, never `eval`; `datetime` does time zones and date math; `json_query` runs jq-style queries over workspace JSON files; `diff` compares two files or texts; `memory` keeps notes in a `KeyValueStore` (opt-in, results `untrusted`); `fetch_url` reads a page as Markdown of its main content (`format: 'text'` or `'raw'` for others; optional `robots.txt` support; http/https only, address checked at connect time against loopback/private/link-local/metadata ranges, redirects re-checked, size/time bounded, results `untrusted`, approval required unless `allowHosts` is set); `web_search` queries a pluggable `SearchProvider` (results `untrusted`); `ask_user` puts a question to the human through a host callback; `delegate` hands a task to a named sub-agent (answer `untrusted` by default). Patches and checks require approval. |
| n/a | `@intellibrix/github`: `githubTools({auth})`, `githubWebhookRoute({secret, target})` | New: issue/PR/comment/review tools (reads `untrusted`, writes need approval) and an HMAC-verified webhook that re-emits `github.<event>[.<action>]` and a catch-all `github` on `target.events`. Must see the raw body: use `builtinMiddleware: { json: false }` on `ExpressBrick`, or mount before any JSON parser. |
| n/a | Message model: `Message`, `Part` (`TextPart`/`ImagePart`/`FilePart`/`ToolCallPart`/`ToolResultPart`), `textMessage()`, `textOf()`, `is*Part()` guards (`messages.ts`) | New: the provider-neutral conversation shape used by `Intelligence`, `ConversationMemory`, MCP and the AI SDK adapter. The 0.x `context` array of `{role, content: string}` becomes `Message[]`. |
| n/a | `Provider`, `GenerateRequest`, `StepResult`, `StreamEvent`, `Usage` (`provider.ts`) | New: implement `generate`/`stream` for exactly one model step; core owns iteration. |
| n/a | `validateToolInput()`, `resolveNeedsApproval()` (`tool.ts`) | New helpers used by the loop; useful when calling a `ToolDefinition` directly in tests. |
| n/a | `resetSharedLogger()`, `resetTracer()` | New test helpers that clear the process-wide logger / tracer singletons. |
| n/a | `@intellibrix/ai-sdk` converters: `toAiSdkMessages`, `toAiSdkTools`, `fromAiSdkStep` and friends | New: exported for custom adapters; most callers only need `provider()`. |
| `Task`, `Step`, `Action` types | Unchanged shapes | `Action.method(payload, brick)` receives the brick as its second argument; prefer it to `this`. |
| n/a | `@intellibrix/cli`: `intellibrix init <dir> [--force]`, `intellibrix mcp serve <entry.ts> [--transport stdio\|http]` | New: `init` scaffolds a project pinned to the current release; `mcp serve` loads a TypeScript entry in-process via tsx and serves its default-exported Brick or Structure (logs go to stderr over stdio). |

## Notes

- `@intellibrix/mcp` is built on `@modelcontextprotocol/sdk@^1.30`. Elicitation uses the SDK's `elicitInput` request (a separate server→client request during `tools/call`), not the newer in-band `inputRequired` result, which SDK 1.30 does not implement. The public `elicit()` shape is transport-agnostic, so it can move to in-band once the SDK supports it. The repo's `minimumReleaseAge` policy currently keeps installs on 1.30.0.

- Zod 4's built-in `StandardSchemaV1` conformance is the expected shape for `ToolDefinition.input`/`output` and `Program.input`/`output`; any Standard-Schema-conformant validator works, not just Zod.
- `Provider` implementations perform exactly one model step per `generate`/`stream` call; `intellibrix` owns iteration, tool dispatch, and approvals. This invariant is documented in `AGENTS.md` and should not be broken by any adapter (`@intellibrix/ai-sdk` or otherwise).
- `@intellibrix/ai-sdk` builds and typechecks with only `ai` installed and no vendor adapters present, since `@ai-sdk/anthropic`/`@ai-sdk/openai`/`@ai-sdk/google`/`@ai-sdk/openai-compatible` are all optional peers, resolved lazily via a guarded `require()` (through `node:module`'s `createRequire`, since the package is ESM) inside `registry.ts`. Only calling `provider('<vendor>:...')` or `compatibleProvider()` for a vendor whose package isn't installed throws.
