The auditor's cron is live, but the auditor itself is optional — and that combination is
safe. Eve creates one Vercel Cron Job per file inagent/schedules/, so a schedule's
location is the on/off switch; enabling or withdrawing it is agit mvplus an operator
decision, never a side effect of a merge. A deployment that sets no auditor credential
no-ops the tick with a log line instead of failing, so you can run the steward with no
auditor at all and get a quiet, green cron. A deployment that sets a partial credential
still fails loudly — that is a misconfiguration, not an absence.
bootstrap-team-self-cognition.md.package.json, npm lockfile, and TypeScript toolchain. The canonical deployment isworkspaces member, so itvercel.json is the manifest inlined below.temper-ts) as an npm file: dependency that lives outside its root{
"$schema": "https://openapi.vercel.sh/vercel.json",
"installCommand": "npm install",
"buildCommand": "npm run build"
}prebuildbuild:dep, which installs and builds temper-ts before the agent compiles, soTEMPER_MCP_URL is required, thrown by the connection'srequireEnv guard).TEMPER_M2M_* orSTEWARD_MODEL value in the dashboard does nothing until the next deployment. A cron runningDo not run eve deploy/vercel deployfrom inside the agent directory. The CLI
uploads only the directory it is invoked from, and the siblingtemper-tsdependency lives
outside the root directory — a CLI deploy launched from the agent directory cannot carry
it, and the build breaks on an unresolvable import. The git path clones the whole repo and
honors "include source files outside the Root Directory". Deploy by pushing.
.vercel/project.json) for vercel env pull and eve dev.link picker rather than eve link: eve link enumerates all youreve link hits the personalvercel env pull writes the AI Gateway key into .env.local for localeve dev. You do not need eve link at all.| steward (root agent) | citation auditor (declared subagent) | |
|---|---|---|
| Credential | TEMPER_M2M_* / TEMPER_CONNECT_CONNECTOR / TEMPER_TOKEN | TEMPER_AUDITOR_M2M_* / TEMPER_AUDITOR_TOKEN — no Connect |
| Model | STEWARD_MODEL (default minimax/minimax-m3) | AUDITOR_MODEL (default anthropic/claude-haiku-4.5) |
| Cogmap reach | --cogmap <ref> — write, it authors into its map | --cogmap <ref>:ro — read-only, or omitted entirely |
kb_events.emitter_entity_id. A shared client idAuditAuthority::Author, whoseAUDITOR_MODEL_FALLBACKS="" if you would rather the tick fail than collapse the personas.--cogmap grant classifies the auditor asAuditAuthority::Author for every finding in that map via can_modify_resource, and:ro, or nothing. See machine-token-contract.md §C.Connect is not an option for the auditor, structurally. A Vercel Connect connector is
deployment-scoped, and both agents share one deployment — so a Connect token would
authenticate the auditor as the steward, which is precisely the collapse above. The
auditor's fetch helper therefore offers only M2M and a static dev token.
resolve_machine_from_claims (the single machine entrykb_machine_clients or a 401.issue requires an instance whose AS is Temper's own. An instance has exactly oneprovision there.--owner-team, which records the--team membership) and write on the map(s) it tends. The--team membership — andtemper team add-member andtemper cogmap grant --to-profile <agent-profile-id> --write — the profile already exists,--write.temper admin machine rebind, which binds the new client id to the existing agent profile.has_system_access reads kb_principal_standing alone,denied — reach does not clear it, and neitherprovision nor issue does.kb_machine_clients looks right, and every request returns 403 SYSTEM_ACCESS_REQUIRED. IfAuth0-fronted (AUTH0_*) | Temper-as-AS (AS_ISSUER set) | |
|---|---|---|
| Who mints the secret | Auth0 — you create the M2M app | Temper — machine issue, printed once |
| Register with | temper admin machine provision --client-id <auth0-id> | temper admin machine issue (registers as it mints) |
| Client id shape | Auth0's opaque id | tmpr_… |
*_M2M_AUDIENCE | required — must equal the API's AUTH_AUDIENCE | omit it — the AS ignores a request-supplied audience and mints with its server-side AS_AUDIENCE |
*_M2M_TOKEN_URL | https://<tenant>.auth0.com/oauth/token | https://<instance>/oauth/token |
| Mint body encoding | form-encoded or JSON (Auth0 tolerates both) | form-encoded only |
Admission (access approve) | required | required |
kb_machine_clients registration | required | required |
Auth0 is the more permissive issuer, so it hides AS-mode defects. Both rows above where
the variants differ in tolerance rather than value are real bugs that stayed green for
as long as Auth0 was the only issuer any client faced. The JSON-vs-form one is documented incredentials.ts: RFC 6749 §4 mandates form encoding, Auth0 tolerates JSON, and Temper's AS
reads the body withreq.formData()— so a JSON mint never reaches its grant branch and
fails only on AS. Anything verified only against Auth0 is verified against the lenient
case. UseClientCredentialsfromtemper-tsrather than hand-rolling a mint; it is the
one implementation that is correct against both.
vercel env add <NAME>) before deploying.| Variable | Required | Value / purpose |
|---|---|---|
TEMPER_MCP_URL | yes | The temper-mcp endpoint, e.g. https://<instance>/mcp. The agent's sole model-facing seam to Temper. Points at your instance by this value alone. |
TEMPER_API_URL | yes | The temper REST base, e.g. https://<instance>. Distinct from TEMPER_MCP_URL; used by the code schedules' direct POST /api/steward/dispatch, GET /api/steward/candidates, and POST /api/cognitive-maps/{id}/materialize. |
TEMPER_M2M_CLIENT_ID | prod | The machine client id — the Auth0 M2M app's id, or the tmpr_… id from machine issue. When set, the agent mints its own client_credentials token and this strategy wins over Connect and TEMPER_TOKEN. |
TEMPER_M2M_CLIENT_SECRET | prod | The client secret. A Vercel env var only — never in code, never seen by the model. |
TEMPER_M2M_TOKEN_URL | prod | The issuer's token endpoint: https://<tenant>.auth0.com/oauth/token for provision, or your own instance's https://<instance>/oauth/token for issue. |
TEMPER_M2M_AUDIENCE | only for an external IdP | The API audience the minted token targets (must equal the API's AUTH_AUDIENCE). OMIT it for a temper-issued (tmpr_) credential — Temper's AS ignores a request-supplied audience entirely and mints with its server-side AS_AUDIENCE. |
STEWARD_MODEL | optional | The primary model, as an AI Gateway model id (same form as the default, minimax/minimax-m3). A change needs a redeploy, and a typo fails the build. |
STEWARD_MODEL_FALLBACKS | optional | Comma-separated AI Gateway model ids, tried in order after the primary fails. Defaults to anthropic/claude-haiku-4.5. Deduped, and the primary is dropped from the list if repeated there. |
TEMPER_CONNECT_CONNECTOR | fallback | Vercel Connect connector id. Used only when TEMPER_M2M_CLIENT_ID is unset. On an Auth0-fronted instance this cannot mint an app token — see below. Steward only — a connector is deployment-scoped, so it can never identify the auditor. |
TEMPER_TOKEN | dev only | An already-OAuth-obtained temper token. Drives eve dev. Cannot re-mint, so a 401 on it is terminal. |
TEMPER_AUDITOR_M2M_CLIENT_ID | auditor, prod | The auditor's own machine client id — a second Auth0 M2M app, or a second tmpr_…. Never the steward's. |
TEMPER_AUDITOR_M2M_CLIENT_SECRET | auditor, prod | The auditor's client secret. Vercel env only. |
TEMPER_AUDITOR_M2M_TOKEN_URL | auditor, prod | Same issuer as the steward's — one instance, one issuer. Only the credential differs. |
TEMPER_AUDITOR_M2M_AUDIENCE | external IdP only | Same rule as TEMPER_M2M_AUDIENCE: required for Auth0, omitted for a tmpr_ credential. |
TEMPER_AUDITOR_TOKEN | dev only | Static auditor bearer for eve dev. The unset-with-no-CLIENT_ID case throws rather than silently falling back to the steward's identity. |
AUDITOR_MODEL | optional | The auditor's primary model. Defaults to anthropic/claude-haiku-4.5 — deliberately not the steward's default. Same build-time freeze and redeploy-to-change semantics. |
AUDITOR_MODEL_FALLBACKS | optional | Defaults to minimax/minimax-m3 (the steward's primary — a documented availability trade). Set to "" to make the tick fail rather than collapse the two personas onto one model. |
temper-auth.ts, and is machine-identity-first:TEMPER_M2M_CLIENT_ID present → mint via the OAuth client_credentials grantClientCredentials from temper-ts). The production path.TEMPER_CONNECT_CONNECTOR → a Vercel Connect app token.TEMPER_TOKEN → a static bearer.mintM2mToken to eve asauth.getToken) and the code schedules (via temperFetch), so the two can never drift on howtemperFetchre-mints once on a 401 and retries. Refresh-ahead-of-expiry is not
sufficient: a schedule resolves a token, then fans out N fetches, and Temper's AS mints
900-second tokens by default — a tick outliving its token is ordinary, not exotic.
Exactly one re-mint: a 401 that survives a fresh token is a real authorization failure
(revoked credential, missing reach), and retrying forever would only bury it. A strategy
that cannot mint (TEMPER_TOKEN) gets its 401 back untouched.temperFetchalso carries
the 5xx cold-start retry — use it, never a barefetch.
agent.ts at build time and freezes the resolved model into the compiledmodel-config.ts):STEWARD_MODEL fails the build, not a 3am cron tick.STEWARD_MODEL and redeploying.minimax/minimax-m3, falling back to anthropic/claude-haiku-4.5) is a costVERCEL_OIDC_TOKEN is injected at runtime) —eve dev; aftervercel link, vercel env pull writes it into .env.local.TEMPER_CONNECT_CONNECTOR is still a live strategy in the code, used whenTEMPER_M2M_CLIENT_ID is unset. temper-mcp is a full OAuth 2.0 server and serves theTEMPER_MCP_URL.scl_…) and a UID of the form<host>/<name>. Either form is a valid TEMPER_CONNECT_CONNECTOR value.On an Auth0-fronted instance the Connect apppath cannot mint a token, and this is not
fixable from Temper's side. Auth0 issuesclient_credentialsonly for a registered M2M
application, and the Connect connector has no Auth0 M2M app behind it — its dynamic
registration does not create one. TheTEMPER_M2M_*vars are the real path. Connect
remains in the code for instances where it does work.
defineSchedule becomes a Vercel Cron Job,0 * * * *, and the auditor dispatch tick at 30 * * * * — half anno auditor credential on this deployment —
skipping tick and returns green. That is the intended resting state for a deployment that[steward-dispatch] tick <correlation-id> starting, then the claimed-job count (or(no drift)), then fans out. [steward-materialize] logs its candidate count. An401 on /dispatch, not askb_invocations: status / outcome / closed_at / correlation_id) and its actskb_events joined on invocation_id). Read them with the MCP tools invocation_show <id>invocation_list --status open (any orphanedAlways snapshot prod before a hand-run DDL/data change. Create a copy-on-write Neon
backup branch first (neonctl branches create … --parent main); restore withneonctl branches restore main <backup-name>.
open invocation with no outcome and (depending onopen past the function's max execution duration AND noinvocation_show (is closed_at set? are acts stillinvocation_list --status open — don't conclude from a single mid-runopen well after the function could have run) meansinvocation_close. It is harmless cruft (append-only), but it is a signal worthsteward_ingest_delta: cognitive map not found is an access-scoped not-found, not an401. It means the credential authenticated but its profile has no reach to that map — go--team / --cogmap reach you registered it with. A genuine auth401 with an explicit message naming thekb_citation_audits, one row per (block, source) weighed, eachaudited_by_profile_id (filled by the projector from the owning event'sGET /api/resources/{id}/citation-audits reads the attributed trail for any finding you can[auditor-dispatch] tick <corr>: claimed N job(s): <job>→<cogmap>(M citation(s) across K finding(s))claimed 0 job(s) (no auditable citations) is the steady state, not a fault. Oncecitations list on a claimed job is skipped, not failed. The schedule skips404 on an audit write is the self-audit denial arm, and it is almost always a reachAuditAuthority is readability minus a self-audit denial, and bothNotFound (so the write can never become an existence oracle). IfAuthor for every finding there — or is sharing the steward's client id. Check the--cogmap suffix before suspecting the data.This is why the auditor must be its own principal, restated from the write side. The
filter is per principal, not per citation: a citation another principal weighed is still
offered, because cross-principal audit is the entire premise. Point two agents at one
client id and they become one principal — each silently suppressing the other's remaining
work.
machine-token-contract.md §C —temperFetch: temper-auth.ts.model-config.ts.steward.ts, materialize.ts, auditor.ts.ClientCredentials mint, correct against both issuers: credentials.ts —instructions.md.