The *.streamline.json format
One file, one streamline, owned by one person: the device and private parts in one flat object. No manifest, no signature, no write API: the person imports it in the app (Files, AirDrop, or a streamline://import?data=<base64url> link). Model calls go straight to the provider with the person's own key from the app's vault. The file never contains a key, token or sign-in; the app refuses one that looks like it does.
Contents
- Example
- Fields
- Tool policy
- Templates and renderers
- Checklist
Example
{
"schema": "streamline.personal/1",
"id": "orders_desk",
"name": "Orders",
"description": "Look up and update my shop's orders.",
"icon": "shippingbox.fill",
"color": { "light": "#1D5C8C", "dark": "#5AA9E6" },
"instructions": "You help one person handle one order per chat. Use the tools. Changes wait for their tap. A result with ok:false is the system's answer: relay it plainly.",
"model": { "provider": "anthropic", "id": "claude-sonnet-5-5", "effort": "medium", "maxTokens": 32000 },
"mcp": [ { "id": "shop", "name": "Shop", "url": "https://mcp.shop.example/mcp", "auth": "oauth",
"tools": { "readOnly": ["order_get", "*_list", "*_search"], "confirm": ["refund_*", "order_cancel"],
"confirmWhenArgs": ["settings"], "hidden": ["admin_*"], "direct": ["order_get"] },
"renderers": { "order_get": "order" } } ],
"templates": { "order": { "root": { "type": "stack", "spacing": 6, "padding": 14, "children": [
{ "type": "text", "text": "Order {{ $.number }}", "style": "headline" },
{ "type": "keyValue", "rows": [ { "label": "Total", "value": "{{ $.total | money }}" },
{ "label": "Status", "value": "{{ $.status | title }}" } ] } ] } } },
"imageHosts": ["img.shop.example"],
"welcome": "Each chat is one order. Give me an order number or a customer.",
"placeholder": "Order number, customer, refund…",
"quickPhrases": [ { "label": "Today's orders", "text": "Show today's orders", "send": true } ]
}
Fields
| Field | Req | Notes |
|---|---|---|
schema | no | streamline.personal/1. |
id | no | [a-z0-9_]{2,32}; made from the name when absent. Importing an existing id replaces that streamline. |
name | yes | |
description, icon, color, theme | no | As in streamlines.md (theme: a preset id, two-sided, or { base?, accent? }; an own theme inline is not built). |
logo | no | data:image/png;base64,… (or jpeg), ≤ 256 KB decoded, square: inline so the file stays one file. Drawn where the icon would be; the icon stays the fallback. |
instructions | no | The system prompt. No business rules as rules, no secrets. |
briefs | no | Personal briefs to include first, in order (personal/briefs/<id>.md beside the file; briefs.md). |
model | no | provider anthropic; id (default claude-sonnet-5-5; Haiku 5.5 and Opus 5.5 offered); effort; maxTokens (32000). The key is vault:<id> if the person set one, else vault:default; never write a key. |
mcp[] | no | id, name, url (https; http://localhost only in debug builds), auth oauth | none (built), password | api-key (designed), tools (policy), renderers, reauthOn. |
mcp[].reauthOn | no | Business error codes that mean "the session behind this token ended", for servers that answer them with HTTP 200 instead of 401. The app discards the token, asks the person to sign in again and retries. oauth only. |
templates | no | { id: { "root": node } }, ids [A-Za-z0-9_.-]{1,64}; the same language and limits (templates.md). A failing template is left out; its tools get the automatic card. |
imageHosts | no | Hosts template images may load from. |
attachments | no | The paperclip, the same object as an organization's (streamlines.md, Attachments). The files go from the device straight to the model with the person's key; chatMaxBytes (default 16 MB) keeps a stream within one request. |
scan | no | The scan button, the same object as an organization's (streamlines.md, Attachments and scanning). A fast-path action.tool must be in a server's tools.direct, and not in tools.confirm when modes allows continuous or sweep scanning. |
archive | no | anytime (the default for a personal streamline), whenDone or never. |
welcome | no | Local bubbles; paragraphs split on a blank line. |
placeholder | no | Composer hint. |
quickPhrases[] | no | label, text, send (default true). The first four are also the starter chips. |
composer.suggestions[] | no | Chips that follow the stream's state after the first message, the same rules as an organization's (streamlines.md, Suggestions). A personal file has no overview, so rules read last.tool / last.result.*, chat.*, stream.*, user.*, tenant.* (overview.* is suggestions.noOverview); a tool chip's tool must be in a server's tools.direct. Example: { "when": { "path": "last.tool", "eq": "invoices_list" }, "label": "Open the first", "text": "Open invoice {{ last.result.invoices[0].id }}", "send": true }. |
composer.modelSuggestions | no | true lets the model end each reply with up to three things the person might say next, shown as chips only when no rule holds. Off by default; about 90 tokens a turn on the person's key; the chips are words only and never reach the transcript. Personal files only. |
Tool policy
Per server, all lists optional, * globs: readOnly (run untapped), confirm (always tap), confirmWhenArgs (tap only when called with any argument), hidden (never offered, refused if called), direct (a card button may call it). Precedence: hidden > confirm > confirmWhenArgs > readOnly > annotations > a tap. See tool-policy.md.
Templates and renderers
renderers maps a tool to a template id in the same file. The card binds $ to structuredContent, or to data for an {ok, data} result. Draft templates from a real result (run the read tool once with the person signed in, or ask for a sample with invented data), never from a guessed shape.
Checklist
- [ ]
nameset;idmatches[a-z0-9_]{2,32} - [ ] Every server inspected; every tool settled as readOnly, confirm, confirmWhenArgs or hidden
- [ ] Writes confirm-gated; admin and bulk tools hidden
- [ ] No key, token, password or personal data anywhere in the file
- [ ] Every renderer names a template in the file; templates checked
- [ ]
https://URLs (localhost only for a debug test) - [ ] The checker is clean