Problem
When a signal fires, the system surfaces the signal in isolation. It doesn't say "this is related to the decision you made last week." The data exists in memory to make these connections, but the heartbeat pipeline doesn't search for related context.
For example: a new issue appears that's similar to one resolved last month. The heartbeat surfaces it as CheckPendingWork, but doesn't mention the previous resolution or the PR that fixed it.
The GraphEngine already has GetEntityContext() which returns an entity's relationships and associated memories. The memory store has similarity search. Both can find related past decisions.
Proposal
When to run
Only on ticks where ShouldAct = true and HeartbeatAnalysis is requested (cfg.llmProvider != nil). This is a premium feature that costs 1 LLM call per actionable tick.
Implementation
Add a context enrichment step after evaluateShouldAct() returns ShouldAct=true but before building the delivery message:
func (k *Keyoku) enrichWithRelatedContext(ctx context.Context, entityID string, result *HeartbeatResult) error {
// 1. Collect the top 3 signal memories (by tier priority)
topSignals := getTopSignalMemories(result, 3)
// 2. For each, search for related past decisions and outcomes
for _, mem := range topSignals {
related, _ := k.store.SearchMemories(ctx, entityID, SimilarityOptions{
Query: mem.Content,
Limit: 3,
MinScore: 0.5,
Types: []MemoryType{TypeActivity, TypePlan, TypeEvent},
States: []MemoryState{StateActive, StateStale},
ExcludeIDs: []string{mem.ID}, // don't match self
})
result.RelatedContext = append(result.RelatedContext, related...)
}
// 3. Also check graph connections
for _, entityID := range result.TopicEntities {
edges, _ := k.graphEngine.GetEntityNeighbors(ctx, entityID, entityID)
// Filter for decision-relevant relationship types
// Add to result.GraphContext
}
// 4. Pass to LLM analysis prompt:
// "Given these active signals AND this related past context,
// explain the connection and what the user should know."
return nil
}
New field on HeartbeatResult
RelatedContext []*Memory // Past decisions/activities related to current signals
LLM prompt addition
Extend the existing analysis prompt (used when analyze: true in heartbeatContext()) with:
Related past context:
- [2 days ago] "Resolved recursive import issue in PR #8 using depth-limited scanning"
- [5 days ago] "dan-and filed Issue #5 requesting recursive import support"
Consider these connections when writing your action brief.
Plugin side (separate PR in keyoku repo)
packages/openclaw/src/context.ts — format related_context in formatHeartbeatContext()
packages/types/src/memory.ts — add related_context?: SearchResult[] to HeartbeatContextResult
Files to modify (engine)
heartbeat.go — add RelatedContext []*Memory to HeartbeatResult
heartbeat_decide.go — add enrichWithRelatedContext(), call after evaluateShouldAct() when ShouldAct && llmProvider != nil
heartbeat_llm.go (or wherever LLM analysis prompt is built) — include related context in prompt
Constraints
- Only runs when
ShouldAct = true AND LLM analysis is enabled (not on suppressed/cooldown ticks)
- Max 3 signal memories enriched, max 3 related memories per signal (9 total max)
- Similarity threshold: 0.5 (looser than dedup's 0.85, we want thematic relevance)
- Related memories must be from a different session than the signal memory (avoid self-referencing)
- LLM cost: ~$0.01-0.05 per enriched tick (added to existing analysis call, not a separate call)
Problem
When a signal fires, the system surfaces the signal in isolation. It doesn't say "this is related to the decision you made last week." The data exists in memory to make these connections, but the heartbeat pipeline doesn't search for related context.
For example: a new issue appears that's similar to one resolved last month. The heartbeat surfaces it as
CheckPendingWork, but doesn't mention the previous resolution or the PR that fixed it.The
GraphEnginealready hasGetEntityContext()which returns an entity's relationships and associated memories. The memory store has similarity search. Both can find related past decisions.Proposal
When to run
Only on ticks where
ShouldAct = trueandHeartbeatAnalysisis requested (cfg.llmProvider != nil). This is a premium feature that costs 1 LLM call per actionable tick.Implementation
Add a context enrichment step after
evaluateShouldAct()returnsShouldAct=truebut before building the delivery message:New field on HeartbeatResult
LLM prompt addition
Extend the existing analysis prompt (used when
analyze: trueinheartbeatContext()) with:Plugin side (separate PR in keyoku repo)
packages/openclaw/src/context.ts— formatrelated_contextinformatHeartbeatContext()packages/types/src/memory.ts— addrelated_context?: SearchResult[]toHeartbeatContextResultFiles to modify (engine)
heartbeat.go— addRelatedContext []*MemorytoHeartbeatResultheartbeat_decide.go— addenrichWithRelatedContext(), call afterevaluateShouldAct()whenShouldAct && llmProvider != nilheartbeat_llm.go(or wherever LLM analysis prompt is built) — include related context in promptConstraints
ShouldAct = trueAND LLM analysis is enabled (not on suppressed/cooldown ticks)