Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Agent application quickstart

The normal RiffDB application path is contract and RiffQL text compiled into generated application operations. The kernel gRPC protocol is not an application-development API.

Before replacing the sample contract, read the repository-local AUTHORING.md and complete its transaction-route worksheet: list each command’s complete atomic mutation set, assign that set one aggregate root and route parameter, then list the route for every page. Define entity keys and indexes only after those decisions. This prevents the most expensive authoring mistake—modeling records as independent roots and later discovering that one command must change them atomically.

$ riffdb new order-desk
created application `order-desk` at order-desk
next: read order-desk/AUTHORING.md, replace the sample domain, then run `riffdb application check --source-only`

$ cd order-desk
$ riffdb dev --seed --run
generated exact application bindings
application boundary check passed
riffdb-dev-seed-v1  1  application-manifest
riffdb-dev-context-v1 database=default role=OrderDeskApplication
riffdb-dev-ready-v1 http://127.0.0.1:... ... OrderDeskApplication

The context line names values that application clients need but that must not be guessed from the role symbol. Pass database=default as the generated client’s database alias. The readiness line remains positional for existing launch supervisors; the context line is labelled for humans and agents.

riffdb new compiles before it writes and refuses to overwrite an existing file or non-empty directory. It accepts either a new child directory or an already-created empty directory:

mkdir my-empty-repository
cd my-empty-repository
riffdb new order-desk --directory .

The destination must be a regular, writable, empty directory. A destination symlink, file, populated directory, or missing parent fails before publication. The repository is compiled in a bounded sibling staging directory. A new child is published as one directory generation. An existing directory retains its inode; known top-level entries are moved in and the compiler lock is published last. Interrupted staging is therefore never treated as a valid application lock, and the retained sibling staging directory is recoverable evidence rather than silently discarded.

The scaffold creates:

riffdb.application.json
riffdb.application.lock.json
riffdb/
  contract.riff
  queries/item_page.riffq
  seed/01-CreateItem.jsonl
generated/
  riffdb.application.exact.json
  rust/client.rs
  go/client.go
  typescript/client.ts
  python/client.py
  mcp/tools.json
src/

The Rust scaffold additionally includes Cargo.toml and an exact Cargo.lock; its first cargo check --locked is reproducible without a separate resolver step. TypeScript and Python scaffolds likewise include their native exact locks.

riffdb.application.json is author-owned symbolic source. It contains names and paths, never compiler hashes, numeric IDs, masks, plans, or encoded keys. riffdb.application.lock.json is compiler-owned and pins the exact contract, query plans, role authority definitions, compiler formats, and generated artifact hashes. The compatible V1 exact manifest under generated/ is also compiler-owned.

Commands that receive a manifest or exact-manifest path discover its application workspace from the nearest ancestor containing the configured contract source. An unrelated outer repository or sibling application cannot make a nested application ambiguous. The nearest workspace still has to pass the ordinary exact identity checks; proximity never substitutes authority.

The explicit authoring operations are:

riffdb application check --source-only
riffdb application check --write
riffdb application lock --write
riffdb application check
riffdb application generate --locked

check --source-only performs no writes and compiles the complete author-owned application and roles without comparing an intentionally stale lock during an edit cycle. Exact check performs no writes and verifies source, lock, and every generated artifact; it cannot report a false green over drift. check --write and lock --write are the only operations that accept new derived identities; both compile symbolic sources, stage generated files, and publish the lock last in one offline step without starting a server. generate --locked reproduces bindings only when the current symbolic sources compile to the exact reviewed lock. A stale, substituted, interrupted, or partially generated application therefore fails before role binding, authorization, deployment, or application execution.

Every successful check also prints the configured seed-input count and the exact development command. Use riffdb dev --seed --run only with one or more ordered seed_inputs; use riffdb dev --run for an intentionally seedless application. After a breaking local contract edit, pass riffdb dev --reset to discard local development state before redeploying. The seeded form fails before startup when the list is empty and names up to eight unreferenced JSONL files as an actionable correction.

