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/blueprints package 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

FieldRequiredMeaning
nameyesStable blueprint id — the --blueprint <name> selector and idempotency key.
versionyesBlueprint version string.
descriptionyesHuman-readable description.
contextIdyesThe app context the profile and key bind to. 3–31 chars; lowercase letter first, then lowercase letters/digits/dashes.
contextNamenoHuman-readable context name; defaults from name.
schemasnoThe record/surface schemas to provision (see below).
accessProfileyesThe least-privilege scope the bootstrap mints for the blueprint's own key.
servicePrincipalyesThe service principal the key is bound to.
seednoDeterministic seed records.
rolesnoReusable, multi-clause scope rules.
identitiesnoDeclared principals (see the glue caveat below).
inputsnoInstall-time variables (resolved with --set/--values).

Schemas

Each schema entry declares a record type:

FieldMeaning
typeNameThe record type name.
displayNameHuman-readable label.
indexModeHYBRID (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.auditHistoryWhether writes emit version history (platform default on).
activeWhether the schema accepts new records.
userId / orgId / clientIdSchema-level ownership defaults.

Each field definition supports:

Field keyMeaning
fieldIdField name.
fieldTypeField type (e.g. string, array, reference).
required / searchable / filterablePer-field flags.
enumValues[]Allowed values for an enumerated field.
validationValidation rules: required, minLength/maxLength, min/max, pattern, email/url/phone, step/multipleOf, minItems/maxItems.
renderHintsUI hints: label, widget (text/textarea/select/date/checkbox), order, section, helpText.
sensitiveMarks 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 / cardinalityOn 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 explicit resource:op forms; coarse verbs and resource:* grant nothing at runtime. Subject to the data-plane scope gate.
  • dataScope — optional ownership binding (userId/orgId/clientId → value lists). A null element is the documented null sentinel: it additively grants access to tenant-level (owner-less) records in addition to the listed owners. Omitting null restricts 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 by externalId for 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 with vectros access grant --role <id>. Every role clause is also gated to the data plane, so a role cannot be a control-plane back door around accessProfile.
  • ${{ self.* }} placeholders are a runtime per-principal sentinel, resolved per-request by the platform. They are only valid inside a role clause's dataScope; 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:

NameWhat it provisions
task-managementStructured task tracking, shareable across sessions, agents, and users. The authoring exemplar.
coding-agent-memoryA persistent memory store for a coding agent.
second-brainA personal knowledge base — capture notes, ideas, and links, then ask them anything.
clinical-intakeA 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 bootstrap requires 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 an identities block (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.
  • reference fields 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-management blueprint references a richer task-management-pro exemplar in its comments; that exemplar is illustrative and is not a shipped blueprint.

Where to go next

  • cli.mdinit, validate, plan, bootstrap, and blueprint-test operate 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.