@vectros-ai/cli 0.25.0 → 0.27.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/AUTHORING.md CHANGED
@@ -8,7 +8,7 @@ ready-to-use data model + a narrow `ssk_*` scoped key — no root key, no manual
8
8
  admin steps. `vectros blueprint apply <file> --tenant test|live` applies the same
9
9
  blueprint WITHOUT minting a key, for re-applying a change from CI.
10
10
 
11
- This guide is the authoring loop. Everything here except `bootstrap` and `blueprint apply` is
11
+ This guide is the authoring loop. Everything here except `blueprint-test`, `bootstrap` and `blueprint apply` is
12
12
  **creds-free and offline** — you can iterate on a blueprint with no account.
13
13
 
14
14
  > The examples use the bare `vectros` binary — install it once with
@@ -22,7 +22,7 @@ vectros blueprint init my-thing # 1. scaffold a starter
22
22
  # …edit ./my-thing.blueprint.yaml…
23
23
  vectros blueprint validate ./my-thing.blueprint.yaml # 2. structural + scope-gate + lint (offline)
24
24
  vectros blueprint plan ./my-thing.blueprint.yaml # 3. preview what it provisions (offline)
25
- vectros blueprint-test ./my-thing.blueprint.yaml --env staging # 4. prove it live (creds; auto-teardown)
25
+ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test # 4. prove it live (creds; auto-teardown)
26
26
  vectros bootstrap --blueprint ./my-thing.blueprint.yaml # 5. apply for real + mint a key (needs a bridge token)
27
27
  vectros blueprint apply ./my-thing.blueprint.yaml --tenant test # 5'. apply for real, no key (CI: add --yes --confirm-existing-principal)
28
28
  ```
@@ -50,7 +50,7 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
50
50
  `validate` runs three layers and exits non-zero on any failure:
51
51
 
52
52
  1. **Structural** — the file parses against the blueprint schema (readable
53
- `path: message` errors, e.g. `schemas[0].fields[1]: Required`).
53
+ `path: message` errors, e.g. `schemas[0].fields[1].fieldType: Invalid input: expected string, received undefined`).
54
54
  2. **Scope + capability gates** — the requested `allowedActions` are data-plane
55
55
  only. This is the trust boundary: `keys`/`users`/`admin`/`app-contexts`/
56
56
  `namespaces` and the `*` wildcard are **hard-rejected, no override**.
@@ -97,12 +97,14 @@ A name must be 2–50 chars: a lowercase letter, then lowercase letters / digits
97
97
 
98
98
  ```
99
99
  Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
100
- + schema 'thing' (1 field, HYBRID) lookup: [title]
101
- + context 'mcp' (MCP — my-thing)
100
+ ...
102
101
  + principal 'my-thing-sp' (SERVICE)
102
+ + context 'mcp' (MCP — my-thing)
103
+ + schema 'thing' (1 field, HYBRID) lookup: [title]
103
104
  + profile scopes [records:r, records:c, records:u, search:r, schemas:r] (data-plane ✓)
104
105
  + key my-thing-<machine> (mint 1 scoped ssk_*)
105
- + seed 1 record(s)
106
+ + seed 1 record
107
+ 6 step(s). This is a preview — nothing was minted.
106
108
  ```
107
109
 
108
110
  **A blueprint whose `accessProfile` composes roles (`roleIds:`) previews every
@@ -111,9 +113,10 @@ sharing an action set can still carry different `dataScope`:
111
113
 
112
114
  ```
113
115
  Plan for blueprint 'my-thing' (v1.0.0) → context 'mcp':
114
- + schema 'thing' (1 field, HYBRID) lookup: [title]
115
- + context 'mcp' (MCP — my-thing)
116
+ ...
116
117
  + principal 'my-thing-sp' (SERVICE)
118
+ + context 'mcp' (MCP — my-thing)
119
+ + schema 'thing' (1 field, HYBRID) lookup: [title]
117
120
  + role 'case-handler' (1 clause, actions [records:cru])
118
121
  clause 1: actions [records:cru], dataScope: scope:org=[${{ self.scope.org }}]
119
122
  + role 'hr-admin' (1 clause, actions [records:r])
@@ -121,7 +124,8 @@ sharing an action set can still carry different `dataScope`:
121
124
  assumable: scope:org=[org_engineering, org_sales]
