converge_pack/effect.rs
1// Copyright 2024-2026 Reflective Labs
2// SPDX-License-Identifier: MIT
3
4//! Suggestor effects — what suggestors produce, the engine merges.
5//!
6//! Effects are proposal-only. Suggestors suggest; the engine validates and promotes.
7
8use crate::context::ContextKey;
9use crate::fact::ProposedFact;
10
11/// The output of a suggestor's `execute()` call.
12///
13/// An effect describes what a suggestor wants to suggest to the context.
14/// The engine collects effects from all eligible suggestors, validates them,
15/// and promotes them serially in deterministic order.
16#[derive(Debug, Default)]
17pub struct AgentEffect {
18 /// New proposals to be validated by the engine.
19 pub proposals: Vec<ProposedFact>,
20}
21
22impl AgentEffect {
23 /// Creates an empty effect (no contributions).
24 #[must_use]
25 pub fn empty() -> Self {
26 Self::default()
27 }
28
29 /// Creates an effect with a single proposal.
30 #[must_use]
31 pub fn with_proposal(proposal: ProposedFact) -> Self {
32 Self {
33 proposals: vec![proposal],
34 }
35 }
36
37 /// Creates an effect with multiple proposals.
38 #[must_use]
39 pub fn with_proposals(proposals: Vec<ProposedFact>) -> Self {
40 Self { proposals }
41 }
42
43 /// Returns true if this effect contributes nothing.
44 #[must_use]
45 pub fn is_empty(&self) -> bool {
46 self.proposals.is_empty()
47 }
48
49 /// Returns the context keys affected by this effect.
50 #[must_use]
51 pub fn affected_keys(&self) -> Vec<ContextKey> {
52 let mut keys: Vec<ContextKey> = self.proposals.iter().map(|p| p.key).collect();
53 keys.sort();
54 keys.dedup();
55 keys
56 }
57}