packages/agent-workflows/steward/ toagent/subagents/auditor/). They are colocated but must never share a credential, aThe 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 the 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 this agent 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. Everything below is what to have in place if you do want one.
packages/agent-workflows/ is a self-contained Eve project with its ownpackage.json, npm lockfile, and TypeScript toolchain. It is deliberately not a Bunworkspaces member, so it never collides with temper-cloud's toolchain and the reponpm install inherits the repo's Bun overrides (e.g. onnxruntime-common) andEOVERRIDE.npx eve@latest. The project pins a specific eve version (0.18.1 at time ofpackage.json). @latest pulls a different version and resolves dependenciespackage.json, tripping the same EOVERRIDE. Use the locally installednpm test (vitest, tests/) from inside the agentnpm run typecheck for tsc. Both run in CI via .github/workflows/test-agents-ts.ymlsteward job), alongside the temper-ts suite.file: dependency on clients/temper-ts — and what it coststemper-ts as an npm file: dependency"temper-ts": "file:../../../clients/temper-ts" in package.json) and composesClientCredentials from it, so the TypeScript client and the Ruby gem cannot drift on how atemper-ts publishes, at which pointprebuild, pretest, and pretypecheck all run build:dep,npm ci && npm run build in clients/temper-ts. You never have to remember to buildsteward-agent Vercel project'spackages/agent-workflows/steward; clients/temper-ts is a sibling severaltemper-ts.steward-agent is git-connected. The Vercel project is wired to github.com/tasker-systems/tempermain. So:main produces a production deployment. The steward does auto-deploy onTEMPER_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. Sincetemper-tsis afile:sibling outside the
Root Directory, a deploy launched frompackages/agent-workflows/stewardcannot carry the
dependency — the upload has noclients/temper-tsin it and the build breaks on an
unresolvable import. The git path has no such problem: Vercel clones the whole repo and honors
"include source files outside the Root Directory".Deploy by pushing. If you genuinely must deploy from the CLI, run it from the repo root
so the sibling directory is in the upload — never from the agent directory.
.vercel/project.json) for vercel env pull, vercel env add, andeve dev. Getting one has a sharp edge.eve link and single-team accountseve link's interactive picker enumerates all your Vercel scopes, including yourvercel project ls --scope <account> for each. VercelCould not list Vercel projects in <username>. vercel project ls --format json --scope <username> exited with code 1.
# → Error: You cannot set your Personal Account as the scope.vercel switch <team> does not help — the pickerlink picker handles the personal account correctly:eve link at all: its jobs are (a) link the project — vercel link covers it| 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 id leaves theAuditAuthority::Author, whose denial arm rendersAUDITOR_MODEL_FALLBACKS="" if you would rather the--cogmap grant classifies the auditor as AuditAuthority::Author forcan_modify_resource, and 404s every audit it attempts — with:ro, or nothing. Seedocs/auth/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.auditorFetchtherefore
offers only M2M and a static dev token.
resolve_machine_from_claims (temper-services, the singlekb_machine_clients or aThis invalidates the old two-phase "deploy, let the first tick create a blank profile, then
grant it reach" flow. That flow does not merely no-op now — every call 401s, forever, until the
client id is registered. Register first, with explicit reach, then deploy.
issue requires an instance whose AS is Temper's own. An instance has exactly one issuer, soprovision there.--owner-team, which records the machine's--team membership) and write on the map(s) it tends. The auditor needs read--team membership — and must not havetemper team add-member andtemper cogmap grant --to-profile <agent-profile-id> --write — the profile already exists,--write.temper admin machine rebind,20260720000110_repoint_predicates.sql)has_system_access reads kb_principal_standing alone, and every mint door births a principaldenied — reach does not clear it, and neither provision nor issue does.kb_machine_clients looks right, and every request returns 403 SYSTEM_ACCESS_REQUIRED.Auth0-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 inclients/temper-ts/src/credentials.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.TEMPER_MCP_URL is
required, thrown by the connection's requireEnv guard — working as designed).| Variable | Required | Value / purpose |
|---|---|---|
TEMPER_MCP_URL | yes | The temper-mcp endpoint, e.g. https://temperkb.io/mcp. The agent's sole model-facing seam to Temper. One agent dir points at temperkb.io or a self-hosted instance by this value alone. |
TEMPER_API_URL | yes | The temper REST base, e.g. https://temperkb.io. 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. Requiring this var is exactly what previously made the steward unable to consume a temper-issued credential. |
STEWARD_MODEL | optional | The primary model, as an AI Gateway model id (same form as the default, minimax/minimax-m3). See below — 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 the 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 (by design — see temperFetch). |
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. |
agent/lib/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.agent/connections/temper.ts, which handsmintM2mToken to eve as auth.getToken) and the code schedules (via temperFetch), so the twotemperFetchre-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 (compileAgentConfig) and freezes the resolved modelagent/lib/model-config.ts):STEWARD_MODEL fails the build, not a 3am cron tick.providerOptions.gateway.models, so a typo there surfaces at runtime, only when it is needed.STEWARD_MODEL and redeploying, which is what making itminimax/minimax-m3, falling back to anthropic/claude-haiku-4.5) is a cost choiceVERCEL_OIDC_TOKEN is injected at runtime) — noeve dev; after vercel link,vercel env pull writes it into .env.local.TEMPER_CONNECT_CONNECTOR is still a live strategy in the code, used when TEMPER_M2M_CLIENT_IDTEMPER_MCP_URL.scl_…) and a UID of the form <host>/<name>temperkb.io/steward, not mcp.temperkb.io/steward). Either form is a validTEMPER_CONNECT_CONNECTOR value.vercel connect token … --subject app from the CLI returns "Token subject is not accessible to--subject user --yes to smoke-test interactively.On the 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 (confirmed: the connector produces no app inauth0 apps list). Advertising
the grant on the MCP server is necessary but not sufficient. TheTEMPER_M2M_*vars are the
real path. Connect remains in the code for instances where it does work.
defineSchedule becomes a Vercel Cron0 * * * *, and the auditor dispatch tick at 30 * * * * — half an hourno auditor credential on this deployment —
skipping tick and returns green. That is the intended resting state for a deployment that does[steward-dispatch] tick <correlation-id> starting, then the claimed-job count (or(no drift)), then fans out. [steward-materialize] logs its candidate count. An unregistered401 on /dispatch, not as silence.agent/schedules/steward.ts is a code handler, not a model prompt. It POSTs/api/steward/dispatch — a server-side reap → sweep → enqueue → claim that returns the claimedcogmap_id — then starts one isolated agent session per claimedreceive(worker, …)), each tending a single map. Single-flight and lease-reaping live inkb_workflow_jobs), so a fixed hourly cadence is safe: a still-running map is notagent/schedules/materialize.ts enumerates GET /api/steward/candidates (the readablecorrelationId, logs it, sends it as x-steward-correlation-id, and theinvocation_open inherits it. So akb_invocations.correlation_id), not merely greppable — and thePOST /api/auditor/dispatch runs reap → sweep → expand → group → enqueue → claim:audit_drift_sweep selects findings whose citations are uncovered or have gone stale.resource_auditable_citations(finding, principal) then expands each one into the citationskb_workflow_jobs enforces(cogmap_id, persona, dispatch_type) and a per-finding enqueue would haveON CONFLICT DO NOTHING silently discard all but the first.ClaimedAuditJob.citations is a list of (finding, block, source) triples, every one of whichThis 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.
20260727000050), the payload carried bare finding ids and the dispatch promptkb_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 orphaned envelopes),01:47:34, closed 01:58:38, 17 nodes + 17 facets). If you query the DB partwayopen invocation with no outcome and (depending on timing) few or no acts yetopen past the function's max execution duration AND no new acts are landing. Confirm withinvocation_show (is closed_at set? are acts still accruing?) and invocation_list --status
open — don't conclude from a single mid-run snapshot.open well after the function could have run) means a tickinvocation_close. It is harmless cruft (append-only), but it is a signal worth checking. Thesteward_ingest_delta: cognitive map not found is an access-scoped not-found, NOT an auth401. It--team / --cogmap reach you registered it with. A genuine auth failure (unregistered or401 with an explicit message naming the client id.kb_citation_audits, one row per (block, source) weighed, each carryingaudited_by_profile_id (filled by the projector from the owning event's emitter, never from anGET /api/resources/{id}/citation-audits[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. Once every liveresource_auditable_citations, never against resource_live_citations.citations list on a claimed job means a pre-deploy payload.AuditJobPayload::citations is #[serde(default)] precisely so a job enqueued before the grain404 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 both denialNotFound to match the evidence read's zero-rows→404 (so the write can never becomeAuthor for every finding there — or is sharing the steward's client id.--cogmap suffix before suspecting the data.Historical note (edges). An early prod tick authored 17 nodes but 0 edges: assert_relationshipfailed for every cogmap-homed source with "no rows returned by a query that
expected to return at least one row." The edge-home lookup hard-filteredanchor_table='kb_contexts',
but a steward's authored-4 nodes are cogmap-homed, so it returned zero rows. Fixed: the backend
now home-detects the source and branches kernel-vs-context (assert_edge_from_source_home/assert_kernel_edgeinDbBackend). If you are looking at nodes authored edgeless in that window,
a later tick retrofitsderived_from+ inter-node edges onto them.
packages/agent-workflows/steward/agent/lib/temper-auth.ts — the strategy order, temperFetch,TEMPER_M2M_AUDIENCE is optional.packages/agent-workflows/steward/agent/lib/model-config.ts — the model resolution and why it ispackages/agent-workflows/steward/agent/schedules/steward.ts — the fan-out dispatcher.clients/temper-ts/src/credentials.ts — the shared ClientCredentials mint, correct againstdocs/auth/machine-token-contract.md §C — the auditor's credential + reach constraints, and why apackages/agent-workflows/steward/agent/schedules/auditor.ts — the auditor dispatcher, itspackages/agent-workflows/steward/agent/subagents/auditor/instructions.md — what the auditorinternal/superpowers/specs/2026-07-01-t5-eve-steward-agent-directory-design.md — the steward directory design.internal/superpowers/specs/2026-07-05-steward-fan-out-drift-sweep-design.md — the fan-out design.internal/superpowers/specs/2026-07-23-set5-adversary-citation-audit-design.md — the citation-audit