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.
The point: providers stop being source code only we can write and start being scenes you can apply, version, fork, and (eventually) publish.
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 — the order of magnitude we'd land on if every user could publish their own — it stops being a code-shaped problem and starts being 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 we carved a kernel that lets a scene express all the data parts, plus 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 themisAccountType: true— that's 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 animpldescribing 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 for now:
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 afetch()call. No code to write or run.code— JavaScript source that reads fromctx.input/ctx.credential/ctx.fetchand returns a value. The source lives in a real.jsfile 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, two code tools — 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:
- Author the blueprint + source files.
- Apply it:
daslab blueprint apply ./scenes/techmeme.blueprint.json. The applier resolves$filerefs, writes the asset rows into Postgres under your chosen parent scene. - On server boot, the registry scans for scenes containing a
daslab/provider_defasset 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 — 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 — bloat that 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, your team's, public-marketplace ones — 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
assetsrows, 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_keyandnoneauth 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-05-15