Blueprints
Explanation — what & why
A blueprint is an app model as configuration. One file declares everything a small Vectros app needs to exist — the record schemas, a least-privilege access profile, a service principal, optional seed data, and optional roles — with stable identifiers so applying it twice converges instead of duplicating.
Blueprints exist to collapse "stand up a new app's data layer" from a sequence of
API calls into a single reviewable artifact. You write the file once; the CLI's
bootstrap and blueprint-test commands turn it into provisioned
infrastructure and a working scoped key (see cli.md).
Two boundaries define what a blueprint is and is not:
- It describes format, not trust. The
@vectros-ai/blueprintspackage owns the format and its structural validation only. The security boundary — the scope gate that confines a blueprint to data-plane access — lives in the CLI binary. A blueprint is untrusted input; the binary is the trust boundary. - It is data-plane only. A blueprint can request scopes on records, schemas, search, documents, folders, and inference — nothing else. Control-plane scopes are hard-rejected when the CLI applies it.
This doc is the format reference the blueprint walkthroughs build on. For narrated, end-to-end builds, see the walkthroughs (getting-started, clinical-intake, coding-agent-memory, second-brain).
How-to
Scaffold, validate, and apply
The full authoring loop runs through the CLI:
vectros blueprint init my-app # scaffold ./my-app.blueprint.yaml
vectros blueprint validate ./my-app.blueprint.yaml
vectros blueprint plan ./my-app.blueprint.yaml
vectros bootstrap --blueprint ./my-app.blueprint.yaml
You can scaffold from a bundled exemplar with init --from <name>. See
cli.md for the lifecycle commands.
A minimal blueprint
Blueprints are authored as YAML or JSON. A minimal one declares a context, one schema, an access profile, and a service principal:
name: my-app
version: 1.0.0
description: A minimal example app.
contextId: my-app
contextName: My App
schemas:
- typeName: note
displayName: Note
indexMode: HYBRID # HYBRID | SEMANTIC | TEXT
fields:
- { fieldId: title, fieldType: string, required: true, searchable: true }
- { fieldId: body, fieldType: string, searchable: true }
- { fieldId: externalId, fieldType: string, required: true, description: Caller-stable id. }
lookupFields: [externalId]
accessProfile:
allowedActions: [records:r, records:c, records:u, search:r, schemas:r]
servicePrincipal:
externalId: my-app
displayName: My App
indexMode, the per-field validation/renderHints, and the schema-level
capabilities/active/ownership fields are all optional — the example stays
minimal on purpose. Note the deliberate absence of records:d in
allowedActions: least privilege is the authoring default.
Reference — what a blueprint can express
Top-level fields
| Field | Required | Meaning |
|---|---|---|
name | yes | Stable blueprint id — the --blueprint <name> selector and idempotency key. |
version | yes | Blueprint version string. |
description | yes | Human-readable description. |
contextId | yes | The app context the profile and key bind to. 3–31 chars; lowercase letter first, then lowercase letters/digits/dashes. |
contextName | no | Human-readable context name; defaults from name. |
schemas | no | The record/surface schemas to provision (see below). |
accessProfile | yes | The least-privilege scope the bootstrap mints for the blueprint's own key. |
servicePrincipal | yes | The service principal the key is bound to. |
seed | no | Deterministic seed records. |
roles | no | Reusable, multi-clause scope rules. |
identities | no | Declared principals (see the glue caveat below). |
inputs | no | Install-time variables (resolved with --set/--values). |
Schemas
Each schema entry declares a record type:
| Field | Meaning |
|---|---|
typeName | The record type name. |
displayName | Human-readable label. |
indexMode | HYBRID (keyword + semantic), SEMANTIC, or TEXT. |
fields[] | Field definitions (see below). |
lookupFields[] | Fields to index for exact/range/prefix lookup. Bare field name, or { fieldName, unique }. Up to 10. |
allowedSurfaces[] | Which typed surfaces may bind the schema: record, document, user, org, client. Defaults to [record]. |
capabilities.auditHistory | Whether writes emit version history (platform default on). |
active | Whether the schema accepts new records. |
userId / orgId / clientId | Schema-level ownership defaults. |
Each field definition supports:
| Field key | Meaning |
|---|---|
fieldId | Field name. |
fieldType | Field type (e.g. string, array, reference). |
required / searchable / filterable | Per-field flags. |
enumValues[] | Allowed values for an enumerated field. |
validation | Validation rules: required, minLength/maxLength, min/max, pattern, email/url/phone, step/multipleOf, minItems/maxItems. |
renderHints | UI hints: label, widget (text/textarea/select/date/checkbox), order, section, helpText. |
sensitive | Marks the field as sensitive: it is redacted/destroyed at write time, blind-indexed for lookups, excluded from the search index, and masked on read unless a token carries the reveal scope. |
targetTypeName / targetField / cardinality | On a reference field, the typed link target (cardinality = one/many). Reference fields are declared in the format; write-time existence/type enforcement is a separate platform capability. |
Access profile
accessProfile:
allowedActions: [records:cru, search:r, schemas:r]
dataScope:
orgId: [org_acme, null]
allowedActions— the scopes the minted key carries. Author explicitresource:opforms; coarse verbs andresource:*grant nothing at runtime. Subject to the data-plane scope gate.dataScope— optional ownership binding (userId/orgId/clientId→ value lists). Anullelement is the documented null sentinel: it additively grants access to tenant-level (owner-less) records in addition to the listed owners. Omittingnullrestricts the key to the listed owners only — so it will not see tenant-level/seed records.
Service principal, seed, roles
servicePrincipal:
externalId: my-app
displayName: My App
seed:
- typeName: note
externalId: seed-welcome
fields: { title: Welcome, body: Created by the bootstrap loader., externalId: seed-welcome }
roles:
editor:
- allowedActions: [records:cru, search:r]
dataScope:
userId: ['${{ self.userId }}']
servicePrincipal— the principal the minted key binds to.seed— deterministic records (keyed byexternalIdfor idempotency).roles— a map of role id → ordered clauses. Each clause is an(allowedActions, dataScope)pair, evaluated per-clause at runtime. Roles are identity-agnostic and reusable; bind them to principals withvectros access grant --role <id>. Every role clause is also gated to the data plane, so a role cannot be a control-plane back door aroundaccessProfile.${{ self.* }}placeholders are a runtime per-principal sentinel, resolved per-request by the platform. They are only valid inside a role clause'sdataScope; using one anywhere else is an authoring error.
Inputs (install-time variables)
A blueprint may declare an inputs: block and reference values with
${{ inputs.<name> }} (plus the built-ins ${{ vectros.context }} /
${{ vectros.suffix }}). Supply values at validate/plan/apply time with --set
or --values. Inputs apply to file blueprints only.
Bundled blueprints
Four blueprints ship with the library:
| Name | What it provisions |
|---|---|
task-management | Structured task tracking, shareable across sessions, agents, and users. The authoring exemplar. |
coding-agent-memory | A persistent memory store for a coding agent. |
second-brain | A personal knowledge base — capture notes, ideas, and links, then ask them anything. |
clinical-intake | A clinical intake data model (synthetic/illustrative). |
List them with vectros blueprint list; apply one with
vectros bootstrap --blueprint <name>.
Notes & limits — honest glue caveats
- A bridge token is a human prerequisite. Applying a blueprint with
bootstraprequires a bridge token from the developer portal — there is no fully unattended path that mints one for you. identities:blocks are declared but not yet wired. The format accepts anidentitiesblock (principals referenced via${{ identities.<name> }}) and validates it, but the apply pass that ensures those principals exist is not yet active. The CLI fails closed on a blueprint that uses identities the loader cannot yet resolve, rather than applying it partially.referencefields are format-level today. A blueprint can declare a typed reference between record types; the platform's write-time existence/type enforcement for references is a separate capability.- No control-plane scopes. A blueprint cannot request keys, profiles, app contexts, users, billing, admin, clients, or orgs scopes — the CLI scope gate hard-rejects them, mints nothing, and exits non-zero.
- The bundled
task-managementblueprint references a richertask-management-proexemplar in its comments; that exemplar is illustrative and is not a shipped blueprint.
Where to go next
- cli.md —
init,validate,plan,bootstrap, andblueprint-testoperate on these files. - mcp.md — the scoped key a blueprint mints is what an MCP agent runs on.
- sdk.md — the operations a blueprint's schemas and scopes govern.
- The blueprint walkthroughs — narrated, end-to-end builds on top of this format.