122
125
  + profile via role(s) [case-handler, hr-admin] (composed — each role's own clauses, concatenated in order)
123
126
  + key my-thing-<machine> (mint 1 scoped ssk_*)
124
- + seed 1 record(s)
127
+ + seed 1 record
128
+ 8 step(s). This is a preview — nothing was minted.
125
129
  ```
126
130
 
127
131
  A role's `assumable` line only appears when the blueprint's top-level
@@ -164,16 +168,17 @@ everything it created:
164
168
 
165
169
  ```
166
170
  vectros login # sign in once (or pass --token)
167
- vectros blueprint-test ./my-thing.blueprint.yaml --env staging
171
+ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test
168
172
  vectros blueprint-test task-management --keep # leave the graph in place to inspect it
169
173
  ```
170
174
 
171
175
  Teardown is the default and runs even if an assertion fails. It is **created-only** — a pre-apply
172
176
  snapshot means it never deletes a schema or AccessProfile that already existed, so it's safe to point at
173
- a shared staging tenant. (It does **not** delete the app-context: that's idempotent and reused.) Exit
174
- `0` = applied + every assertion passed; `1` = an assertion failed or apply errored; `2` = no credential,
175
- or the blueprint asked for control-plane scope (the same gate `validate` enforces). Run this before you
176
- ship a blueprint — it catches an unusable or mis-scoped credential that the offline checks can't.
177
+ a tenant other people use. (It does **not** delete the app-context: that's idempotent and reused.) Exit
178
+ `0` = applied + every assertion passed; `1` = an assertion failed, apply errored, or an agent the run created could not be retired or purged; `130`/`143` = stopped by SIGINT (^C) / SIGTERM; `2` = no credential,
179
+ or the blueprint asked for control-plane scope (the same gate `validate` enforces), or an agent name is too long
180
+ to take the run's suffix. Run this before you ship a blueprint — it catches an unusable or mis-scoped credential
181
+ that the offline checks can't. A blueprint that declares `agents:` runs under agent names of its own: see *Agents*.
177
182
 
178
183
  ## The format, field by field
179
184
 
@@ -226,7 +231,7 @@ accessProfile:
226
231
  # A SEPARATE gated dimension from allowedActions — named platform effects a
227
232
  # resource:ops verb can't express (e.g. creating/removing an identity in
228
233
  # your tenant). 'member-lifecycle' and 'delegate-principal-stamp' are
229
- # admitted today; every other name is hard-rejected, same posture as the
234
+ # admitted; every other name is hard-rejected, same posture as the
230
235
  # scope gate. See "Capabilities" below.
231
236
  #
232
237
  # Instead of allowedActions (+ optional dataScope/capabilities), accessProfile
@@ -367,6 +372,16 @@ identities:
367
372
  YAML or JSON are both accepted (chosen by extension). The bundled library is
368
373
  authored in TypeScript; YAML is the authoring affordance for your own.
369
374
 
375
+ ### Inputs — values you supply when you install
376
+
377
+ A blueprint can declare a top-level `inputs:` block (each input has a `type` of `string`, `number` or `boolean`, and may
378
+ have `required`, `default` and `description`) and use the values in any string with `${{ inputs.<name> }}`. The built-ins
379
+ `${{ vectros.context }}` and `${{ vectros.suffix }}` are the blueprint's literal `contextId` and a short stable token derived
380
+ from it. Supply values to `validate`, `plan` and `apply` with `--set name=value` (repeatable) or `--values <file>` (a YAML or
381
+ JSON map); `--set` wins over `--values`, which wins over the declared `default`. An unknown input, a missing value with no
382
+ default, or a value of the wrong type stops the command before any request is sent. Write `$${{` for a literal `${{`. The
383
+ `@vectros-ai/blueprints` README describes the block in full.
384
+
370
385
  ### Choosing lookup indexes — a permanent decision
371
386
 
372
387
  `lookupFields` are how you reach records *directly* — by exact value, by an
@@ -424,7 +439,6 @@ each tell you "records with this status" or "records with this area", but never
424
439
 
425
440
  ```yaml
426
441
  lookupFields:
427
- - externalId
428
442
  - { fieldNames: [status, area], sortBy: createdAt, allowOverflow: true }
429
443
  ```
430
444
 
@@ -562,10 +576,82 @@ Full field grammar, including the byte caps on `source` and
562
576
  `declaredInputContract` and the `manifest` verb vocabulary:
563
577
  [`@vectros-ai/blueprints`](https://www.npmjs.com/package/@vectros-ai/blueprints).
564
578
 
579
+ ### Agents — stored model definitions
580
+
581
+ A blueprint can declare the agents it needs. The block needs `formatVersion: 2`, so a CLI that predates the
582
+ format refuses the file instead of misreading it.
583
+
584
+ ```yaml
585
+ formatVersion: 2
586
+ agents:
587
+ intake-triage: # the key is the agent's name: 1-64 letters, digits, '_' and '-'
588
+ description: Summarises a new intake record and flags missing fields.
589
+ model: claude-sonnet-5-5
590
+ system: You triage intake records. Be concise.
591
+ tools:
592
+ - { name: records_get }
593
+ - { name: records_create, allowWrites: true } # a tool that changes data needs allowWrites ...
594
+ limits: { maxToolCalls: 10, maxWrites: 2 } # ... and limits.maxWrites of at least 1
595
+ budget: { maxInferenceCents: 100 }
596
+ ```
597
+
598
+ `apply` and `bootstrap` create each agent in the blueprint's context, after the roles, scripts and triggers and
599
+ before any key is minted, and `plan` lists one step per agent. The write is convergent, and the platform does the
600
+ comparing: a new name is created at version 1, an identical definition writes nothing, and a changed definition
601
+ becomes the next version. Nothing is edited in place, so every earlier version stays readable. The apply summary
602
+ names what it did to each agent (`created at version 1`, `unchanged at version 2`, `version 2 -> 3`, or `adopted`).
603
+
604
+ - **The applying credential needs `agents:c`**, and `agents:u` and `agents:r` as well whenever the agent already exists, even
605
+ when its definition is unchanged (the platform checks `u` before it compares, and without `r` it cannot show the existing agent
606
+ and answers that the name is in use). A re-apply with only `agents:c` is refused, and the error says which scopes are missing.
607
+ A principal without them gets a `403`, and the apply stops with an error that names the scope and the context.
608
+ Agents are written before the key, so a refused apply mints none. This is a grant on the credential that applies
609
+ the blueprint, not something to put in the blueprint's `accessProfile`: `agents` is a control-plane resource, so
610
+ the scope gate refuses it there.
611
+ - **Each agent is marked as this blueprint's.** Another blueprint's agent of the same name is refused, not rewritten, so two
612
+ blueprints cannot declare one name, and renaming a blueprint that already created agents strands them: the new name is
613
+ refused for them. The mark is set once and cannot change. It is the blueprint's own `name`, which the file declares
614
+ for itself (the same trust as for scripts and triggers): any file that declares `name: X` rewrites the agents marked
615
+ `X`, so the adoption flag below guards unmarked agents only.
616
+ - **An agent made by hand under the same name is refused unless you pass `--allow-agent-adoption`** (to `apply` or `bootstrap`; a
617
+ flag only the operator can give, never something the blueprint declares). Adopting marks it as this blueprint's and writes the
618
+ declared definition as its next version if that differs, so every run that uses the agent's latest version changes; earlier
619
+ versions stay readable. Nothing is written to a refused agent. Once adopted it is an ordinary managed agent and later applies
620
+ need no flag. This is how you bring agents you created by hand under a blueprint. It is checked per agent as the agents are
621
+ written, partway through the apply, so what an earlier agent or section already wrote stays.
622
+ - **Hand edits to a managed agent do not last.** A version written with `vectros agents version` (or the API) on an agent a
623
+ blueprint declares is replaced by the declared definition at the next apply, as another version. Change the blueprint.
624
+ - **A retired agent that the blueprint still declares fails the apply** (`409`), after the sections before it are written. Restore it
625
+ with `vectros agents version <id> --restore --file <definition>`, or remove the entry; a purged agent cannot be restored.
626
+ - **Removing an entry deletes nothing.** The agent stays stored and runnable. Retire it deliberately through the
627
+ API (`DELETE /v1/agents/{id}`); a retired agent keeps its name, so the same name cannot be created again.
628
+ - **`blueprint-test` runs a blueprint that declares agents, under names of their own.** A retired or purged agent keeps
629
+ its name for good, so the test applies a copy of your blueprint whose agent names carry a suffix unique to the run
630
+ (`triage` becomes `triage-t1a2b3c4d`; the report lists each), then retires and purges exactly the agents it created,
631
+ even when an assertion failed. Agents that exist under your declared names are never touched, and the file you wrote is
632
+ not changed. Because the suffix takes 10 characters, a name can be at most 54 characters long here (exit `2`, before any
633
+ write). The credential needs `agents:d` as well as the scopes an apply needs; if a retire is refused the agent stays active, the report names the API call that retires it, and the run exits
634
+ `1` (a teardown failure on any other resource is still reported without changing the exit code). **^C, SIGINT and SIGTERM.** Once the apply has begun, only the agent step has a stop point (a signal during context provisioning or the snapshot ends the run before the apply starts). After the first signal the run writes no further agents and skips
635
+ the assertions if they have not begun, but the rest of the apply still runs to its end (schemas, roles, the profile, the scoped-key mint,
636
+ seed records, the index poll), so a request in progress or a long seed delays the stop. Then the same teardown as after a failed run
637
+ removes what the apply's result lists (never the context, the service principal or namespaces), the report is printed, and the exit
638
+ code is `130` (SIGINT) or `143` (SIGTERM); a run stopped this way never passes, and these codes win over `1` (the report still names
639
+ an agent that could not be removed). A step that never answers holds the run until you send the signal again. That second signal starts no
640
+ teardown: it prints the id of each agent this run created with the calls that retire and erase it, names a create that was sent and never
641
+ answered, notes that other things the run wrote are not listed, and leaves at once. With `--keep` nothing is torn down. A process manager that
642
+ follows SIGTERM with SIGKILL after a grace period ends the process wherever it is, and SIGKILL cannot be handled: what was written so far
643
+ stays. A create of a tracked agent is sent with no client retries, so a single 429 or 5xx fails that create (the run reports it and tears
644
+ down) instead of being retried; a create that may have gone through is searched for by name. Each run leaves one purged row per declared agent (the name stays reserved), which
645
+ `agents list --include-retired` shows. With `--keep` the agents stay, under their run names.
646
+ - `authority` is `CALLER` (the default and the only value the platform accepts): a run calls tools with the identity
647
+ and permissions of whoever starts it, further limited by the tool whitelist.
648
+
649
+ The same API is available directly as `vectros agents list|get|create|version`; see the README.
650
+
565
651
  ### The scope gate — what you can and can't request
566
652
 
567
653
  The gate allows only **data-plane** resources:
568
- `records`, `schemas`, `search`, `documents`, `folders`, `inference`, `entities`
654
+ `records`, `schemas`, `search`, `documents`, `folders`, `inference`, `entities`, `runs`
569
655
  (per-namespace identity-entity CRUD — `org`/`client`/any namespace you
570
656
  registered — ownership-gated the same way as `records`; it grants no
571
657
  authority over the namespace *registry* itself, which stays root-key-only).
@@ -605,7 +691,7 @@ An `accessProfile` or role clause may also declare `capabilities: [...]` —
605
691
  named platform effects (like creating or removing an identity in your tenant)
606
692
  that a `resource:ops` scope token can't express. This rides its own field,
607
693
  separate from `allowedActions`, and has its own allowlist: **`member-lifecycle`
608
- and `delegate-principal-stamp` are admitted today.** Every other capability
694
+ and `delegate-principal-stamp` are admitted.** Every other capability
609
695
  name — including ones the platform's own registry recognizes — is refused,
610
696
  the same fail-closed, deliberately-narrow posture as the scope gate above. `validate`,
611
697
  `plan`, `blueprint-test` and `bootstrap` all report a rejected capability the
@@ -693,6 +779,11 @@ stays, because there is no second, principal-shaped factor to safely gate a
693
779
  DELETE the way a trigger row's provenance marker does. Remove one deliberately
694
780
  with `vectros scripts delete`.
695
781
 
782
+ **A FOURTH qualification, and here the platform does the comparing: an `agents:` entry is idempotent server-side,
783
+ and a change adds a version.** For an agent that exists, the loader first makes a plain create call to see the agent's mark and
784
+ version, then an upsert, which compares the declared definition with the stored latest (after defaults are filled in) and writes
785
+ nothing when they are identical. The two versions give the exact outcome. See *Agents*, including `--allow-agent-adoption`.
786
+
696
787
  ## Distribution
697
788
 
698
789
  - **Bundled** — reviewed, versioned blueprints shipped in the library
package/CHANGELOG.md CHANGED
@@ -3,6 +3,84 @@
3
3
  All notable changes to `@vectros-ai/cli` are documented here.
4
4
  This project adheres to [Semantic Versioning](https://semver.org).
5
5
 
6
+ ## 0.27.0 — 2026-11-07
7
+
8
+ ### Added
9
+
10
+ - **`vectros blueprint apply` and `bootstrap` create the agents of a blueprint's `agents` section** (it needs `formatVersion: 2`), after the
11
+ roles, scripts and triggers and before any key is minted. `blueprint plan` lists one step per agent (model, tools, limits, budget,
12
+ prompt size). A new name is created at version 1, an unchanged definition writes nothing, a changed one becomes a new version; the
13
+ summary says which (`created at version 1`, `unchanged at version 2`, `version 2 -> 3`).
14
+ - **Agents are marked as the blueprint's own.** Another blueprint's agent of the same name is refused, and an agent made by hand is
15
+ refused unless you pass `--allow-agent-adoption` (adopting it writes the declared definition as a new version, which changes every
16
+ run that uses the latest version). Removing an entry from the blueprint deletes nothing.
17
+ - **Scopes:** the applying credential needs `agents:c`, and `agents:u` and `agents:r` as well when the agent already exists, even if
18
+ its definition is unchanged. On a `403` the apply stops, names the agent, scope and context, and mints no key.
19
+ - **`vectros agents list|get|create|version`.** `--context` is required and `--tenant test|live` is accepted. `create` takes a name
20
+ (1 to 64 letters, digits, `_` or `-`) and a JSON or YAML definition file. `version` takes the agent's id and a file, writes a new
21
+ version as a full replacement, and takes `--expected-version` (refuse the write if someone else got there first) and `--restore`
22
+ (bring a retired agent back). `list` takes `--include-retired`; `get` takes the id and `--at-version`. There is no `delete` verb:
23
+ retire an agent through the API.
24
+ - **A blueprint can grant `runs:c` and `runs:r`.** A trigger cannot be granted `runs`, directly or through a role.
25
+ - **`blueprint validate` and `plan` warn about a schema field named `externalId`, `partnerUserId`, `scopes` or `userId`** (the platform
26
+ reserves them; exit `0`); a lookup field with one of those names is an error (exit `2`).
27
+ - **A blueprint's names, labels and descriptions with more invisible characters are refused.**
28
+
29
+ ### Changed
30
+
31
+ - **`vectros --version` reports SDK 0.49.0.**
32
+ - **`apply` and `bootstrap` ask before reusing a trigger's service principal that this blueprint did not mark:** a prompt, or
33
+ `--confirm-existing-principal` in a pipeline (exit `3` without it, before anything is written). Principals from earlier releases
34
+ carry no mark, so the first re-apply of a blueprint with triggers needs the confirmation once per trigger; marking needs `users:u`.
35
+ - **`vectros blueprint-test` runs a blueprint with agents.** Agent names get a per-run suffix (a declared name over 54 characters exits
36
+ `2`), the test retires and purges only the agents it created, and the credential needs `agents:d`. `^C` or SIGTERM during the run
37
+ tears down what the apply listed and exits `130` or `143`. Each run leaves one purged row per agent, and `--keep` leaves the agents.
38
+ - **Text from blueprint files, agent definitions and the platform reaches the terminal without control, invisible or bidirectional
39
+ characters,** cut at 200 lines or 20,000 characters (`[truncated]`). YAML parser warnings are no longer printed.
40
+ - **Blueprint validation runs on zod 4.** An integer beyond JavaScript's safe-integer range and `Infinity` are refused, a string length
41
+ counts characters, and a wrong-typed value stops the checks that depend on it. zod's own messages are reworded (a missing field reads
42
+ `Invalid input: expected string, received undefined`): match on the field path, not on the text.
43
+
44
+ ## 0.26.0 — 2026-10-25
45
+
46
+ ### Added
47
+
48
+ - **`vectros whoami --tenant test|live`** shows the session for that tenant (default `live`). The output gains a `tenant type`
49
+ line and `--json` gains `tenantType`. The test tenant shows your own user id there, which needs platform 0.47.0 or later and
50
+ shows `—` on an earlier platform. Any other value stops with a usage error (exit 2).
51
+ - **`vectros access list --principal` prints a note on standard error naming the context it searched.** The command searches
52
+ the `default` context, and depending on the caller the platform may not look beyond it, so an empty result means none in
53
+ `default`; the note gives the `vectros access get --principal … --context <c>` command to check another. Standard output
54
+ and `--json` are unchanged. A `2>&1 | jq` pipeline fails on the note; redirect standard error instead.
55
+
56
+ ### Fixed
57
+
58
+ - **A blueprint file whose trigger `dataScope` uses `${{ input.userId }}` or `${{ input.scope.<namespace> }}` loads in
59
+ `validate`, `plan`, `apply` and `bootstrap`.** The platform resolves these placeholders against the record that fires the
60
+ trigger.
61
+
62
+ ### Changed
63
+
64
+ - **`vectros --version` reports SDK 0.48.0.**
65
+ - **`vectros blueprint apply` stops with the platform's message when another request changes the same item at the same
66
+ moment.** When apply updates a schema, role or trigger rule, the platform answers `409` (`VERSION_CONFLICT`) instead of
67
+ overwriting the other change. Run apply again once the other change has finished.
68
+ - **`vectros provider-config create` and `update` stop with the platform's `503` when it cannot store the provider key.**
69
+ Nothing is saved; run the command again shortly.
70
+ - **The bundled `@vectros-ai/blueprints` is 0.20.3, and the package description covers the whole command set.**
71
+ - **The comments in the shipped code describe the code as it is today.** Behaviour is unchanged, except for the text of the
72
+ error for a missing Cognito client id.
73
+
74
+ ### Documentation
75
+
76
+ - **The README documents `vectros login`, `logout`, `whoami` and `switch`, and `vectros keyring prune --dry-run`.**
77
+ - **The README says `vectros blueprint-test` exits `1` for a blueprint that cannot be read, and that
78
+ `vectros provider-config create` and `update` stop with the platform's `503` when it cannot store the provider key.**
79
+ - **The authoring guide describes the `inputs:` block of a blueprint: its types, `required` and `default`, `--set` and
80
+ `--values`, and the `vectros.context` and `vectros.suffix` built-ins.**
81
+ - **The authoring guide shows an abbreviated `plan` output, with the steps in the order they run and the closing step count, and
82
+ uses `--tenant test` for `blueprint-test`.**
83
+
6
84
  ## 0.25.0 — 2026-10-12
7
85
 
8
86
  ### Breaking changes
package/README.md CHANGED
@@ -5,6 +5,8 @@
5
5
 
6
6
  The Vectros provisioning CLI — the `vectros` binary:
7
7
 
8
+ - **`vectros login`** / **`logout`** / **`whoami`** — sign in through your browser (`--no-browser` prints the URL instead, for headless machines), clear the stored session, and show the signed-in session for a tenant (`--tenant test|live`, default `live`; `--json`).
9
+ - **`vectros switch <alias>`** — shortcut for `keyring use`.
8
10
  - **`vectros bootstrap`** — provision a least-privilege MCP credential (+ optional blueprint).
9
11
  - **`vectros blueprint`** — author + inspect blueprints (`init` / `validate` / `plan` / `list`, creds-free), and
10
12
  **`apply`** one into a tenant without minting a key (for CI).
@@ -18,6 +20,7 @@ The Vectros provisioning CLI — the `vectros` binary:
18
20
  - **`vectros role`** — define reusable, context-scoped access rules.
19
21
  - **`vectros scripts`** — push, list, inspect, and delete the versioned script source a trigger
20
22
  fires, or your own app executes directly.
23
+ - **`vectros agents`** — list, inspect, create, and version stored agents (a model, a system prompt, and a tool whitelist).
21
24
  - **`vectros assume`** — switch identity namespace values on your own token, per a role's `assumable` grant.
22
25
  - **`vectros access`** — bind principals to contexts, and inspect what a binding grants.
23
26
  - **`vectros key`** — issue, list, revoke, and rotate scoped API keys (`ssk_*`).
@@ -76,7 +79,8 @@ On a machine with no local record of a prior mint (e.g. an ephemeral CI
76
79
  container re-running `--yes` for the first time) against a blueprint whose
77
80
  service principal already exists in the tenant, `--yes` alone isn't enough —
78
81
  add `--confirm-existing-principal` to make that expected case explicit and
79
- non-interactive; see AUTHORING.md's "idempotency contract" for why. For a CI re-apply that has no use for the
82
+ non-interactive; see AUTHORING.md's "idempotency contract" for why. The same flag covers a trigger's service principal
83
+ that already exists without this blueprint's mark (see the `apply` section below). For a CI re-apply that has no use for the
80
84
  key, `vectros blueprint apply` (below) is the better fit.
81
85
 
82
86
  **An issuer that needs verification.** A blueprint's `issuers` entry registered without `restrictedToDomain` starts
@@ -140,7 +144,7 @@ vectros blueprint apply ./my-thing.blueprint.yaml --tenant live --yes --confirm-
140
144
 
141
145
  `apply` runs the same pipeline as `vectros bootstrap` (the same credential, the same scope and capability gates,
142
146
  the same loader) with the key step switched off: the context, its service principal and access profile, schemas,
143
- roles, issuers, triggers and scripts are created or reconciled (and, only if you pass `--seed`, the blueprint's sample
147
+ roles, issuers, triggers, scripts and agents are created or reconciled (and, only if you pass `--seed`, the blueprint's sample
144
148
  seed records are written), but **no scoped key is minted,
145
149
  nothing is written to a keyring, and no MCP client config is touched**. (Short-lived bootstrap tokens are minted
146
150
  to call the platform, as with `bootstrap`; none is a scoped key.) That is what makes it fit for re-applying
@@ -164,11 +168,18 @@ run. Use `bootstrap` when you also want the credential.
164
168
  place, as `bootstrap` does.)
165
169
  - **An existing service principal needs `--confirm-existing-principal`** (with `--yes`, or with no terminal),
166
170
  which is every apply once the principal exists, on any machine: `apply` never reads a local key record, so a
167
- machine that ran `bootstrap` is not exempt. It lets `apply` rewrite that principal's access profile, and covers the
168
- blueprint's own service principal only (the principals a blueprint's triggers use are ensured without this check,
169
- as with `bootstrap`). In a pipeline this flag is standing consent: it also waves through a blueprint whose
170
- `servicePrincipal.externalId` collides with an unrelated identity, so keep the blueprint's source reviewed. Every
171
- run that proceeds prints which principal it reused (id, external id, created time) and why.
171
+ machine that ran `bootstrap` is not exempt. It lets `apply` rewrite that principal's access profile. The same flag
172
+ covers a trigger's service principal that already exists and that this blueprint did not mark. A principal created
173
+ for a trigger is marked with the blueprint's name (a `provisionedBy` key on the principal), so the confirmation
174
+ is asked only for one that has no mark (an earlier release made it, or someone made it by hand) or carries another
175
+ blueprint's: the first confirmation marks it as this blueprint's (an overwrite that needs the `users:u` scope in the
176
+ blueprint's context and is skipped when the principal's payload reads back masked; if the run does not write it, it says
177
+ why and the next run asks again), and with no
178
+ terminal and no flag the run stops before it writes a schema, role, profile, trigger, key or seed record. The mark is advisory, a guard against
179
+ an accidental name collision or a pre-created principal; it does not stop someone who can create principals and
180
+ copies the format. In a pipeline this flag is standing consent: it also waves through a blueprint whose
181
+ `servicePrincipal.externalId` or trigger name collides with an unrelated identity, so keep the blueprint's source
182
+ reviewed. Every run that proceeds prints which principal it reused (id, external id, created time) and why.
172
183
  - **Sample seed records are opt-in: `apply` writes none unless you pass `--seed`,** in either tenant, and never asks.
173
184
  This is the opposite of `bootstrap` (which seeds unless `--no-seed`) on purpose: a blueprint's seeds are
174
185
  illustrative, not real data, and a re-apply from a pipeline must not write them by omission. When the blueprint
@@ -184,13 +195,13 @@ run. Use `bootstrap` when you also want the credential.
184
195
  but `--require-verified-issuers` was given and an issuer is pending verification.
185
196
 
186
197
  Flags it shares with `bootstrap` (`--set`, `--values`, `--allow-tenant-wide-namespaces`,
187
- `--allow-script-adoption`, `--require-verified-issuers`, `--token`, `--base-url`) behave the same. `--tenant`,
198
+ `--allow-script-adoption`, `--allow-agent-adoption`, `--require-verified-issuers`, `--token`, `--base-url`) behave the same. `--tenant`,
188
199
  `--yes` and `--confirm-existing-principal` are shared but behave as described above (`--tenant` is required here,
189
200
  `--yes` is required without a terminal, and the principal flag applies to every re-apply). The flags that concern a
190
201
  key or your MCP client (`--rotate`, `--client`, `--no-merge`, `--activate`, `--no-activate`, `--use-scoped-token`,
191
202
  `--test`) belong to `bootstrap` and are refused with a pointer; the blueprint is an argument, not `--blueprint`, and
192
- there is no `--blank`. A script pushed onto an existing hand-authored name (`--allow-script-adoption`) is checked
193
- when the scripts are pushed, partway through, not up front. `vectros blueprint apply --help` lists everything.
203
+ there is no `--blank`. A script pushed onto an existing hand-authored name (`--allow-script-adoption`) and an agent
204
+ that exists with no blueprint mark (`--allow-agent-adoption`) are checked when they are written, partway through, not up front. `vectros blueprint apply --help` lists everything.
194
205
 
195
206
  ## `vectros blueprint-test` — verify it actually provisions (creds-required)
196
207
 
@@ -210,10 +221,17 @@ vectros blueprint-test ./my-thing.blueprint.yaml --tenant test
210
221
  vectros blueprint-test task-management --keep
211
222
  ```
212
223
 
213
- Exit `0` = applied and every assertion passed; `1` = an assertion failed or apply
214
- errored; `2` = no credential, or the scope gate or the capability gate refused the blueprint.
224
+ Exit `0` = applied and every assertion passed; `1` = an assertion failed, apply
225
+ errored, the blueprint could not be read, or an agent the run created could not be retired or purged (the one teardown failure that changes the result); `130` = stopped by SIGINT (^C) and `143` = by SIGTERM, after the rest of the apply finishes (no further agents are written; a key mint and seed records still run) and the teardown it still runs (a second signal leaves at once and names the agents that may still be active); `2` = no credential, or the scope gate or the capability gate refused the blueprint,
226
+ or an agent name is too long to take the run's suffix.
215
227
  The app-context is intentionally **not** deleted (it's idempotent and reused).
216
228
 
229
+ A blueprint that declares `agents` (see [`vectros agents`](#vectros-agents--stored-versioned-agent-definitions)) runs too. Because a retired or purged agent keeps its name for good, the
230
+ test applies a copy of your blueprint whose agent names carry a suffix unique to the run (`triage` runs as `triage-t1a2b3c4d`; the report
231
+ lists each), then retires and purges exactly the agents it created, even when an assertion failed. Agents that exist under your declared names
232
+ are never touched. A name can be at most 54 characters long here, and the credential needs `agents:d` as well as the scopes an apply needs.
233
+ Each run leaves one purged row per declared agent behind, because a name stays reserved; `vectros agents list --include-retired` shows them.
234
+
217
235
  ## `vectros context` — app contexts
218
236
 
219
237
  A context is the namespace all data (records, documents, schemas, roles, access
@@ -317,13 +335,14 @@ config up itself is refused by the platform with a `400` for anything but `--bil
317
335
  `byo-model-inference:customer-key` waiver type, and a plain `https://` endpoint on port 443 (no credentials,
318
336
  query or fragment). On `update`, only a **change** to one of those is refused; a value the config already
319
337
  holds is accepted. Changing an endpoint's host needs the key again (`--api-key-secret-file`). The key must
320
- be printable ASCII, at most 4096 characters. A tenant holds at most 25 provider configs.
338
+ be printable ASCII, at most 4096 characters. A tenant holds at most 25 provider configs. If the platform cannot store the key it answers `503`, nothing is saved, and the command stops with that message; run it again shortly.
321
339
 
322
340
  The CLI does not sign the risk waiver: an account owner signs it in the developer portal, under **Model
323
341
  Providers**, for each environment (live or test). A config is usable only while that waiver is in force for
324
- your account. It is not in force when it is neither signed nor granted to your account, when it was signed
325
- against a text that has since changed, or when it has been revoked. A waiver that Vectros granted to your tenant,
326
- for which your tenant has no signed acceptance, is not affected by any of these. The portal shows which of these
342
+ your account. It is not in force when it is neither signed nor granted to your account, when it has been revoked,
343
+ or when Vectros has required a newer version than the one your owner signed (a waiver Vectros granted to your tenant
344
+ without a signature ends then too). A new published version of the waiver changes nothing by itself: the version your owner signed stays in force
345
+ until Vectros requires a newer one, and account owners are told in advance of the date. The portal shows which of these
327
346
  applies. An account that has accepted the Business Associate Agreement with Vectros in the developer portal needs one more thing before a call can
328
347
  use a config: an account owner attests that config in the developer portal (Model Providers). Until then the
329
348
  platform refuses the call with a `403` that says it needs a BAA-holder attestation. The CLI does not attest
@@ -506,6 +525,42 @@ A blueprint can ship a script instead of pushing one — see `scripts:` in
506
525
  convergent (an unchanged source pushes nothing) and never deletes: removing a `scripts:` entry
507
526
  leaves every version in place, which is why this command group exists to remove one deliberately.
508
527
 
528
+ ## `vectros agents` — stored, versioned agent definitions
529
+
530
+ A stored agent is a named definition of a model, a system prompt, a tool whitelist, per-run limits and a
531
+ spending cap, kept in an app context. Every change writes a new immutable version and the agent always
532
+ points at its newest one; nothing is edited in place. These verbs and a blueprint's `agents:` section
533
+ write through the same API, so use them for an agent no blueprint manages and to read what an apply stored.
534
+
535
+ ```bash
536
+ vectros agents create intake-triage --context my-app --file ./intake-triage.yaml
537
+ vectros agents list --context my-app # each agent at its newest version
538
+ vectros agents list --context my-app --include-retired
539
+ vectros agents get 3b1c9f2e7a5d… --context my-app # newest version, with its prompt
540
+ vectros agents get 3b1c9f2e7a5d… --context my-app --at-version 1 # an earlier version
541
+ vectros agents version 3b1c9f2e7a5d… --context my-app --file ./intake-triage.yaml --expected-version 1
542
+ ```
543
+
544
+ The definition file is JSON, or YAML for a `.yaml`/`.yml` file, with the fields of a blueprint's `agents` entry:
545
+ `model` (required), `description`, `authority`, `system`, `tools` (`{ name, allowWrites? }`), `limits` and `budget`.
546
+ A key outside that list is refused before anything is sent, because the platform would drop it and store the agent
547
+ without what you meant it to have.
548
+
549
+ | Subcommand | Flags | Notes |
550
+ |---|---|---|
551
+ | `create` | the agent name; `--context <c>`, `--file <path>` (both required); `--tenant <test\|live>` | Stores version 1. If the name already exists the stored agent is returned **untouched** and nothing is written; the output says so. A retired or purged name is refused (`409`) because the name stays reserved. |
552
+ | `version` | the agent id; `--context <c>`, `--file <path>` (both required); `--expected-version <n>`, `--restore`, `--tenant <test\|live>` | Writes the next version. A **full replacement**: a field the file leaves out takes its default, nothing is carried over from the version before. `--expected-version` refuses the write (`409`) if the agent has a newer version than the one you read. `--restore` brings a **retired** agent back, with the file as its next version; a purged agent cannot be restored. |
553
+ | `list` | `--context <c>` (required); `--include-retired`, `--tenant <test\|live>` | Each agent at its newest version, newest first. Retired agents are left out unless asked for. |
554
+ | `get` | the agent id; `--context <c>` (required); `--at-version <n>`, `--tenant <test\|live>` | One agent, with its prompt and write-enabled tools marked. Ids are opaque: copy them from `agents list`. |
555
+
556
+ `--context` is required on all four, because an agent belongs to one app context. `create` needs the
557
+ `agents:c` scope on the credential, `version` needs `agents:u`, and `get` needs `agents:r`; `list` needs `agents:r` too, or `runs:c`, which lists each agent's name, version and status without its definition. A `403` names the scope. (A credential that
558
+ can create but not read gets "the name is already in use" for a name that exists; the output says why.)
559
+ There is no `delete`: retiring an agent keeps its name reserved, so it stays a deliberate call to the API.
560
+
561
+ A blueprint can declare agents instead; see *Agents* in [`AUTHORING.md`](./AUTHORING.md). To bring agents you created by hand
562
+ under a blueprint, apply it once with `--allow-agent-adoption`.
563
+
509
564
  ## `vectros assume` — switch identity namespace values on your own token
510
565
 
511
566
  Re-mints your own verb token with one or more `identity.<namespace>` values
@@ -541,12 +596,24 @@ vectros access grant --principal me --context my-app --role editor
541
596
  vectros access grant --principal usr_<id> --context my-app --actions records:r,search:r \
542
597
  --scope org:<orgId>
543
598
  vectros access list --context my-app # who's in this context
544
- vectros access list --principal me # which contexts I'm bound to
599
+ vectros access list --principal me # which contexts I'm bound to (see the note below)
545
600
  vectros access get --principal me --context my-app
546
601
  vectros access explain --principal me --context my-app
547
602
  vectros access revoke --principal usr_<id> --context my-app
548
603
  ```
549
604
 
605
+ `access list --principal` searches one context, `default`, because the command's token is pinned to it:
606
+ depending on the caller the platform may not look beyond that context, so a grant you hold in another
607
+ context can be missing from the list, and an empty list means no grant in `default`, not no grants
608
+ anywhere. The command says so on standard error, so `--json` output on standard output stays a bare array,
609
+ and it drops the line when a row from another context shows the lookup did span contexts. To see a grant
610
+ in a specific context, run `vectros access get --principal me --context <context-id>` (add `--tenant test`
611
+ for the test tenant), or `vectros access list --context <context-id>` for everyone in it.
612
+
613
+ The line is also written with `--json`, and there is no switch to turn it off. A script that merges standard
614
+ error into standard output (`2>&1`) will see one extra line and a `2>&1 | jq` pipeline will fail on it;
615
+ redirect standard error (`2>/dev/null`) to drop it.
616
+
550
617
  `--principal` accepts `usr_<userId>`, `key_<keyId>`, or the alias `me` (your
551
618
  own signed-in user, resolved from your login) — every `access` subcommand
552
619
  shares this grammar. In the **test tenant** your principal has a different id from your live user, so `me` resolves it per tenant: add `--tenant test` and it still works, e.g. `vectros access grant --principal me --context <context-id> --role <role> --tenant test`. This needs platform 0.47.0 or later; on an earlier platform the command typically stops and points you to `vectros identity list-all --tenant test` (an owner login), which shows the id to pass as `--principal usr_<id>`.
@@ -612,7 +679,7 @@ is optional and stripped if present) — narrower than `access`'s grammar: there
612
679
  is no `me` alias on any `key` subcommand, and a key never authenticates as a
613
680
  `key_*` principal. Look your own id up first with `vectros identity list
614
681
  --type user` (or `vectros whoami`; both show your live tenant, and for the test tenant
615
- use `vectros identity list-all --tenant test`), or grant/issue via `vectros join`, which
682
+ use `vectros whoami --tenant test` or `vectros identity list-all --tenant test`), or grant/issue via `vectros join`, which
616
683
  does resolve `--as me`.
617
684
 
618
685
  A minted secret has exactly two destinations, and the flags for choosing one
@@ -630,7 +697,7 @@ are shared by `issue` and `rotate`:
630
697
  | `issue` | `--principal <p>`, `--context <c>` (required); `--name <n>` (default `default`), `--label <l>`, `--tenant test\|live`, destination flags above | Idempotent on `(tenant, context, principal, name)` — re-issuing an existing tuple returns the key **without** the secret (never re-disclosed); the CLI detects that and points you at `rotate`. |
631
698
  | `list` | `--principal <p>`, `--context <c>`, `--tenant test\|live` **or** `--all-contexts` (owner-only; spans both tenants, not combinable with `--tenant`) | **The default lists only ONE context's keys** (the token's context — `default`, or `--context`), not your whole account — easy to miss since the active-key limit is tenant-wide across all contexts. `--all-contexts` is the true account-wide view. |
632
699
  | `get <keyId>` | `--context <c>`, `--tenant test\|live` | `--context` pins the verb token to the key's context — omit it for a key bound to a non-default context (e.g. one `bootstrap` minted) and the lookup 404s. |
633
- | `revoke <keyId>` | `--context <c>`, `--tenant test\|live` | Soft-delete; ~5 minutes to take effect (authorizer cache). Same `--context` pinning caveat as `get`. |
700
+ | `revoke <keyId>` | `--context <c>`, `--tenant test\|live` | Soft-delete; it takes about 5 minutes to take effect. Same `--context` pinning caveat as `get`. |
634
701
  | `rotate` | `--principal <p>`, `--context <c>` (required); `--name <n>`, `--force-new`, `--tenant test\|live`, destination flags above | Revokes the matching active key (by the exact `(context, principal, name)` tuple), then mints a fresh one — there is no dedicated rotate endpoint. **Default-name guard:** if no key is currently active under `--name` (default `default`) but the principal/context has *other* active keys, `rotate` refuses rather than silently minting an unrelated second key — pass the right `--name` or `--force-new` to proceed anyway. |
635
702
 
636
703
  `issue`/`rotate` have no `--json` (use `--format json` instead); `list`/`get`
@@ -652,6 +719,7 @@ vectros keyring show # the active entry's fields + its key
652
719
  vectros keyring show --format raw # JUST the secret, for scripts (see below)
653
720
  vectros keyring doctor # is the keyring healthy? which keys are live?
654
721
  vectros keyring prune # drop entries whose stored secret is gone
722
+ vectros keyring prune --dry-run # report what would be removed, remove nothing
655
723
  vectros keyring remove <alias> # drop one entry + its secret
656
724
  ```
657
725
 
@@ -714,8 +782,8 @@ Blueprints are **untrusted input** (agent- or community-authored) and the
714
782
  CLI runs under an elevated ephemeral bridge token. So the trust boundary is
715
783
  **this binary, not the blueprint**: a hardcoded **scope gate** bounds every
716
784
  mint to a data-plane allowlist (`records` / `schemas` / `search` / `documents`
717
- / `folders` / `inference` / `entities`). Most control-plane scope (keys /
718
- app-contexts / users / billing / admin / namespaces / triggers) or a
785
+ / `folders` / `inference` / `entities` / `runs`). Most control-plane scope (keys /
786
+ app-contexts / users / billing / admin / namespaces / triggers / agents) or a
719
787
  `*` wildcard **hard-rejects** — the CLI mints nothing and exits non-zero. There is
720
788
  intentionally **no override flag**.
721
789
 
@@ -754,7 +822,7 @@ A blueprint's `accessProfile` and role clauses may also declare
754
822
  identity in your tenant) that reach across a partition boundary in a way no
755
823
  `resource:ops` verb can express. This rides a field entirely separate from
756
824
  `allowedActions`, and it has its own allowlist: **`member-lifecycle` and
757
- `delegate-principal-stamp` are admitted today.** Every other capability name —
825
+ `delegate-principal-stamp` are admitted.** Every other capability name —
758
826
  including ones the platform's own registry recognizes — is refused, the same
759
827
  fail-closed, deliberately-widen posture as the scope gate above. `vectros blueprint validate`/`plan` and
760
828
  `bootstrap` all report a rejected capability the same way they report a