Skip to content

Cross-signal reasoning: connect related signals via knowledge graph #13

Description

@tyejcoleman

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions