Skip to main content

Agent platform (MCP)

Jentrix exposes its entire task surface as an MCP server, so AI agents operate boards with the same operations, authorization, and audit trail as the UI. This page is the platform reference: tokens, scopes, the tool catalog, and the contracts (errors, idempotency, concurrency) agents rely on. For client-by-client connection instructions, see Agent setup variants.

Endpoint and authentication

EndpointUse
https://tm.jentrix.ai/api/mcpFlat Streamable HTTP server: the full tool surface is listed immediately.
https://tm.jentrix.ai/api/mcp-dynamicSession-based Streamable HTTP server: starts with discovery tools and loads only the tools the client selects.

Both endpoints use Authorization: Bearer <token>. The dynamic endpoint also returns an Mcp-Session-Id; send it on later requests and DELETE the session when finished.

Two bearer types are accepted:

  • Personal access tokens (tm_…) — minted at /account/tokens. The right choice for your own scripts, cron agents, and CLIs.
  • OAuth 2.1 access tokens (tmo_…) — issued by the built-in authorization server for third-party clients (see OAuth).

CLI access

Every tool on this page is also reachable from the shell via the jentrix CLI — a thin client over this same MCP endpoint, so it inherits scopes, workspace pinning, rate limits, idempotency, and freshness unchanged. It is a peer of connecting an MCP client directly: jentrix task list --board X --json | jq …, an escape hatch (jentrix tool <name> --args '<json>') for any tool, and frozen exit codes for scripting. See the CLI reference.

Tokens, scopes, and workspace pinning

Create tokens at /account/tokens. Each token has:

  • Scopes — exact-match classes, no hierarchy:

    ScopeAllows
    readAll list_* / get_* / search tools and MCP resources
    writeCreating and updating tasks, comments, subtasks, contacts, sidecars, links, labels, columns, boards
    adminStructural/destructive and outward-facing tools: convert_board_kind, webhooks, automations

    A tool call outside the token's scopes returns a structured FORBIDDEN error before anything runs. (Tokens created before scoping existed have full access until rotated — the tokens page nags about them.)

  • Workspace pinning (recommended) — restrict the token to a single workspace. Every authorization check rejects anything outside it, and list_workspaces only returns the pinned one.

  • Agent identity — an optional display name and emoji. Every mutation the token makes is badged in the UI as via <emoji> <name> (activity feeds, comment threads, the home digest). The identity is snapshotted into each activity at write time, so the audit trail survives token renames and deletion.

  • Usage tracking — the tokens page shows per-token daily requests/mutations for the last 7 days plus the token's recent actions.

Tokens are personal credentials — only their owner manages them at /account/tokens. For governance, workspace admins additionally see every token pinned to their workspace (any member's) in workspace settings — owner, scopes, last used — and can revoke them there. Unpinned tokens aren't listed; removing the member disables those, since every call re-checks the owner's role.

Least-privilege examples: a read-only digest bot needs just read; a triage bot needs read + write; only a bot that manages webhooks or automation rules needs admin. Pin everything to one workspace unless it genuinely needs more.

Rate limits

60 requests per minute per token. Over the limit, calls still authenticate but every tool returns a RATE_LIMITED error envelope with retryAfterSeconds — agents should back off and retry, not treat it as a dead token.

Bulk export (GET /api/export)

The workspace/board export at GET /api/export?workspaceId=…[&boardId=…]&format=csv|json accepts the same bearer tokens as the MCP surface, so an agent can pull a full board dump without a browser session. JSON nests boards → columns → tasks; CSV is one flat task row per line; archived boards, columns, and tasks are excluded from both.

The endpoint reads the Authorization header first, and the bearer scheme takes strict precedence:

  • Any syntactically present Bearer <credential> (case-insensitive scheme, any prefix) is authenticated exclusively on the bearer path. There is no tm_/tmo_ pre-filter — prefix legality is the token verifier's job.
  • A verification failure — empty, malformed, unknown, revoked, or expired credential — returns 401 and never falls through to session auth, even if a valid session cookie is also present.
  • A valid token needs the read scope (a grandfathered empty-scope token has full access); anything less is 403. A workspace-pinned token exporting any other workspace is 403.
  • When the token is a governed worker's credential (an agent-credential-linked token, or a run-scoped bearer), the export is subject to that worker's compiled permission revision exactly as every MCP tool call is: the compiled read scope-class check and the compiled workspace authz boundary both apply, so a worker whose published/pinned permissions refuse the read — or bind it to another workspace — gets 403 even though its owner is a member. The export is never a way around the post-claim permission boundary.
  • The project boundary is enforced by failing closed, not by narrowing. GET /api/export authorizes at workspace grain and has no project axis — it names a workspace and optionally a board, never project ids — so a governed worker whose compiled revision binds it to a non-empty set of projects is 403, even for its own workspace. The endpoint refuses rather than invent a board/task mapping for a project-scoped restriction (which would be a false narrowing), following the permission core's rule that a restriction which cannot be applied must refuse, never silently widen. A worker with an empty project boundary means "the whole workspace" and exports unchanged; honoring a project boundary on export would be a separate change that gives the endpoint a project axis. Both this refusal and the workspace boundary above are resolved from the same binding — the revision the credential is actively governed by for the requested workspace (the run it is executing, via that run's claim-time pin; otherwise its workspace-pinned or template-head revision) — so widening a worker's template head mid-run cannot re-open the export to a run whose claim pinned a project boundary.
  • Each successful bearer read is counted in the token's existing daily usage rollup — no separate export counter.

Only a request with no Authorization header, or a non-Bearer scheme, uses the operator session (unchanged): a signed-in workspace member exports, everyone else gets 401. Either way the workspace-role check and the export payload are identical.

Error envelope

Failed tool calls return isError: true with a machine-readable JSON body:

{ "error": { "code": "CONFLICT", "message": "…", "hint": "…" } }
CodeMeaning
FORBIDDENMissing scope, wrong workspace, or insufficient role
NOT_FOUNDEntity doesn't exist or isn't visible to you
INVALID_INPUTFailed validation (the message says which field)
RATE_LIMITEDOver 60 req/min — carries retryAfterSeconds
CONFLICTIdempotency-key reuse with different args, or a stale write (see below)
INTERNALUnexpected server error

The hint is written for agents — follow it instead of retrying blindly.

Concurrency: expectedUpdatedAt

