Skip to main content

systemprompt_models/brain/declarations/
mod.rs

1//! Declarative brain config: the YAML shapes under `services/brain/` and
2//! their cross-reference validation.
3//!
4//! `topics.yaml`, `filters.yaml`, `importance.yaml`, `retrieval.yaml` and
5//! `synonyms.yaml` are single documents; `kinds/`, `interests/` and `profiles/`
6//! hold one declaration per file. [`BrainDocs`] is the whole set and
7//! [`validate`] checks it.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12mod importance;
13mod synonyms;
14mod validate;
15
16use std::collections::BTreeMap;
17
18use chrono::{DateTime, Utc};
19use serde::{Deserialize, Serialize};
20use systemprompt_identifiers::{FactKindId, InterestId, TopicId, UserId, WorkflowId};
21
22pub use importance::{
23    BaseImportance, FillOrder, ImportanceConfig, InContextImportance, ModelHint, QuorumUnit,
24    Recency, RetrievalDoc, TierWeights, UsageWeights,
25};
26pub use synonyms::{SynonymsDoc, SynonymsError};
27pub use validate::{BrainDeclError, validate};
28
29use super::envelope::Pathway;
30use super::kind_decl::KindDecl;
31use crate::notifications::AudienceSubject;
32
33/// How finely a topic or interest may report about people.
34#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize)]
35#[serde(rename_all = "snake_case")]
36pub enum Granularity {
37    #[default]
38    Aggregate,
39    Individual,
40}
41
42/// A controlled topic and the audience it widens fact scope to.
43#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct TopicDecl {
46    pub id: TopicId,
47    pub name: String,
48    #[serde(default)]
49    pub description: Option<String>,
50    #[serde(default)]
51    pub audience: Vec<AudienceSubject>,
52    #[serde(default)]
53    pub granularity: Granularity,
54}
55
56/// `topics.yaml`.
57#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
58#[serde(deny_unknown_fields, default)]
59pub struct TopicsDoc {
60    pub topics: Vec<TopicDecl>,
61}
62
63/// How an interest's matches are delivered.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
65#[serde(rename_all = "snake_case")]
66pub enum InterestDelivery {
67    Memory,
68    Digest,
69    Alert,
70}
71
72/// Deterministic predicates of an interest; an empty list matches all.
73#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
74#[serde(deny_unknown_fields, default)]
75pub struct InterestMatch {
76    pub kinds: Vec<FactKindId>,
77    pub topics: Vec<TopicId>,
78    pub subjects: Vec<String>,
79    pub keywords: Vec<String>,
80}
81
82/// A standing question with an owner, pathways and delivery.
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct InterestDecl {
86    pub id: InterestId,
87    pub name: String,
88    pub owner: UserId,
89    pub question: String,
90    #[serde(default)]
91    pub pathways: Vec<Pathway>,
92    #[serde(default)]
93    pub r#match: InterestMatch,
94    #[serde(default)]
95    pub granularity: Granularity,
96    #[serde(default)]
97    pub audience: Vec<AudienceSubject>,
98    #[serde(default)]
99    pub delivery: Vec<InterestDelivery>,
100    #[serde(default)]
101    pub expires: Option<DateTime<Utc>>,
102}
103
104/// Which actors an event filter admits.
105#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
106#[serde(deny_unknown_fields, default)]
107pub struct ActorFilter {
108    pub humans: bool,
109    pub attended_agents: bool,
110    pub unattended_agents: bool,
111}
112
113impl Default for ActorFilter {
114    fn default() -> Self {
115        Self {
116            humans: true,
117            attended_agents: true,
118            unattended_agents: false,
119        }
120    }
121}
122
123/// One declared event filter.
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(deny_unknown_fields)]
126pub struct FilterDecl {
127    pub name: String,
128    pub pathways: Vec<Pathway>,
129    #[serde(default)]
130    pub actors: ActorFilter,
131    #[serde(default)]
132    pub kinds: Vec<FactKindId>,
133    #[serde(default)]
134    pub debounce_secs: Option<u32>,
135}
136
137/// `filters.yaml`.
138#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(deny_unknown_fields, default)]
140pub struct FiltersDoc {
141    pub filters: Vec<FilterDecl>,
142}
143
144/// A workflow profile under `profiles/*.yaml`: the kinds a workflow cares
145/// about, who may confirm in it, and its injection limits.
146#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
147#[serde(deny_unknown_fields)]
148pub struct WorkflowProfileDecl {
149    pub id: WorkflowId,
150    pub name: String,
151    #[serde(default)]
152    pub kinds: Vec<FactKindId>,
153    #[serde(default)]
154    pub confirm_role: Option<String>,
155    #[serde(default)]
156    pub quotas: BTreeMap<FactKindId, u32>,
157    #[serde(default)]
158    pub budget_tokens: Option<u32>,
159}
160
161/// Every brain declaration, loaded together.
162#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
163#[serde(deny_unknown_fields, default)]
164pub struct BrainDocs {
165    pub topics: TopicsDoc,
166    pub filters: FiltersDoc,
167    pub importance: ImportanceConfig,
168    pub retrieval: RetrievalDoc,
169    pub kinds: Vec<KindDecl>,
170    pub interests: Vec<InterestDecl>,
171    pub workflows: Vec<WorkflowProfileDecl>,
172}