Problem
Signals are evaluated independently in evaluateShouldAct(). The system doesn't detect that two separate signals are about the same underlying situation:
CheckDeadlines fires for a task due Friday
CheckRelationship fires because the assigned contributor went silent for 7 days
- These are the same problem, but the system treats them as unrelated TierImmediate + TierLow signals
The knowledge graph already has the data to connect these. GraphEngine.GetEntityNeighbors() can find that the deadline memory references an entity who is also the subject of the relationship alert. The entity_mentions and relationship_evidence tables link memories to entities.
Proposal
Add a cross-reference pass after signal collection but before confluence scoring.
Implementation
func (k *Keyoku) crossReferenceSignals(ctx context.Context, entityID string, result *HeartbeatResult) []CompoundSignal {
// 1. Collect entity IDs from all signal memories
// - Deadlines: memory → entity_mentions → entity_id
// - PendingWork: memory → entity_mentions → entity_id
// - Relationships: alert.EntityID directly
// - StaleMonitors: memory → entity_mentions → entity_id
// 2. Find overlapping entities across signal types
// - If a deadline memory mentions Entity X, and a relationship alert is about Entity X
// → compound signal: "deadline + silent assignee"
// 3. Use GraphEngine.GetEntityNeighbors() for 1-hop expansion
// - If deadline is about Project Y, and Project Y -[assigned_to]-> Person X,
// and Person X has a relationship alert → compound even without direct mention
// 4. Return compound signals with boosted tier
}
Compound signal types
| Combination |
Compound Name |
Tier Boost |
| Deadline + silent relationship |
deadline_blocked |
→ TierImmediate (already, but add context) |
| PendingWork + GoalProgress stall |
blocked_goal |
Normal → Elevated |
| Conflict + related PendingWork |
conflict_blocking_work |
Elevated (no change, but add context) |
| StaleMonitor + Deadline |
monitor_deadline_risk |
Elevated → Immediate |
How it affects confluence
Compound signals replace their constituent signals in the confluence calculation. A deadline_blocked compound contributes its boosted tier weight instead of the individual deadline + relationship weights. This prevents double-counting while reflecting the increased urgency.
Graph enrichment
The existing GraphContext field on HeartbeatResult already carries relationship strings like "Alice (person) -[works_at]-> ClientCo (organization)". Compound signals should add their connection explanation to GraphContext so the LLM analysis and plugin formatting can reference them.
Files to modify
heartbeat_decide.go — add crossReferenceSignals(), call between signal collection (step 6) and confluence scoring (step 7) in evaluateShouldAct()
heartbeat.go — add CompoundSignals []CompoundSignal field to HeartbeatResult, define CompoundSignal struct
engine/graph.go — may need a batch GetEntitiesForMemories() to avoid N+1 queries
Constraints
- Graph traversal depth: 1 hop only (performance)
- Only compound signals that involve at least one TierElevated+ signal (don't compound two TierLow signals)
- Cache entity lookups per tick (multiple signals may reference the same entity)
- If graph data is sparse (entity_mentions empty), skip gracefully
Problem
Signals are evaluated independently in
evaluateShouldAct(). The system doesn't detect that two separate signals are about the same underlying situation:CheckDeadlinesfires for a task due FridayCheckRelationshipfires because the assigned contributor went silent for 7 daysThe knowledge graph already has the data to connect these.
GraphEngine.GetEntityNeighbors()can find that the deadline memory references an entity who is also the subject of the relationship alert. Theentity_mentionsandrelationship_evidencetables link memories to entities.Proposal
Add a cross-reference pass after signal collection but before confluence scoring.
Implementation
Compound signal types
deadline_blockedblocked_goalconflict_blocking_workmonitor_deadline_riskHow it affects confluence
Compound signals replace their constituent signals in the confluence calculation. A
deadline_blockedcompound contributes its boosted tier weight instead of the individual deadline + relationship weights. This prevents double-counting while reflecting the increased urgency.Graph enrichment
The existing
GraphContextfield onHeartbeatResultalready carries relationship strings like"Alice (person) -[works_at]-> ClientCo (organization)". Compound signals should add their connection explanation toGraphContextso the LLM analysis and plugin formatting can reference them.Files to modify
heartbeat_decide.go— addcrossReferenceSignals(), call between signal collection (step 6) and confluence scoring (step 7) inevaluateShouldAct()heartbeat.go— addCompoundSignals []CompoundSignalfield toHeartbeatResult, defineCompoundSignalstructengine/graph.go— may need a batchGetEntitiesForMemories()to avoid N+1 queriesConstraints