Provider Scenes

A provider scene is a provider authored as data. Instead of writing a TypeScript module, you create a scene that contains a handful of typed rows, and Daslab loads it as a provider at boot, indistinguishable from a hand-coded one.

Providers become scenes you can apply, version, and fork.

A provider is mostly data

The provider model from Providers is fine when there are ten of them. At fifty, the boilerplate adds up. At five hundred it stops being a code-shaped problem and becomes a content-shaped one.

A provider is mostly data: identity, asset type shapes, tool schemas, an execution strategy. The actual code part is usually one or two functions. So a scene can express all the data parts, with a small standardised execution path for the code parts.

Three asset types describe a provider

All three are ordinary typed rows:

  • daslab/provider_def: top-level identity. One per provider scene. Holds id, name, icon, color, logo, visibility, connection text. The presence of this asset is what makes the scene a provider.
  • daslab/asset_type_def: a typed shape the provider declares. Mark exactly one of them isAccountType: true, the credential-bearing account asset type (or a no-auth marker for credential-free providers).
  • daslab/tool_def: a typed callable. Has a name, description, JSON Schema for inputs, and an impl describing how to run it.

Each row's fields JSONB holds the actual config; there are no schema changes needed to add a new provider.

Tool implementation

Two impl kinds:

  • http_call: declarative HTTP recipe (method, url, query, headers, body, output.path). Template values pull from the tool's input and the account's credential blob. A tiny interpreter turns the recipe into a fetch() call. No code to write or run.
  • code: JavaScript source that reads from ctx.input / ctx.credential / ctx.fetch and returns a value. The source lives in a real .js file alongside the blueprint and is inlined at apply time via $file, and runs isolated with hard timeouts.

A minimal example

A scene blueprint with one provider, one account asset type, and two code tools, which is the actual blueprint we use to ship Techmeme:

{
  "id": "scn_kernel_techmeme",
  "parentId": "scn_daslab_assets",
  "name": "Techmeme Provider",
  "icon": "newspaper",
  "tint": "#2D6A2E",
  "assets": [
    {
      "id": "provider", "type": "daslab/provider_def", "name": "Techmeme",
      "fields": {
        "providerId": "techmeme",
        "visibility": "public",
        "requiresConnection": true
      }
    },
    {
      "id": "account", "type": "daslab/asset_type_def", "name": "Techmeme Account",
      "fields": {
        "typeId": "account",
        "isAccountType": true,
        "auth": { "type": "none" },
        "cardinality": "singleton"
      }
    },
    {
      "id": "tool_feed", "type": "daslab/tool_def", "name": "techmeme_feed",
      "fields": {
        "toolName": "techmeme_feed",
        "description": "Get today's top tech news stories from Techmeme.",
        "readOnly": true,
        "inputSchema": {
          "type": "object",
          "properties": {
            "date":  { "type": "string", "description": "YYYY-MM-DD; defaults to today." },
            "limit": { "type": "number", "description": "Max stories (default 20)." }
          }
        },
        "impl": {
          "kind": "code",
          "source": { "$file": "./techmeme/feed.js" },
          "timeoutMs": 12000
        }
      }
    }
  ]
}

./techmeme/feed.js is plain JS: no escaping, no JSON quoting, linted and diffed like any other file.

const API_BASE = "https://techmemer.usemodule.com";
const date = ctx.input.date;
const limit = (ctx.input.limit && Number(ctx.input.limit)) || 20;
const url = date ? API_BASE + "/?date=" + encodeURIComponent(date) : API_BASE + "/";
const res = await ctx.fetch(url);
if (!res.ok) throw new Error("Techmeme API error: " + res.status);
const data = await res.json();
// …reshape and return as string

Apply and load

The flow:

  1. Author the blueprint and source files.
  2. Apply it: daslab blueprint apply ./scenes/techmeme.blueprint.json. The applier resolves $file refs, writes the asset rows into Postgres under your chosen parent scene.
  3. On server boot, the registry scans for scenes containing a daslab/provider_def asset and registers each as a provider.

From there on, the provider is indistinguishable from a TS-coded one: tools show up in the agent loop, the account asset appears in the iOS add-asset sheet, tool calls dispatch normally.

Scoping

A provider scene's tools become available in a consumer scene only when that consumer scene has the provider's account asset linked. This matches how all other providers in Daslab work: the provider lives where you add it. Set requiresConnection: true on the provider_def to get this behaviour; keylessDefault: true (auto-inferred for auth: "none") lets the user materialize a virtual account by tapping Add in iOS.

This avoids the trap where authless providers bolt their tools onto every chat's prompt globally, which bloats the prompt and hurts time-to-first-token across the workspace.

Where they live

Provider scenes can live anywhere in your scene tree. Daslab maintains a built-in world called Daslab Assets that holds the platform-published provider scenes (currently Techmeme Provider and npm Provider). Other scenes, yours or your team's, can apply blueprints under that world or under any other parent.

The world boundary is the visibility boundary: who can see a scene, who can adopt its assets. Same rules as everywhere else in Daslab.

What's the same as a code-defined provider

  • Same registration as any other provider.
  • Same auth flows (api_key, none).
  • Same iOS UX (add asset, browse, tool calls).
  • Same MCP exposure, same OTel tracing, same metrics.

What's different

  • Source of truth is assets rows, not code.
  • Updates apply, they don't hot-reload. Changes to a $file-referenced source take effect on the next apply.
  • One account asset type per scene. A single gating account type; child types via parent: aren't supported.
  • No OAuth. Provider scenes carry api_key and none auth only.

Two production providers run this way

Two providers run in production today entirely from DB blueprints: Techmeme (authless, two code tools) and npm (api_key credential carrier, zero tools). Both replaced their previous hand-coded TS modules with no behavioural difference to users.

What's next

  • Providers: the code-defined model, still the majority.
  • Blueprints: scenes as templates; the same applier handles provider scenes and regular scenes.
  • Cells: the typed-row substrate everything sits on top of.

Updated 2026-08-03