Blueprints

A blueprint is a scene authored as data. One JSON file (or a directory with sidecar HTML and markdown) describes everything that makes a scene a scene: identity, assets, recurring jobs, evaluable tasks, who has access. Apply it and the scene appears. Export a live scene and you get the same shape back, ready to fork.

Blueprints are the substrate for provider scenes, the catalog at /blueprints, and any scene you want to version, share, or evolve outside the iOS app. A blueprint can even carry credentials — as references into a vault, never as values — so applying one can provision connected accounts with no OAuth flow and no human in the loop.

What's in a blueprint

{
  "version": 1,

  "id":   "scn_my_world",
  "slug": "my-world",
  "name": "My World",
  "icon": "globe",
  "tint": "#4C8BF5",

  "settings": {
    "description": "Built from a blueprint."
  },

  "assets":       [/* scene-owned content */],
  "linkedAssets": [/* foreign rows pinned in place */],
  "schedules":    [/* recurring agent tasks */],
  "tasks":        [/* evaluable scenarios */],
  "members":      [{ "email": "you@example.com", "role": "owner" }],
  "secrets":      {/* credential refs — see below */},
  "scenes":       [/* child scenes, recursive */]
}

Two things are worth understanding deeply.

Identity sits at the top, settings sit in settings. Identity (id, slug, name, icon, tint) is load-bearing for cross-references and the visual header. Configurable state (description, feature flags, welcome suggestions, notification preferences) lives in settings — same as the iOS settings sheet. The applier only writes columns the blueprint declares, so anything you set in iOS that the blueprint doesn't carry stays intact. assets[] versus linkedAssets[] is the load-bearing split. Scene-owned content goes in assets[] — the blueprint creates these rows and owns their contents. Foreign rows go in linkedAssets[] — account chips, GitHub repos, Slack channels, SAP entities. Their DB ids stay stable across applies; the blueprint pins them, can optionally edit their content in place, but never forks them into duplicate rows.

Apply, export, merge

# Apply a blueprint
daslab blueprint apply ./my-world.blueprint.json

# Export a live scene as a blueprint
daslab blueprint export <sceneId> --children --extract-html --out=./my-world.blueprint.json

# Apply with conflict-detection
daslab blueprint apply ./my-world.blueprint.json --strict

Apply is idempotent — same blueprint, same input, same output. Deterministic IDs from (sceneId, type, externalId) mean re-running upserts the same rows.

Export captures the scene's current head commit and tree hash in meta.exported_from. The next apply reads those, compares to the scene's current state, and runs a real three-way merge per asset: clean fast-forward, behind (live moved ahead, your apply would clobber), diverged (history rewritten), or conflict (both sides changed the same row differently). --strict refuses on conflict; default warns and proceeds.

This is git semantics for scenes. Branch a scene, hack on it in your editor, merge back safely.

Credentials are references, never values

A blueprint can provision credentialed accounts headlessly. The credential is never a plaintext field — it's a reference in the secrets block:

"assets": [
  { "id": "brave1", "type": "brave/account", "name": "Brave", "fields": {} }
],
"secrets": {
  "brave/account:brave1": { "from": "kern:BRAVE_ACCT" }
}

At apply time the reference resolves — kern:NAME from an age-encrypted vault, env:NAME from the server environment — and the credential fields merge into the account asset. Export runs the same split in reverse: secret fields (api_key, access_token, client_secret, …) are sealed out of the JSON, leaving only refs. A blueprint with credentials is safe to commit, share, and fork by construction.

Providers with a single shared key can skip the blueprint entirely: an envFallback declaration on the provider exposes a virtual default account straight from the server environment. Use a secrets ref when you want a real, pinned account — multiple accounts, or credentials that aren't one env var.

Push a blueprint without touching git

daslab blueprint push scene.blueprint.json

# supply a credential that lives only on your machine:
daslab blueprint push scene.blueprint.json \
  --secret "brave/account:brave1:api_key=sk-…"

# print the request, send nothing:
daslab blueprint push scene.blueprint.json --dry-run

The CLI sends the blueprint — plus any --secret overlays — over TLS to the apply endpoint under your token; the server applies it and adds you as owner of each root scene. A credential reaches the server one of two ways: already sealed in the server's vault (refs resolve server-side), or inline via --secret — encrypted in transit, never in git.

Edit-in-place loop

Most scenes mix assets you author (notes, dashboards, view components) with rows that live outside the blueprint (account chips, the underlying SAP/Slack/GitHub entities). The format handles both cleanly.

# 1. Branch
daslab blueprint export <sceneId> --extract-html --out=./mine.blueprint.json

# 2. Edit
$EDITOR ./mine.blueprint.json
$EDITOR ./mine/<asset>.html

# 3. Merge back, refusing to clobber drifted edits
daslab blueprint apply ./mine.blueprint.json --strict

The big content fields — cockpit HTML, markdown notes, code snippets — extract into adjacent files via $file references on --extract-html. The blueprint stays a small JSON file; the HTML becomes a real HTML file you can edit with syntax highlighting. Re-apply inlines them transparently.

For provider-managed rows (a SAP product, a GitHub repo, a Slack channel), the export puts them in linkedAssets[] with the row's current name and fields. Edit those fields in the blueprint, re-apply, and the existing row's content is updated — the DB id stays the same. No fork, no duplicate.

One file or a directory — both apply the same

A blueprint is either a single file or a directory. Both work identically with the applier.

  • Single file — everything inline. Fits in a gist. Easy to share via paste.
  • Directory<name>.blueprint.json plus a sidecar folder containing referenced HTML, markdown, code. Package as a tarball or a git submodule. Helm-chart shape.

Use either as your content size dictates — the applier doesn't care.

What a blueprint is not

  • Not a configuration management tool. It mutates the database, but only for the scene tree it describes. It doesn't manage server state, user accounts outside the scene, or anything cross-scene.
  • Not a build artifact. Source of truth is the JSON file; the applier writes to the DB, but the JSON is the spec.
  • Not a backup format. It captures the structure of a scene plus its scene-owned content. Job histories, conversation traces, scene annotations, and runtime state are not included — those live in their own systems with their own retention rules.
  • Not a code substitute. Blueprints declare; they don't compute. Schedules and tasks reference prompts the agent reads at runtime, but the format itself has no functions, loops, or conditionals.

Where blueprints come from

We ship a small set of high-quality starting points — see /blueprints. Provider scenes are blueprints too: a provider's identity, asset types, and tool definitions packaged as a scene that the registry loads at boot.

Anything you build in the iOS app is exportable as a blueprint. Anything someone else built is forkable as a blueprint.

Reference

The full v1 spec, every field, every semantic — scenecast/docs/scene-blueprint.md.

What's next

Updated 2026-07-18