Skip to content

feat(ui): Brain Orb — Cornelius-class agent page: static render + live scope control (abilityai/trinity-enterprise#58)#1389

Merged
dolho merged 25 commits into
devfrom
feature/58-brain-orb
Jul 3, 2026
Merged

feat(ui): Brain Orb — Cornelius-class agent page: static render + live scope control (abilityai/trinity-enterprise#58)#1389
dolho merged 25 commits into
devfrom
feature/58-brain-orb

Conversation

@vybe

@vybe vybe commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Capability-gated /agents/:name/brain page that renders a Cornelius-class agent's live 3D knowledge-graph orb from data the agent produces in its own container — plus live scope control (mount/unmount → agent re-export → in-place rebuild).
  • Two self-contained, default-OFF phases. Voice (Gemini Live), KB-write actions, transcript capture, and headless-skill injection remain deferred to later epic children.
  • Ships default OFF behind BRAIN_ORB_ENABLED + a per-agent brain-orb capability token → zero impact on existing agents or the UI.

Phase 1 — static render + read path

The orb is shipped as verbatim first-party static assets (src/frontend/public/brain-orb/) hosted in a thin Vue view via a same-origin iframe — first-party + external scripts, exactly what prod CSP script-src 'self' permits, so it runs with no nginx change (#979 only bit because it iframed agent-origin + inline scripts). three/marked/DOMPurify/JetBrains-Mono vendored locally; note bodies DOMPurify-sanitized (H-005). JWT reaches the iframe's fetch via origin-pinned postMessage (never a URL) — no new ticket primitive. Read proxy GET /api/agents/{name}/brain-orb/data (AuthorizedAgentByName) → agent-server GET /api/brain-orb/data streams data.json (FileResponse).

Phase 2 — live scope control

The scope panel (S key) lists the agent's vault scopes; toggling one mutates the active set, the agent re-exports at the new scope (rewriting data.json), and the orb re-fetches /data and rebuilds in place. The old localhost voice-proxy X-Orb-Token is replaced by the platform JWT + an owner gate.

Changes

Layer What
Orb assets public/brain-orb/{index.html, orb.js, styles.css, orb-trinity.css, vendor/*} — CSP-clean; scope fetches routed through the brokered proxy w/ Bearer; scope panel un-hidden
Frontend lazy /agents/:name/brain route (flag beforeEnter); AgentBrainOrb.vue iframe host + postMessage JWT; Brain tab gated on flag + brain-orb capability
Backend routers/agent_brain_orb.pydata/scopes (read) + scope (owner mutation); brain_orb_available flag (BRAIN_ORB_ENABLED, default OFF)
Agent-server agent_server/routers/brain_orb.py — data FileResponse + scope-hook runner
Docs architecture.md subsystem + endpoints; feature-flows/brain-orb.md; requirements §46 (FR-1…6)

Invariants: #5 agent-server mirror · #8 agent owns generation + scope state · #4 route order · #15 nesting. No DB change, no migration, no new secret, no MCP tool.

Test Plan

  • tests/unit/test_brain_orb.py24 passing: backend data/scopes/scope proxies (flag-off, read vs owner gate, success pass-through, 404/413/502/503/504 mapping) + agent-server data read + real-subprocess scope hooks (present/absent, stdin forwarding, timeout→504, invalid-JSON→502, non-zero-exit→502).
  • Frontend vite build clean; orb asset port verified in a browser against the real 5.6 MB data.json (renders the graph; scope panel opens on S; voice/action panels hidden; only benign caught fetches).
  • Secrets/PII scan of committed orb assets — clean.
  • Full in-stack scope parity needs a real Cornelius-class agent providing the ~/.trinity/brain-orb/ hooks (manual; noted in the flow doc).

Notes

  • Cross-repo: the issue lives in the private abilityai/trinity-enterprise tracker but the code is core, so it lands here. Fixes does not auto-close across repos — closed at release.

Fixes abilityai/trinity-enterprise#58

🤖 Generated with Claude Code

Eugene Vyborov and others added 2 commits June 30, 2026 18:06
…s (static-render foundation) (abilityai/trinity-enterprise#58)

Capability-gated /agents/:name/brain page that renders an agent's live 3D
knowledge-graph orb from data the agent produces in its own container. This
is the static-render foundation — voice, scope-mutation, KB-write actions,
transcript capture, and headless-skill injection are deferred to later epic
children. Default OFF, so zero impact on existing agents and the UI.

- First-party CSP-clean assets (public/brain-orb/): the verbatim orb's inline
  module is externalized to orb.js; three/marked/DOMPurify/JetBrains-Mono are
  vendored locally; it runs under prod CSP script-src 'self' / font-src 'self'
  with NO nginx change (the #979 trap was agent-origin + inline; this is
  first-party + external). Note bodies are DOMPurify-sanitized (H-005).
- Frontend: lazy /agents/:name/brain route (platform-flag beforeEnter guard);
  AgentBrainOrb.vue thin chrome + same-origin iframe; JWT handed to the iframe
  via origin-pinned postMessage (never a URL) — no new ticket primitive; Brain
  tab gated on brainOrbAvailable AND the template.yaml `brain-orb` capability.
- Backend: GET /api/agents/{name}/brain-orb/data — AuthorizedAgentByName read
  proxy via agent_httpx_client (#1159), byte pass-through (no re-serialize),
  flag-gated 404, 503/504/502 error mapping.
- Agent-server: GET /api/brain-orb/data streams data.json via FileResponse
  (agent owns generation — Invariant #8; auto-gated by the #1159 middleware).
- brain_orb_available feature flag (BRAIN_ORB_ENABLED, default OFF).
- 10 unit tests (backend proxy branches + agent-server read); architecture.md,
  feature-flows/brain-orb.md, requirements §46.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…in-place rebuild (abilityai/trinity-enterprise#58)

Adds the button-driven "self-rendering" scope-control loop on top of the
static-render foundation. No Gemini/voice — that stays deferred to a later child.

- Orb: route the scope panel's fetches through the per-agent brain-orb proxy
  base carrying the platform JWT (replaces the localhost voice proxy + per-start
  X-Orb-Token); decouple setScope from the voice-session token (gate on the
  Trinity embed); re-fetch /brain-orb/data after a remount; un-hide the scope
  panel (S key). Voice + action panels stay hidden.
- Agent-server: GET /api/brain-orb/scopes + POST /api/brain-orb/scope run the
  agent's ~/.trinity/brain-orb/{scopes,scope} convention hooks (mirrors
  ~/.trinity/pre-check) via hardened async subprocess (timeout-kill, output cap,
  JSON-parse + non-zero-exit guards); 404 when a hook is absent. The agent owns
  scope state + the re-export (Invariant #8).
- Backend: GET .../brain-orb/scopes (AuthorizedAgentByName, read) + POST
  .../brain-orb/scope (OwnedAgentByName — the only mutating brain-orb route;
  64 KB body cap, 200s timeout); shared gate/proxy helper, byte pass-through.
- 14 more unit tests (24 total) incl. real-subprocess hook coverage (stdin
  forwarding, timeout->504, invalid-JSON->502, non-zero-exit->502). Docs:
  architecture.md, requirements §46 FR-6, feature-flows/brain-orb.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vybe vybe changed the title feat(ui): Brain Orb — native per-agent page for Cornelius-class agents (abilityai/trinity-enterprise#58) feat(ui): Brain Orb — Cornelius-class agent page: static render + live scope control (abilityai/trinity-enterprise#58) Jun 30, 2026
…ad-only KB search (abilityai/trinity-enterprise#60)

Adds the voice control surface on top of the Phase 1/2 orb (static render +
live scope control). The browser holds its own Gemini Live session — it connects
DIRECTLY to Gemini using a short-lived, config-locked ephemeral token minted by
Trinity — so the voice→tool→orb loop stays in-browser and Trinity never proxies
the audio.

Backend:
- POST /api/agents/{name}/brain-orb/voice-token (AuthorizedAgentByName, per-user
  rate-limited): mints an ephemeral Gemini Live token via a dedicated v1alpha
  genai client (NOT the cached voice singleton). live_connect_constraints lock
  the model + the whole config incl. a READ/VISUAL/SCOPE-only tool manifest;
  uses=1; ~60s new-session window; expiry = VOICE_MAX_DURATION. Response field is
  `ephemeral_token` (never `token`, which would flip orb.js's Phase-4 write
  surface on). services/brain_orb_voice_service.py.
- POST /api/agents/{name}/brain-orb/tool (read-only): proxies to the agent's
  ~/.trinity/brain-orb/search hook (scope-aware, no writes; 404 when absent).
- BRAIN_ORB_VOICE_ENABLED flag → brain_orb_voice_available (= flag && GEMINI_API_KEY),
  distinct from brain_orb_available.

Frontend:
- New CSP-clean voice tile: public/brain-orb/voice/{orb.html,voice.js,mic-worklet.js}.
  Adapted from the Cornelius page with the hardcoded API key and p5-from-CDN
  stripped, logic externalized (script-src 'self'), the mic worklet shipped as a
  same-origin file. Hand-rolled Gemini Live client uses the Constrained method +
  access_token param + setup{model} (config locked in the token).
- orb.js: mints the token via the JWT-gated broker and relays only the ephemeral
  token to the nested voice iframe over postMessage (JWT never enters the iframe
  or a URL); repoints navigate_to_note's vault fallback at the read-only /tool;
  un-hides the voice tile on the voice flag. Same-origin postMessage guards.
- AgentBrainOrb.vue: allow="microphone" on the host iframe; relays voiceAvailable.

Writes stay off by construction (locked manifest + no /session route). Tests
extend tests/unit/test_brain_orb.py (39 pass): mint gating/field-name/v1alpha/
rate-limit, /tool proxy, search hook, and the locked-manifest guard. The live
ephemeral handshake + end-to-end loop are /verify-only (need a live agent+Gemini).

Docs: requirements §46 FR-7, feature-flows/brain-orb.md Phase 3, architecture.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Eugene Vyborov and others added 2 commits July 1, 2026 09:46
…or the voice tile (abilityai/trinity-enterprise#60)

Local deploy surfaced that the pinned google-genai==1.12.1 has NO `auth_tokens`
attribute, so the Brain Orb voice-token mint 502'd (`'Client' object has no
attribute 'auth_tokens'`) — the ephemeral-token API was added in a later release.

- docker/backend/Dockerfile: google-genai 1.12.1 → 1.63.0. Verified in the local
  stack: POST /voice-token returns a real auth_tokens/… token (model+voice locked,
  11 read/visual/scope tools, zero write tools, no bare `token` field), and
  services/gemini_voice.py (existing VOICE-001 backend-proxied path) still imports
  cleanly under 1.63. A live VOICE-001 voice-session smoke test is a pre-merge item.
- docker-compose.yml: pass BRAIN_ORB_ENABLED + BRAIN_ORB_VOICE_ENABLED through to
  the backend container (they were never wired into compose, so the flags couldn't
  reach config.py even when set in .env).
- Docs: brain-orb.md / voice-chat.md updated for the new pin.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…orb animation (abilityai/trinity-enterprise#60)

Two fixes surfaced by local testing on a real Cornelius agent:

1. STRICT GATING — the orb is no longer launchable on a non-Cornelius agent, even
   via a raw URL. The route guard (router/index.js beforeEnter) now fetches
   GET /api/agents/{name}/info and redirects to AgentDetail unless the agent
   declares the `brain-orb` capability (in addition to the platform flag) — not
   just the tab. Previously the raw /agents/{name}/brain URL loaded an empty orb
   for any agent; now a non-capable (or stopped) agent redirects. The voice tile
   inside the page then only needs the platform voice flag (capability already
   guaranteed by the guard).

2. VOICE ORB ANIMATION RESTORED — the audio-reactive orb in the voice tile (the
   p5 smoke/core sketch) was cut when p5 was removed for loading from a CDN
   (CSP script-src 'self'). Re-added by vendoring p5 locally
   (voice/vendor/p5.min.js — no eval/Function, CSP-clean) and restoring the
   sketch + an output AnalyserNode, so the orb breathes when idle and pulses with
   Cornelius's speech (enqueueAudio routes each output buffer through outAnalyser).

Verified end-to-end on a locally-deployed Cornelius agent (brain-orb capability):
the orb renders the real graph, the Brain tab appears only on Cornelius, a raw
URL to a non-Cornelius agent redirects, and the voice tile shows the animated orb;
the /tool search + /scopes hooks and the ephemeral-token mint all function.

Docs: requirements §46 FR-2/FR-7, feature-flows/brain-orb.md, architecture.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 1, 2026

Copy link
Copy Markdown

⚠️ Nightly unit-suite check skipped — merge conflict against dev.

Resolve by running git merge dev locally and pushing the result. The next nightly run will re-test once the conflict is gone.

Eugene Vyborov and others added 3 commits July 1, 2026 11:34
…b (abilityai/trinity-enterprise#60)

- Auto-start: opening the voice tile (V key / dock chip) now starts the
  conversation automatically instead of requiring a manual "Start" click. The
  tile auto-starts on load, and re-opening an already-loaded tile re-starts via
  an `orb-voice-start` message. The Start button remains as a retry if the mic is
  denied (auto-start → ERROR).
- Voice-orb jank: cap the p5 audio-reactive orb to pixelDensity(1). The sketch is
  fill-bound (many radial gradients/frame); at retina 2x it costs ~4x and starves
  the main WebGL knowledge orb of frames, so the big orb visibly jerks during its
  slow rotation/fly. 1x is imperceptible on the small voice orb and frees the main
  render loop. (The main orb's animation loop is unchanged — byte-identical to the
  upstream Cornelius source, dt-clamped; the jank was pure frame contention added
  by the restored voice orb inside the Trinity SPA + nested iframes.)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…abilityai/trinity-enterprise#60)

- Brain TAB is now an in-page panel (BrainPanel.vue) — brain settings placeholder
  + an "Open Brain Orb" launch button — instead of auto-jumping to the full-page
  orb on tab select (removed the route-push watcher). Clearer, and leaves room for
  real brain settings.
- Header brain LOGO: a small orb glyph in the AgentDetail header (AgentHeader),
  capability-gated (platform flag AND the agent's brain-orb capability), that opens
  the orb page directly.
- Every orb WIDGET now has a small, always-visible close ✕: the `.tile-x` is no
  longer hover-only (opacity .32, brightens on hover), and lens/scope/search — which
  had none — get one too. Search gains a `find` dock chip to re-open; the briefing
  cards re-inject their ✕ after their innerHTML re-render. Every panel is closeable
  and re-openable from the dock.
- Also cap the main WebGL orb to pixelRatio 1.5 on hi-DPR displays (was 2.0) for
  GPU headroom inside the Trinity SPA — a retina jank mitigation (verify on device).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- orb.js renderNoteInto: route every path to innerHTML through
  DOMPurify.sanitize (marked output AND the raw-markdown fallbacks),
  degrading to textContent when DOMPurify is somehow absent. Clears the
  js/xss + js/xss-through-dom alerts and hardens the previously
  unsanitized fallback paths (H-005).
- Add .github/codeql/codeql-config.yml with paths-ignore for vendored
  third-party bundles (three.js/marked/DOMPurify under public/**/vendor,
  **/vendor/**, **/*.min.js) and wire it via config-file. Clears the
  js/insecure-randomness alert in vendored three.module.js (not our code).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vybe
vybe requested a review from AndriiPasternak31 as a code owner July 1, 2026 12:45
@vybe
vybe marked this pull request as draft July 1, 2026 12:45
…ink) (abilityai/trinity-enterprise#61)

Completes the Brain Orb write surface with the low-risk half of Phase 4:
owner/admin-only capture-a-note and link-two-notes. Split from the full
Phase 4 during /autoplan (two independent reviews) — run_skill (headless
claude -p exec), automatic transcript capture, and transcript injection
are deferred to trinity-enterprise#66 (they carry the exec/injection risk
and depend on an unproven Gemini-Live-transcription path).

Backend:
- routers/agent_brain_orb.py: GET /brain-orb/actions + POST /brain-orb/action
  (OwnedAgentByName). Action verb enum-validated at the boundary (run_skill/
  capture_transcript -> 400, never forwarded), body-capped (413), rate-limited
  per (user, agent, action), audit-logged, Idempotency-Key deduped (Invariant
  #18, key folded per verb; effect_guard doesn't fit — no execution_id).
- services/brain_orb_voice_service.py: can_write folds capture_note/link_notes
  into the LOCKED voice manifest only for owners; shared users keep the
  read-only Phase-3 manifest. can_write computed in the mint route.
- config.py + routers/settings.py: BRAIN_ORB_WRITE_ENABLED kill-switch
  (default OFF, distinct from BRAIN_ORB_ENABLED) -> brain_orb_write_available.

Agent-server (Invariant #5 mirror):
- routers/brain_orb.py: GET/POST /api/brain-orb/action[s] via the hardened
  _run_hook over the agent's ~/.trinity/brain-orb/action convention hook
  (agent owns the write, Invariant #8; stdin-only, no shell string).

Frontend:
- orb.js: rewire initActions/postAction/doCapture/doLink from the dead
  localhost voice proxy (X-Orb-Token) to the broker + Bearer JWT +
  Idempotency-Key; un-hide #actions via body.brain-orb-write only after the
  broker confirms owner + flag + hook; add capture_note/link_notes to
  ORB_TOOLS; double-submit re-entrancy guard.
- orb-trinity.css / AgentBrainOrb.vue / stores/sessions.js: gate the panel on
  the write flag, relay writeAvailable in the init handshake.

Tests: 14 new cases (owner-gate 403, flag-off 404, no-hook 404, enum 400,
413, 429 + claim-release, idempotency replay/409, owner-only voice manifest,
agent-server action hook). 62/62 green. Security: /review 0 critical,
/cso --diff 0 findings.

Docs: requirements §46 FR-8, feature-flows/brain-orb.md Phase 4a, learnings
(effect_guard vs Idempotency-Key), cso-diff-2026-07-01 report.

Refs abilityai/trinity-enterprise#61

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Eugene Vyborov and others added 10 commits July 1, 2026 15:53
…he flag reaches the container (abilityai/trinity-enterprise#61)

The Phase 4a write flag existed in config.py + settings feature-flags but was
never added to the backend `environment:` block in docker-compose.yml (mirrors
BRAIN_ORB_ENABLED/BRAIN_ORB_VOICE_ENABLED at lines 40-41). Without it the flag
never reaches the container, so brain_orb_write_available stays false and the
write routes 404 regardless of .env. Surfaced during the localhost demo.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ost-session processing (abilityai/trinity-enterprise#66)

Mirrors the original cornelius-internal voice proxy (resources/agent-visualization/
voice/{proxy_server,transcript_io}.py): the client captures per-turn transcription,
the agent renders + saves it, Trinity brokers. De-scoped per direction — transcript
saving (for debug/analyze) + an optional post-session prompt; run_skill (arbitrary
exec) stays out.

- Mint (brain_orb_voice_service): add input_audio_transcription +
  output_audio_transcription to the LOCKED LiveConnectConfig so the constrained
  ephemeral token returns per-turn transcription. Verified: auth_tokens.create
  accepts it and the mint returns a real token (de-risks the F6.1 unknown).
- voice.js: buffer content.inputTranscription/outputTranscription into conversation
  events (session_start/user_turn/model_turn/tool_call/session_end); flush in
  endConversation() (the correct seam — onclose early-returns on wsClosedByUs) AND
  the error-close branch; relay {session_id, events} to the parent orb.
- orb.js: on 'orb-capture-transcript' (owner + ACTIONS.enabled) →
  postAction('capture_transcript', {..., process:true}) with session_id as the
  Idempotency-Key (double session-end = one save). postAction gains an optional key.
- Backend (agent_brain_orb): accept capture_transcript + process_transcript verbs
  (still OwnedAgentByName, audited); raise the action body cap to 1 MiB for whole
  conversations. run_skill remains rejected (400).
- Agent-server (brain_orb): raise the /action body cap to 1 MiB to match.
- Agent action hook (cornelius template): render markdown transcript (ported
  transcript_io) → resources/inbox/Voice Conversations/; process_transcript runs the
  agent's configured prompt (~/.trinity/brain-orb/voice-postprocess.md — configuring
  it is the opt-in) over the transcript via a DETACHED claude -p with the transcript
  on STDIN (no shell string → no command injection), writing a processed note.

Security posture (owner-gated exec): OwnedAgentByName + owner-authored prompt +
owner's-own-transcript; transcript via stdin closes command injection. Residual
(follow-up): the post-process claude -p uses --dangerously-skip-permissions (full
tools) — a tool-constrained sub-invocation + transcript credential-sanitization are
the hardening path if this graduates beyond a debug tool.

Verified on localhost: mint accepts transcription config; synthetic voice events
render + save to the KB; process_transcript spawns the detached claude -p (ran end
to end — only blocked by the agent's Claude credit balance, not code). Full
live-audio transcription streaming is a manual voice-session check. 64 unit tests
green.

Refs abilityai/trinity-enterprise#66

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te control (abilityai/trinity-enterprise#67, #68)

Fixes the feedback from the live voice test: notes/links created via the orb
landed in the inbox but never appeared on the graph, and there was no way to
confirm integration happened.

#67 (mechanism):
- Backend `POST /brain-orb/refresh` (OwnedAgentByName, 200s timeout mirroring
  /scope, audited `brain_orb_refresh`) → agent-server `POST /api/brain-orb/refresh`
  → the `action` hook's `refresh` verb reindexes + re-exports data.json (folds
  inbox notes + _links.md edges into the graph; agent owns generation, Invariant #8).
- orb.js `refreshGraph()` refetches /data and rebuilds in place (same machinery as
  setScope). Auto-triggered after capture/link; voice writes debounced ~4s so a
  burst coalesces into one rebuild.

#68 (observability):
- Visible "↻ integrate & refresh" control in the actions panel, an "integrating…"
  state, and a "graph updated · +N notes, +M links" / "graph up to date"
  confirmation toast so the user can see the write land.

Verified on localhost: capture → refresh folds the note in as a real graph node
(1072 → 1079 nodes, +3 edges), and the UI control rebuilds with the confirmation
toast. 70 unit tests green (6 new refresh cases).

Refs abilityai/trinity-enterprise#67 abilityai/trinity-enterprise#68

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…yai/trinity-enterprise#70)

Voice-test feedback: navigating to a note or highlighting a topic/group lit the
target but didn't zoom/center to fit it — it settled at a fixed distance
(focusNode→195, highlightGroup→250) regardless of where the selection sat or how
spread out it was, so the highlight read as "not focused."

Add frameNodes(indices): centers on the selection's centroid direction AND derives
a fit distance from the selection's max shell radius + angular spread vs the camera
FOV (single note zooms in; a spread-out cluster zooms out), clamped to the existing
zoom bounds and never inside the node shell. Route focusNode and highlightGroup(frame)
through it, replacing the fixed 195/250.

Verified on localhost: navigate to a single note 330→144 (zooms in; was fixed 195);
highlight a cluster computes a per-group fit; the highlighted group is centered and
framed. (Client-side WebGL camera math — no pytest surface; verified in-browser.)

Fixes abilityai/trinity-enterprise#70

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ding refresh on miss (abilityai/trinity-enterprise#70)

Second round of voice-test feedback: after "create a node, link it to Dopamine,
now show me the connection," the user saw the Dopamine node but not the edge to the
new note. Two causes, both under the navigate/focus (#70) umbrella:

1. Framing showed a LONE node. focusNode already lit up the node + neighbours + drew
   the connecting edges (setHover), but frameNodes([i]) fit only the single node — so
   the other endpoint of a just-made link sat off-screen. Now frame the node TOGETHER
   with its highlighted neighbours (hoverSet), so both ends of the edge are in view.
2. Timing race. Voice writes integrate on a ~4s debounce, so navigate_to_note could
   fire before the new note was a graph node and fall back to the vault reader. Now,
   on a miss, if an integration is pending, flush it (refreshGraph) and retry before
   the vault fallback — so "create X → show me X" lands on the real graph node.

Verified on localhost: navigate to the just-created "Random Node 24" frames it with
Dopamine and draws the connecting edge (both endpoints visible, link line rendered).

Fixes abilityai/trinity-enterprise#70

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…points (abilityai/trinity-enterprise#71, #72)

Two more voice-test issues in the write→refresh loop:

#72 — selection lost on refresh. applyData()→teardownGraph() wipes
lastInspectedIdx/hover and node indices change, so a capture/link auto-refresh
deselected whatever the user was looking at mid-task. refreshGraph() now snapshots
the selection BY ID (inspected node, or highlighted group) before the rebuild and
re-selects/re-frames it after; and it SKIPS the rebuild entirely when nothing
changed (added_nodes==0 && added_edges==0) so a no-op refresh never disturbs the
selection. Camera view was already preserved by teardown.

#71 — link to a non-existent note silently "succeeded" (dangling reference,
false success, no renderable edge). The agent action hook's `link` verb now
validates BOTH endpoints resolve to real notes (graph node or inbox file) and
returns a clear "note not found — create it first" error otherwise (agent-owned
per Invariant #8; the orb already surfaces r.error). This repo change is the
contract note + the orb selection fix; the demo cornelius hook was updated to
match and the real agent adopts the same.

Verified on localhost: selecting Dopamine then refreshing (with a pending capture
forcing a real rebuild) keeps Dopamine selected + inspector shown; linking to a
non-existent "Random Node 25" now returns a clear not-found error.

Fixes abilityai/trinity-enterprise#71 abilityai/trinity-enterprise#72

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ntegrates it (abilityai/trinity-enterprise#72)

Feedback: "add the node" captures + refreshes a few seconds later, but the new
node isn't selected — you had to ask to select it separately. Now a capture sets a
_pendingFocusTitle that refreshGraph() consumes after the rebuild: it focuses the
just-created note (inspector + highlight + framing) once it's folded into the graph,
taking priority over restoring the prior selection. Falls back to restoring the
prior selection when there's no pending capture. Purely client-side — the agent
doesn't need a second navigate_to_note call.

Verified on localhost: a voice capture_note with no follow-up navigate ends with the
new note auto-selected in the inspector after the debounced refresh.

Refs abilityai/trinity-enterprise#72

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…toggle + prompt editor) (abilityai/trinity-enterprise#73)

The Brain tab was a launch button + a "no settings yet" placeholder. Give it its
first real control: configure the post-voice-session processing (#66) from the UI.

- BrainPanel.vue: "Post-voice processing" section — an on/off toggle ("run a
  processing step after each voice session") + a prompt textarea + Save, loaded on
  mount. Shown only when brain_orb_write_available + agent running.
- Backend GET/PUT /api/agents/{name}/brain-orb/postprocess (OwnedAgentByName,
  write-flag-gated, PUT audited) → agent-server GET/PUT /api/brain-orb/postprocess
  (direct file I/O). Config is agent-owned (Invariant #8):
  ~/.trinity/brain-orb/voice-postprocess.json = {enabled, prompt}, with the legacy
  .md as a prompt-only read fallback.
- The action hook's _spawn_postprocess now gates on the `enabled` flag (default OFF)
  — disabling keeps the saved prompt but skips the claude -p.

Verified on localhost: the Brain tab shows the toggle + prompt (loaded from config);
saving writes voice-postprocess.json and GET reflects it; with enabled:false a
capture_transcript still saves the transcript but the post-process is skipped
("post-processing disabled"). 76 unit tests green (6 new).

Refs abilityai/trinity-enterprise#73

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…gn system (abilityai/trinity-enterprise#73)

The Brain-tab Save + Open Brain Orb buttons used bg-state-autonomous-500/600
(amber, and a non-standard lighter shade — the only -500/-600 primary buttons in
the app). The app-wide primary-action convention is bg-blue-600 hover:bg-blue-700
(22 usages); focus rings are focus:ring-blue-500 (35 usages). Aligned the buttons,
the enable checkbox accent, and the prompt textarea focus ring to blue. Amber
(state-autonomous) is kept ONLY for the Brain Orb identity (the orb icon + BETA
badge). No shared Button component exists, so this matches the established Tailwind
convention rather than a component.

Verified on localhost: both buttons compute rgb(37,99,235) = blue-600.

Refs abilityai/trinity-enterprise#73

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…meout stagger, safe audit (abilityai/trinity-enterprise#61)

Applies the fixes from the final pre-merge review of the Phase-4 branch:

- F1 (med): /refresh drove the agent action hook but was gated only on
  BRAIN_ORB_ENABLED — the write kill-switch wasn't airtight. Added
  _require_write_enabled() + test_refresh_write_flag_off_404.
- F3 (low/med): /action backend timeout == agent-server timeout (60/60); a
  premature backend 504 could release the idempotency claim → double-write on
  retry. Staggered backend to 90s > agent 60s (mirrors the scope route).
- F4 (low): /postprocess PUT capped at 1MiB vs the agent-server's 64KiB; capped
  the backend at 64KiB to match.
- F5 (low): audit awaited after complete() unguarded → an audit failure 500s a
  completed write (fresh-key retry double-writes). Added _safe_audit best-effort
  wrapper on all post-write audit calls.
- orb-tool postMessage handler lacked an origin check; 4a added owner-write tools
  to ORB_TOOLS. Added the origin guard (matches the two sibling handlers;
  CSP frame-ancestors already mitigated it).
- F2 (med): the committed 4a-only CSO report declared "0 findings / no claude -p"
  — stale vs the shipped exec/transcript surface. Rewrote it as the full-branch
  final review (findings + resolution).

Auth boundaries, can_write split, enum gate, stdin-only exec, and idempotency
ordering all verified clean by an independent reviewer. 77 unit tests green.
Learning captured (kill-switch completeness across incremental routes).

Refs abilityai/trinity-enterprise#61

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vybe
vybe marked this pull request as ready for review July 2, 2026 09:24
dolho added 2 commits July 2, 2026 12:36
feat(brain-orb): Phase 4a/4b KB-write + voice-transcript + write→graph refresh (ent#61, #66, #67, #68)
feat(ui): Brain Orb Phase 3 — client-held Gemini Live voice tile + read-only KB search (Abilityai/trinity-enterprise#60)
Comment thread src/frontend/public/brain-orb/voice/voice.js Fixed
Comment thread src/frontend/public/brain-orb/voice/voice.js Fixed
# Conflicts:
#	docs/memory/learnings.md
#	docs/memory/requirements.md
Eugene Vyborov and others added 2 commits July 2, 2026 21:10
…NG session-id fallback (abilityai/trinity-enterprise#58)

- js/insecure-randomness (high): _uuid()'s non-secure-context fallback used
  Math.random(); the id becomes the capture_transcript Idempotency-Key, so the
  fallback now builds a v4 UUID from crypto.getRandomValues.
- js/cross-window-information-leak (medium): the transcript relay posted to '*';
  all orb↔voice-iframe postMessage sends (both directions, incl. the ephemeral
  token reply) now target window.location.origin — receivers already enforced
  same-origin, this pins the send side too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hardening, flags, route wiring (abilityai/trinity-enterprise#61)

/update-tests review of the Phase 1-4b surface. 26 new tests, all passing
(103 total across the three files against the live backend).

tests/unit/test_brain_orb.py (+18, file total 95):
- POST /scope owner-gate 403 — the one mutating route missing the proof its
  Phase-4 siblings had (incomplete-fix-chain class)
- PUT /postprocess 413 body cap (review F4)
- idempotency semantics: key folds the action verb; claim released on agent
  502/504; success completes with the parsed snapshot; missing key = fail-open
  (Invariant #18)
- audit-sink failure never masks a completed write (review F5, action+refresh)
- agent-server _run_hook hardening: output cap → 502, non-executable hook →
  404, scope/search 413 body caps
- postprocess (#73): invalid JSON 400, 8000-char prompt truncation, legacy .md
  fallback, corrupt-JSON degrades to defaults
- voice mint: _build_system_instruction clause selection + 2000-char prompt
  cap; new-session window < token expiry

tests/test_settings.py (+3 SMOKE): feature-flags requires auth; brain_orb_*
keys present + boolean; write⇒available compound-gate invariant (#61
kill-switch layering) — first coverage of any brain-orb feature-flag key.

tests/test_brain_orb_api.py (new, 5 SMOKE): live-backend route-wiring proof
(#1069 escape class) — the unit suite mounts the router on a synthetic app,
so only these prove main.py registration + the real AuthorizedAgentByName /
OwnedAgentByName chain.

Test-runner catalog updated in the trinity-dev submodule (rides separately).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@dolho

dolho commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review — Brain Orb (automated review, Claude)

Substantial, carefully-engineered PR. The trust boundary is right: backend proxy applies flag + AuthorizedAgentByName/OwnedAgentByName gates, transport goes through agent_httpx_client (per-agent HMAC, #1159), agent-server routes sit behind AgentAuthMiddleware, and the frontend JWT→iframe handoff is origin + source pinned on both send (targetOrigin = location.origin) and receive (e.origin and e.source both checked). Idempotency-before-rate-limit ordering and claim-release on 429/5xx are correct; can_write is correctly restricted to owner/admin (can_user_share_agent = owner-or-admin), so the voice write-tool manifest can't leak to shared users, and the /action OwnedAgentByName gate is the real boundary regardless. Nice.

1. PR body materially understates the shipped scope (please update before merge). The summary says "Voice (Gemini Live), KB-write actions, transcript capture … remain deferred" and describes "two phases", but the diff ships all of it: brain_orb_voice_service.py (voice-token mint), public/brain-orb/voice/* assets, the /voice-token route, KB-write routes (/action, /actions, /refresh, /postprocess), the capture/link/capture_transcript/process_transcript verbs, and two more flags (BRAIN_ORB_VOICE_ENABLED, BRAIN_ORB_WRITE_ENABLED). The stacked test PR #1428 even calls this "Phase 1–4b". A reviewer trusting the body would under-review the real attack surface (client-held Gemini token, owner-gated arbitrary-hook exec). Please align the body with what's actually here.

2. google-genai 1.12.1 → 1.63.0 is a 51-minor jump on a shared dependency. It's needed for the v1alpha auth_tokens.create ephemeral mint, but the same package backs existing Voice and VoIP (Gemini Live). Blast radius is all Gemini usage, not just the orb. Please confirm existing voice/VoIP were re-exercised against 1.63.0 (not just the new mint path).

3. Open-core policy check. This lands a lot of core backend + agent-server code and detailed public design docs (architecture.md +81, feature-flows/brain-orb.md, requirements/core-agent.md) for a feature tracked in the private trinity-enterprise#58, gated by plain env flags rather than the entitlement seam (requires_entitlement). VoIP/voice set a flag-gated-core precedent, so this may be fine — but please confirm Brain Orb is intended open-core (vs. a gated paid module that should route through entitlements and keep its design docs private per the CLAUDE.md standing rule / enterprise-docs-guard).

Minor

  • Scope creep: the CodeQL workflow/config and the docs/security-reports/cso-diff-* artifacts are bundled into a feature PR. The security-report drop matches existing convention; the CodeQL wiring is arguably its own change.
  • _run_hook maps a non-executable hook to 502, while the route-level _hook_ready gate maps the same condition to 404 — only reachable as a TOCTOU race, but the two disagree.

Not blocking beyond items 1–3 (1 is a docs fix, 2–3 are confirmations).

test(brain-orb): close coverage gaps — owner gate, idempotency, hooks, flags, route wiring (ent#61)
@dolho
dolho merged commit ed62849 into dev Jul 3, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants