Skip to main content
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 except list. 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:
That refusal is deliberate. A defaulted tenant on a multi-tenant board is how one customer’s work ends up in another customer’s ledger.

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 is submit. 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 $.
submit answers 501 not_configured on this build. The refusal names three missing dependencies: there is no durable dispatcher seam to hand a goal to (forwarding to the legacy Hermes dispatcher would open a second write path into a frozen runtime), manifest_digest and required_attestations have no storage, and the idempotency middleware is an in-process Map with no cross-replica sharing — so --idempotency-key cannot be honoured honestly.To send work today, use the legacy surface: naive tasks create or naive ceo run. Both still work and are not going away.

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 in hints:
Fenced: 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.
--fail-on-unverified is a hermes check, and on a durable tenant exit 0 does not mean “attested”.unverified is written by naive’s own completion adjudicator onto its task mirror. A durable tenant’s board is not that mirror — it is the runtime’s own board, translated — and the runtime’s task states are a closed set (todo ready doing review done blocked) with no unverified among them. The flag therefore cannot fire there. It is not finding nothing; it is unable to find anything.naive teams tasks --fail-on-unverified prints a warning on stderr when the board it read came back with provider: "durable", so a CI job does not silently inherit a check that cannot fail. stdout is untouched — still exactly one JSON document.On that runtime the equivalent signal is attempts: a rejected completion is retried rather than recorded, so attempts > 1 on a terminal card means the reviewer turned at least one completion down. See Board & cards.--fail-on-terminal on watch keeps a real reach on both runtimes, but only failed of its three statuses occurs on the durable one.
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 with 501 not_configured and an error.details.missing array naming every absent dependency by its real name. For example:
This is deliberate and it is the reason 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.