pub trait Suggestor: Send + Sync {
// Required methods
fn name(&self) -> &str;
fn dependencies(&self) -> &[ContextKey];
fn accepts(&self, ctx: &dyn Context) -> bool;
fn execute<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 dyn Context,
) -> Pin<Box<dyn Future<Output = AgentEffect> + Send + 'async_trait>>
where 'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait;
}Expand description
The core suggestor contract.
Every suggestor in the Converge ecosystem implements this trait — whether it wraps an LLM, a policy engine, an optimizer, analytics, knowledge retrieval, or a simple rule.
The engine calls accepts() to determine eligibility, then execute()
to collect effects. Effects are merged by the engine in deterministic
registration order via [crate::types::SuggestorId].
§Async
execute() is async, allowing suggestors to call LLM providers, search
backends, and other I/O without blocking. The engine awaits each
suggestor and controls concurrency — suggestors don’t need to manage
their own parallelism.
§Thread Safety
Suggestors must be Send + Sync because the engine may execute eligible
suggestors concurrently in the future.
Required Methods§
Sourcefn name(&self) -> &str
fn name(&self) -> &str
Human-readable name, used for logging and provenance.
Must be unique within a convergence run. Deterministic execution order is derived from registration order, not lexical name sorting.
Sourcefn dependencies(&self) -> &[ContextKey]
fn dependencies(&self) -> &[ContextKey]
Context keys this suggestor reads from.
The engine uses this to determine when a suggestor becomes eligible: a suggestor is a candidate when at least one of its dependency keys has been modified since the last cycle.
Sourcefn accepts(&self, ctx: &dyn Context) -> bool
fn accepts(&self, ctx: &dyn Context) -> bool
Pure predicate: should this suggestor execute given the current context?
§Contract
- Must be pure: no side effects, no I/O, no state mutation.
- Must be deterministic: same context → same answer.
- Must check idempotency via context: look for your own
contributions in context (both
Proposalsand target key), not internal flags.
Sourcefn execute<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 dyn Context,
) -> Pin<Box<dyn Future<Output = AgentEffect> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
fn execute<'life0, 'life1, 'async_trait>(
&'life0 self,
ctx: &'life1 dyn Context,
) -> Pin<Box<dyn Future<Output = AgentEffect> + Send + 'async_trait>>where
'life0: 'async_trait,
'life1: 'async_trait,
Self: 'async_trait,
Produce effects given the current context.
§Contract
- Read-only: do not mutate context. Return effects instead.
- Effects are collected by the engine and merged after all eligible suggestors have executed.
- For LLM suggestors: emit
ProposedFacttoContextKey::Proposals, not directly to the target key.