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:
- contract language and bounds
- command and invariant cookbook
- application source schema
- symbolic inspection
- RiffQL v1
- authoring diagnostics
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.