Viby v1 API
This is the normative contract for the current Viby-native SDK. Viby v1 means the product API
generation, even while the npm package is released under 0.x during early development. For a
method-oriented reference, start with the SDK reference.
Ownership boundary
The host application owns:
- authentication and authorization;
- model-provider credentials and the selected AI SDK provider;
- its user, organization, subscription, and product UI;
- Postgres provisioning and
DATABASE_URL; - sandbox and browser infrastructure, artifact-store provisioning, HTTP hosting, integration-app credentials, and redirect routes.
Viby owns:
- tenant- and user-scoped chat history;
- durable logical generations, immutable attempts, ordered events, and usage;
- immutable attempt cost and cumulative logical-generation cost when the host configures it;
- typed plan, question, and permission tasks;
- resolved skill snapshots;
- immutable, parent-linked source versions;
- framework-native source ZIPs and Web-standard stream/download responses;
- durable binary and visual-artifact metadata with opaque external-store references;
- durable sandbox-backed preview sessions bound to immutable versions;
- immutable design evaluations linked to validated source, attachment, and visual evidence;
- migrations for the dedicated
vibyschema.
There is no Viby API key, managed Viby database, globally hosted preview URL, or deployment URL in v1. A configured sandbox adapter may produce a provider URL after the host starts a preview process. Configured repository and deployment integrations may use the durable connection lifecycle, but the host still owns each provider application, callback route, authorization policy, and provider adapter.
Installation and environment
npm install @viby/sdk ai
npx viby db migrateDATABASE_URL=postgresql://postgres:postgres@localhost:5432/viby
VIBY_SECRET_KEY=base64-encoded-32-byte-keyThe selected AI SDK provider reads its own environment variables. Viby records the provider and model IDs plus token usage, but never stores the provider credential.
Client
import { createViby, modelsFrom, skillRead } from "@viby/sdk";
import { openai } from "@ai-sdk/openai";
const viby = createViby({
framework: "farmjs",
...modelsFrom(openai, {
default: "gpt-5.6-sol",
fast: "gpt-5.6-sol-fast",
}),
skills: {
core: [skillRead("./skills/product")],
design: ["owner/repository/design-skill"],
frontend: ["owner/repository/frontend-skill"],
security: [],
},
});VibyConfig
interface VibyConfig<Framework extends string> {
framework: Framework; // built-in autocomplete plus custom string values
model: LanguageModel; // or engine: GenerationEngine<Framework>
models?: Readonly<Record<string, LanguageModel>>;
storage?: {
database?: DatabaseAdapter | PersistenceAdapter;
artifacts?: ArtifactStore;
connections?: IntegrationConnectionStore;
secrets?: SecretStore;
};
integrations?: {
repository?: Record<string, RepositoryIntegration>;
deployment?: Record<string, DeploymentIntegration>;
};
environment?: { store?: EnvironmentVariableStore };
deployment?: {
preparation?: {
install?: SandboxCommand;
build: SandboxCommand;
outputDirectory?: string;
collectorCommand?: string;
maxFiles?: number;
maxBytes?: number;
};
};
persistence?: PersistenceAdapter; // deprecated alias for storage.database
artifactStore?: ArtifactStore; // deprecated alias for storage.artifacts
connectionStore?: IntegrationConnectionStore; // deprecated alias
secretStore?: SecretStore; // deprecated alias
skills?: Record<string, readonly SkillReference[] | undefined>;
skillResolver?: SkillResolverAdapter;
tools?: {
sources?: Readonly<Record<string, ToolSource>>;
adapters?: Readonly<Record<string, ToolSourceAdapter>>;
select?: ToolSourceSelector;
policy?: ToolSourcePolicy;
};
sandbox?: SandboxAdapter;
preview?: {
files?: readonly SandboxFile[];
prepare?: readonly SandboxCommand[];
start: SandboxCommand;
port: number;
path?: string;
environment?: EnvironmentName;
env?: Readonly<Record<string, string>>;
sandboxTimeoutMs?: number;
readiness?: Omit<SandboxReadinessOptions, "path" | "signal">;
};
browser?: BrowserAdapter;
sandboxPolicy?: SandboxCommandPolicy;
generation?: {
execution?: "embedded" | "worker";
workspace?: { preview: "eager" };
quality?: {
prepare?: readonly GenerationQualityCommand[];
checks: readonly GenerationQualityCommand[];
checkConcurrency?: number;
};
};
retention?: { deletedChatsMs?: number | null };
events?: { sinks?: readonly OutboundEventSink[] };
telemetry?: VibyTelemetry;
cost?: GenerationCostConfig;
}
type SkillReference =
| `${string}/${string}/${string}`
| { source: "file"; path: string }
| { source: "inline"; name: string; description?: string; files: readonly SkillFile[] }
| {
source: "resolver";
resolver: string;
locator: string;
metadata?: Record<string, JsonValue>;
};framework is one type-safe string, not an array. Built-in suggestions include farm, tanstack-start, and next, while custom framework strings remain legal. Framework-specific behavior is supplied through skills and the model prompt instead of a hard-coded defineFramework registry.
storage.database owns structured durable Viby records. Omit it to select the built-in PostgreSQL adapter from DATABASE_URL, or use postgres({ url }) from @viby/sdk/storage/postgres for explicit configuration. storage.artifacts owns binary bytes independently. A custom database factory defined with defineDatabaseAdapter receives the configured artifacts adapter through open({ artifacts }), while a raw PersistenceAdapter continues owning its own artifact references. storage.connections and storage.secrets override the integration stores. Legacy top-level aliases remain accepted but cannot be combined with their categorized equivalents.
s3(...) from @viby/sdk/storage/s3 is the production S3-compatible artifact implementation. It accepts a bucket plus optional region, endpoint, credentials, path-style, prefix, and server-side-encryption configuration. AWS S3 uses its normal endpoint; R2, MinIO, and compatible providers supply endpoint. The adapter stores opaque tenant/user-scoped keys, validates checksums before writes, preserves immutable existing objects, returns defensive byte copies, and treats missing reads and repeated deletes idempotently.
environment: {} enables durable chat-scoped project variables with PostgreSQL metadata from DATABASE_URL and encrypted secret values from storage.secrets or the VIBY_SECRET_KEY default. environment.store replaces only the metadata adapter. A variable belongs to one type-safe environment string, allowing distinct development, preview, production, or custom values. Public records expose a secret flag but never the secret value or reference.
chat.environment.set, list, and delete manage the public surface. version.sandbox({ environment }) opts into just-in-time sandbox injection. version.deploy({ environment }) resolves that environment automatically for prebuilt sandbox/build commands and the deployment adapter. Resolved values are runtime-only and must not enter prompts, messages, durable events, telemetry, histories, or command records.
model is any AI SDK LanguageModel, so the application can choose a direct provider, a gateway, or its own compatible model implementation. models adds stable aliases for request-level selection. The top-level model is always addressed as default; model objects are never written to the database.
skills are categorized. Built-in categories are core, product, design, frontend, backend, data, ai, testing, security, accessibility, performance, and delivery; custom categories are allowed. skillRead(path) points at a local SKILL.md file or its containing directory. A string uses the stable skills.sh-compatible owner/repository/slug form. skillInline(...) supplies an immutable in-memory snapshot.
skillFrom(resolver, locator, metadata?) creates an opaque serializable reference for a custom skillResolver. Define the adapter with defineSkillResolver({ id, resolve }); its resolve method receives one reference plus the selected category and prompt, and returns exact skill files or null to use the next/built-in resolver. This allows application-owned catalogs, databases, object storage, and Git services without leaking their clients into the Viby contract. References and resolved snapshots are durable, so resolver metadata must never contain credentials.
sandbox is an optional provider adapter. Viby passes only the selected immutable source snapshot and explicitly supplied environment values to it. Provider credentials stay in the adapter configuration.
sandboxPolicy authorizes normalized foreground and background commands before an adapter receives them. It is shared by all providers and is optional for compatibility; products executing untrusted model actions should configure it.
integrations groups external providers by portable product capability. The durable connection lifecycle uses tenant/user-scoped metadata, hashed single-use authorization state, bounded callbacks, refresh, and local revocation. DATABASE_URL supplies the default connection store; VIBY_SECRET_KEY supplies the separate AES-256-GCM key for the default encrypted secret store. Advanced hosts can replace them through storage.connections and storage.secrets. Provider credentials never appear in public connection records, messages, events, telemetry, or model inputs.
connect() accepts an optional provider-neutral authorization preference with
account: "existing" | "new" and an optional externalAccountId. This lets product UI distinguish
reconnecting an installed provider account from provisioning another account without introducing
provider-specific routes into the SDK boundary.
Durable tools.adapters may independently declare an authorization lifecycle. A registered source exposes connect, connection, and disconnect; the root client exposes viby.toolSources.callback(request) for provider redirects. Authorization state is hashed and single use, callback origin/path substitution is rejected, public source configuration remains credential-free, and opaque credentials resolve only inside the selected adapter immediately before tool discovery or execution. Connection metadata is part of storage.database; secret bytes use storage.secrets. The host remains responsible for provider app registration, the HTTP callback route, and authenticating product requests.
generation.execution defaults to embedded. Use worker when request handlers should only persist queued attempts and separate Viby worker processes should claim them.
retention.deletedChatsMs defaults to 30 days. It defines when newly deleted chats become eligible for permanent purge. Use null for indefinite retention or 0 for immediate eligibility; applications may override the value for one deletion.
events.sinks is an optional provider-neutral registry. Configure signed sinks with signedOutboundEventSink; delivery remains explicit so the embedding product chooses its worker, queue, cron, or workflow runtime. Viby persists per-event delivery claims, retry state, and dead letters.
telemetry receives provider-neutral spans and metrics. openTelemetry({ tracer, meter? }) adapts structural OpenTelemetry-compatible objects without taking ownership of global SDK registration or exporters. Telemetry failures are fail-open.
cost defines a host-owned currency or credit unit plus a calculator returning safe integer micro-units. Attempt cost is immutable and logical-generation cost is cumulative across retries, resumes, and task resolution.
Repository integrations
user.integrations.repository.use(id) returns a user-scoped handle for one configured repository adapter. The same handle exposes owner, repository, branch, and pull-request operations; creates source-import descriptors for user.chats.import(...); and is passed to version.push(...).
const provider = user.integrations.repository.use("github");
const chat = await user.chats.import({
source: provider.source({
repository: { owner: "acme", name: "starter" },
ref: { branch: "main" },
}),
});
const result = await (await chat.latestVersion())!.push({
using: provider,
repository: { owner: "acme", name: "app", createIfMissing: true },
branch: { name: "main", createIfMissing: true },
commit: { message: "feat: publish generated app", expectedHead: "last-observed-commit" },
// Optional. The SDK otherwise derives this from the immutable version and target.
idempotencyKey: "publish-generated-app-v1",
});
const links = await chat.repositoryLinks();
const pushes = await (await chat.latestVersion())!.repositoryPushes();Every push sends the complete immutable version snapshot, including artifact-backed binary entries. Missing remote paths are therefore removed by the adapter's new tree. A stale expectedHead produces a typed conflict result instead of overwriting another commit. Branch creation and an optional pull request can be requested in the same call. Repository provider credentials remain inside the selected adapter operation context.
version.push(...) writes a pending record before contacting the provider and completes it as pushed, conflict, or failed. The record retains the immutable version, selected integration and connection, repository target and durable link, branch, commit and pull-request results, error, timestamps, and idempotency key. Repeating a completed idempotency key replays the persisted result instead of creating another remote effect. Use version.repositoryPushes() for one snapshot or chat.repositoryPushes() and chat.repositoryLinks() after a process restart.
The provider-neutral contract is RepositoryIntegration; adapter packages can expose provider-specific options through its generics. verifyRepositoryIntegration from @viby/sdk/integrations/repository/conformance exercises a disposable repository and leaves cleanup to the adapter test owner. Included implementations are exported from @viby/sdk/integrations/github and @viby/sdk/integrations/bitbucket; vendor types do not enter the core contract.
Deployment integrations
user.integrations.deployment.use(id) returns a connected deployment handle. It exposes project discovery/creation and deployment lookup/cancellation. An immutable version can deploy through that handle:
const provider = user.integrations.deployment.use("hosting");
const deployment = await version.deploy({
using: provider,
project: { name: "generated-app", createIfMissing: true },
environment: "preview",
});
const history = await version.deployments();
const projects = await chat.deploymentProjects();
const prepared = history[0] ? await version.deploymentArtifact(history[0].id) : null;A string project target means an existing project name; { id } selects an existing provider project ID; { name, createIfMissing: true } explicitly allows creation. Each deployment adapter declares whether it accepts immutable framework source or prebuilt files. For source adapters, the SDK materializes the complete immutable version, including artifact-backed binary paths. For prebuilt adapters, it materializes the version in any configured SandboxAdapter, runs the declarative install/build contract, stores the build ZIP through the provider-neutral artifact store, and gives the adapter only the built files. The integration contract contains no sandbox, framework, or artifact-store implementation types.
Every deployment receives a stable idempotency key derived from the version, integration, project, and environment. Applications can override that key for an app-owned operation identity. A retry reuses an existing prepared artifact when provider work must be attempted again. preparation.env reaches the sandbox at runtime but is not persisted; artifact command metadata records only environment names. version.deploymentArtifact(id) verifies the stored ZIP size and checksum before returning bytes. version.download() remains the independent raw framework-source archive.
Deployment URLs are provider results, not SDK-hosted preview URLs. They remain null until the selected provider reports one. version.deploy(...) persists the version, selected integration connection, project target and durable project link, environment, provider deployment ID, URL, errors, timestamps, and idempotency key. Every changed provider observation is appended as an ordered status transition. Calls through provider.deployments.get(...) and provider.deployments.cancel(...) update a matching durable deployment automatically. Use version.deployments() for one immutable snapshot or chat.deployments() and chat.deploymentProjects() after a process restart.
verifyDeploymentIntegration from @viby/sdk/integrations/deployment/conformance verifies project creation, deployment idempotency, lookup, and optional cancellation against a caller-owned disposable project. The Vercel implementation is exported from @viby/sdk/integrations/vercel; the Cloudflare Pages Direct Upload implementation is exported from @viby/sdk/integrations/cloudflare. Provider-specific project and deployment options do not enter this contract.
Methods
interface Viby<Framework extends string> {
readonly framework: Framework;
forUser(scope: UserScope): ScopedViby<Framework>;
worker(options: GenerationWorkerOptions): GenerationWorker<Framework>;
close(options?: { preserveSandboxes?: boolean }): Promise<void>;
}
interface UserScope {
tenantId: string;
userId: string;
}The canonical built-in IDs are farmjs, nextjs, svelte, sveltekit, vue, nuxt, solid,
solidstart, tanstack-start, react-router, astro, and vite. Selecting one automatically
adds its package-owned immutable skill to the core skill group. Custom strings remain valid and
are governed entirely by host-provided skills and source. farm and next are migration aliases;
new records should use farmjs and nextjs.
Durable Streamable HTTP MCP registrations use mcpAdapter() from @viby/sdk/tools/mcp. Its default public registration schema is { url: "https://…" }; loopback HTTP is allowed for local development. Pass a provider-neutral authorization lifecycle for tenant-owned OAuth. The live opaque credential becomes a bearer header inside the transport boundary by default, while headers and connect support custom authentication and transports without changing the core tool-source contract.
New generations snapshot the public durable tool-source registrations selected for their chat in GenerationConfigurationData.toolSources. The snapshot fixes adapter type, name, description, public configuration, and revision timestamps for workers and retries; credentials are neither copied nor persisted in the generation and remain governed by the live secret-backed connection.
Durable tool-source adapter packages can use verifyToolSourceAdapter from @viby/sdk/tools/conformance. The reusable suite accepts a caller-owned registration, generation context, harmless call, and optional opaque credential resolver; it verifies the provider-neutral adapter lifecycle without selecting a protocol or provider.
Every sandbox adapter declares a provider-neutral capability record. Applications can inspect session.capabilities or call session.supports(name) instead of branching on the provider name. Capability flags describe the configured Viby adapter surface, not every feature that may exist in the provider's native SDK.
session.start(command) returns a provider-neutral background process with wait and idempotent kill. It is available only when backgroundProcesses is true. session.waitForPort(port, options) polls an HTTP readiness check through the adapter's port URL and supports a caller-provided check for protected previews. Neither API assumes a framework command, package manager, image, or provider.
Call close during process shutdown or at the end of a short-lived script.
Tenant and user scope
const userViby = viby.forUser({
tenantId: organization.id,
userId: session.user.id,
});Both values are required, non-empty application identifiers. Every repository query constrains both values. The SDK does not infer identity from request globals and does not query the host application's auth tables.
Chats
interface ChatCollection<Framework extends string> {
create(input?: { title?: string; metadata?: ChatMetadata }): Promise<Chat<Framework>>;
import(input: ImportProjectInput): Promise<Chat<Framework>>;
get(id: string): Promise<Chat<Framework>>;
list(options?: ChatListOptions): Promise<CursorPage<Chat<Framework>>>;
snapshot(
id: string,
options?: { messages?: PageOptions; versions?: PageOptions },
): Promise<{
chat: Chat<Framework>;
messages: CursorPage<MessageData>;
versions: CursorPage<Version<Framework>>;
}>;
restore(id: string): Promise<Chat<Framework>>;
purgeDeleted(input?: { limit?: number }): Promise<number>;
}
interface ChatListOptions extends PageOptions {
metadata?: ChatMetadata;
}
interface Chat<Framework extends string> {
readonly id: string;
readonly title: string;
readonly metadata: ChatMetadata;
readonly framework: Framework;
readonly createdAt: Date;
readonly updatedAt: Date;
update(input: UpdateChatInput): Promise<Chat<Framework>>;
delete(input?: { retentionMs?: number | null }): Promise<ChatDeletionData>;
start(input: GenerateInput): Promise<Generation<Framework>>;
generate(input: GenerateInput): Promise<Version<Framework>>;
getGeneration(id: string): Promise<Generation<Framework>>;
latestVersion(): Promise<Version<Framework> | null>;
getVersion(id: string): Promise<Version<Framework>>;
listVersions(options?: PageOptions): Promise<CursorPage<Version<Framework>>>;
getMessage(id: string): Promise<MessageData>;
listMessages(options?: PageOptions): Promise<CursorPage<MessageData>>;
getAttachment(id: string): Promise<AttachmentContent>;
}
interface GenerateInput {
prompt: string;
model?: string;
instructions?: string;
skills?: Record<string, readonly SkillReference[] | undefined>;
metadata?: ChatMetadata;
attachments?: readonly AttachmentInput[];
}
interface AttachmentInput {
filename: string;
mediaType: string;
bytes: Uint8Array;
}chats.snapshot(...) loads the chat plus independently paginated message and
version windows for a detail view. PostgreSQL serves the complete snapshot with
one aggregate query. Custom persistence adapters may implement the optional
readChatSnapshot fast path; adapters that omit it use a parallel portable
fallback with identical cursors and ownership checks.
Generation overrides are flat and optional. model selects a configured alias, request skills overlay the client defaults by category, and metadata is host-owned JSON. Viby stores the effective serializable configuration before execution, records the resolved provider/model identity, and snapshots resolved skill contents on the first attempt. A worker can therefore resume the same request after a process restart without persisting provider objects or credentials.
Attachments are immutable byte snapshots, not expiring remote URLs. The default repository inserts them atomically with the user message and generation, then supplies standard AI SDK file parts to the configured model or agent. Message pages contain only AttachmentData metadata; chat.getAttachment(id) performs explicit tenant-, user-, and chat-scoped content retrieval. Inputs are limited to 10 files, 10 MB each, and 25 MB total per generation.
chats.import accepts typed text and binary source entries, ZIP bytes, or a provider-neutral source adapter. It creates the chat and its first immutable version atomically without invoking the model. Binary inputs use { type: "artifact", path, bytes, mediaType }; archive imports classify known binary assets and invalid UTF-8 octet streams as artifact-backed entries. Imports reject unsafe or duplicate paths, encrypted and ZIP64 entries, symbolic links, oversized inputs, and excessive expanded size. filePolicy.locked accepts "all" or a path list; file-list imports may additionally set locked: true per entry. Policy paths are normalized and must exist in the imported tree.
interface SourceImportAdapter<Input, Framework extends string = string> {
readonly name: string;
import(
input: Input,
context: {
tenantId: string;
userId: string;
framework: Framework;
signal?: AbortSignal;
},
): Promise<{
source: ImportProjectSource;
title?: string;
summary?: string;
}>;
}Use { type: "adapter", adapter, input } as the import source. Adapter input stays application-owned and is never persisted. The adapter may return title and summary hints, while explicit caller values win. All returned data re-enters the ordinary file/ZIP validation and immutable snapshot pipeline. Adapter failures use SourceImportError without copying provider error text or credentials into the public message.
create only creates a durable chat. start commits the user message, logical generation, initial queued attempt, and first events in one transaction before contacting the model. It returns an addressable handle while execution continues. generate is the synchronous convenience form of start followed by wait; it resolves only after a successful immutable version and assistant message have been committed.
Paginated methods default to 20 and accept 1–100. Each page contains items and an opaque nextCursor; pass that value back as after. Chat records are ordered by most recently updated, versions by descending number, and messages chronologically. chat.getMessage(id) returns one message with its complete ordered parts and rejects ids from another chat, tenant, or user as not found. chats.list({ metadata }) applies tenant-scoped JSON containment before pagination, including nested objects and array members; reuse the same filter with subsequent cursors. A cursor is resource-specific and malformed or cross-resource cursors throw ConfigurationError.
chat.update returns a refreshed handle and can replace the title, metadata, or both. Metadata must be a JSON object, is limited to 64 KB and 20 levels, and rejects non-finite numbers, unsafe keys, cycles, and non-JSON values.
chat.delete atomically tombstones a chat and returns { chatId, deletedAt, purgeAfter }. Deleted chats are excluded from get, list, and pre-existing resource handles immediately. Deletion fails while a generation is queued, running, or waiting. chats.restore(id) is tenant- and user-scoped and succeeds only before purgeAfter, or at any time for indefinite retention. chats.purgeDeleted({ limit }) permanently deletes only due tombstones in bounded batches and returns the count; the host application decides when to call it from a worker or scheduled task.
Typed message parts
MessageData.content remains the convenient plain-text transcript. MessageData.parts is the ordered, durable representation for products that render agent activity:
interface MessageData {
readonly id: string;
readonly chatId: string;
readonly generationId: string | null;
readonly role: "user" | "assistant";
readonly content: string;
readonly finishReason: string | null;
readonly parts: readonly MessagePart[];
readonly attachments: readonly AttachmentData[];
readonly createdAt: Date;
}
interface AttachmentData {
readonly id: string;
readonly chatId: string;
readonly messageId: string;
readonly generationId: string;
readonly filename: string;
readonly mediaType: string;
readonly size: number;
readonly checksum: string;
readonly createdAt: Date;
}
type MessagePartType =
| "text"
| "status"
| "reasoning-summary"
| "file-read"
| "file-edit"
| "search"
| "command"
| "tool-call"
| "error"
| "usage";
type FileEditMessagePartData =
| {
readonly operation: "create" | "update" | "write" | "delete";
readonly path: string;
}
| {
readonly operation: "move";
readonly from: string;
readonly to: string;
};finishReason is the model completion reason committed with an assistant message. It is null for user messages and historical assistant messages created before this field was introduced. MessagePart is a discriminated union on type; each variant has a corresponding typed data object. Every part has an id, messageId, nullable generationId and attemptId, zero-based position, and createdAt. The SDK emits text, status, file-edit, and usage parts for its current built-in flow. Agent runners and tool providers use the remaining portable variants without putting provider-specific payloads in the message contract.
reasoning-summary is provider-safe summary text, not hidden chain-of-thought. Part order is immutable after a message is committed. Live lifecycle updates use durable generation events and converge on these persisted message parts.
New file writes are classified against the immutable base version as create or update. write is retained only so applications can render historical records created before this distinction was available.
Versions and iteration
interface Version<Framework extends string> {
readonly id: string;
readonly chatId: string;
readonly generationId: string | null;
readonly parentVersionId: string | null;
readonly number: number;
readonly origin: "generated" | "imported" | "edited" | "forked" | "restored";
readonly framework: Framework;
readonly title: string;
readonly summary: string;
readonly createdAt: Date;
startIteration(input: { prompt: string }): Promise<Generation<Framework>>;
iterate(input: { prompt: string }): Promise<Version<Framework>>;
apply(input: ApplySourceChangesInput): Promise<Version<Framework>>;
fork(input?: ForkVersionInput): Promise<Chat<Framework>>;
restore(input?: RestoreVersionInput): Promise<Version<Framework>>;
files(): Promise<VersionFile[]>;
entries(): Promise<VersionEntry[]>;
projectArtifact(id: string): Promise<ProjectArtifactContent>;
changes(): Promise<SourceChange[]>;
workspace(): Promise<AgentWorkspace<Version<Framework>>>;
generation(): Promise<GenerationData | null>;
recordDesignEvaluation(input: RecordDesignEvaluationInput): Promise<DesignEvaluationData>;
getDesignEvaluation(id: string): Promise<DesignEvaluationData>;
listDesignEvaluations(options?: PageOptions): Promise<CursorPage<DesignEvaluationData>>;
download(): Promise<DownloadArtifact>;
sandbox(options?: SandboxOpenOptions): Promise<SandboxSession>;
}iterate generates from that exact version, even when it is not the latest. The built-in generator requests typed write, delete, and move changes for an existing project, validates them against that exact base, and persists both the ordered changes and a new complete snapshot. Its parentVersionId is the selected version. Historical rows and files are immutable.
apply performs typed write, delete, and move operations without a model request. Viby validates the complete result and persists it as an edited child snapshot. Missing deletes, unsafe paths, move collisions, locked-file changes, duplicate paths, empty projects, and size-limit violations fail before persistence.
fork atomically creates a new chat and a forked first version containing the exact selected files. Its parent points to the source version across chats. restore creates a new latest restored version in the same chat, with the selected historical snapshot as its parent. Neither operation invokes the model or mutates existing files. Both preserve locked-file metadata.
files remains the source-compatible text-only view and returns paths sorted lexicographically. entries returns the complete VersionEntry union: text entries carry content, while artifact entries carry artifactId; both include path, mediaType, byte size, SHA-256 checksum, and locked. projectArtifact loads bytes only when that artifact is referenced by the selected version and owned by the caller's tenant and user. Once an entry is locked, direct changes, generated changes, and workspace tools cannot write, delete, or move it.
type VersionEntry =
| { type: "text"; path: string; content: string; mediaType: string; size: number; checksum: string; locked: boolean }
| { type: "artifact"; path: string; artifactId: string; mediaType: string; size: number; checksum: string; locked: boolean };changes returns the ordered operations that produced an edited or incrementally generated version. Full-tree generations, imports, forks, and restores return an empty array. Change records are immutable and scoped with their version.
Design evaluations
interface RecordDesignEvaluationInput {
evaluator: string;
status: "passed" | "warning" | "failed";
score: number;
summary: string;
criteria: readonly {
id: string;
label: string;
status: "passed" | "warning" | "failed";
score: number;
summary: string;
evidence?: readonly DesignEvaluationEvidence[];
}[];
evidence?: readonly DesignEvaluationEvidence[];
metadata?: ChatMetadata;
}
type DesignEvaluationEvidence =
| { type: "version-file"; path: string; description?: string }
| { type: "attachment"; attachmentId: string; description?: string }
| { type: "artifact"; artifactId: string; description?: string }
| { type: "url"; url: string; description?: string }
| { type: "note"; text: string };Evaluation execution is host-owned and provider-neutral: a browser audit, model judge, visual regression service, or custom rubric can all record the same contract. recordDesignEvaluation validates scores and evidence, verifies version-file paths against the exact immutable snapshot, and verifies attachment and visual-artifact ownership. evaluateVisual composes the configured browser with a URL or sandbox preview, captures routes, stores screenshots through the persistence adapter's artifact store, invokes caller-supplied quality gates, and records the result. No design model is hard-coded.
Agent workspace
interface AgentWorkspace<Result> {
readonly committed: boolean;
readonly tools: {
listFiles(input?: { prefix?: string }): Promise<readonly AgentWorkspaceFile[]>;
readFile(input: { path: string }): Promise<VersionFile>;
search(input: {
query: string;
prefix?: string;
caseSensitive?: boolean;
limit?: number;
}): Promise<readonly AgentWorkspaceSearchResult[]>;
writeFile(input: { path: string; content: string; mediaType?: string }): Promise<SourceChange>;
deleteFile(input: { path: string }): Promise<SourceChange>;
moveFile(input: { from: string; to: string }): Promise<SourceChange>;
};
changes(): readonly SourceChange[];
files(): readonly VersionFile[];
commit(input?: { title?: string; summary?: string }): Promise<Result>;
}version.workspace() copies the selected snapshot into process memory. Tool calls read or stage changes only in that copy and never invoke a model, sandbox, database write, or external provider. The default Viby agent maps these functions into AI SDK tools; a host-provided generation engine may map the same plain functions into another model runtime.
Every staged operation is normalized and validated against the full base snapshot, including immutable file locks. listFiles exposes locked so an agent can avoid proposing prohibited changes. commit requires at least one change, accepts concurrent callers as one atomic operation, and creates exactly one edited child version. A failed persistence attempt can be retried; after success the workspace is sealed. Use a new workspace for further edits.
Default workspace and sandbox agent
createViby uses AgentProjectGenerator, a provider-neutral bounded tool-loop over the configured AI SDK model. The agent always receives workspace list, read, search, write, delete, and move tools. It must stage a complete source tree for an initial generation or an immutable change set for an iteration before returning its typed completion output.
const viby = createViby({
framework: "farmjs",
model,
agent: {
maxSteps: 20,
maxDurationMs: 300_000,
maxTokens: 200_000,
maxCommands: 20,
commandTimeoutMs: 60_000,
maxCommandOutputBytes: 200_000,
sandboxPorts: [3000],
},
});The limits bound completed model steps, total wall time, observed aggregate input/output tokens, sandbox command count, individual command duration, and returned stdout/stderr. A token or step boundary stops the loop rather than permitting another model step; if no valid final output exists, the attempt fails durably instead of committing partial source.
When an iteration has an immutable base version and a sandbox adapter is configured, the runner materializes that version for the attempt and always stops the session afterward. Sandbox file reads, argv commands, streamed command output, and configured port URLs are included only if the session declares files, commands, commandStreaming, and portUrls, respectively. No provider name is inspected. Commands accept separate executable and argument fields, no environment values, pass through the configured sandbox command policy, and consume the command budget. An approval-required decision pauses before adapter execution with a durable permission task. An initial generation has no persisted base version and therefore uses workspace tools without a sandbox.
Workspace writes are the source of truth and successful writes are synchronized into the active sandbox when possible. The final workspace change set, not sandbox filesystem state, creates the immutable version. This keeps generated output deterministic across providers.
Sandboxes
version.sandbox() creates an isolated provider session and writes the selected version's complete source snapshot into its project root. It throws SandboxUnavailableError when the client has no adapter.
interface SandboxAdapter {
readonly provider: string;
readonly capabilities: SandboxCapabilities;
create(input: SandboxCreateInput): Promise<SandboxInstance>;
reconnect?(input: SandboxReconnectInput): Promise<SandboxInstance>;
}
interface SandboxSession {
readonly id: string;
readonly provider: string;
readonly leaseId: string | null;
readonly capabilities: SandboxCapabilities;
readonly stopped: boolean;
authorizeCommand(
command: SandboxCommand,
action?: "run" | "start",
): Promise<SandboxCommandGrant>;
run(command: SandboxCommand, grant?: SandboxCommandGrant): Promise<SandboxCommandResult>;
start(command: SandboxCommand, grant?: SandboxCommandGrant): Promise<SandboxProcess>;
writeFiles(files: readonly SandboxFile[]): Promise<void>;
readFile(path: string): Promise<Uint8Array>;
url(port: number): Promise<string>;
stop(): Promise<void>;
}All paths are relative to the project root and validated before reaching an adapter. Commands contain a distinct command and args array, optional cwd, environment overrides, timeout, abort signal, and onOutput callback. A non-zero process exit is returned as a normal typed result; provider transport and lifecycle failures throw SandboxError.
Command policy
type SandboxCommandPolicy = (
request: SandboxCommandPolicyRequest,
) =>
| { decision: "allow" }
| { decision: "deny"; reason: string }
| { decision: "approval-required"; reason: string }
| { allow: true }
| { allow: false; reason: string }
| Promise<SandboxCommandPolicyDecision>;
const policy = sandboxCommandPolicy({
allowCommands: ["node", "pnpm"],
denyCommands: ["sudo"],
actions: ["run", "start"],
environment: ["CI"],
maxTimeoutMs: 300_000,
maxArgs: 100,
});The request contains the action, adapter provider, tenant/user/chat/version/framework context, executable, copied argument list, normalized project-relative working directory, environment variable names, and timeout. Environment values, signals, and output callbacks are excluded. The core evaluates the policy immediately before SandboxInstance.run or start; denial throws SandboxCommandDeniedError, while approval-required throws SandboxCommandApprovalRequiredError with the secret-free proposed action. The legacy { allow: true | false, reason? } result is normalized to allow or deny. A thrown policy error or invalid decision throws SandboxError and still prevents execution. The same policy is restored on a session reconnected from a durable lease.
The default agent catches the approval-required result before invoking the adapter, persists SandboxCommandProposedAction on its permission task, and stops the attempt. An allowed task resolution starts a new attempt with only that exact action fingerprint approved. A denied resolution keeps the fingerprint denied. The resulting command tool call is an external effect whose stable idempotency key is the proposed-action fingerprint: a prior success is reused, and a prior pending effect is never executed again without reconciliation. Low-level consumers can apply the same handshake with authorizeCommand(command, action) and pass the opaque, one-use grant to run or start.
Opening a version persists a portable sandbox lease before materialization completes. Lease records are constrained by tenantId and userId and contain the framework, chat and version ids, adapter provider, opaque provider sandbox id, declared ports, status, and expiration. Environment values, adapter credentials, and provider response payloads are deliberately excluded.
const session = await version.sandbox({ ports: [3000] });
const lease = await userViby.sandboxes.get(session.leaseId!);
const reconnected = await userViby.sandboxes.reconnect(lease.id);Reconnect first loads and scopes the Viby lease, rejects stopped or expired leases, checks that the currently configured adapter owns the same provider name, and calls the adapter's explicit reconnect primitive. E2B, Vercel Sandbox, and Cloudflare currently declare reconnect: true; adapters without that primitive fail with SandboxUnavailableError instead of recreating an unrelated environment.
E2B adapter
Install the optional e2b peer and import e2bSandbox from @viby/sdk/sandbox/e2b. It uses E2B_API_KEY by default or accepts an explicit server-side apiKey, plus template, domain, request timeout, security, and metadata options. Viby command arguments are individually shell-quoted because E2B accepts a shell command string.
Vercel Sandbox adapter
Install the optional @vercel/sandbox peer and import vercelSandbox from @viby/sdk/sandbox/vercel. Authentication uses Vercel OIDC automatically, or an explicit token, teamId, and projectId tuple. The adapter accepts an image or legacy runtime, resources, network policy, tags, and an optional name factory. Viby sessions are non-persistent and support up to four declared ports.
Daytona adapter
Install the optional @daytona/sdk peer and import daytonaSandbox from @viby/sdk/sandbox/daytona. Authentication uses the Daytona environment variables by default or accepts explicit API key, API URL, and target options. The adapter creates ephemeral, TTL-bound sandboxes from an image, snapshot, or the Daytona default; supports resource, network, secret, label, user, language, and name configuration; streams stdout and stderr through Daytona command sessions; and exposes directly usable signed preview URLs.
Modal adapter
Install the optional modal peer and import modalSandbox from @viby/sdk/sandbox/modal. The provider package requires a server-side Node.js 22 or newer runtime and authenticates from the standard Modal token environment variables or an explicit token ID and secret pair. The adapter creates a bounded-lifetime sandbox in a configurable Modal App, writes binary source files, executes argv without shell interpolation, streams both output channels, exposes declared encrypted tunnels, and terminates the sandbox on cleanup. Modal-specific image, secret, GPU, resource, region, cloud, OIDC, tag, idle timeout, and network policy options remain isolated to the adapter.
Cloudflare Sandbox adapter
Install the optional @cloudflare/sandbox peer and import cloudflareSandbox from @viby/sdk/sandbox/cloudflare inside a Cloudflare Worker. The application owns the required Container, Durable Object binding, migration, image, and capacity configuration and passes env.Sandbox as the adapter binding. The adapter creates a unique normalized Durable Object identity, performs sessionless command execution with safely quoted arguments, streams output, transfers binary files, maps inactivity to the Viby session timeout, supports quick tunnels or Worker-hostname previews, and destroys the container on cleanup. Quick tunnels require RPC transport. Preview URLs are public, and Cloudflare reserves port 3000 for its control server.
Local Docker adapter
Import dockerSandbox from @viby/sdk/sandbox/docker; it has no provider package dependency but requires access to a Docker CLI and daemon. Defaults include a read-only root, temporary project filesystem, dropped Linux capabilities, no-new-privileges, resource limits, and loopback-only random host ports. Source is streamed through docker exec and is never bind-mounted from the host. Docker is intended for trusted local or single-tenant environments rather than hostile multi-tenant isolation.
Durable generations
A generation is the stable logical request. An attempt is one immutable execution of that request. Retry, resume, and task resolution append attempts instead of overwriting failure history.
GenerationData.configuration contains the durable model alias, optional instructions, effective categorized skill references, and request metadata. The immutable attempt records continue to carry the concrete provider and model IDs used for cost and telemetry attribution.
Retries and resumed workers reload attachment bytes by generation ID and checksum. The public attachment contract does not expose PostgreSQL or require a provider-specific upload handle, so a future repository implementation can place byte content in object storage without changing generation calls.
interface Generation<Framework extends string> {
readonly id: string;
readonly chatId: string;
data(): Promise<GenerationData>;
attempts(): Promise<GenerationAttemptData[]>;
tasks(): Promise<GenerationTaskData[]>;
steering(): Promise<GenerationSteeringData[]>;
steer(input: string | GenerationSteeringInput): Promise<GenerationSteeringData>;
toolCalls(): Promise<ToolCallData[]>;
events(options?: { after?: string; limit?: number }): Promise<GenerationEventPage>;
deliverEvents(options: {
sink: string;
after?: string;
limit?: number;
signal?: AbortSignal;
}): Promise<{
deliveries: readonly OutboundEventReceipt[];
cursor: string;
hasMore: boolean;
}>;
stream(options?: {
after?: string;
pollIntervalMs?: number;
signal?: AbortSignal;
}): AsyncGenerator<GenerationEvent>;
wait(options?: {
pollIntervalMs?: number;
signal?: AbortSignal;
}): Promise<GenerationOutcome<Framework>>;
cancel(reason?: string): Promise<GenerationData>;
retry(): Promise<this>;
resume(): Promise<this>;
resolve(input: ResolveGenerationTaskInput): Promise<this>;
}Logical generation statuses are queued, running, waiting, succeeded, failed, and cancelled. Attempt statuses additionally include interrupted. Attempt reasons are initial, retry, resume, and task_resolution.
ScopedViby.generations.get(id) restores a handle after a request, page reload, or process restart. retry is available for failed or cancelled generations. resume also takes over a durable queued or running record whose original process is gone; an attempt with an unexpired worker lease cannot be interrupted.
cancel first commits cancellation, then aborts the local AI SDK model signal. Aborting stream or wait only stops that subscriber and never cancels the generation.
steer commits a tenant-scoped user message and GenerationSteeringData before it reaches a
worker. It is valid in queued, running, and waiting states; terminal generations reject new
steering and should be iterated instead. An optional idempotencyKey deduplicates repeated product
requests for the same logical generation, and attachments use the normal immutable attachment
contract. steering() returns ordered queued and applied records.
Workers consume steering under the active attempt lease and append steering.queued and
steering.applied to the ordinary resumable cursor. The built-in bounded agent consumes between
model/tool steps through AI SDK loop preparation. Provider-neutral engines receive
GeneratorOptions.steering.consume() and choose equivalent safe boundaries. Applying steering
never interrupts an in-flight command or authorizes replay of an external effect.
Configure provider-neutral quality gates when generated source must install and build before it becomes an immutable version:
const viby = createViby({
framework: "farmjs",
model,
sandbox,
preview: {
prepare: [{ command: "pnpm", args: ["install", "--prefer-offline"] }],
start: { command: "pnpm", args: ["dev", "--host", "0.0.0.0"] },
port: 3000,
},
generation: {
workspace: { preview: "eager" },
quality: {
prepare: [{
id: "install",
command: "pnpm",
args: ["install", "--prefer-offline"],
}],
checks: [
{ id: "typecheck", command: "pnpm", args: ["typecheck"] },
{ id: "build", command: "pnpm", args: ["build"] },
],
checkConcurrency: 2,
},
},
});workspace.preview: "eager" materializes the base version and begins preview preparation while
generation runs. workspace.started, workspace.prepared, preview.ready, and preview.failed
use the existing resumable cursor, so a product can show the first coherent preview without waiting
for final validation. The completed candidate is synchronized into the same sandbox, preparation is
reconciled, and up to checkConcurrency checks run concurrently. Viby commits only after every
command exits with code 0, then retargets the existing durable preview to that immutable version
without restarting it. Without eager preview, quality gates retain their fresh-sandbox lifecycle.
Failed checks fail the attempt without persisting a partial version. quality.started and
quality.completed remain durable, while command output is intentionally not persisted because it
may contain secrets. The configured sandbox command policy applies to every quality command.
Quality and workspace policy are host configuration and must be identical on all durable workers.
Embedded execution remains the default. Set generation: { execution: "worker" } to leave new attempts queued for a portable database-backed worker:
const worker = viby.worker({
id: "worker-eu-1",
concurrency: 4,
leaseMs: 30_000,
heartbeatMs: 10_000,
pollIntervalMs: 500,
});
await worker.run({ signal });
// Or claim and process at most one attempt:
await worker.runOnce();Claims use FOR UPDATE SKIP LOCKED and are restricted to the client's framework, model provider, and model id. Each running attempt records its worker id, heartbeat, and lease expiration. An opaque lease token fences output events, skill snapshots, task pauses, failures, and completion transactions. A worker can reclaim a running attempt only after its lease expires; the old owner then loses write access. This is an at-least-once execution contract and assumes no queue service, workflow vendor, or particular JavaScript host.
When a request-scoped worker hands its generation sandbox to an eager durable preview, close the
request-owned client with viby.close({ preserveSandboxes: true }). This closes stores and provider
clients without stopping reconnectable sandbox leases. Ordinary shutdown keeps the default
viby.close() behavior and stops locally tracked previews and sandboxes.
Events and resumable cursors
Every lifecycle transition, structured-output text delta, and agent trace update is stored in viby.generation_events. cursor is an opaque, monotonically increasing integer string. Consumers persist the last acknowledged cursor and pass it back through after:
for await (const event of generation.stream({ after: lastCursor })) {
lastCursor = event.cursor;
publishToClient(event);
}For Web-standard HTTP hosts, generation.toEventStreamResponse({ request }) and generationEventStreamResponse(generation, { request }) return a standards-compliant SSE Response. They read the resumable cursor from Last-Event-ID unless after is explicit, emit id, typed event, and JSON data fields, set no-buffer/no-cache headers, and combine request cancellation with an explicit signal.
The event stream ends at waiting, succeeded, failed, or cancelled. Calling it again with the saved cursor replays only later events. The initial release uses portable Postgres polling; applications do not need Redis or provider-specific stream storage.
Agent traces use part.started, part.delta, part.completed, and part.failed. A started event assigns a stable UUID, typed message-part kind, and trace-local position. Delta events carry incremental text for that id. A completed event carries the typed part data and the completed part is persisted into the final assistant message with the same id. A failed event carries a normalized message, nullable code, and retryable flag; failed and incomplete parts do not become final message parts.
Part events share the generation's existing durable cursor. No second checkpoint or provider stream identifier is required, and attempt ownership prevents a stale worker from appending trace data after its lease is fenced.
Signed outbound event sinks
signedOutboundEventSink({ id, secret, keyId?, source?, send }) adapts the durable generation event log to an application-owned transport. generation.deliverEvents({ sink, after, limit, retry? }) reads one durable page and sends it in cursor order. Delivery state is persisted by generation, event cursor, and sink. A row is claimed with a lease before transport execution, so concurrent workers cannot intentionally repeat the same active delivery.
Each request contains a CloudEvents-style JSON body plus lowercase viby-event-id, viby-event-key-id, viby-event-timestamp, and viby-event-signature headers. The signature is v1=<base64url HMAC-SHA256> over timestamp.eventId.body. Secrets must contain at least 32 bytes and remain in process memory. verifySignedOutboundEvent(request, { secret, keyId, toleranceMs }) verifies the key, replay window, raw body, and signature using constant-time comparison before returning the typed envelope.
Retry policy bounds attempts, exponential backoff, maximum delay, and claim lease duration. An exhausted delivery becomes dead_lettered; inspect records with generation.outboundDeliveries({ sink, status? }) and explicitly redrive one with generation.redriveOutboundEvent({ sink, cursor }). A redrive resets attempts and makes the event claimable again. Sink or transport errors never change generation state. Because an endpoint may accept a request before the caller observes failure, receivers must still deduplicate stable event IDs. Viby owns durable delivery state, not a scheduler or queue runtime.
MCP tools
registerVibyMcpTools(server, { viby, prefix? }) from @viby/sdk/mcp exposes a scoped subset of the durable API on an official @modelcontextprotocol/server McpServer. It registers chat list/create/get, generation start/get/events/cancel/steering/task resolution, version list/files/iteration, and source download tools.
The supplied ScopedViby is already bound to one authenticated tenant/user scope. Viby does not accept identity as tool arguments and does not own MCP transport, authentication, OAuth, connection storage, or third-party credentials.
Tool calls and results
Every tool execution can be recorded through the runner's provider-neutral tool-call writer. Applications inspect the durable records through generation.toolCalls():
interface ToolCallData {
readonly id: string;
readonly generationId: string;
readonly attemptId: string;
readonly messageId: string | null;
readonly providerCallId: string;
readonly name: string;
readonly effect: "read" | "write" | "external";
readonly arguments: JsonValue;
readonly result: JsonValue | null;
readonly status: "pending" | "succeeded" | "failed";
readonly error: string | null;
readonly idempotencyKey: string | null;
readonly createdAt: Date;
readonly completedAt: Date | null;
}Arguments and results are validated as finite, bounded JSON. Keys commonly used for passwords, tokens, cookies, authorization headers, credentials, private keys, secrets, and API keys are replaced with [REDACTED] before the repository receives them. The SDK never stores an unredacted copy.
External effects require a non-empty idempotency key. (tenant, user, tool name, idempotency key) identifies the effect across attempts and generations; a duplicate start returns the original record with created: false. Callers must reuse its terminal result or reconcile a pending record instead of repeating an uncertain external action. Lease fencing applies to call creation and settlement. Tool calls retain attempt ownership immediately and are linked to the final assistant message when its tool-call part is committed.
Typed tasks
When the model cannot safely continue, a generation can enter waiting with one of three discriminated tasks:
plan: steps that requireapproveorrevisewith feedback;question: a question, optional choices, and a required answer;permission: an action and requested permissions requiringallowordeny; sandbox-agent tasks also include a typedproposedAction.
const outcome = await generation.wait();
if (outcome.status === "waiting") {
const task = outcome.tasks[0];
if (task?.kind === "permission") {
await generation.resolve({
taskId: task.id,
resolution: {
kind: "permission",
decision: "allow",
note: "Read-only access only",
},
});
}
}The resolution kind must match the task kind. Resolving commits the response and a new queued attempt in one transaction, then continues the same logical generation. Proposed sandbox actions are stored inside the task record and contain command arguments and environment names, never environment values.
Messages and usage
Viby persists:
- provider and model identifiers;
- input, output, and total tokens when the provider returns them;
- timestamps and error text;
- the user and assistant messages;
- the exact skills and content hashes reused by every attempt in the logical generation;
- the base version from which generation started.
A failed attempt stores its failure and does not create an assistant message or partial version. Generation.wait returns the durable failure. The synchronous chat.generate convenience throws GenerationError with generationId, so the host product can correlate logs and support requests without exposing a secret.
Generated projects use Structured Outputs. Every file includes a required mediaType field that may be null; a null value is replaced by Viby MIME inference before persistence.
Telemetry and cost
Generation and attempt execution emit provider-neutral spans and low-cardinality counters/histograms for latency, attempts, outcomes, token usage, and attributed cost. Attributes intentionally exclude prompts, source, tool arguments/results, provider credentials, and tenant/user IDs. A throwing telemetry implementation cannot fail generation.
When a cost calculator is configured, its estimate is committed with the attempt outcome and added to the logical generation in the same durable lifecycle. The value is { amountMicros, currency }; Viby does not encode vendor prices, exchange rates, or billing policy. A configured result also appears in the final usage message part.
Downloads
const artifact = await version.download();
artifact.filename; // e.g. analytics-dashboard.zip
artifact.contentType; // application/zip
artifact.bytes; // Uint8Array
artifact.toResponse();// standard Web ResponseThe ZIP is built from the selected persisted version and materializes both text and artifact-backed binary entries. It is the raw framework-native source tree and excludes secrets, lockfiles, dependency folders, build output, and provider-specific deployment configuration.
Persistence model
| Table | Purpose | Written when |
|---|---|---|
viby.chats | scoped conversation, framework, app metadata, and retention tombstone | creation, update, successful generation, deletion, and restoration |
viby.messages | user/assistant conversation | attempt creation and successful completion |
viby.message_parts | ordered typed message content and agent activity with attempt ownership | with the owning user or assistant message |
viby.generations | logical request, active attempt, aggregate status and usage, base version | before execution and on every transition |
viby.generation_attempts | immutable execution history, reason, model IDs, usage, errors | before every initial, retry, resume, or task-resolution execution |
viby.generation_events | ordered lifecycle, structured-output, and agent part trace events | in the same transaction as transitions and during model or agent streaming |
viby.generation_tasks | typed blocking requests, proposed actions, and resolutions | when pausing and resuming a generation |
viby.generation_steering | ordered queued/applied instructions, owning messages and attempts, and idempotency keys | when steering is submitted and consumed at a safe boundary |
viby.tool_calls | redacted typed tool arguments, results, ownership, status, and idempotency | before and after each runner tool execution |
viby.tool_sources | tenant/user-scoped provider-neutral tool registrations and public configuration | source creation, update, disable, or archive |
viby.chat_tool_sources | explicit source selection for one chat | selection replacement or source archive |
viby.tool_source_authorization_sessions | hashed, expiring, single-use provider callback state and secret-store references | authorization start and callback consumption |
viby.tool_source_connections | provider-neutral account, status, scopes, expiry, and secret-store reference | authorization completion, refresh, or revocation |
viby.versions | immutable source snapshot metadata, origin, and parent lineage | successful generation, source import, or direct source change |
viby.version_files | complete text or artifact-backed entries and immutable lock metadata for one version | successful completion |
viby.project_artifacts | scoped binary source metadata and external artifact-store references | project import and retention purge |
viby.version_changes | ordered typed operations that produced an incremental version | agent workspace commit, direct source change, or generated iteration |
viby.repository_links | provider-neutral links between a chat and a connected remote repository | first completed push and subsequent repository metadata refreshes |
viby.repository_pushes | version-bound push, conflict, commit, pull-request, error, and idempotency history | before and after every version.push(...) effect |
viby.deployment_project_links | provider-neutral links between a chat and a connected deployment project | first completed deployment and subsequent project metadata refreshes |
viby.deployments | version-bound deployment identity, environment, latest status, URL, error, and idempotency history | before and after every version.deploy(...) effect |
viby.deployment_status_transitions | ordered provider-neutral deployment lifecycle observations | initial request, provider result, lookup, cancellation, and failure |
viby.deployment_artifacts | immutable prebuilt ZIP metadata and external artifact-store reference linked to one deployment and source version | successful sandbox preparation before a prebuilt provider effect |
viby.skill_snapshots | content-addressed resolved skill files | first use of each exact skill revision |
viby.generation_skills | ordered category/activation links | before the first model request |
viby.sandbox_leases | portable sandbox identity, source context, ports, expiration, and lifecycle state | sandbox open, reconnect, stop, or expiration |
viby.outbound_event_deliveries | delivery claims, attempts, retry schedule, errors, receipts, and dead letters | before and after each configured sink effect |
viby.schema_migrations | migration ledger | explicit CLI migration |
Generation and attempt rows also retain optional cost amount/currency columns. Migrations are explicit, transactional, and protected by a Postgres advisory lock. Application requests never run migrations implicitly. Published migration checksums, a historical-schema upgrade fixture, and frozen public API/export fixtures are enforced in CI.
Error contract
All SDK errors extend VibyError:
ConfigurationError: invalid config, prompt, identifier, or generated source shape;DatabaseNotReadyError:vibymigrations have not been applied;NotFoundError: missing or out-of-scope resource;SkillResolutionError: local or remote skill could not be safely resolved;SourceImportError: an application-provided source adapter failed without exposing its input;IntegrationAuthorizationError: a provider authorization request or callback failed;IntegrationConnectionRequiredError: the selected operation has no usable connection;ToolSourceAuthorizationError: a tool-source provider authorization request or callback failed;ToolSourceConnectionRequiredError: a selected durable tool source has no usable connection;IntegrationOperationError: a connected provider operation failed without copying credentials;OutboundEventSinkError: a signed sink transport failed without copying transport details;OutboundEventDeliveryError: delivery stopped and includes the exact safe resume cursor;OutboundEventSignatureError: an inbound signed envelope failed verification;GenerationError: the durable model attempt failed;GenerationCancelledError: the synchronous convenience API observed cancellation;GenerationStateError: retry, resume, cancel, or task resolution is invalid for the current state;GenerationTaskRequiredError: the synchronous convenience API encountered pending typed tasks.
Not in v1
The following names are intentionally not stubbed. Applications should not have to discover at runtime that a declared method is unavailable:
- in-place version mutation or tarball output;
- SDK-owned preview hosting, bundled deployment-provider adapters, or a bundled Bitbucket adapter.
See v0-core.md for the audited parity roadmap and persistence rules.
The normative Viby lifecycle, persistence, ownership, and provider-neutral behavior contract.