naive teams is the CLI for the durable runtime. A team is a declared
team({ runtime, lead, agents, edges, brain }) in your naive.config.ts; this command group
addresses one of those teams for one tenant and drives it.
What works depends on which runtime the tenant is on, and every one of the 28 subcommands
addresses a mounted route. 19 of them return real rows for a durable tenant. 12 do for a
hermes tenant. The other 9 answer
501 not_configured and name the missing dependency —
for the runtime that tenant is actually on. That split is measured against this build, not
aspirational: ci/runtime-surface-counts.test.ts derives it from routes/teams.ts and fails
the build if this sentence disagrees.Which of the two you get is decided by naive up, not by this command group. A tenant lands
on the durable runtime when a teams: block declares runtime.durable(...) and the
operator exports NAIVE_DURABLE_CREDENTIAL_<TEAM> — that is the one production path to
registerVettaRuntime(), and it refuses any tenant that already has a live Hermes container.
naive teams migrate remains one of the 9 that refuse: registering overwrites the tenant’s
legacy coordinates and the way back is not symmetric, so moving an existing tenant stays an
operator act rather than a subcommand. Every tenant naive up did not place answers
provider: "hermes" and gets the 12.Run naive teams plan <team> --tenant <id> to see which runtime a tenant is on. Every response
carries provider; every refusal carries error.details.runtime.The addressing tuple
The durable runtime addresses work as (company, tenant, team).- company comes from your API key. You never pass it.
- team is the first positional argument — the key you gave the team in
naive.config.ts. - tenant is
--tenant <tenant-user-id>, and it is required on every subcommand exceptlist. There is no default and no fallback.
--tenant @self is the one shorthand. It resolves to the subject you selected with
naive use <id> — not to a server-side default. If nothing is selected it is an error:
Subcommands
Every subcommand takes<team> as its first argument and --tenant (except list).
Both verdict columns are the measured ones. A single “does it work” column could not be
true — the answer depends on which runtime the tenant is on, and provider on every response
tells you which you are reading.
Reading the board
Changing something
Submit is the one work verb
There is exactly one way to give a team work, and it issubmit. It carries the same name on
every surface that offers it:
--brief <file> (or --brief - for stdin) exists because prose on argv is mangled by every
shell that touches it. Use it for anything with quotes, newlines or $.
The write fence
Eleven subcommands are fenced — they pass through the manifest write fence and are evaluated under a governance ActionId. The CLI prints which one inhints:
submit, say, unblock, decide, settle, schedule, unschedule, model,
stop, migrate, rollback. Reads are never fenced.
decide is the fenced write that works on this build:
--because is a required option on seven subcommands — unblock, decide, settle,
model, stop, migrate and rollback. A destructive or overriding act with no recorded
reason is a row nobody can audit later, so the command line refuses it rather than review
catching it. (watch has one other required option: --run <id>.)
Exit codes
naive teams is the only command group with exit codes beyond 0 and 1.
Exit 3 exists because
unverified is a third terminal state, not a flavour of success. A
card whose claims were never attested has finished without having been shown to be right.
Nothing exits 3 unless you ask for it.
Exit 2 is opt-in for the same reason terraform made it opt-in: reusing exit 1 for “changes
exist” is indistinguishable from a real error in a CI script.
What this build does not serve yet
The 15 subcommands marked 501 above are not 404s and not stubs. Each addresses a mounted route that authorises your request, then refuses with501 not_configured and an
error.details.missing array naming every absent dependency by its real name. For example:
naive teams list does not return {"items": []}. An
empty list is a claim about your company — that you declared no teams. A 501 is a claim about
this build. They are not the same sentence and a script cannot tell them apart.
If a
naive teams command instead reports that the route is absent rather than the team,
you are talking to an API build where /v1/teams is not mounted at all. The CLI distinguishes
the two: an unmounted path produces the API catch-all’s resource_not_found with a literal
Cannot GET /path message, and the CLI rewrites that into a message naming the missing mount
rather than sending you to check your team name.Coming from the legacy runtime
Four legacy capabilities have no replacement, and that is by design rather than an
oversight — see each page for the recorded reason:
tasks complete, tasks dispatch,
tasks run, the employees provisioning verbs (hire / fire / configure), and
cron pause / cron resume.
Related
naive brain— the company brain a team reads and writesnaive approvals— the company-wide approval queuenaive use— selecting the subject--tenant @selfresolves to