Part of Trinity's requirements set. Index & write-path rule: requirements.md.
- Status: ✅ Implemented
- Description: Create agents from templates (GitHub or local) or from scratch
- Key Features: Web UI, REST API, GitHub templates (
github:Org/repo), local templates, credential schema auto-detection
- Status: ✅ Implemented (Updated 2026-01-26)
- Description: Start and stop agent containers via unified toggle control
- Key Features: Toggle switch shows Running/Stopped state, loading spinner during action, consistent UI across Dashboard, Agents page, and Agent Detail page
- Components:
RunningStateToggle.vue- Reusable toggle component with size variants (sm/md/lg)
- Status: ✅ Implemented (2026-03-01)
- Description: Rename agents via UI or MCP without deleting and recreating
- Key Features: Inline editing with pencil icon,
rename_agentMCP tool, atomic DB updates, Docker container rename, WebSocket broadcast - Restrictions: System agents cannot be renamed, only owners/admins can rename
- API:
PUT /api/agents/{name}/renamewith{new_name: string}
- Status: 🚧 In Progress
- Implements: trinity-enterprise#181 (OSS-core — maintainer decision)
- Description: A human-readable label an owner can edit freely, with the
agent's slug (
agent_name) left untouched. Renaming a thing you can see is the common case; re-keying its identity is not. - FR-1 — The slug is the identity, the label is presentation: everything
machine-facing keeps using
agent_name— routes, Docker container/volume names + labels, MCP keys, A2A cards, Redis keyspaces, everyagent_namecolumn. The label is rendered, never resolved. This is the whole point: §1.3's slug rename must rewrite ~20 tables, rename the container, clear every per-agent Redis keyspace, and still strands the agent's volumes under the old base (Docker can rename neither a volume nor its immutabletrinity.agent-namelabel) — the root of #1664/#1665/#1667/#1669/#1671. A label change touches one column and nothing else. - FR-2 — NULL means "use the slug":
agent_ownership.display_label TEXT, nullable, no backfill. Every existing agent renders exactly as it does today until someone sets a label; clearing the label reverts to the slug. Dual-track migration (Invariant #3). - FR-3 — One label everywhere a name renders: agent detail header, dashboard
cards, grid tiles, pickers/lists. A label applied on some surfaces and not
others shows one agent under two names with no way to tell which is real —
worse than no label. Resolution goes through a single helper, not per-site
||chains. - FR-4 — The slug stays visible and copyable: it is what URLs, MCP keys, containers and volumes are keyed on, so the UI shows it as secondary text wherever the label replaces it. A label that hides the identity trades one confusion for another.
- FR-5 — The slug rename is demoted, not removed: §1.3 stays available behind a secondary "advanced" affordance with copy that states what it actually does (restart, re-key, volumes stay under the old name). Owners who genuinely need it keep it; it stops being the default gesture for "call it something else".
- API:
GET/PUT /api/agents/{name}/label— owner-only,{label: string|null}. - FR-6 — Remaining surfaces resolve the label off the agents store, not new
payloads (#1643): operator queue, monitoring, executions, the collaboration
graph, tab titles and prose/toasts render only a slug in their own payloads.
Rather than grow a mutable
display_nameon each of those high-volume endpoints (staleness risk, N duplicated presentation fields), the frontend resolves slug → label off the loaded agents (store gettersdisplayNameForSlug/agentRefForSlug, live via theagent_label_changedWS handler). An unloaded slug falls back to itself, so nothing regresses on a cold surface. Render rule by class: dense operational tables (executions, operator/monitoring rows, RACI matrix) keep the slug primary and surface the label as a hover tooltip (agentNameTooltip); prose / toasts use the label alone (agentDisplayName); the collaboration graph renders the label but keepsdata.label= slug as the action key (router.push/ toggles).AgentAvataralways receives the slug. Tab titles resolve the label on warm SPA nav and fall back to the slug on a cold direct load (the store isn't fetched yet); the next navigation self-heals. Comma-joined agent lists (e.g. the GitHub-PAT propagation failure list) keep the slug — long labels make them unreadable. - FR-7 — Findable by display name: pickers, search, sort (#1642): the
picker surface class carries the slug inline —
<option>s renderDisplay name (slug)viaagentOptionLabel(else the bare slug), and the<option>value stays the slug so filtering/selection never keys on the label. Six dropdowns:ExecutionsPanel,ReportsPanelFleet, operatorQueueList+NotificationsPanel,FileManager,Settings(subscription assignment).Agents.vuename search matches both the slug and the display name (case-insensitive) — otherwise typing "TOM" against atom-marketing-opsslug returns nothing. Sort-key decision (AC): the "Name (A-Z / Z-A)" sort orders by the display name when set, else the slug (agentDisplayName, in the store's_getSortedAgents) — sorting by the slug while the row renders the label would order the list by an invisible key. Every per-agent lookup (getActivityState/tags/stats/router actions) still keys onagent.name; only the option label, the search predicate, and the sort comparator changed. No store-shape change — the label is resolved off the loaded agents (FR-6 resolvers), soagentNames/availableAgentsstay slug-string arrays.
- Status: ✅ Implemented
- Description: Delete agents and cleanup resources
- Key Features: Container cleanup, network cleanup, cascade delete sharing records
- Status: ✅ Implemented
- Description: View container logs for debugging
- Key Features: Logs tab, fixed-height scrollable container, auto-refresh, smart auto-scroll
- Status: ✅ Implemented
- Description: Real-time container metrics in agent header
- Key Features: CPU/memory usage, network I/O, uptime display, auto-refresh every 10 seconds
- Status: ✅ Implemented
- Description: Auto-discovery from
config/agent-templates/ - Create-time resolution contract (#1793 + #1759):
local:<name>is resolved against the curated catalog first, then the deploy-local store (/data/deployed-templates, #950). A well-formed but unresolvable id fails with a named 404UNKNOWN_LOCAL_TEMPLATE(#1793) raised before any side effect (no container, no MCP key, no volume, nothing to roll back) — completing the loud-reject contract #843 opened for unprefixed template strings. An empty / non-mapping / unparseabletemplate.yamlfails in the same pre-side-effect band with 400LOCAL_TEMPLATE_INVALID(#1759), matching the strictness the listing surface (GET /api/templates) already applied; without it a present but malformed template reached the identical blank-agent-at-200 outcome through a broadexcept Exception. The traversal barrier keeps precedence: a malformed name is still 400INVALID_LOCAL_TEMPLATE_NAME.template: null/""(Blank Agent) never enter this branch and are unaffected. Hidden templates (hidden: true) are omitted from the listing but remain creatable by id — the resolver never reads the flag.- The error is one identical sentence whichever root missed, carrying no filesystem path and no root name — deploy-local templates are named after agent names, so a root-distinguishing message would let a
creator-role caller probe another user's agents (#186 adjacency). - Manifest deploys surface it per agent via the ent#125
failed[]report (status_code: 400), so one typo'd template no longer sinks a whole system. - The curated root falls back to the in-repo
config/agent-templates/when the container bind mount is absent, so the gate is live in source-run backends too (aligning create with the listing surface, which has had that fallback since #843).
- The error is one identical sentence whichever root missed, carrying no filesystem path and no root name — deploy-local templates are named after agent names, so a root-distinguishing message would let a
- Status: ✅ Implemented
- Description: Clone via
github:Org/repoformat with PAT authentication
- Status: ✅ Implemented
- Description: Admin can configure which GitHub repos appear as agent templates via Settings UI. All metadata (display name, description, resources, MCP servers) is fetched from each repo's
template.yamlvia GitHub API (cached 10 min). - Key Features:
config.pyholds default repo list (no metadata),system_settingstable (github_templateskey) stores admin overrides,GET/PUT/DELETE /api/settings/github-templatesendpoints, Settings UI with add/remove/save/reset - Behavior:
None(key missing) = use defaults,[]= no GitHub templates,[{...}]= custom list. Admin-provided display_name overrides repo's template.yaml value.
- Status: ✅ Implemented
- Description: Read template.yaml for display name, description, resources, credentials
- Status: ✅ Implemented (2026-07-06)
- Description: A GitHub template can declare
fork_to_own: requiredin itstemplate.yaml; creating an agent from it copies the template into a repo the user owns (private by default) and the agent'soriginpoints there — captures, operator Push, and auto-sync write to the user's repo, never the shared upstream template. Cornelius is the first user; the mechanism is template-generic. - Key Features:
POST /api/agentsaccepts an optionalfork_to_ownblock:{destination_repo: "owner/name", github_pat (SecretStr), private: true}. The copy (repo creation + push of the template's default branch with full history) runs under the user's PAT — the platform PAT is read-only for the template clone.- Backend enforces
fork_to_own: required(400FORK_TO_OWN_REQUIREDwithout the block) so MCP/CLI paths can't silently create upstream-pointed agents.@branchtemplate syntax andlocal:templates are rejected with the block (400). - Privacy: destination repo is private by default; public requires an explicit
private: falsethe UI gates behind a loud warning. - The user PAT is persisted as the agent's per-agent PAT (#347, AES-256-GCM) so recreates re-bake it — the agent never falls back to the platform PAT.
- Destination collision handling: non-empty repo → 409
FORK_DESTINATION_EXISTS, unless its only branch head matches the template tip (retry-safe reuse); repo already bound to a live agent → 409FORK_DESTINATION_IN_USE; empty repo (incl. pre-created without README) is reused. upstreamremote auto-added in the agent workspace (credential-less, public templates) sogit pull upstream mainadopts template improvements;GIT_UPSTREAM_REPOenv var baked at creation.- Fork-to-own agents are pinned to source mode (origin main = the brain) with the 15-min auto-sync heartbeat enabled (pushing to your own main is the point).
- Create Agent modal renders templates carrying
fork_to_ownas featured cards (tagline surfaced from template.yaml) with destination/PAT/visibility fields.
- Out of scope (v1): MCP
create_agenttool does not acceptfork_to_own(tool args are audit-logged — a PAT arg would persist in plaintext); PAT expiry/rotation UX (sync-health alerts detect push failures); upstream-update UI affordance.
- Status: ✅ Implemented (2025-12-25)
- Description: Browser-based xterm.js terminal with Claude Code TUI
- Key Features: PTY forwarding, mode toggle (Claude/Gemini/Bash), resize support
- Flow:
docs/memory/feature-flows/agent-terminal.md
- Status: ✅ Implemented
- Description:
/api/agents/{name}/chatendpoint with stream-json output parsing
- Status: ✅ Implemented
- Description: Persistent chat history per agent stored in database
- Status: ✅ Implemented
- Description: Token usage display (e.g., "45.5K / 200K") with color-coded progress bar
- Status: ✅ Implemented
- Description: Cumulative cost display across conversation
- Status: ✅ Implemented (2026-02-19)
- Description: Dedicated Chat tab in Agent Detail with simple bubble UI for authenticated users
- Key Features: Session selector dropdown, New Chat button, Dashboard activity tracking (uses
/taskendpoint), shared components with PublicChat - Spec:
docs/requirements/AUTHENTICATED_CHAT_TAB.md - Flow:
docs/memory/feature-flows/authenticated-chat-tab.md
- Status: ✅ Implemented (2026-03-03, extended 2026-03-04)
- Description: Real-time status labels in Chat tab and Public Chat reflecting agent activity (replaces static "Thinking...")
- Key Features: SSE stream subscription, tool-name-to-label mapping, 500ms anti-flicker, 10s heartbeat timeout, async_mode task execution with session persistence
- Scope: Authenticated Chat tab + Public Chat links (both use async_mode + SSE streaming)
- Persistence hardening (#1444):
async_mode+save_to_sessionchat-session persistence is fail-loud (a write error logs at ERROR with a stack trace and achat_persist_failedmarker on the sync response; never silently swallowed, never 500s a billed turn) and owner-checks a caller-suppliedchat_session_id(IDOR fix). Guarded on a SUCCESS terminal only (FAILED/CANCELLED turns write no session). Covered by a fast unit regression guard (tests/unit/test_1444_chat_session_persistence.py) — the slowrequires_agentintegration tests (test_dynamic_thinking_status.py::TestAsyncModeSessionPersistence) now also assert the execution reachedsuccessbefore demanding a session, disambiguating an execution failure from a persistence failure. - Spec:
docs/requirements/DYNAMIC_THINKING_STATUS.md - Flow:
docs/memory/feature-flows/authenticated-chat-tab.md
- Status: ✅ Implemented (2026-05-01), GA (2026-05-04)
- Requirement ID: SESSION_TAB_2026-04
- GitHub Issue: #651
- Description: New Agent Detail tab that lives alongside the existing Chat tab. Each turn reattaches to the same Claude Code session via
claude --print --resume <uuid>, preserving tool-result memory, mid-skill state, and reasoning state across messages — strictly more capable than Chat's stateless text-replay model. - Key Features:
- New
agent_sessionsandagent_session_messagestables, strictly parallel tochat_sessions/chat_messages(no shared state, no FK between them) - Six endpoints under
/api/agents/{name}/sessions*(create, list, get, message, reset, delete) SessionPanel.vue+stores/sessions.jsreuse Chat sub-components for visual parity- Stream-json parser fix recognises
{"type":"system","subtype":"init"}(Phase 1.3) persist_sessionflag plumbed throughParallelTaskRequest → AgentRuntime → ClaudeCodeRuntime- Resume-failure fallback: clears cache, retries cold once on missing JSONL (Anthropic upstream #39667 / #53417)
- Per-
(agent, claude_uuid)Redis lock (SET NX EX 300s, 30s wait ceiling) prevents JSONL corruption (Anthropic #20992) - Per-user ownership returns 404 on mismatch (does not leak session-id existence — E6)
- JSONL cleanup service: synchronous best-effort reap on reset/delete + 6h periodic sweep with 1h race guard
- JSONL-side fallback recovery for stdout pipe race + JSONL-side compact event capture
- Cross-session contamination empirical gate (
test_session_cross_contamination.py, Anthropic #26964)
- New
- Default: ON (
session_tab_enabledflag flipped to True for GA on 2026-05-04, PR #652) - Spec:
docs/planning/SESSION_TAB_2026-04.md - Flow:
docs/memory/feature-flows/session-tab.md - Unified Chat tab (#1112): the separate Session tab is collapsed into the single
Chat tab, which carries a Session-mode toggle (default ON, persisted
per-user in
localStorage['trinity.chatMode']). ON →SessionPanel; OFF → legacyChatPanel. The toggle is hidden and the tab falls back to legacy whensession_tab_enabledis off or the runtime lacks--resume(Codex) — never zero chat surfaces.?tab=sessionaliases to the Chat tab; execution-resume (resumeSessionId) forces legacy for that landing without changing the saved preference. See architecture → Session Tab.
- Status: ✅ Implemented
- Description: Real-time tool execution tracking with
--output-format stream-json --verbose
- Status: ✅ Implemented
- Description: Visual counts per tool type, sorted by frequency
- Status: ✅ Implemented
- Description: List of all tool calls with timestamps and durations
- Status: ✅ Implemented (2025-12-02)
- Description: Centralized
agent_activitiestable for all runtime activities - Flow:
docs/memory/feature-flows/activity-stream.md
- Status: ✅ Implemented (2025-11-29)
- Description: Agents communicate via Trinity MCP with agent-scoped API keys
- Flow:
docs/memory/feature-flows/agent-to-agent-collaboration.md
- Status: ✅ Implemented (2025-12-10, Updated 2026-02-19)
- Description: Explicit permission model controlling which agents can call which
- Key Features: Permissions tab in UI, restrictive default (no auto-grant), explicit opt-in
- Flow:
docs/memory/feature-flows/agent-permissions.md
- Status: ✅ Implemented (2025-12-13)
- Description: File-based collaboration via shared Docker volumes
- Key Features: Expose/consume toggles, permission-gated mounting
- Flow:
docs/memory/feature-flows/agent-shared-folders.md
- Status: ✅ Implemented (2025-12-02)
- Description: Real-time visual graph showing agents and animated connections
- Key Features: Vue Flow, draggable nodes, context progress bars, replay mode
- Flow:
docs/memory/feature-flows/agent-network.md
- Status: ✅ Implemented (2026-01-10)
- Description: Graph/Timeline mode toggle with execution visualization
- Key Features: Execution boxes (color-coded by trigger), collaboration arrows, live streaming
- Flow:
docs/memory/feature-flows/dashboard-timeline-view.md
- Status: ✅ Implemented (2026-01-04)
- Description: Waterfall-style timeline visualization of agent activities
- Key Features: Zoom controls (50%-2000%), agent rows, activity bars, communication arrows
- Flow:
docs/memory/feature-flows/replay-timeline.md
- Status: ❌ Removed (2025-12-23)
- Reason: Individual agent planning deferred to orchestrator-level. Claude Code handles task management internally.
- Status: ✅ Implemented (2026-07-06)
- Description: Third dashboard mode (Grid / Graph / Timeline) — a magnetic tile canvas: rich 384×216 landscape agent tiles snapping to a sparse, unbounded lattice the operator arranges freely, on the same pan/zoom dotted-canvas language as the graph view. Not the default (Timeline remains default for new users); selection persists to localStorage.
- Key Features: iPhone-style drag with live socket preview + swap-with-preview; Tidy up / Reset; keyboard arrow reorder; per-user layout (
agent → {col,row}, localStorage v1, self-healing); five-zone tile (identity with half-out avatar, adaptive chip strip with live working timer, Activity·14d stacked-by-trigger + Context·7d trend charts, success micro-meter + stats, Run/Auto toggles); system agent keeps its purple treatment;prefers-reduced-motionhonored. - Performance (first-class): skeleton-first render from
/api/agents; per-tile analytics hydrate lazily (viewport-gated, concurrency-capped) into the existing(agent, window)cache with stale-while-revalidate; batch endpoints for chip data (sync-health, operator-queue) on a visibility-aware poll that tears down when the mode is inactive; viewport culling for 50+ fleets. No new backend endpoints — reads/api/agents/{name}/analytics(#1107), fleet context/execution/slot stats,/api/agents/sync-health(#389), operator-queue pending. - Out of scope (follow-ups): fleet KPI strip; "Needs your attention" + live-activity right rail.
- Flow:
docs/memory/feature-flows/dashboard-grid-view.md
Description: A capability-gated per-agent page that renders a Cornelius-class agent's live
3D knowledge-graph orb from data the agent produces in its own container, with live scope control
and a client-held voice tile. Shipped: static render (Phase 1, FR-1…5) + scope mount/unmount →
re-export → live rebuild (Phase 2, FR-6) + client-held Gemini Live voice tile + read-only KB search
(Phase 3, FR-7) + owner-gated KB-write actions capture/link (Phase 4a, FR-8) + voice-transcript
capture & configurable post-session processing (Phase 4b, FR-9, #66). Only run_skill (arbitrary
headless exec from the orb) remains out of scope. Default OFF — no impact on other agents or the UI.
See feature-flows/brain-orb.md.
-
FR-1 — First-party CSP-clean assets: the orb ships as verbatim first-party frontend assets (
public/brain-orb/), withthree/marked/DOMPurify/font vendored locally and the inline module externalized, so it runs under prodscript-src 'self'/font-src 'self'with no nginx change. Only mechanical orb edits (externalize, vendor, repoint data fetch, neutralize the deferred voice proxy, hide deferred panels). Note bodies are DOMPurify-sanitized (H-005). -
FR-2 — Capability gating: a
/agents/:name/brainroute (lazy +beforeEnterplatform-flag guard) and a Brain tab shown only whenbrain_orb_available(runtime-resolved platform flag — admin setting →BRAIN_ORB_ENABLEDenv fallback, default OFF; FR-11) AND the agent'stemplate.yaml capabilitieslist contains the generalizablebrain-orbtoken (surfaced by/api/agents/{name}/info) — never a hardcoded agent name. -
FR-3 — Same-origin iframe host:
views/AgentBrainOrb.vueembeds the first-party page in a same-origin iframe (not agent-origin → avoids the #979 CSP trap, no Vue rewrite of the renderer). -
FR-4 — Auth via postMessage, standard Bearer: the host hands the user's JWT to the iframe via origin-pinned
postMessage(never in a URL); the data route uses standardAuthorizedAgentByNameBearer auth — no new ticket primitive. Abrain-orb:errormessage shows an empty state. -
FR-5 — Read-only proxy (agent owns generation):
GET /api/agents/{name}/brain-orb/data(AuthorizedAgentByName) proxies viaagent_httpx_client(#1159) to the agent-serverGET /api/brain-orb/data, which streams~/resources/agent-visualization/data.json. Byte pass-through (no re-serialize of the multi-MB JSON); 404 when the flag is off / no export, 503/504 unreachable, 502 agent error. Trinity never runsexport_data.py(Invariant #8). -
FR-6 — Live scope control (Phase 2): the orb's scope panel mounts/unmounts vault scopes, driving an agent re-export → live in-place rebuild (no reload).
GET /api/agents/{name}/brain-orb/scopes(AuthorizedAgentByName, read) lists selectable + active scopes;POST .../brain-orb/scope(OwnedAgentByName— owner/admin) mutates the set. The agent provides two executable convention hooks (~/.trinity/brain-orb/{scopes,scope}, mirrors~/.trinity/pre-check); the agent-server runs them via hardened async subprocess (timeout-kill, output cap, JSON-parse + non-zero-exit guards) and 404s when absent. The agent owns scope state + the re-export (Invariant #8); Trinity only brokers. Replaces the local voice proxy's per-startX-Orb-Tokenwith the platform JWT + owner gate. -
FR-7 — Client-held Gemini Live voice tile + read-only KB search (Phase 3, #60): the orb's voice tile holds its own Gemini Live session client-side — the browser connects DIRECTLY to Gemini Live (mic capture + playback in the same-origin iframe), Trinity never proxies the audio. Deliberately distinct from Trinity's backend-proxied workspace voice (VOICE-001), to keep the voice→tool→orb loop in-browser. Ephemeral-credential broker:
POST /api/agents/{name}/brain-orb/ voice-token(AuthorizedAgentByName; per-(user,agent) rate-limited) mints a short-lived, config-locked Gemini Live ephemeral token viaauth_tokens.create(live_connect_constraintspins model + the whole config incl. the tool surface;uses=1; ~60s new-session window; expiry =VOICE_MAX_DURATION). Built with a dedicated v1alpha genai client (NOT the cached voice singleton). The token is minted by the orb page (which holds the JWT) and relayed to the nested voice iframe overpostMessage— the JWT never enters the voice iframe or a URL; the voice iframe only ever sees the single-use Google token. Response field isephemeral_token(nevertoken, which would flip the deferred write surface on). Visual-only tools (highlight_related_notes,navigate_to_note,list_converged_topics, …) run in-browser via the existingorb-toolpostMessage bridge. Scope-by-voice reuses Phase 2 (mount_scope/unmount_scope→ the FR-6/scopebroker — no new mutation surface). Read-only KB search:POST /api/agents/{name}/ brain-orb/tool(AuthorizedAgentByName) → agent-server runs the agent's~/.trinity/brain-orb/ searchconvention hook (scope-aware, read-only; 404 when absent). Writes stay off by construction: the locked tool manifest declares only read/visual/scope tools; the browser cannot widen it, and orb.js'sACTIONSwrite surface stays disabled (no/sessionroute). Gating: a newbrain_orb_voice_availableflag (BRAIN_ORB_VOICE_ENABLED && GEMINI_API_KEY, default OFF) — distinct from the staticbrain_orb_available— AND the agent'sbrain-orbcapability, enforced by BOTH the route guard and the tab (the orb is never launchable on a non-Cornelius agent, even via a raw URL — thebeforeEnterguard reads/infocapabilities and redirects otherwise, #60). CSP-clean:connect-srcalready allowswss:; the Gemini JS client is hand-rolled (no SDK), the voice logic and mic worklet are externalized same-origin files (script-src 'self'); the standalone page's hardcoded key is stripped; its p5.js audio-reactive voice orb is vendored locally (not CDN) so the speech animation is retained CSP-clean. The outer host iframe carriesallow="microphone". -
FR-8 — Owner-gated KB-write actions: capture + link (Phase 4a, #61): the orb's action panel (
#actions,Akey) + inspector connect are un-hidden and rewired from the dead standalone voice proxy to the platform broker. Two owner/admin-only write verbs — capture (a note into the agent's inbox) and link ([[wikilink]]two notes).POST /api/agents/{name}/brain-orb/action(OwnedAgentByName) enum-validates the verb (run_skill/capture_transcript → 400, Phase 4b), body-caps (413), rate-limits per (user, agent, action), audit-logs (brain_orb_capture/brain_orb_link), and dedups viaIdempotency-Key(Invariant #18, key folded per verb — NOT the #1084 effect_guard, which is execution_id-scoped and has no execution here);GET .../brain-orb/actions(OwnedAgentByName) reports{enabled, skills}so the orb un-hides the panel only for owners (403/404 otherwise). Both proxy to the agent-server, which runs the agent's~/.trinity/brain-orb/actionconvention hook via the hardened_run_hook(agent owns the write, Invariant #8; 404 when absent). Voice write tools are owner-gated: the mint route computescan_write(owner + flag) and only then foldscapture_note/link_notesinto the locked manifest — shared-user sessions keep the read-only Phase-3 manifest, and the/actionroute is the hard gate regardless. Own kill-switchBRAIN_ORB_WRITE_ENABLED(env, default OFF; distinct fromBRAIN_ORB_ENABLEDso writes disable without downing read/voice) →brain_orb_write_availablein feature-flags. No DB change, no migration. -
FR-9 — Voice-transcript capture + configurable post-session processing (Phase 4b, #66): mirrors the original
cornelius-internal/resources/agent-visualization/voice/(client captures, agent renders/saves). The mint addsinput_audio_transcription/output_audio_transcriptionto the lockedLiveConnectConfig, so the constrained ephemeral token returns per-turn transcription.voice.jsbuffers input/output transcription into conversation events (session_start/user_turn/model_turn/tool_call/session_end) and, onendConversation(the correct flush seam —oncloseearly-returns onwsClosedByUs), relays them toorb.js, which POSTscapture_transcript {session_id, events, process}(session-id =Idempotency-Key→ a double session-end saves one transcript). Theactionhook renders a markdown transcript intoresources/inbox/Voice Conversations/(portedtranscript_io). Post-session processing (process_transcript, orcapture_transcript {process:true}): if the agent ships~/.trinity/brain-orb/voice-postprocess.md(the "formulated prompt config" — configuring it is the opt-in), the hook runs that prompt over the transcript via a detachedclaude -p(transcript piped on stdin — no shell string → no command injection), writing a processed note. Owner-only (OwnedAgentByName+ACTIONS.enabled), body cap raised to 1 MiB (backend + agent-server) for whole conversations. No DB change. Confirmed on localhost: constrained-token mint accepts the transcription config, and synthetic voice events render + save; full live-audio transcription streaming is a manual voice-session check. -
FR-10 — Write → graph refresh loop + visible integration (#67, #68): closes the gap where captured notes / links landed in the inbox but never appeared on the orb.
POST /api/agents/{name}/brain-orb/refresh(OwnedAgentByName, 200s timeout mirroring/scope, auditedbrain_orb_refresh) → agent-serverPOST /api/brain-orb/refresh→ theactionhook'srefreshverb reindexes + re-exportsdata.json(folds inbox notes +_links.mdedges into the graph; the agent owns generation, Invariant #8).orb.jsrefreshGraph()refetches/dataand rebuilds in place (same machinery assetScope), auto-triggered after capture/link (voice writes debounced ~4s so a burst coalesces into one rebuild), plus a visible "↻ integrate & refresh" control, an "integrating…" state, and a "graph updated · +N notes, +M links" confirmation toast (#68). No DB change. Confirmed on localhost: capture → refresh folds the note in as a real graph node (1072 → 1079), and the UI control rebuilds with the confirmation toast. -
FR-11 — Admin-configurable platform flags (trinity-enterprise#85): the three platform flags (
brain_orb_enabled,brain_orb_voice_enabled,brain_orb_write_enabled) are runtime-resolved, not import-time env constants:system_settingsrow ("true"/"false", wins in both directions) →BRAIN_ORB_*env var honored as opt-in fallback → default OFF (theworkspace_enabledidiom via one shared_resolve_bool_flaghelper). Resolvers are fail-open (a settings-read failure falls back to the env/default leg — a raise would 500feature-flagsand zero every flag in the frontend store) and deliberately uncached (--workers 2cross-worker consistency, #506 rationale). All route gates inrouters/agent_brain_orb.pyand the threefeature-flagsvalues read the resolvers, so an admin flip applies without restart; the voice-token mint additionally composes with the base flag (base ∧ voice, closing the base-OFF mint gap) andbrain_orb_voice_available = base ∧ voice ∧ GEMINI_API_KEY. Admin surface:GET/PUT /api/settings/brain-orb(admin-only, registered before the/{key}catch-all) — GET returns per-flag{value, source: override|env|default}+gemini_key_configured; PUT takes partial booleans and/orclear: [flag,…]to revert a flag to its env/default (the env var is otherwise dead once a DB override exists), audit-logged with per-flag old→new values. Settings → General hosts the panel (per-flag source display, write-surface warning, post-saveloadFeatureFlags(force); other open sessions pick the change up on next page load). GEMINI_API_KEY stays env-only (secret). No migration (system_settingsKV).
Still out of scope: run_skill (arbitrary allow-listed headless exec from the orb) — the full exec surface
with a template.yaml allow-list ceiling + #1083 detached-execution integration remains unbuilt; open a fresh
issue if it's ever wanted. Also deferred: data.json caching/streaming.
- Status: ✅ Implemented (2026-07-07)
- Description: A fresh Trinity install auto-seeds a default "Cornelius" second-brain agent with the
Brain Orb enabled, so a first-run operator lands on a working knowledge-graph agent out-of-the-box
(no manual create/clone). Provisioned by
services/cornelius_agent_service.py::CorneliusAgentService.ensure_seeded(). - Key Features:
- Bundled local template: a new
config/agent-templates/cornelius/LOCAL template (capabilities: [brain-orb],CLAUDE.md,.trinity/brain-orb/hooks, a pre-generatedresources/agent-visualization/data.jsonseed graph so the orb renders immediately, and a minimal seedBrain/vault). Sourced from the publicgithub.com/Abilityai/cornelius; provisioned via the ordinarycreate_agent_internalfromlocal:cornelius(no PAT / network / clone). - First-run-only: a durable
cornelius_seededsystem-setting flag gates the seed — an operator who deletes Cornelius is not re-provisioned. - Fresh-install-scoped: skipped when any non-system agent already exists (
db.count_non_system_agents()), so upgrades of established fleets aren't surprised by a new agent. - Existence-guarded flag enable: turns on the
brain_orb_enabledplatform flag only when unset — never clobbers an admin who set it OFF. - Triggers: the setup-completion handler (
routers/setup.py, fresh installs, FastAPI BackgroundTask)- a
main.pylifespan safety-net gated onsetup_completed && !cornelius_seeded(upgrades). A Redis SETNX lock (cornelius:provision, fail-open, mirrors the #1464 leader-lock) guards the--workers 2race.
- a
- Bundled local template: a new
- Known deviation (local bundle): the default Cornelius is a LOCAL bundle, not github-native, so it has
no git origin — it won't auto-
git pullupstream template updates. Durable ownership is deferred to fork-to-own (trinity-enterprise#109). No DB migration (system_settingsis free-form KV). The Brain Orb was already fully OSS (flag-gated, not entitlement-gated), so no de-gating was needed. - Flow:
docs/memory/feature-flows/cornelius-default-agent.md