Every update_* tool and move_task accept an optional expectedUpdatedAt (the entity's updatedAt you last read). If someone changed the entity since, the call fails with CONFLICT — and the envelope embeds the entity's current state under error.current, so you can merge and retry without an extra read. Omit the field to last-write-wins.

Idempotency: idempotencyKey

Every create_* and bulk_* tool accepts an optional idempotencyKey (24-hour memory, scoped per token):

  • Same key + same arguments → the stored response is replayed; nothing is created twice.
  • Same key + different arguments → CONFLICT.

Derive keys from your trigger (e.g. a webhook delivery id) so retries are naturally safe.

create_workspace is the one exception where the key is required: provisioning a workspace mints a workspace, its OWNER membership, and the default governance pack, so a retried CI step must never mint a second one. A key-less create_workspace call is refused with INVALID_INPUT at the schema boundary, before the handler runs. Every other create tool — invite_member included — keeps the key optional.

Membership governance

Workspace membership changes are governed in the ops core, so the gate holds on every surface (there is no self-serve membership MCP tool yet — the enforcement lives in the core, not an MCP wrapper). Three frozen action keys are evaluated through the Policy Engine before the write:

Action keyGoverns
workspace.member.inviteInviting a member (and minting an owner invite)
workspace.member.removeRemoving a member
workspace.member.set_roleChanging a member's role

Every new workspace is seeded with a default pack — a REQUIRE_APPROVAL rule (overridable, so an admin can approve) for each of the three keys, one rule per key. An admin resolves a held change exactly as for any other policy evaluation (resolve_approval). Seeding is idempotent and only runs on the create path, so pre-existing workspaces are untouched.

Concurrency here is value-CAS, not expectedUpdatedAt — a workspace and a membership row carry no updatedAt. Pass expectedName when renaming a workspace, or expectedRole when removing a member / changing a role; a stale value fails with CONFLICT carrying the current one.

Response sizes: response_format

list_tasks, get_task, list_contacts, and the harness reads (list_harnesses, get_harness, list_harness_stages, list_harness_findings) take response_format: "concise" | "detailed". Lists default to concise (≤400 chars per task, ≤20k chars per page — a frozen contract); get_task and get_harness default to detailed. Paginated lists return totalCount and a notice when truncated. Every task in any response includes its number and human key (STK-123), and get_task accepts { workspaceId, number } as an alternative to the task id.

Dynamic tool loading

Connect tool-count-sensitive clients to /api/mcp-dynamic. A new session exposes only search_tools, describe_tools, and load_tools; tools loaded in one request become callable on the next request and remain selected in the session's Postgres-backed state across application instances. Sessions expire after 30 minutes. Their loaded-tool manifest is capped at 48 KiB, evicting the oldest selections when necessary; after expiry, create a new session and reload.

The original /api/mcp endpoint remains the compatibility surface for clients that expect every tool in tools/list.

Long-running MCP Tasks

run_eval and request_harness_stage_run advertise optional MCP Tasks support. Task-capable clients can create a task, poll tasks/get, cancel it with tasks/cancel, and collect the final value through tasks/result. Other clients receive the normal synchronous-looking tool result while the SDK polls for them.

Task handles expire after 10 minutes and currently live in process memory. A serverless cold start can therefore lose an outstanding handle; the durable eval, harness, job, and run records remain the source of truth for recovery.

Connector authorization elicitation

authorize_connector uses URL-mode MCP elicitation to send an admin to Jentrix' connector authorization flow. Secrets never travel in MCP arguments or results; the successful result contains only a brokered credentialRef. The tool requires admin scope plus the workspace ADMIN role, and clients without elicitation support receive INVALID_INPUT instead of a partially configured connector.

Tool catalog (242 tools)

The surface is frozen by contract tests — names, annotations, and output schemas only change deliberately. The curated tables below cover the core task-platform families; the M13 quality-plane families (evals, immutable releases, experiments) are documented in Quality plane, the M14 Skill Registry family (list_skills / get_skill / convert_playbook_to_skill) in Skill registry, and the harness family in Harness. The complete, machine-generated list of every tool is the full generated catalog at the end of this section — pinned to the live surface by the registry drift test and reachable at runtime via search_tools.

Orientation and reads (read scope)

ToolWhat it does
list_workspacesWorkspaces visible to the token
get_board_snapshotBoard + columns + concise tasks in one call — start here
search_tasksWorkspace-wide search by exact task key or full text (titles, descriptions, comments)
find_similar_tasksSemantic similarity — by example task (taskId) or free-text query (workspaceId + query). Embeddings refresh on a ~5-minute cron, so very recent edits may not match yet. Returns an empty result with a notice (never an error) when semantic search isn't configured (no embeddings provider key)
search_toolsDiscover this server's own tools: capability manifests (scope, safety annotations, parameters) filtered by query / scope / task profile, plus the derived task profiles (include_profiles). Reads the CI-generated, drift-pinned registry — see the generated catalog and task profiles below
list_activityActivity feed by task or board, filterable by time, type, and source (ui / mcp / automation) — separates humans from agents
list_boards / list_columns / list_labels / list_membersWorkspace structure (list_boards returns active and archived boards with archivedAt, so archived ids can be passed to unarchive_boards)
list_tasks / get_taskTasks (filters, pagination, concise/detailed)
list_comments / list_attachmentsTask discussion + files (attachments via short-lived signed URLs)
list_contacts / list_initiatives / list_task_linksDirectory, roadmap, and link-graph reads
list_github_linksTasks linked to GitHub issues (url, state, last-synced) — see GitHub sync
list_delivery_itemsThe delivery graph's pull-request links: PR state, link state (MATCHED/ORPHAN), owning task key, aggregate CI signal. Filter by board, linkState (ORPHAN = adoptable PRs), or PR state — see GitHub sync
get_pull_requestOne PR link in full: review decision, mergeable state, head/base branch, CI runs with check counts, redacted CI-evidence count, GitHub deep link
list_ci_runs / get_ci_runCI runs (filter by PR or task) with check roll-up counts; the single-run read adds the individual child checks
list_projects / get_projectProject containers with owner, links, milestones, risks, and counts — see Projects & decisions
list_decisions / get_decisionThe Decision Register: status, options, dissent, supersession lineage, and revision history
list_work_orders / get_work_orderAgent-executable briefs with versions, status, and the latest pinned launch
get_context_packA Work Order version's versioned, checksummed source/tool/policy manifest + token-budget estimate
list_agent_jobs / get_agent_jobPlan → review → execute → review workflow jobs: phase, turn, runs, findings, and approval gates — see Agent workflows. Harness-bound jobs also carry harnessRun (run id, repo, base branch) for reviewer grounding. On a harness stage execution job get_agent_job additionally carries a harness block (run/stage ids, branch/repo/base + strategy/template, commit policy, acceptance criteria) so the harness-aware runner detects and drives the stage
list_agents / get_agentThe Agent Registry: teammate identity (role, provider, runtime, trust level, status), scopes/skills, eval & failure caches, linked credentials (no secret material), and a computed launch-eligibility breakdown
list_control_tower_itemsThe Control Tower: OPEN, actionable operational items ranked highest-severity-then-oldest. Agent workflow, delivery, harness, SLA, eval, policy, and credential queues are live. Governed memory review lives on the dedicated Memory page; the legacy MEMORY_CANDIDATES Tower adapter has no MemoryItem source. Each item carries a queue, stable sourceKey, an "explain why this is here" reason, severity, optional project/agent, and valid actions
list_policies / get_policy_evaluation / list_policy_evaluationsThe Policy Engine: layered rules and the append-only evaluation feed — see Policies
list_leases / get_leaseConflict control: active/historical work leases (target, holder, mode, conflict policy, expiry, takeover/handoff lineage)
list_specs / get_specExecutable specs + their immutable versions (user story / constraints / non-goals / test plan / rollback / evidence / definition of done) with weak-spec quality warnings and a 0-100 score
list_artifactsFirst-class outputs (PR/diff/log/screenshot/eval report/run summary/…) linked to task/project/run/Work Order, filterable by type/creator. Internal R2 objects expose hasObject; redacted artifacts are tombstones
list_outcomesOutcome metrics (cycle time, reopen rate, human edits, approval burden, …) split by agent-assisted vs human-only cohort; substrateGated kinds await the run-ledger/eval substrate
list_sourcesGoverned knowledge sources (trust/freshness/privacy/owner/client/supersession); superseded excluded unless includeSuperseded
list_incidents / get_incidentPostmortems: timeline, root cause, affected entities, rollback log, policy/eval gaps, linked decisions/policies/credentials/artifacts, and follow-up tasks — see Later hardening
list_simulations / get_simulationDry-run plans + their projectedEffects (diffs, projected notifications/activities/webhooks/automations/cost/credentials/approvals/lease-conflicts) computed before any side effect
list_playbooksPromoted reusable workflows with trigger, required policy/eval coverage, and enabled state
list_communication_sourcesGoverned Slack/email/calendar/doc connectors (extend a source record; outboundEnabled gates drafts)
list_linked_messagesStakeholder messages/meetings linked to a task/project/contact/decision/Work Order, with meeting commitments
list_harnesses / get_harnessHarness Automation runs (the governed spec→plan→stage→commit build loop): status, current stage, freeze state, and the LIVE registry projection — per-stage status/gate/branch/commit/evidence + done/total — generated from the stage records, not a stored markdown blob. The get_harness output is a frozen projection; read pending gates, active/stuck runs, and operability blockers through the companion calls (list_harness_stages, list_agent_jobs / get_agent_job, list_control_tower_items) rather than expecting them here. See Harness Automation
list_harness_stagesA run's stages in order: status, the human gate it parks at (plan_review/execution_review/commit_gate), branch, next build action, open-finding + evidence counts, launchable (whether the stage has a backing Work Order — false for a draft stage; it means "not a draft", not "launchable right now", since serial ordering + run/stage status still apply at launch), and (detailed) Work Order/lease ids + acceptance criteria
get_harness_artifactOne harness artifact by its link id (from get_harness): kind, version, checksum, supersession lineage, producing agent/model, plus a short-lived signed download pointer — the body is never inlined and the title is redacted
list_harness_findingsA run's review findings (severity / category / status / target stage), filterable by stage, status, or severity

Harness writes (write scope)

The Phase-2 tools that let an external runner drive the harness build loop. No gate-approval, merge, or push tool exists, and an agent has no path to unfreeze its own run — those human boundaries stay UI-first. (Freeze/resume are exposed as admin tools below for operators, never for a run's own agent token.) See Harness Automation for the full system and Agent workflows for the loop engine it reuses.

ToolWhat it does
create_harnessCreate a harness run over a task/project/repo scope (idempotencyKey); returns its id + slug
submit_harness_artifactAttach a generated artifact (PRD/plan/prompt/review/summary/diff/test-evidence) — capture new redacted markdown or classify an existing visible Artifact (idempotencyKey); returns the link or null when the artifact store is unconfigured
submit_harness_reviewReviewer verdict (APPROVED / CHANGES_REQUESTED) + structured findings on a stage/plan job — the SAME M7 review contract; the findings normalize into the harness finding table so the validation gate + re-review continuity see them (idempotencyKey)
update_harness_findingResolve a finding (ACCEPTED / FIXED / REJECTED / VERIFIED / SUPERSEDED, or OPEN to reopen a fixed/verified finding on a regression; rejection/supersession need a note) with an expectedUpdatedAt optimistic-concurrency guard (stale → CONFLICT embedding the finding's current status/resolution)
request_harness_stage_runLaunch the run's first undone stage (execution is serial, so it is the only one in line): acquires the stage lease + opens a fresh M7 AgentJob (idempotencyKey). The stage must have executable backing — a first-undone draft stage (no Work Order, e.g. a create_harness_stage draftOnly placeholder) is rejected as a draft, never launched. Optional stageId asserts which stage you expect (a mismatch is rejected, never launches the wrong one)
submit_harness_stage_resultRecord a stage's structured execution output (changed files, commands, tests run/not-run, risks, and the branch/commit + diffLines it landed on) as a redacted execution-summary + link diff/test evidence (idempotencyKey); returns the validation gate (validatable + missing evidence + blocking-finding count). When a commit is reported, pass your claimed jobId — it binds the commit report to your attempt so a superseded attempt's late report is dropped (no commit-on-red), and it is what the M9.4 commit boundary later pushes/PRs per policy

Harness admin (admin scope)

The run-level operator controls — freeze is one of the two controls the builder keeps (the other is the gate). Freeze is enforced at every Jentrix boundary immediately: claim_* rejects, submit_* rejects/parks, heartbeat_* returns a frozen signal so the runner pauses its subprocess on its next tick, and brokered repo:* / deploy:* / cloud credential requests fail closed. Resume restores the run's exact recorded prior status and is fully attributed. These controls require the admin scope and the admin role — an agent's own run token can never reach them.

ToolWhat it does
freeze_harnessFreeze a run (the human STOP control): rejects new claims, parks submissions, signals frozen on heartbeat, and fails brokered credentials closed. Records the freezing actor + reason (redacted) + the prior status to restore. expectedUpdatedAt guard
unfreeze_harnessResume a frozen run — restores its exact prior status and clears the freeze. An operator control (admin scope + role): no agent self-resume. Records the resuming actor + reason. expectedUpdatedAt guard
cancel_harnessCancel a run while preserving its audit history (artifacts/findings/decisions/activity stay queryable) — a terminal state move, not an archive/delete
set_harness_policyChange a run's loop policy: iteration and auto-retry budgets, ordering, reviewer/human-gate requirements, auto-advance (including the first decomposition loop after spec acceptance), and branch strategy. expectedUpdatedAt guard
set_harness_agentsSet the run's active PLANNER + REVIEWER agents so it can launch execution jobs — pass an AgentProfile id per role (plannerAgentId/reviewerAgentId); each must be ACTIVE, usable in the workspace, with exactly one usable linked PAT. The token is resolved server-side from the agent, never accepted from input. Supersedes prior assignments (archived, not deleted). Idempotent (re-applying the same agents is a no-op) with an expectedUpdatedAt guard so two admins can't silently clobber each other (stale → CONFLICT embedding the run's current planner/reviewer/ready state)
start_harness_authoringMove a DRAFT run into the authoring loop: DRAFT → AUTHORING, seed a run-owned draft Spec, and open the run-bound authoring agent job (the planner/reviewer run the review loop toward the run's spec), emitting the normal agent.handoff. The run-owned spec is a scaffold seed pinned on the run, so two harnesses on one anchor task never share a draft (wiring the planner's reviewed output into that spec's versions is the harness-aware runner's job, H10.6). Requires an anchor task, a repository on the run, and active planner+reviewer agents (set via set_harness_agents). Approving the resulting plan-approval gate (UI / Control Tower — no MCP self-approval) accepts the run-owned spec, pins its SpecVersion, and advances the run AUTHORING → PLANNING. A BLOCKED authoring job can be superseded in place by calling the tool again; the blocked job is archived and a fresh attempt is opened. expectedUpdatedAt guard. Returns the run's new status + the opened job id
start_harness_decompositionMove a PLANNING run (spec validated + pinned) into the decomposition loop: open the run-bound decomposition agent job, emitting the normal agent.handoff. The planner proposes ordered stages via submit_plan's harnessStages (each tracing the accepted spec criteria it delivers — keyed to the submitting job and persisted as the proposal the accept gate mints); the reviewer validates the plan version the same submit freezes. Approving the resulting plan-approval gate (UI / Control Tower — no MCP self-approval) validates that every accepted spec criterion is covered (before the gate is consumed and again inside the mint tx, against that job's persisted proposal), then mints the ordered stages + 1:1 Work Orders, snapshots the registry, and advances the run PLANNING → READY — an uncovered plan is rejected (naming the orphaned criteria) and the gate stays pending. Requires an anchor task, a repository, and active planner+reviewer agents; at most one live decomposition job per run. A BLOCKED decomposition job can be superseded in place by calling the tool again. The run stays PLANNING until the gate mints the stages. expectedUpdatedAt guard. The generic request_agent_workflow cannot start decomposition; this harness-bound tool is the only way in. Returns the run's status + the opened job id
create_harness_stageHand-author a stage directly on a run — the fallback to the coverage-enforced spec→plan decomposition path (start_harness_decomposition). admin scope + ADMIN role — authoring a stage changes the run's executable plan scope. Appends it after the existing stages (next 1-based ordinal) carrying the same full plan a decomposition proposal does — title, acceptance criteria, traced spec criteria, expected branch behavior, suggested tests, risk notes, branch, commit policy (idempotencyKey); it still bypasses decomposition's coverage enforcement. A non-draft stage gets executable backing: it auto-mints a 1:1 Work Order when workOrderId is omitted, so it can be passed to request_harness_stage_run (the run must be task-anchored and non-terminal; a supplied workOrderId must back the run's anchor task; and the stage must carry at least one acceptance criterion + one suggested test). Pass draftOnly: true for a non-executable placeholder (no Work Order minted; request_harness_stage_run rejects it as a draft, and list_harness_stages/the UI mark it launchable: false). Returns the new stage's id + number
authorize_harness_local_commitThe external runner's pre-commit repo:commit permission check (keyed on the stage). Authorization only: honors freeze / leases / repo:commit policy but mints no credential and approves no gate — the runner performs the local git commit itself, then reports the SHA via submit_harness_stage_result. Pass the resolved branch + the changedPaths / diffLines the commit will touch so branch-scoped leases and the high_risk_change diff-size policy evaluate the actual target before the commit (the not-yet-pinned-branch case otherwise bypasses them). Only a VALIDATED/COMMIT_GATE stage on a non-frozen run can authorize; otherwise CONFLICT/FORBIDDEN. admin scope + ADMIN role — a run's own agent token can never reach it. Returns { authorized, harnessRunId, branch }. The push/PR remains the single-use brokered repo:push / repo:pr_create path; merge stays the separate always-human MERGE gate

Task writes (write scope)

ToolWhat it does
create_task / update_task / move_task / archive_taskThe core lifecycle (archive = complete)
set_task_labels / set_task_assigneesReplace-style setters
bulk_create_tasksUp to a column's worth in one all-or-nothing transaction
bulk_update_tasks / bulk_move_tasksPer-item validation; valid writes apply in one transaction with per-item results
create_comment / update_comment / delete_commentComments (mentions param notifies members; edits are author-only)
create_subtask / toggle_subtask / delete_subtaskChecklists
add_task_link / remove_task_linkDependency graph (BLOCKS / DEPENDS_ON / RELATES_TO / DUPLICATES; cycles rejected with the path)
link_task_to_issueLink an existing task to a GitHub issue by number (idempotent) — see GitHub sync
link_task_to_prAdopt an ORPHAN pull-request link onto a task (idempotent; expectedUpdatedAt guard). The task must be on the board linked to the PR's repo (repo ↔ board is 1:1) — see GitHub sync
acquire_leaseClaim a work lease before starting work; returns a CONFLICT (with the active lease in error.current) when the target is exclusively/blocking-leased and no policy-approved takeover is supplied
release_leaseRelease a lease you hold (idempotent; re-release no-ops)
attach_artifactAttach a first-class artifact by external URL or internal R2 object, linked to a task/project/run/Work Order
create_project / create_decisionCreate a project container / propose a Decision Register entry (graduated in M6)
create_work_order / version_work_order / request_runCreate a Work Order, freeze an immutable version + Context Pack, and request an agent run (PENDING until a runner claims it)
create_simulationDry-run a staged plan; returns diffs + projected side effects and never commits (approve + execute stay on the human surface — no execute_simulation tool in v1)
create_incidentEscalate a failed run / bad action into a structured incident

Kind sidecars and contacts (write scope)

ToolWhat it does
update_deal / set_deal_contactsCRM deal fields + linked contacts
update_ticket / link_contactSupport ticket fields + its contact
update_bug_reportBug severity, repro, versions, resolution
update_initiativeRoadmap quarter, confidence, effort, RICE
create_contact / update_contact / archive_contactThe People directory

Structure (write scope)

ToolWhat it does
create_board / rename_boardBoards (kind chosen at creation)
manage_columns / manage_labelsConsolidated action-param tools (create / rename / move / archive…)

Agent workflows (write / admin)

The plan → review → execute → review loop (full reference). The agent-facing protocol is write; the runner's lifecycle tools are runner-only admin.

ToolClassWhat it does
request_agent_workflowwriteStart a two-agent workflow on a task (planner + reviewer agents, max iterations)
submit_planwritePlanner turn: freeze the plan as a Work Order version; optional finding resolutions. For a harness decomposition job, pass harnessStages — the proposed ordered stages the accept gate mints (rejected for any other workflow)
submit_reviewwriteReviewer turn: verdict (APPROVED / CHANGES_REQUESTED) + findings
submit_execution_resultwriteExecutor turn: execution summary + artifact ids
claim_agent_jobadminRunner-only: claim the current turn → opens a run, returns its runId
heartbeat_agent_jobadminRunner-only: keep the active run alive (10-minute TTL). For a harness stage job, the result carries frozen: true when the run has been frozen — the runner must pause/interrupt its subprocess on that tick
fail_agent_jobadminRunner-only: hard-fail the active run → the job goes BLOCKED

Connected sessions (M20.1)

Project-scoped interactive Claude Code / Codex sessions (CLI guide). One AgentSession anchor; ordered events live in redacted R2 TRACE artifacts, closure stores a deterministic RUN_SUMMARY. Session heartbeat and trace-part ingestion are authenticated REST boundaries (/api/agent-sessions/*), deliberately not tools.

ToolClassWhat it does
resolve_projects_for_reporeadCross-visible-workspace project candidates for a normalized owner/name; refused for workspace-pinned credentials (PROJECT_DISCOVERY_REQUIRES_UNPINNED_LOGIN)
create_agent_sessionwriteCreate a STARTING session before provider launch (project confirmed, repo link checked fail-closed). Requires idempotencyKey
attach_agent_sessionwriteBind a TRUSTED current provider thread — late-bind a started session (CAS) or attach-create; repeats converge, competitors fail SESSION_ALREADY_BOUND. Requires idempotencyKey
get_agent_session / list_agent_sessionsreadSession metadata, capture integrity (orthogonal to lifecycle), the stable usage object (provider receipts or null — never estimated), and — on the detail read — the append-only task-alignment interval history (alignments[], task-performance-monitoring PRD AC1.5)
resume_agent_sessionwriteReopen an eligible INTERRUPTED session (same project/repo/operator). Requires expectedUpdatedAt
complete_agent_sessionwriteClose with an honest outcome; the server verifies the trace-part manifest and stores the RUN_SUMMARY. Requires expectedUpdatedAt
get_work_order_versionreadThe FULL frozen body of one immutable plan version (+ Context Pack reference)

Session correlation header. The runner sends X-Stacks-Session-Id next to the bearer: the bearer authorizes, the header only correlates (a session id grants nothing). Verification applies syntax checks only; safe() validates the claim lazily once per request and caches the verdict, so requests without the header pay zero session queries, and artifact/activity writers stamp the validated session id automatically — no tool takes a sessionId argument. This header is UNRELATED to the dynamic mount's Mcp-Session-Id (an MCP transport session): X-Stacks-Session-Id names a durable AgentSession row. MCP resources deliberately stay uncorrelated.

Admin (admin scope)

ToolWhat it does
convert_board_kindConvert a board between kinds (backfills sidecars)
unarchive_boardsAtomically restore 1–50 boards in one workspace; already-active boards are idempotent no-ops
create_webhook / list_webhooks / delete_webhook / list_webhook_deliveriesOutbound webhooks
list_automations / create_automation / set_automation_enabledAutomation rules
list_github_connections / link_github_repo / unlink_github_repoGitHub sync — connection + repo↔board mapping
resolve_approvalResolve a REQUIRE_APPROVAL policy evaluation by minting a scoped, time-bounded approval binding (Policies)
create_agent_release / clone_agent_release / mark_release_eligible / set_active_releaseImmutable-release lifecycle: cut or safely clone a CANDIDATE, record the green gating verdict (CANDIDATE → ELIGIBLE), move the active pointer (Quality plane)
seed_starter_eval_datasetSeed grader smoke plus the explicit real-provider managed release gate; returns the managed dataset head to pin (Quality plane)
request_credential_grantMint a short-lived, scoped, action-bound credential grant; material is minted only at the injection boundary (Credential broker)
freeze_harness / unfreeze_harness / cancel_harness / set_harness_policy / set_harness_agents / start_harness_authoring / start_harness_decomposition / authorize_harness_local_commitRun-level harness operator + runner controls (see Harness admin above)

All tools carry proper MCP spec annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint: false) and typed output schemas, so clients can reason about safety before calling.

Provisioning primitives (M19.1)

The Foundry Provisioning Plane exposes ~25 already-shipped operations over MCP (plus four tightenings) so a headless agent can provision and run a workspace end to end. Every tool wraps the same ops core the UI uses — no new engine, and the harness five add no transition legality. All are admin scope except create_workspace (write) and get_token_context (read).

ToolWhat it does
create_workspace / update_workspaceCreate a workspace (idempotencyKey required — a key-less call is refused before the handler runs; refused for a workspace-pinned token) and rename it (slug/taskPrefix stay write-once; concurrency is VALUE-CAS via expectedName, not expectedUpdatedAt). Delete stays UI-only
invite_member / revoke_invitation / list_invitations / set_member_role / remove_memberStaff a workspace under the default membership-governance pack (invite/remove/set-role park behind REQUIRE_APPROVAL); membership concurrency is VALUE-CAS via expectedRole. See Membership governance
archive_board / delete_automation / stop_experiment / merge_contactsStructure & cleanup closure (the destructive inverses of the shipped create paths)
update_harness / archive_harness / transition_harness_stage / link_harness_decision / record_harness_dispositionHarness lifecycle closure — the ops keep refusing illegal §10.6 actions (Harness)
create_agent_profile / update_agent_profile / link_agent_credential / save_worker_draftAgent authoring: profile → credential link → worker draft
create_model_profile / discover_model_profiles / set_model_profile_enabled / archive_model_profile / set_provider_connection_enabled / archive_provider_connection / create_credential_recordProvider/model/credential authoring (credential records carry references only — no secret enters via MCP)
create_eval_dataset / add_eval_casesCut a gating dataset — each append cuts a new immutable version (Quality plane)
import_contacts / import_tasks / import_trello_boardBulk data in (idempotent by externalId/email; chunked; Trello lists→columns)
list_installation_repos / set_repo_config / start_repo_import / map_github_userGitHub repo wiring after the one human install (GitHub sync)
get_token_contextReport THIS token's scopes (incl. grandfathered-empty), workspace pinning, identity, and rate-limit window — no bearer material. jentrix whoami renders it
retire_agent_releaseTerminal release retirement (CONFLICT while the pointer holds it; idempotent replay on RETIRED) (Quality plane)

Adjacent to these — authorised OUTSIDE the M19.1 R-list (AGE-898) but load-bearing for the remediation contract below — is publish_permission_revision (admin; governed worker.permission.publish, seeded REQUIRE_APPROVAL): the atomic compile + GRANT boundary that writes a worker's CURRENT permission revision. It clears the permission_revision_unset and compiled-artifact staleness blockers a launch preview reports when the launch inherits that current revision — but it writes the profile side, which an explicit per-assignment pin overrides, so a PINNED revision is corrected in the template draft instead (below) (Harness).

Every launch-preview blocker names a remediation (C14 / M19-AC16). Each blockers[]/warnings[] finding from preview_harness_template_launch carries a remediation — an ORDERED, NON-EMPTY sequence of steps [{ kind: "tool" | "ceremony", ref }, …] applied in order — next to its verbatim message. Each step's ref is either an MCP tool name present in this catalog or an H1–H5 ceremony id from the provisioning ledger (prds/foundry-provisioning-plane-prd.md §1). A live-state fix that takes effect immediately is one step (a worker with no active release → [set_active_release]); a fix that edits the template DEFINITION is two, because the preview resolves against the IMMUTABLE PUBLISHED version — the draft edit is inert until published — so it is [update_harness_template_draft, publish_harness_template_version]. The compiled-permission blockers split by PROVENANCE, not by code: an assignment that PINS an unusable revision resolves to the two-step draft-then-publish sequence (correct or clear the pin so it falls through to the worker's current revision), while an inherited or absent one resolves to [publish_permission_revision] (republishing writes the profile side the launch reads directly). The remediation is REQUIRED and never empty — a blocker with no reachable path is the prose-only dead end AC16 forbids — and the remediation-map test proves each sequence both names real refs AND actually clears its blocker, by triggering it through the real preview_harness_template_launch, applying every step in order, and re-running the preview (operator ruling #4 on cmsbltbg800ww04jsj3xu5uhj, 2026-08-03: a single ref could not express the draft-then-publish case truthfully).

Full generated catalog

Every registered tool, its required token scope, and its safety flags — generated from the live MCP surface by pnpm gen:tool-registry and pinned to the registry (src/lib/mcp/tool-registry.generated.json) by a drift test, so a hand edit that diverges from the surface is refused in CI. search_tools serves the same manifests at runtime.

242 tools, generated from the live MCP surface and pinned to the registry by tests/mcp/tool-registry-docs.test.ts. Reachable via search_tools.

ToolScopeRead-onlyDestructiveIdempotent
acquire_leasewrite
add_eval_casesadmin
add_project_linkwrite
add_task_linkwrite
align_agent_sessionwrite
archive_boardadmin
archive_contactwrite
archive_harnessadmin
archive_model_profileadmin
archive_provider_connectionadmin
archive_taskwrite
attach_agent_sessionwrite
attach_artifactwrite
authorize_connectoradmin
authorize_harness_local_commitadmin
bulk_create_taskswrite
bulk_move_taskswrite
bulk_update_taskswrite
cancel_harnessadmin
claim_agent_jobadmin
clone_agent_releaseadmin
clone_harness_templateadmin
compare_harness_template_versionsread
complete_agent_sessionwrite
configure_software_companyadmin
convert_board_kindadmin
convert_playbook_to_skilladmin
create_agent_profileadmin
create_agent_releaseadmin
create_agent_sessionwrite
create_automationadmin
create_boardwrite
create_commentwrite
create_contactwrite
create_credential_recordadmin
create_decisionwrite
create_eval_datasetadmin
create_harnesswrite
create_harness_stageadmin
create_harness_templateadmin
create_incidentwrite
create_model_profileadmin
create_operations_requestwrite
create_projectwrite
create_provider_connectionadmin
create_simulationwrite
create_subtaskwrite
create_taskwrite
create_webhookadmin
create_work_orderwrite
create_workspacewrite
delete_automationadmin
delete_commentwrite
delete_subtaskwrite
delete_webhookadmin
discover_model_profilesadmin
export_harness_templateread
fail_agent_jobadmin
find_similar_tasksread
freeze_harnessadmin
get_agentread
get_agent_jobread
get_agent_releaseread
get_agent_sessionread
get_artifactread
get_board_snapshotread
get_ci_runread
get_company_pulseread
get_context_packread
get_decisionread
get_eval_caseread
get_eval_datasetread
get_eval_runread
get_experimentread
get_harnessread
get_harness_artifactread
get_harness_templateread
get_incidentread
get_leaseread
get_memoryread
get_policy_evaluationread
get_projectread
get_pull_requestread
get_simulationread
get_skillread
get_software_companyread
get_specread
get_taskread
get_token_contextread
get_work_orderread
get_work_order_versionread
heartbeat_agent_jobadmin
import_contactsadmin
import_harness_templateadmin
import_tasksadmin
import_trello_boardadmin
invite_memberadmin
launch_harness_templateadmin
link_agent_credentialadmin
link_contactwrite
link_github_repoadmin
link_harness_decisionadmin
link_task_to_issuewrite
link_task_to_prwrite
list_activityread
list_agent_jobsread
list_agent_releasesread
list_agent_sessionsread
list_agentsread
list_artifactsread
list_attachmentsread
list_automationsadmin
list_boardsread
list_ci_runsread
list_columnsread
list_commentsread
list_communication_sourcesread
list_contactsread
list_control_tower_itemsread
list_decisionsread
list_delivery_itemsread
list_eval_casesread
list_eval_datasetsread
list_eval_runsread
list_experimentsread
list_github_connectionsadmin
list_github_linksread
list_harness_findingsread
list_harness_stagesread
list_harness_templatesread
list_harnessesread
list_incidentsread
list_initiativesread
list_installation_reposadmin
list_invitationsadmin
list_labelsread
list_leasesread
list_linked_messagesread
list_membersread
list_memoryread
list_outcomesread
list_playbooksread
list_policiesread
list_policy_evaluationsread
list_projectsread
list_provider_connectionsread
list_release_transitionsread
list_simulationsread
list_skillsread
list_sourcesread
list_specsread
list_task_linksread
list_tasksread
list_webhook_deliveriesadmin
list_webhooksadmin
list_work_ordersread
list_workersread
list_workspacesread
manage_columnswrite
manage_labelswrite
map_github_useradmin
mark_release_eligibleadmin
merge_contactsadmin
move_taskwrite
preview_harness_template_launchread
promote_experimentadmin
promote_harness_template_from_runadmin
publish_harness_template_versionadmin
publish_permission_revisionadmin
reconcile_software_companyadmin
record_harness_acceptance_evidenceadmin
record_harness_dispositionadmin
record_run_usageadmin
release_leasewrite
remove_memberadmin
remove_project_linkwrite
remove_task_linkwrite
rename_boardwrite
request_agent_workflowwrite
request_credential_grantadmin
request_delivery_workflowwrite
request_harness_stage_runwrite
request_runwrite
resolve_approvaladmin
resolve_projects_for_reporead
resume_agent_sessionwrite
retire_agent_releaseadmin
retry_harness_stageadmin
revoke_invitationadmin
run_evalwrite
save_worker_draftadmin
search_artifactsread
search_tasksread
search_toolsread
seed_starter_eval_datasetadmin
send_communicationadmin
set_active_releaseadmin
set_automation_enabledadmin
set_deal_contactswrite
set_harness_agentsadmin
set_harness_policyadmin
set_harness_template_archivedadmin
set_member_roleadmin
set_model_profile_enabledadmin
set_provider_connection_enabledadmin
set_repo_configadmin
set_software_company_enabledadmin
set_task_assigneeswrite
set_task_labelswrite
set_worker_enabledadmin
start_experimentadmin
start_harness_authoringadmin
start_harness_decompositionadmin
start_repo_importadmin
stop_experimentadmin
submit_execution_resultwrite
submit_harness_artifactwrite
submit_harness_reviewwrite
submit_harness_stage_resultwrite
submit_planwrite
submit_reviewwrite
toggle_subtaskwrite
transition_harness_stageadmin
unarchive_boardsadmin
unfreeze_harnessadmin
unlink_github_repoadmin
update_agent_profileadmin
update_bug_reportwrite
update_commentwrite
update_contactwrite
update_dealwrite
update_harnessadmin
update_harness_findingwrite
update_harness_template_draftadmin
update_initiativewrite
update_projectwrite
update_taskwrite
update_ticketwrite
update_workspaceadmin
validate_harness_templateread
verify_provider_connectionadmin
version_work_orderwrite

Task profiles

Task profiles are recommended tool bundles per agent workflow, derived from the registry + tool-span telemetry (which tools co-occur within a task's mcp.tool_call spans) — never hand-curated. They are generated alongside the catalog and served by search_tools (include_profiles: true, or profile: "<key>" to filter the catalog to one bundle).

Task profiles are DERIVED from the registry + tool-span telemetry (co-occurrence of mcp.tool_call tool names within a task session), never hand-curated. Fetch them via search_tools with include_profiles.

ProfileTaskScopesTools
daily_standupDaily standupreadget_board_snapshot, list_activity, list_task_links
pipeline_reviewPipeline reviewreadget_task, list_boards, list_tasks
sla_watchSLA watchread, writecreate_comment, get_task, list_tasks
triage_bugsTriage bugsread, writeadd_task_link, create_comment, get_board_snapshot, get_task, move_task, search_tasks, update_bug_report

Resources

Read-only context documents (require read scope; honor workspace pinning):

URIContents
stacks://workspace/{id}Workspace overview: boards, members, counts (the template lists your workspaces)
stacks://board/{id}Concise board snapshot
stacks://task/{key}Full task detail by human key, e.g. stacks://task/STK-42
stacks://lease/{id}One work lease (target, holder, mode, conflict policy, status, expiry, lineage)
stacks://artifact/{id}One artifact + a short-lived signed download URL (internal) or the external URL; redacted ⇒ tombstone

Prompts

Canned workflows over the tool surface, ready to invoke from MCP clients that support prompts:

  • triage_bugs — work the Triage column of a bugs board
  • daily_standup — summarize the last 24h of activity, humans vs. agents
  • pipeline_review — CRM pipeline health pass
  • sla_watch — find tickets at or near SLA breach

OAuth 2.1 (for third-party clients)

For clients you don't mint a PAT for (e.g. hosted agent products), Jentrix ships a complete OAuth 2.1 authorization server:

  • Discovery: RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource; RFC 8414 AS metadata at /.well-known/oauth-authorization-server.
  • Flow: authorization code + PKCE (S256, required). GET /oauth/authorize shows a consent screen where you pick the scopes and workspace the client gets; POST /oauth/token exchanges codes and rotates refresh tokens (single-use; the paired access token is revoked on rotation).
  • Client registration, two supported ways, no manual step in either: CIMD, where the client_id is an HTTPS URL serving the client's metadata document (preferred — anyone can re-fetch it and check who the client claims to be), and RFC 7591 dynamic registration at POST /oauth/register for clients that implement only that (Codex), which issues an opaque tmc_ client id. Registration mints an identifier, never an authorization: public clients only (no client secret), PKCE still required, and the consent screen marks a self-registered client as unverified so its chosen name isn't mistaken for a checked one.
  • Tokens: access tokens (tmo_) live 1 hour; refresh tokens (tmr_) are never valid as bearers. OAuth grants get the same scope/workspace model as PATs, and the client's name automatically becomes the agent identity badge.
  • Token lists stay clean: because each refresh mints a fresh access token, expired and rotated-away tmo_ rows are hidden from the tokens page and workspace governance list — only the live one shows. (Revoked PATs remain visible for audit.)

Attribution and auditability

Everything an agent does is tagged source: "mcp" with the token's id, name, and identity snapshotted into the activity payload. In the UI this renders as the agent badge; over the API, list_activity can filter by source — so "what did the bots do yesterday?" is one call. The bundled standup agent is exactly that.

Agent Registry and Control Tower

Agents are teammates; tokens are credentials. An AgentProfile is a durable, routed identity — name/emoji, role, provider, runtime, trust level, scopes, approved skills, routing boundaries, cost profile, eval/failure caches, escalation path, and status (ACTIVE / PAUSED / DISABLED / DEPRECATED). Tokens stay credentials; an AgentCredentialLink is the only bridge between them and never stores secret material — it references existing token rows. Personal agents live at /account/agents; workspace-governed agents at /[workspace]/settings/agents (registering, trust changes, status, and credential links are admin-gated). An admin binds a worker by choosing its model — in the registry row itself, and on the agent's own page. That one choice is the whole binding: a model profile already belongs to a provider connection and a runtime adapter, so both are derived from it rather than asked for as prior steps. Its parameters, prompt and fallback sit behind the same control, and only the generation controls the resolved adapter can actually enforce are editable — the rest render disabled, carrying the adapter's own reason. Existing displayName tokens are backfilled into default agent profiles so historical attribution badges keep resolving.

get_agent returns a launch-eligibility breakdown — one pure, explainable check per dimension (active status, trust floor, workspace/project routing, credential-bound scope coverage — capability is bounded by the agent's active linked credentials, not just the profile's declared scopes — skill coverage, eval health where a null score is "unknown, not blocking", and a recent-failure ceiling). The legacy recentRuns and latestEvals arrays in this response remain empty; current run and quality state is available through the Agent Job, trace, eval, and release surfaces. Disabling an agent flips it to ineligible and blocks new credential links, but never deletes its runs, activity, or links (revoked credential links are kept for audit).

The Control Tower (/[workspace]/control-tower, workspace-first with a personal rollup) is the operational inbox across twenty-five queues. Agent-workflow, delivery, harness, SLA, eval-regression, policy-event, and credential-event sources are live. Governed memory candidates instead use the dedicated /[workspace]/memory review queue; the legacy MEMORY_CANDIDATES Tower adapter is not connected to MemoryItem and reports no source. Harness queues are de-duplicated from their generic agent-run counterparts, and human approvals are highlighted consistently in workspace and personal views. Items rank highest-severity then oldest, explain why they appeared, deep-link to their source, and record audited resolutions. list_control_tower_items exposes the read side; resolution actions remain UI-only. The sweep notifies owners when items age past their threshold.

The execution lifecycle view

Above the queues, the tower shows every execution across the seven lifecycle phases — PRD → Plan → Code → Test → Deploy → Monitor → Improve — with its Work Item, current phase and step, health, elapsed time, runtime-reported cost, last event, and blocker. Monitor is built from records the platform already writes (deployment receipts, incidents, provider-connection verification state, stale runner nodes, and attempt failure counts); there is no telemetry or SLO ingestion, and a signal kind that does not map to an existing model is dropped rather than absorbed.

The view is a derived, query-time projection over authoritative records — the same rule as the queue aggregator. Nothing is stored, so nothing can go stale: opening the page recomputes it. If a cost cache is ever added, the cron sweep converges it; ops code never hand-maintains projection state.

Every piece of evidence is labelled with who owns its truthprovider_owned (GitHub checks, deployment receipts), runner_attested (test evidence the runner executed and bound to the reviewed diff), platform_recorded, human_recorded, or agent_asserted. An agent's claim about CI, a deployment, or a monitoring result is recorded and displayed but is never authoritative: an agent cannot mark its own CI, deploy, or monitoring result successful, and unattested "test evidence" is shown as the agent assertion it is.

Active and historical executions both project. Completed, cancelled, and archived runs resolve to their terminal phase, stay filterable on every dimension whose value they hold, and remain directly addressable — the workspace scope, not the soft-delete flag, is what authorizes the read.

Live work reads first; stopped work folds away. The list opens on the executions that are still going and groups everything finished, cancelled, frozen, or archived into one collapsed section that states its count. The fold is a presentation grouping over the same projected rows — not an eighth view and not a second query — so an active primary view still narrows the whole set and the fold then applies to whatever it returned. Collapse state is a local preference and stays out of shared links.

A stopped execution is never described as live work. A run's status answers whether it stopped; its phases do not, because cancelling freezes the pipeline wherever it stood and routinely leaves the last step blocked or mid-attempt. So a cancelled run reads cancelled (not blocked), reports no blocker, and offers no next action. It stays in Failed, where it belongs. A completed run reads complete even when Deploy and Monitor never ran, which is the normal shape of a docs-only execution.

Stopping is not the same as being done with it. A stopped run's step gates are moot — nobody will approve the commit gate of a step on a cancelled execution — so they stop calling for a human. Its acceptance verdict is a different question: acceptanceState rides alongside status, so COMPLETED means "all steps committed", never "accepted". A finished execution still waiting on an operator's acceptance decision therefore stays in Needs attention and stays out of the fold, because hiding it in the one view meant to surface it is how outstanding work goes missing. The distinction is only about what calls for attention: the approval filter still records what each record holds, so a run that died at its commit gate remains findable by that gate forever.

Each row shows step progress (how many of the run's steps are done), the current step, and the newest attempt behind it — whether something is running right now, its outcome if not, and the provider and model that executed it. It carries navigation to the Work Item, PRD, current step, active attempt, evidence, pull request, CI run, deployment, incident, and improvement work — each destination carrying the same two identifiers (Work Item ID for business-level correlation, Execution ID for the instantiated lifecycle). Destinations that do not exist yet are counted rather than narrated: a run with no delivery graph shows "N not correlated" with each reason on hover, instead of one line of prose per absent record. The reasons themselves are unchanged and the execution detail page still spells them out.

Acting on a row

The human actions for an execution are available on its tower row, not only on its detail page: launch the current step, freeze or resume, cancel, archive a finished run, and record the §10.6 next action — including the terminal disposition a failed or cancelled execution owes, which the row flags as owed.

Every control is the same one the execution detail page mounts, and legality is the server's answer: the actions offered come from the same rule that decides what the ops core will accept, resolved for the whole page in one batched read. Admin-gated operations stay admin-gated and are re-checked server-side, and each mutation still carries the run's expectedUpdatedAt, so a row that has moved since the page was read is refused rather than silently overwritten. Hiding a control is never the enforcement — the ops core refuses an illegal action regardless of what was rendered.

Filters and views

Thirteen filter dimensions narrow the list: lifecycle phase, project, Work Item, harness template/version, AI worker and release, runtime/provider and model, environment, status, approval state, incident severity, time window, cost range, and human owner. Multiple values within one dimension are OR; distinct dimensions are AND; an absent dimension narrows nothing. A row with no value for an actively-filtered dimension is excluded, and an empty list names which filter excluded the rows.

Filter state is URL state only — no saved view, no per-user default, no server-side filter cache — and the parser is total: a hand-edited URL with a malformed date, a non-numeric cost bound, or an unknown key drops exactly that dimension and the rest still apply, so a bad link degrades to a wider result set rather than a blank tower. The cost range compares reported cost only: a run whose runtime reported nothing is excluded from a cost filter rather than being read as $0 (there is no pricing engine).

Seven primary views preset those filters — Needs attention, Active, Scheduled, Completed, Failed, Paused, and Archived. A view is a named preset over the same rows, not a second query path; Needs attention keeps the exception queues as its source adapters, and a view with no representable source today (Scheduled has no scheduler record) says so instead of showing a placeholder row.

Filters search the whole workspace, not a page of it. Search, every filter dimension, the view, and the sort are applied to every execution the workspace holds — active and historical, including archived ones — and only then is the first page cut. There is no scan bound between the read and the filter, so a match is found however far back it sits: the Archived view answers with real history rather than with whatever happened to be recent, and a Work Item, time window, status, or owner that was last touched thousands of executions ago is still reachable. The choices each filter offers are collected the same way, before any narrowing, so selecting one value never removes the others from the menu and a second value can still be OR-ed in. Changing a filter re-runs the read and the control shows that it is working for the whole round trip.

Only the workspace, the project scope, and a direct execution link narrow the underlying query; every one of the thirteen dimensions is evaluated over the projected rows. That keeps the filter and the database from holding two different opinions about what a dimension means, and it is why the facet menus can be honest about what the workspace contains. The cost is that a request projects the workspace's executions rather than a slice of them — the honest price of a query-time projection with no cache to converge.

Execution detail: Pipeline and Attempts

Opening a row goes to the execution's own page (/[workspace]/harnesses/[slug]), which gains two tabs alongside Overview, Stages, Artifacts, Findings, Runs, Delivery, Decisions, and Settings:

  • Pipeline — the seven phases with the evidence that justifies each, its ownership label, and whether that evidence is authoritative, plus the full correlated navigation set.
  • Attempts — per-attempt telemetry: runtime adapter and whether it is registered, provider connection, model, pinned release, permission revision, tokens and cost (runtime-reported — an attempt whose runtime reported nothing shows "not reported", never $0), duration (an open attempt reads "in flight"), retry ordinal and the stage's retry count, policy decisions with the deciding revision, and the attempt's trace id (a diagnostic, not a correlation key).

Both tabs render the same query-time projection the tower list is derived from — one read layer, one execution-detail surface.

Managed execution backend and reference software company

Managed eval workloads run on a real sandbox-backed backend (M16), gated on a live worker — not a constant. list_workers shows the deployment-global, platform-owned worker registry (liveness, driver class, revision, current work); set_worker_enabled (admin) disables/re-enables a worker. The managed-eval gate is green only while an enabled, live worker advertises an approved production driver class (local-docker; in-process/host-process are excluded permanently, so simulated evidence can never satisfy managed gating). A managed-mode gating run with no approved worker is refused, never silently demoted to the simulator.

Reference-software-company enablement is fail-closed and operator-driven. configure_software_company binds boards/agents/repo; set_software_company_enabled (enable/pause/resume) re-runs the fail-closed preflight on enable and resume — enablement is refused unless the managed-eval gate is green and every board/agent/release binding resolves. reconcile_software_company (the same op the 5-minute sweep runs) starts no new work for a disabled or paused profile, and auto-pauses on binding drift (a binding/release that goes invalid pauses new launches and opens one Control Tower exception, leaving reads + active work intact; repair + resume clears it). Operational kill switches — profile pause, harness freeze, set_worker_enabled disable, and provider/workload-token revocation — each independently stop new managed work.

Provider connections and runtime adapters (Operator Control Plane)

A provider connection is what a runtime bills through and where it sends requests (Operator Control Plane PRD §3 Layer 2, §11 R1). Every connection is workspace-scoped and owned by a registered runtime adapter — the runtime the platform launches to reach a provider, never a provider Jentrix calls itself. The registered set is claude (Claude Agent SDK) and codex (OpenAI Codex SDK); runtimeAdapterKey is an open string validated in code (src/server/runtime-adapters), not a Postgres enum. A vendor without a registered adapter — Google Gemini today — is refused with an explanation at connection time, never silently accepted; adding one is a separate adapter project with its own sandboxing and capability review. Connection kind is likewise an open string (no per-vendor enum migration), and connections are created from operator-supplied configuration plus a credential referenceno environment-file editing.

Connection types are adapter-dependent (src/server/providers/connections.ts): api_key, subscription, and custom_openai_compatible. Claude supports an Anthropic API key or a Claude subscription; Codex supports an OpenAI API key, a ChatGPT subscription (codex login), or a public-HTTPS custom OpenAI-compatible endpoint (OpenRouter, Kimi/Moonshot). A custom endpoint is validated through the existing webhook SSRF guard (src/lib/webhooks/security.ts): https-only, no embedded credentials, no localhost/loopback/link-local/private destinations — a private runner-network endpoint is refused, never reached.

Verification is per-adapter (src/server/providers/verification.ts). API-key and custom connections are checked server-side through the SSRF guard (a model-list / auth probe). A subscription authenticates node-locally on a runner host, so it cannot be pinged server-side; it is proved by a runner-reported probe that yields a time-bounded, per-runner attestation (src/server/providers/attestations.ts) surfaced as "verified by runner name at time." A bare { runnerToken, connectionId } is not sufficient: the runner must submit signed, validated evidence that it actually ran the node-local probe and it succeeded — an ok flag plus a distinguished success outcome mapped through the same taxonomy, carrying the tested adapter and runtime evidence, HMAC-signed by the runner identity and bound to the connection (signAttestationEvidence) and freshness-checked server-side (verifyAttestationEvidence). A forged signature, an adapter mismatch, a stale probe, or a probe the runner reports as failed (invalid credentials, model access denied, provider unavailable, …) mints no attestation and never marks the connection healthy — the failed outcome is surfaced instead. A stale attestation degrades that node to ineligible; when no live attestation remains the connection degrades to "configured but unverified," never a false "healthy." A workspace-level "verified" summary never substitutes for node eligibility, and a claim using a subscription connection is eligible only on a node with a live attestation for it. Verification distinguishes eight outcomes — invalid credentials, unsupported endpoint, network failure, rate limiting, model access denied, provider temporarily unavailable, success-with-no-models, and plain success — and never collapses them into a bare "failed."

Raw secrets are accepted only through the protected credential-entry flow and are never returned. A raw API key enters exactly once, through enterProviderSecret, which seals it into an AES-256-GCM envelope (ProviderSecret; the key is derived from the existing app secret, so no environment-file editing) and returns only a non-secret last-4 fingerprint. A connection stores only the opaque providerSecretId reference; subscription connections carry no server-side secret at all. The sealed secret is recovered server-side only to run the /v1/models verification probe (a listing, never a model invocation). Redaction is asserted at the ops-core serialization boundary (src/server/providers/redaction.ts): every external-facing payload — browser responses, MCP results, exports, logs, and audit rows — is assembled from a fixed field allowlist and run through the M4 secret redactor, so a secret pasted into a free-text field is scrubbed and a record that smuggles a raw secret field is refused at the boundary. The same discipline runs at the write boundary for every operator/runner-supplied metadata bag, so raw material never reaches database JSON through any route but enterProviderSecret:

  • Runner-reported probe metadata is reduced to a non-secret allowlist (normalizeAttestationEvidence) and scrubbed (redactAttestationProbeForPersist) before it is persisted, so a runner cannot route apiKey/authorization/session material into ProviderConnectionAttestation.probe JSON through the attest endpoint.
  • A connection's config bag carries no operator free-form metadata: sanitizeConnectionConfig enforces empty-refusal in the ops core before the row is written, and the create schema is a .strict(), empty object — both accept only an absent or empty ({}) config. An ADMIN through the UI or MCP could otherwise submit raw credential material under a benign key (config.note = "sk-ant-…", config.region = "<opaque token>") outside the credential-entry flow; the boundary refuses a secret-bearing key (config_secret_field, at any depth) and refuses any other key (config_not_allowed) — never silently dropped or stored. An allowlist of "benign" string keys would still admit an unpatterned secret sitting in a stored value that a read-boundary redactor cannot recognize, so the boundary admits no free-form value at all. A genuine future need for non-secret routing metadata must arrive as a typed, secret-incapable field, never a JSON bag; raw secrets are accepted only through enterProviderSecret.

The shipped surfaces. The ops core (src/server/providers/operations.ts) composes those pure helpers into a persisted, authenticated feature over four models — ProviderConnection, ProviderConnectionAttestation, ProviderSecret, and the node-scoped WorkerClaimCredential. Workspace ADMINs create, verify, enable/disable, and archive connections from the Providers page (/{workspace}/settings/providers) or the create_provider_connection, verify_provider_connection, and list_provider_connections MCP tools. A runner node proves a subscription login by POSTing signed, validated probe evidence to /api/providers/attest (authenticated by its heartbeat identity secret); it then obtains a single-use, node-scoped claim credential from /api/providers/claim-credential, which is issued only when that node holds a live attestation. That credential is then consumed at the scheduling boundary: claim_agent_job (src/server/workflows/operations.ts) takes the single-use workerClaimToken and, inside the claim transaction, resolves it to its runner node, re-checks that node's live attestation for the selected subscription connection (a mint-time proof can lapse before the claim lands), binds the opened run to the node (AgentRun.claimedByRunnerNodeId / subscriptionConnectionId), and burns the credential single-use — all atomic with the run, so a rolled-back claim leaves the token unconsumed. When a workspace bills a runtime adapter only through a subscription connection, a tokenless claim is refused fail-closed. This closes the loop on the mechanical enforcement that a claim using a subscription connection is eligible only on a node with a live attestation.

The browser connection form presents the supported combinations directly: Anthropic API key or Claude subscription through claude, and OpenAI API key or ChatGPT subscription through codex (plus the existing custom OpenAI-compatible Codex path). A new API key can be entered inline, but still passes through the same one-way enterProviderSecret boundary before the connection stores its opaque reference. The create flow immediately runs the redaction-safe /v1/models verification probe and surfaces the distinguished failure instead of leaving the operator to notice an unverified row later. Selecting a subscription does not create a permanently unverified browser-side record. Instead, the page shows one workspace-level Copy setup command action (also shown when old subscription rows lack a live attestation). The copied fail-fast command installs the matching current CLI and runner packages, runs an admin-scoped jentrix login, and then runs workspace-scoped jentrix runner setup with --runtime claude, --runtime codex, or --runtime all according to the Claude Code and Codex checkboxes. Existing unverified rows are selected and reused by default. That preview-first setup checks only the selected locally authenticated runtime or runtimes, reuses or creates their subscription connections and selected model profiles only after confirmation, enrolls the existing runner identity, and posts signed node-local evidence without making a model call. It never installs or signs in to a provider CLI.

The packaged local onboarding path composes these same boundaries. The admin-authenticated POST /api/runners/setup accepts only the runner's normalized, non-secret probe envelope and returns a preview or one-time apply grants; it creates no alternate runtime identity. Apply uses /api/workers/enroll, /api/workers/heartbeat, and signed /api/providers/attest; the running worker mints each subscription claim token through /api/providers/claim-credential. Workspace-pinned tokens remain restricted at setup. Setup may save worker drafts but never publishes or activates a release or launches work; immutable release routing remains authoritative at claim.

Model profiles and capability-gated pre-launch validation (PRD §11 R2/R4) persist which models and settings are valid for a runtime + connection pair. A ModelProfile is owned by a provider connection (so its runtime adapter is authoritative, never operator-picked), records the provider model identifier, advisory capability metadata, and an optional free-text pricing note — there is no pricing engine and no numeric rate column (PRD §6): costs come from runtime-reported usage. Verifying an API-key connection imports that provider's catalog as model profiles (the same idempotent discover_model_profiles operation, upserting on [providerConnectionId, modelIdentifier], so re-verifying preserves operator edits); ids a runtime adapter can never drive a worker turn with — embedding, speech, image, moderation, rerank endpoints — are not persisted (isWorkerModelId, a deny-list, so an unrecognized id is always imported and a newly-released chat model is never hidden). Subscription profiles come from the selected model in the runner's read-only local probe. Model choice is not a provider-page decision and has no UI there: which model a worker runs on, and with which parameters, is per-agent and lives on the agent's own page — a models section on the provider page asked the operator to configure a model with no worker in view. That agent surface renders every normalized generation control, editable only where the owning adapter can enforce it and otherwise disabled carrying the adapter's own reason, so an unenforceable control (fast latency on either shipped adapter, maxTurns on Codex) is explained rather than absent. Provider model-list APIs do not publish an authoritative generation-parameter schema, so agent configuration renders parameters from the registered runtime adapter capability schema and refuses unsupported controls at save/launch rather than inventing provider metadata. Capability schemas are keyed by runtimeAdapterKey and are authoritative; discovered metadata is advisory. Before a Work Order launch records a WorkOrderLaunch, validateGenerationParameters (src/server/providers/capability-gating.ts, wired into launchWorkOrder) refuses an unsupported reasoningMode/latencyMode, a raw runtime-native param (e.g. temperature), or maxTurns for Codex — which manages its own loop budget and has no turn cap until a host-side limit exists — each with a readable reason; only the normalized, enforceable controls are pinned on the launch.

Permission templates (PRD §11 R5) are authoring sugar over the shipped enforcement planes, never a second policy engine. Creating an AI worker automatically creates a worker-owned, least-privilege PermissionTemplate from its role preset (src/server/permissions/templates.ts) — no preset grants the admin scope class, and every preset starts at deny-all egress, no shell, no deployment authority, and drafts instead of sends. Publishing a revision compiles that snapshot into the artifacts that already enforce (src/server/permissions/compilation.ts): M4 policy rows (hashed with the existing policyContentHash), P2.1 read/write/admin scope classes, M4 broker credential classes, claim/heartbeat budgets, the authz boundary, and the selected adapter's runtime sandbox configuration. There are no per-tool MCP allow/deny lists in v1.

Compilation is atomic and fail-closed. A refusal produces no artifacts; a revision is unpublishable unless every required artifact is present and content-hashed; and the claim/launch preflight (assertClaimReleasePreflightassertPermissionArtifactsForLaunch) refuses a worker whose compiled bundle is absent, incomplete, or stale — compiled from different content, or by an older compiler revision. The policy engine's default-allow floor never stands in for a restriction that failed to compile: a declared restriction with no expressible enforcement path (e.g. an approval gate on an action no shipped policy template can gate) is refused at compile time, naming the field.

The compiled bundle is applied at run time, not merely stored. A claim pins the revision the launch gate just verified on AgentRun.permissionRevisionId, and every runtime trust boundary resolves that pin — never the worker's mutable template head — through src/server/permissions/enforcement.ts, so publishing a revision mid-run can neither widen nor narrow an in-flight turn and "which permissions did this run execute under?" has exactly one answer. Every one of the six required artifact kinds is read by a production boundary (pinned by templates.test.ts, so a seventh kind cannot land without a consumer): evaluatePolicy folds the compiled policy rows in beside the workspace's hand-authored ones (same layerApplies + resolvePolicy, same persisted PolicyEvaluation). Those rows are agent-scoped, so the acting worker must be named in the evaluation: verifyToken resolves it from the credential's agent link, the MCP request context carries it, and policyActorFor folds it into every PolicyContext — and because an ordinary tool call names no run, the binding maps the acting role token through the M5 turn guard to its open runs and enforces those pins (the template head governs only a worker holding no open turn). Beyond policy: issueRunCapability narrows the run's authorization envelope before the STS mints, so a capability can never carry a scope the bundle withheld or an action class it denied; the runner-bootstrap role tokens are rotated with the compiled scope classes instead of a fixed read+write, and the compiled scope class is re-checked on every governed tool call — a mint-time narrowing binds only what it mints, and a role token lives an hour, a brokered token outlives its grant, and an agent-linked PAT never rotates, so without the per-call check a token issued while the revision allowed write would keep writing after it narrowed to read (the token's own scopeViolation stays the credential's upper bound; the compiled class is the worker's); the M4 credential broker refuses an action class the worker's compiled credential classes do not authorize (and intersects a minted Jentrix token's scopes with them), so a permissive CredentialRecord cannot widen what the revision granted — at every grant boundary, not only the one that mints material: request_credential_grant is an MCP tool, so a governed worker can name its own run, and gating only redemption would leave it holding an ISSUED CredentialGrant for a class its revision never authorized (the row the Control Tower and an auditor read as the platform's authorization record); the claim response carries the compiled sandbox snapshot on workerExecution.permissions, so the selected adapter configures its shell, writable roots, and network egress from the compiled artifact rather than its own defaults; the compiled authz boundary binds both at the claim and for the rest of the turn — a worker cannot open a run on work outside the workspace (or, when its template names them, the projects) its revision froze, and it cannot reach outside that boundary afterwards either: every MCP tool call by a worker credential installs a compiled-boundary guard on the request context and requireWorkspaceRole / requireProjectAccess / requireWorkOrderAccess — the choke point every authz helper funnels through — evaluate it with the same claimWithinAuthzBoundary the claim ran, resolved from the run's pin (UI/session callers never carry the guard, so human access is unchanged); and the heartbeat applies the compiled budgets, refusing to keep a run alive past its wall-clock or spend ceiling from the control plane's own facts (AgentRun.startedAt / costUsd), so a runner that ignores the budgets in its sandbox snapshot still cannot exceed them.

Because the same compiled scope classes decide both, the claim gate also checks that a worker's revision can sustain the turn it is about to claim: every run role ends its turn through a write-class submit tool (submit_plan, submit_execution_result / submit_harness_stage_result, submit_review / submit_harness_review), so a worker whose revision compiles read-only is refused before a run opens — with the refusal recorded on the preflight-refusal surface — instead of claiming a turn whose submit the MCP boundary would refuse and leaving the job open until the sweep expired it. Widen the worker's permission template (add the write scope class) and republish it, or bind a worker whose revision already grants it.

The self-hosted runner has a second issuance path, because one shared role token cannot represent per-run authority: it is a single row used by every run of that role, so the only revision it can be narrowed by is the worker's mutable head — which would re-scope an in-flight turn the moment an operator publishes, and could not honor two overlapping runs pinned to different revisions. So the M11.1 claim endpoint (POST /api/agents/claim/{grantId}) accepts an optional runId: with it the shared token is left alone and the plane mints a separate run-scoped bearer carrying role base ∩ THAT RUN's pinned compiled classes (AgentRun.permissionRevisionId, never the head), stamped with ApiToken.mintedForRunId. That column binds the bearer at both ends — the M5 turn guard admits it for exactly the job whose activeRunId it names (run A's bearer can never submit for run B), and the MCP boundary resolves that run's pinned revision from it, which is what keeps it governed even though a run bearer is deliberately not agent-credential-linked (linking it would break resolveAgentTokenId's exactly-one-usable-link invariant). Its TTL is the same ROLE_TOKEN_TTL_MS (60 min) the shared role token uses — by construction never shorter-lived than the bearer it replaces, so the supported 30-minute turn envelope cannot regress — and run close revokes it, so a run's authority dies with the run instead of lingering for the rest of the TTL. "Run close" means every path that closes a run, through one shared revocation helper: a submit_*/fail_agent_job terminal move, a claim that supersedes an expired turn, the provider-claim rollback, cancel_agent_job, cancel_harness (a bulk close of every one of the harness run's jobs), and the heartbeat-expiry sweep — the one close path a hung runner reaches. Revoking matters even though the turn guard already refuses the bearer for the next run: until it is revoked it is a live credential whose every ordinary tool call resolves the now-closed run's pin and is admitted by it. A refused pin (absent/incomplete/stale) or an empty intersection mints nothing, and a claim naming a run that is not this grant's open turn (superseded, closed, or another role's) is refused. A claim WITHOUT a runId is byte-identical to the pre-M18.2 rotation, so a runner that predates this keeps working — and is still governed, because the per-call check above resolves its open turn's pin either way; adopting run bearers narrows the credential's blast radius, it does not add authority the shared token escaped. Order of operations: the control plane ships this first — a runner may only start sending runId once the deployment it talks to carries the column.

Each boundary fails closed: a governed worker whose bundle is absent, incomplete, or stale is denied — verified through the same assertCompiledArtifactsForLaunch gate the claim runs, so a bundle refused at claim can never be honored mid-run — and an intersection that authorizes no scope mints nothing, because an empty ApiToken.scopes reads as grandfathered full access. A worker with no template at all predates the plane (PERMISSION_PLANE_ENFORCEMENT) and is skipped with the reason surfaced, never silently.

Every permission field displays its enforcement classserver-enforced, runtime-configured, or advisory — wherever it appears (the worker page's Effective permissions panel). Token scopes, credential classes, budgets, workspace/project boundaries, deployment and send rights, and approval gates are server-enforced. Shell, repository access, path patterns, and network egress destinations are runtime-configured: honored by the runtime sandbox, and on a self-hosted runner node with no live attestation only advisory — attested, not guaranteed. Egress stays runtime-configured until the M16.5 egress plane (AGE-387) lands, and a template that asks for the managed engine (AGE-384) or server-enforced egress is refused rather than compiled into a promise nothing keeps.

Effective permissions are the intersection of system restrictions, workspace policy, the worker's permission revision, a harness-step narrowing, the credential-grant scope, and runtime sandbox capabilities. No layer can increase authority a stricter layer withheld — a broader grant is ignored and reported (src/server/permissions/effective-preview.ts), never silently applied. Set fields intersect, caps take the minimum, ordered levels take the least authoritative, booleans AND, and approval gates union (one more gate is stricter, not broader); the single-valued workspace boundary intersects to none when two layers name different workspaces, because neither may re-point a boundary the other withheld. Every field reaches the panel as a value, so a restriction the runtime enforces is never displayed as a blank. The preview itself is produced by the same evaluation path that enforces at run time: it calls layerApplies + resolvePolicy from @/server/policies/engine, the two functions evaluatePolicy calls, and holds no matcher, ordering, or outcome resolution of its own.

Conflict control, specs, artifacts, outcomes, and source governance

Leases (WorkLease) add intent on top of optimistic concurrency. A holder (human/agent/automation/system) claims a target — task, project, repo path, GitHub issue/PR, contact/deal/ticket, Work Order, or agent job — for a bounded window with a conflict policy (WARN / REQUIRE_APPROVAL / BLOCK / ALLOW_PARALLEL) and a mode (ADVISORY / EXCLUSIVE / REVIEW / HANDOFF). Before expensive execution, acquire_lease runs duplicate-run detection: an EXCLUSIVE/BLOCK active lease returns a CONFLICT envelope whose error.current is the live lease, so an agent can wait, take over (admin + a policy-approved policyEvaluationId for EXCLUSIVE), hand off, or run in parallel — before burning a run. Objective-similarity detection reuses semantic search. Leases carry a required expiry for agent holders and are auto-expired by the 5-minute sweep (expired = visible but non-blocking). Takeover/handoff preserve Work Order, run, and artifact lineage via the takenOverBy chain. Lease badges render on the task panel and Control Tower; the second-run conflict dialog appears before launch.

Specs (Spec / SpecVersion) turn a rough task into an executable, versioned brief — user story, constraints, non-goals, test plan, rollback plan, evidence required, definition of done, open questions. Weak-spec detection flags a vague objective, missing acceptance criteria, a missing/trivial test plan, or conflicting constraints, and scores 0-100. Accepting a spec versions and links it, then seeds an M2 Work Order whose launch preview surfaces those quality warnings.

Artifacts (Artifact) make agent output first-class: PRs, diffs, logs, screenshots, eval/policy reports, run summaries, decision memos. Each links to a task/project/run/decision/Work Order/approval/memory-candidate, stores metadata (source/checksum/retention/visibility), and supports external-by-URL or internal-by-R2 (short-lived signed URLs on read). Visibility is enforced (workspace / admins / private). Redaction never hard-deletes — it clears the content, marks the row redacted + archived, and writes an ARTIFACT_REDACTED audit row, leaving a tombstone so links still resolve. Redaction is admin-gated.

Outcomes (OutcomeMetric) measure whether agent work helped. A daily cron rolls up Jentrix-native and delivery signals — cycle time, reopen rate, human edits, approval burden, DORA, and agent-native delivery metrics — split into agent-assisted vs human-only cohorts where applicable. Legacy project cards still mark PR-acceptance, cost-per-task, and eval-rate slots as substrate-gated rather than deriving them from the newer run/eval registries. Cards show on project pages and agent profiles.

Source governance (SourceRecord) classifies knowledge sources by trust, freshness, privacy, owner, project/client, and supersession. The Work Order launch preview warns on stale/low-trust sources and blocks a client-private source from entering an unauthorized cross-client context. Superseded sources stay visible (toggle) but are excluded by default in list_sources and context assembly. Management lives at /[workspace]/settings/sources (admin-gated).

All five are workspace-scoped subjects: their audit rows are WorkspaceActivity, and when a lease/artifact targets a board task the board also gets a task.updated so card badges refresh. Lease acquire/release and artifact attach are the only Phase-2 MCP writes; takeover and redaction stay UI/admin-only for now.