feat(prime): surface teammate names and sync'd context throughout sessions#508
feat(prime): surface teammate names and sync'd context throughout sessions#508rsnodgrass merged 1 commit intomainfrom
Conversation
…ions Thread teammate identity through whispers, discussions, and attribution guidance so agents credit specific people alongside SageOx as the enabler. Co-Authored-By: SageOx <ox@sageox.ai> SageOx-Session: https://sageox.ai/repo/repo_019c5812-01e9-7b7d-b5b1-321c471c9777/sessions/2026-04-13T15-04-ryan-OxcXPu/view
📝 WalkthroughWalkthroughThis PR adds principal identity attribution throughout the agent system. It extends heartbeat payloads to capture PrincipalID from user credentials, updates whisper XML rendering to include teammate attribution via derived first names, adds participant tracking from discussion transcripts, expands prime XML instructions for crediting teammates, and provides identity utility functions. Changes
Sequence DiagramsequenceDiagram
participant Agent as Agent<br/>(cmd/ox/agent.go)
participant Heartbeat as Heartbeat<br/>(internal/daemon/heartbeat.go)
participant Identity as Identity<br/>(internal/identity/person_info.go)
participant Whisper as Whisper<br/>Renderer
Agent->>Heartbeat: Emit heartbeat with token user info
Heartbeat->>Heartbeat: Extract & store PrincipalID from token<br/>(Name or Email)
Agent->>Heartbeat: Query GetAgentPrincipalID()
Heartbeat-->>Agent: Return PrincipalID
Agent->>Identity: Derive first name via<br/>FirstNameFromSlug(PrincipalID)
Identity-->>Agent: Return capitalized first name
Agent->>Whisper: Format whisper entry with<br/>from="FirstName"
Whisper-->>Agent: Return XML with attribution
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
cmd/ox/heartbeat.go (1)
193-201:⚠️ Potential issue | 🟡 Minor
HeartbeatWithCredsdoes not setPrincipalID, unlikeHeartbeat.The
Heartbeatfunction setspayload.PrincipalIDfrom token user info (lines 101-106), butHeartbeatWithCredsomits this. This inconsistency means sessions usingHeartbeatWithCredswon't have teammate attribution.🔧 Proposed fix to add PrincipalID in HeartbeatWithCreds
if token, err := auth.GetTokenForEndpoint(projectEndpoint); err == nil && token != nil { hbCreds.AuthToken = token.AccessToken hbCreds.UserEmail = token.UserInfo.Email hbCreds.UserID = token.UserInfo.UserID + // derive principal ID for teammate attribution + if token.UserInfo.Name != "" { + payload.PrincipalID = token.UserInfo.Name + } else if token.UserInfo.Email != "" { + payload.PrincipalID = token.UserInfo.Email + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/ox/heartbeat.go` around lines 193 - 201, HeartbeatWithCreds currently populates hbCreds.AuthToken, hbCreds.UserEmail and hbCreds.UserID but does not set payload.PrincipalID, causing missing teammate attribution; update HeartbeatWithCreds so that after retrieving the token from auth.GetTokenForEndpoint(projectEndpoint) you assign payload.PrincipalID = token.UserInfo.UserID (same field Heartbeat uses), ensuring payload.PrincipalID is set alongside hbCreds.AuthToken, hbCreds.UserEmail and hbCreds.UserID.
🧹 Nitpick comments (2)
cmd/ox/heartbeat.go (1)
101-106: Email fallback produces incorrect first names for teammate attribution.When
token.UserInfo.Nameis empty and the fallback toFirstNameFromSlug(used downstream incmd/ox/agent.gofor whisper display) will not handle the email format correctly.
splitIdentifieronly splits on.,-,_— not@. For an email like"ryan@example.com", it splits to["ryan@example", "com"], producing"Ryan@example"as the display name instead of"Ryan".Extract the local part before storing as
PrincipalIDwhen using the email fallback. The codebase already has this pattern inresolvePrincipal()(internal/daemon/file_change_source.go):♻️ Proposed fix to handle email format
// derive principal ID for teammate attribution if token.UserInfo.Name != "" { payload.PrincipalID = token.UserInfo.Name } else if token.UserInfo.Email != "" { - payload.PrincipalID = token.UserInfo.Email + // extract local part for consistent slug-like format + if idx := strings.IndexByte(token.UserInfo.Email, '@'); idx > 0 { + payload.PrincipalID = token.UserInfo.Email[:idx] + } else { + payload.PrincipalID = token.UserInfo.Email + } }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/ox/heartbeat.go` around lines 101 - 106, The fallback that sets payload.PrincipalID to token.UserInfo.Email can produce incorrect display names because FirstNameFromSlug/splitIdentifier doesn't strip the email domain; update the chain in heartbeat.go so that when token.UserInfo.Name is empty you extract the local part (substring before the '@') from token.UserInfo.Email and assign that to payload.PrincipalID instead of the full email; mirror the existing pattern used by resolvePrincipal() in internal/daemon/file_change_source.go and ensure you still preserve the Email fallback only when a non-empty local part exists.cmd/ox/agent_prime_xml.go (1)
113-117: Consider centralizing the teammate-attribution copy.The same policy text now lives here and in
internal/prime/attribution.goLines 33-40. A shared helper/constant would keep the XML and plain-text renderers from drifting.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@cmd/ox/agent_prime_xml.go` around lines 113 - 117, Extract the duplicated teammate-attribution text into a single shared symbol (e.g., a package-level constant TeammateAttributionCopy or accessor GetTeammateAttribution()) in the internal/prime package, then replace the inline sb.WriteString(...) block in agent_prime_xml.go and the duplicated block in internal/prime/attribution.go to reference that single symbol; ensure the symbol name is exported or accessible from both files and update imports/usages so both XML and plain-text renderers read from the same source.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In `@cmd/ox/heartbeat.go`:
- Around line 193-201: HeartbeatWithCreds currently populates hbCreds.AuthToken,
hbCreds.UserEmail and hbCreds.UserID but does not set payload.PrincipalID,
causing missing teammate attribution; update HeartbeatWithCreds so that after
retrieving the token from auth.GetTokenForEndpoint(projectEndpoint) you assign
payload.PrincipalID = token.UserInfo.UserID (same field Heartbeat uses),
ensuring payload.PrincipalID is set alongside hbCreds.AuthToken,
hbCreds.UserEmail and hbCreds.UserID.
---
Nitpick comments:
In `@cmd/ox/agent_prime_xml.go`:
- Around line 113-117: Extract the duplicated teammate-attribution text into a
single shared symbol (e.g., a package-level constant TeammateAttributionCopy or
accessor GetTeammateAttribution()) in the internal/prime package, then replace
the inline sb.WriteString(...) block in agent_prime_xml.go and the duplicated
block in internal/prime/attribution.go to reference that single symbol; ensure
the symbol name is exported or accessible from both files and update
imports/usages so both XML and plain-text renderers read from the same source.
In `@cmd/ox/heartbeat.go`:
- Around line 101-106: The fallback that sets payload.PrincipalID to
token.UserInfo.Email can produce incorrect display names because
FirstNameFromSlug/splitIdentifier doesn't strip the email domain; update the
chain in heartbeat.go so that when token.UserInfo.Name is empty you extract the
local part (substring before the '@') from token.UserInfo.Email and assign that
to payload.PrincipalID instead of the full email; mirror the existing pattern
used by resolvePrincipal() in internal/daemon/file_change_source.go and ensure
you still preserve the Email fallback only when a non-empty local part exists.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 81af0b30-1d5d-46e4-8f67-9b576a5ec101
📒 Files selected for processing (11)
cmd/ox/agent.gocmd/ox/agent_prime_xml.gocmd/ox/agent_team_ctx.gocmd/ox/distill_discussions.gocmd/ox/heartbeat.gointernal/daemon/daemon.gointernal/daemon/heartbeat.gointernal/daemon/ipc.gointernal/identity/person_info.gointernal/prime/attribution.gointernal/vtt/parse.go
Summary
Weaves teammate identity throughout agent sessions so AI coworkers credit specific people by name alongside SageOx as the enabler — transforming generic "Based on SageOx guidance" into "SageOx surfaced Ryan's discussion about API design."
from=attribute — Murmur whisper entries now carryfrom="Ryan"derived from the sender's principal ID, giving agents a name to useox agent team-ctxextracts unique speakers from VTT transcripts and displays them alongside discussion titlesMermaid: Attribution Flow
flowchart LR A[Heartbeat] -->|PrincipalID| B[Daemon Instance Store] B --> C[InstanceInfo.PrincipalID] D[Murmur] -->|principal_id| E[Whisper Entry] E -->|FirstNameFromSlug| F["from='Ryan' in XML"] G[VTT transcript] -->|UniqueSpeakers| H["Discussion (Ryan, Sarah)"] I[Prime guidance] -->|attribution phrases| J["'SageOx surfaced Ryan's discussion...'"]Test plan
make lint— cleanmake test— 12,940 tests passox agent prime— attribution section includes teammate+SageOx phrasesfrom=attribute when PrincipalID presentox agent team-ctxshows speaker names next to discussion titlesCo-Authored-By: SageOx ox@sageox.ai
Summary by CodeRabbit