The source-tree development runner uses Cargo’s effective target directory for all RiffDB binaries. A workspace Cargo target-dir setting or an inherited CARGO_TARGET_DIR therefore applies consistently to riffdb, riffdbd, the driver host, and development-only benchmark binaries; no unset or repository- local target-directory workaround is required.

For a Python application, the source-tree runner also builds the current riffdb_application wheel with Maturin and adds it as an ephemeral uv run overlay. The application’s exact lock remains unchanged, while the disposable loopback cannot mix a current server and generated client with an older registry-pinned native runtime. This source workflow requires python3, uv, and the maturin module; installed release runners continue to use the matching wheel shipped with the release.

Generated query calls pin the compiler-owned plan hash as well as the contract and module. Successful Rust, Go, TypeScript, and Python results expose the verified contract and operation identity. Native transports verify the server tuple directly; driver-host transports first bind the exact manifest and catalog handshake, then return the matching compiler-owned result identity. A missing or different identity rejects the response; application code never assembles an identity from numeric IDs or unreviewed server state.

For a finite order family, that public operation identity is the complete family hash pinned by the generated catalog. Continuation state additionally binds the selected immutable order member, so changing the enum choice rejects the cursor without making successful responses appear to be a different named operation.

Complete public references:

Every new scaffold also includes AUTHORING.md, a local compact guide to the safe aggregate, relationship, invariant, indexed-page, dependent-key batch, and generated read-after-write patterns. Start there while replacing the sample; use the complete references when a diagnostic points to a narrower rule.

The sealed kit also provides riffdb-builder-mcp --workspace <path>. It is a credential-less local MCP server with reference resources and only six authoring tools: describe, check, diagnostic explanation, lock preview, explicit lock write, and exact generation. It cannot deploy, bind a role, execute a query or command, read storage, or obtain a credential. The mutating lock/generation tools remain explicit and are marked accordingly in MCP.

riffdb dev starts one local server, waits for explicit readiness, deploys the exact contract and module, compiles and binds the symbolic application role, refreshes the lock only for its product-owned ephemeral database, regenerates all bindings, checks the application boundary, executes seed JSONL through ordinary idempotent commands, and shuts down its child on failure or interrupt. Credentials live only in a mode-protected temporary directory. If role compilation or binding fails, the installed runner prints the public role bind error before cleaning up its temporary service state. A failed development run must not collapse that diagnostic into a silent non-zero exit.

With --run, the same product-owned workflow starts the repository’s generated application after readiness. A repository must contain exactly one supported runner: Cargo.toml for Rust, go.mod for Go, package.json for TypeScript, or pyproject.toml for Python. Every generated SDK exposes a zero-ceremony environment connection and a causal read-after-write handle (GeneratedApplicationClient::from_env().await? and client.causal(&write_result) in Rust, connectGeneratedApplicationFromEnv() and client.causal(writeResult) in TypeScript, GeneratedApplicationClient.from_env() / AsyncGeneratedApplicationClient.from_env() and client.causal(write_result) in Python, and generated.ConnectFromEnv(ctx) and client.Causal(writeResult) in Go) alongside the explicit constructors. Rust and Python receive the protected application endpoint, credential path, and database alias owned by their first-party runtime. They receive the values both as the existing three positional arguments and as RIFFDB_ENDPOINT, RIFFDB_CREDENTIAL_FILE, and RIFFDB_DATABASE; observing both forms with different values is an error. Go and TypeScript receive only a private driver-host socket plus compiler-owned handshake identity (RIFFDB_DRIVER_SOCKET and RIFFDB_DRIVER_HANDSHAKE or the positional arguments). The Go and TypeScript positional ABI is exactly:

SOCKET MANIFEST_HASH CATALOG_HASH DATABASE ROLE ROLE_HASH REMOTE_HASH LINEAGE VERSION BUNDLE_HASH

Application code must use ConnectFromEnv / connectGeneratedApplicationFromEnv or copy the closed decoding in the generated starter rather than infer another ordering. The application does not need to parse the database-readiness line or keep a separate bootstrap process alive. Rust, Go, and Python one-shot runners exit normally. A TypeScript web runner stays in the foreground. Leave it running after the application prints riffdb-app-ready-v1<TAB>PORT, then issue the application HTTP request from another terminal; the generated starter is exercised with curl http://127.0.0.1:PORT/item. The foreground command has not completed merely because the ready marker appeared. Stopping it shuts down the scoped driver and development daemon. Application stdout is caller-owned output and is streamed directly, not copied into RiffDB diagnostics or logs.

riffdb dev supports roles declaring either "tenant_scope": "global" or "tenant_scope": "tenant". When the selected role declares "tenant_scope": "tenant", riffdb dev binds the local development credential to the tenant supplied by --tenant <tenant_id> or RIFFDB_TENANT (defaulting to "dev-tenant" when omitted). Passing --tenant for a global role is rejected with an explicit diagnostic so role scope remains unambiguous.

A release installation places the reviewed riffdb-dev workflow beside the riffdb and riffdbd binaries. That installed workflow takes precedence over an application-local script, so opening an unfamiliar repository cannot replace the development control plane. Source checkouts retain the application-local fallback for first-party development. The release workflow uses only installed binaries and public packages; it never rebuilds or reads RiffDB implementation source.

The application boundary

Handwritten application code may use the stable application facade and compiler-generated operations. It may not:

  • depend on kernel, storage, service, protobuf, gRPC, Tonic, or Prost packages;
  • construct raw entity/index requests, field masks, numeric schema IDs, or encoded keys;
  • pack or decode named-query records by hand;
  • create handwritten RiffDB transport wrappers;
  • place handwritten code under generated/.

Run the mandatory check with:

scripts/check-application-boundary .

Generated bindings contain a compiler marker and are the only files allowed to contain protocol adaptation. That exception is narrow: application authors cannot obtain a trusted marker by moving handwritten code into the generated tree. The check examines application manifests and source, while ignoring the standard package/build output trees target/, node_modules/, and .venv/. Those trees may contain generated transport internals supplied by reviewed driver packages; handwritten application source remains checked regardless of which supported language it uses.

Administrative and kernel credentials remain separate from the generated application role. The normal scaffold has no kernel dependency, feature, role, credential, MCP tool, or documentation path. Kernel access is an explicit source-repository operational workflow and must not reuse an application credential.

Whole-query bounds

Plain Limit means a caller may request any positive page size through 499. The compiler therefore charges a take $limit binding as 499 rows, even when the parameter has a smaller default. Use Limit<100> (or another reviewed maximum from 1 through 65,534) when callers need a runtime page size but the role should authorize a narrower bound. Index-scan charges from every binding in one named query are added before a role is accepted.

For a page with several independently bounded collections, use reviewed fixed limits or bounded runtime limits whose aggregate index-scan maximum fits the role:

many comments from Comment
    where site_id == $site_id && post_id == post.post_id
    order by created_at asc, comment_id asc
    take 100 after $comments_after

many post_tags from PostTag
    where site_id == $site_id && post_id == post.post_id
    order by tag_id asc
    take 50

riffdb application check performs this role derivation before writing a lock or starting a server. RDB-AR007 names the role and query when its aggregate worst-case scan exceeds the stable bound. RiffDB never replaces this rejection with a hidden scan, partial result, or larger ambient grant.

Product safety rule

Application authors express only symbolic intent. The compiler owns every derived identity, access plan, schema, capability requirement, visibility set, cost bound, cursor codec, and transport adaptation.

If an application can hand-author or override one of those derived values, the surface is a kernel or administrative API, not an application API. Normal templates, generated clients, CLI commands, MCP tools, and documentation must not expose it. RiffDB rejects the whole operation on identity, authorization, cardinality, boundedness, locality, or generated-artifact drift; it does not silently omit fields, choose an ambient version, accept a broader role, or continue with partially updated output.