Console
The console is a program section at /p/<slug>/console, not a fifth product. It appears in the rail when
program.threads.read_own or program.threads.read passes and is used with program.threads.use. Its
status word is designed; the roadmap names the gates that move it to preview.
Pilot positioning, quoted from the contract: “non-GxP decision support with a light validation package”. The
ELN stays the scientific record, the QMS stays the SOP record, Trovensa is the record of what agents proposed
and what people decided.
Regions
| Region | Content | Width at 1440 px and above | Remembered in |
|---|---|---|---|
| Conversation | messages (role=feed), plan and approval cards, citation markers [n], composer with Stop and slash commands | 560 to 720 px | nothing |
| Canvas | current artifact, version selector, selection-to-edit, promote, version diff, source viewer | 480 to 640 px, collapsible | cookie console_canvas |
| Run rail | steps, tool calls (name, argument summary, scope, policy decision, permission_exercised, span id), sources read, budget used, SOP binding, model configuration record | 320 px, collapsible | cookie console_rail |
Below 1100 px the canvas and rail become a tabbed drawer; below 768 px a bottom sheet with full-width cards. Mobile reads, approves, declines and stops; artifact editing is desktop-only and the button says why. Cookies hold layout only; everything else is a record.
Thread states: empty, planning, awaiting approval, running, paused (the composer prints “Paused,
waiting for a decision”), finished, failed (with the public error code, for example gate_expired,
policy_denied, config_mismatch), archived.
Threads and messages
| Record | Fields (DTO) | Rule |
|---|---|---|
Thread (ThreadOut) | title, status (open, archived), visibility (private, program), created_by, scope_type, scope_id, product_slug, launch_context, pinned_context, schedule, last_message_at, message_count, redacted_at | private by default; the owner reads it with program.threads.read_own; quality reviewers and leads read every thread with program.threads.read; only the owner shares, with a confirmation naming what becomes visible (ThreadShare.confirm_counts) |
Message (MessageOut) | seq, role (user, agent, system, tool), status (streaming, complete, failed), content_md, content_json, agent_run_id, author_user_id, parent_message_id, citation_count, claims_without_source, feedback (none, helpful, corrected, dismissed), feedback_by, feedback_note | text passes redaction before storage and never contains retrieved source text; citations reference document_chunks.id; feedback is rendered as labelled icons, not status chips |
Agent run (AgentRunOut) | phase-1 fields plus thread_id, mode (interactive, background), lane, agent_version_id, plan_hash, usage, trace_id | one console_turn run per agent turn; runtime_session_id equals the run id |
Interrupt (RunInterruptOut) | seq, kind (plan_approval, write_approval, consent, question), payload, status, resolved_by, resolved_at, resolution (decision, reason, edits diff), permission_exercised, expires_at | default expiry 24 h, extendable; resolution needs program.threads.use plus the write’s own permission |
Tool call (ToolCallOut) | specialist (from the run token), tool_key, arguments (redacted), proposal_hash, approved_hash, status, approved_by, approved_at, permission_exercised, resolution_reason, policy_decision, injection_flag, result_ref, otel_trace_id, otel_span_id | writes need an approved row whose approved_hash matches the payload the server writes |
Snapshot (RunSnapshotOut) | context (chunk ids and hashes, record ids, sop_version_ids, prompt version, allowlist, policy version, permission snapshot), context_hash, model_check (expected_model_config_id, reported_model_id_hash, inference_profile_id, outcome) | every run is reproducible from the database; events stay excerpt-free |
The composer shows the fixed phrase “Private to you. Quality reviewers and program leads can read this thread
for review.” A record created from a thread links (thread_id, message_id, artifact_id, artifact_version, agent_run_id) and its page reads “From thread T-31” or “From a private thread” according to the reader’s
rights. Conversations are redacted to tombstones after organizations.settings.thread_retention_months
(default 24) while tool calls, interrupts, snapshots, signatures and audit rows persist (thread.redact).
Run modes and plan-first gates
Every run starts with a plan artifact (actions, tools, sources, expected writes, budget). The approved plan
(agent_runs.plan_json, plan_hash) is the control flow: a tool outside it is refused with plan_violation
and surfaces as a new interrupt. The mode is remembered per user in users.settings.console_run_mode
(PATCH /me/settings):
| Mode id | Label | Behaviour |
|---|---|---|
approve_each_write | Run and approve each write | reads run; every write pauses the run for a decision |
auto_reads_gated_writes | Run with automatic reads and gated writes | default; reads run within the plan, writes pause |
keep_planning | Keep planning | the plan is refined without running tools |
Approval card
One component handles plan approval, write approval, connector consent and questions. It shows what will be
written, which sources it cites, the downstream consequence and the permission exercised (disabled with the
permission name when the viewer lacks it). Buttons: Approve (Enter), Edit then approve, Decline
(Esc, the reason is stored as evaluation data); Resume re-opens an expired gate; Stop ends the run.
Edits are applied by the server to the stored proposal, approved_hash is recomputed, the diff goes into
resolution, and the record is written from the server-side payload only; the record page then reads
“edited before approval” with the diff. A card that waits more than 15 minutes becomes a background run card
and stays actionable. Approval is not a signature: signatures are reserved for approve routes and
requirement-set acceptance (signatures).
Artifacts and canvas
Kinds are a fixed vocabulary rendered by UI-owned components: plan, comparison_table, finding_set,
hypothesis_set, protocol_draft, report_draft, deviation_table, sop_diff, milestone_view,
summary. Versions are append-only within a lineage_id; selection-to-edit yields v(n+1) with
origin = agent, direct edits yield v(n+1) with origin = human and authored_by_user_id. Promote turns
a version into a tool_calls proposal awaiting approval, never a direct write and never a visibility change.
Items of kind observation need non-empty chunk_refs and cannot be promoted otherwise. Footer:
“Agent-generated draft, unsigned.” followed by “Cites N sources.” and “Model configuration MC-n (recorded
<date>)”.
Citations and citation coverage
Every factual sentence carries at least one [n] bound to (document_id, chunk_id, char_start, char_end).
The source viewer shows title, provenance, licence code, retrieved_at, snapshot stamp, the cited span and
“Open in notebook” or “Open at publisher”; rendering follows the licence codes.
Citation coverage is a deterministic count of claim-shaped sentences without a marker (the pattern list lives
in web/lib/content.ts claimPatterns); the header reads “All 9 claims carry a citation” or “1 claim without
a citation”. It verifies markers, not that the chunk supports the claim; semantic grounding is a judged check
in the evaluation suite. “Needs sources” withholds the write gate until citations are repaired or the user
overrides with a note.
Record actions, palette and slash commands
Ctrl K gains Ask. On any record page . opens the record actions “Ask about this”, “Check against
SOP”, “Prepare a draft from this”, “Compare with…”, “Send to QA” and “Accept into record”, filtered by the
viewer’s permissions. The composer accepts /check, /draft, /compare, /brief and /pin. “Ask the
agent” replaces the phase-1 agent action button; the run rail replaces the run dialog; legacy run kinds stay
callable as canned prompts.
Keyboard and accessibility
role=feed composites for messages and rail; streamed text is announced through a polite role=log in
sentence chunks; approval cards receive focus on appearance and return it on resolution; the sticky composer
never obscures focus. Single-key shortcuts (a accept, . record actions, Enter, Esc) are active only
with focus on the card or record header; users.settings.shortcuts remaps them to alt_chords or turns them
off. Agent-origin content never uses the pass tone until accepted.
Notifications
notifications (bell and /me/notifications) fire on approval_requested, needs_input, run_finished,
run_failed, sop_changed, batch_completed and connector_reauth. E-mail carries the record type, the
human-set program name, the record id and a link only: no titles, no agent text. Reading a notification is
audited (notification.read).
Performance budgets (design targets)
Optimistic echo under 100 ms; working indicator under 1 s (the state event is written in the message
transaction); INP under 200 ms; SSE keepalive every 15 s; reconnect from Last-Event-ID within 2 s.
First-token latency and the cold bound are measured and recorded in CLAIMS-REGISTER.md before any number
appears in copy; none appears here.
Source: web/lib/content.ts (consoleSection, consoleCopy, runModes, recordActions, slashCommands, approvalActions, claimPatterns), web/lib/api/types.ts, web/lib/api/endpoints.ts, api/app/db/models/conversation.py, api/app/db/models/agent.py, api/app/auth/permissions.py, docs/lib/content.ts, PRODUCT-CONTRACT.md sections 10.1 to 10.4, PRODUCT-PLAN.md Part 4 section 3