converge_pack/context.rs
1// Copyright 2024-2026 Reflective Labs
2// SPDX-License-Identifier: MIT
3
4//! Context keys and the shared context contract.
5//!
6//! Context is the API. Suggestors don't call each other — they read from and
7//! write to shared context through typed keys.
8
9use serde::{Deserialize, Serialize};
10
11use crate::fact::{Fact, ProposedFact};
12
13/// Typed keys for the shared context namespace.
14#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
15#[cfg_attr(feature = "strum", derive(strum::EnumIter))]
16pub enum ContextKey {
17 /// Initial inputs from the root intent. Set once at initialization.
18 Seeds,
19 /// Proposed ideas and hypotheses from analysis suggestors.
20 Hypotheses,
21 /// Action plans and strategic recommendations.
22 Strategies,
23 /// Limitations, rules, and boundary conditions.
24 Constraints,
25 /// Observations, market data, and signals from the environment.
26 Signals,
27 /// Competitive intelligence and comparisons.
28 Competitors,
29 /// Assessments, ratings, and evaluations of other facts.
30 Evaluations,
31 /// LLM-generated suggestions awaiting validation.
32 Proposals,
33 /// Error and debugging information. Never blocks convergence.
34 Diagnostic,
35}
36
37/// Read-only view of the shared context.
38///
39/// Suggestors receive `&dyn Context` during `accepts()` and `execute()`.
40/// They cannot mutate it directly — mutations happen through `AgentEffect`
41/// after the engine collects all effects and merges them deterministically.
42pub trait Context: Send + Sync {
43 /// Check whether any facts exist under this key.
44 fn has(&self, key: ContextKey) -> bool;
45
46 /// Get all facts under this key.
47 fn get(&self, key: ContextKey) -> &[Fact];
48
49 /// Get all proposed facts (unvalidated).
50 fn get_proposals(&self, key: ContextKey) -> &[ProposedFact] {
51 let _ = key;
52 &[]
53 }
54
55 /// Count of facts under a key.
56 fn count(&self, key: ContextKey) -> usize {
57 self.get(key).len()
58 }
59}