Skip to main content

okbot_core/
knowledge.rs

1//! The knowledge port.
2
3use std::fmt;
4use std::time::Duration;
5
6use crate::agent::McpServer;
7use crate::caps::ToolPolicy;
8use crate::id::{ChannelId, ConversationKey, UserId, UserRef};
9use crate::message::Attachment;
10use crate::secret::Secret;
11use crate::{BoxError, async_trait};
12
13/// A knowledge failure.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum KnowledgeError {
17    /// The bundle could not be opened or indexed.
18    #[error("knowledge bundle: {0}")]
19    Bundle(#[source] BoxError),
20    /// Any other failure.
21    #[error("knowledge error: {0}")]
22    Other(#[source] BoxError),
23}
24
25/// Who is asking, which decides what knowledge and memory is visible.
26#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct Requester {
28    /// The conversation.
29    pub conversation: ConversationKey,
30    /// The user.
31    pub user: UserRef,
32    /// The room may see documents marked `audience: private`.
33    pub internal: bool,
34    /// The person this account belongs to, when the operator linked several accounts
35    /// (`[people.<id>]`): they share one profile and personal memory.
36    pub person: Option<Person>,
37    /// The requester's personal data (profile, memory, tasks, notes) is reachable: a one-to-one
38    /// chat, a room the operator marked personal, or the terminal. In other group chats everyone
39    /// reads the answer, so only the room's and shared data are.
40    pub personal: bool,
41}
42
43/// Several platform accounts of one human, linked by the operator.
44#[derive(Debug, Clone, PartialEq, Eq)]
45pub struct Person {
46    /// Id from `okbot.toml` (`luan`): a lowercase slug.
47    pub id: String,
48    /// Every linked account, as `(channel, user)`.
49    pub accounts: Vec<(ChannelId, UserId)>,
50}
51
52/// What a run needs from the knowledge tools.
53#[derive(Debug, Clone, Copy)]
54pub struct RunInfo<'a> {
55    /// The run's tool policy.
56    pub tools: ToolPolicy,
57    /// How long the access stays valid.
58    pub ttl: Duration,
59    /// Media the user sent with the messages being answered (offered through a viewing tool).
60    pub attachments: &'a [Attachment],
61    /// The requester administers the bot (the owner of a personal bot, or `[bot] admins`): only
62    /// they may create routines and save documents to the shared knowledge.
63    pub admin: bool,
64    /// The run reads untrusted data (an automation, ADR 0013): the bot's own tools only read
65    /// (no memory, task, note or knowledge writes).
66    pub read_only: bool,
67}
68
69/// Knowledge placed in the system prompt.
70#[derive(Debug, Clone, PartialEq, Eq, Default)]
71pub struct KnowledgeContext {
72    /// The bot's persona documents.
73    pub persona: String,
74    /// The bundle catalog, as visible to the requester.
75    pub catalog: String,
76    /// The requester's profile, if one exists.
77    pub user_profile: Option<String>,
78}
79
80/// Access to knowledge tools for one agent run. Dropping it revokes the access.
81pub struct RunGrant {
82    /// MCP servers to give the agent.
83    pub mcp: Vec<McpServer>,
84    /// Environment variables carrying the run token.
85    pub env: Vec<(String, Secret<String>)>,
86    _revoke_on_drop: Box<dyn Send + Sync>,
87}
88
89impl RunGrant {
90    /// Creates a grant; `guard` is dropped (and should revoke the token) when the grant is dropped.
91    pub fn new(
92        mcp: Vec<McpServer>,
93        env: Vec<(String, Secret<String>)>,
94        guard: impl Send + Sync + 'static,
95    ) -> Self {
96        Self {
97            mcp,
98            env,
99            _revoke_on_drop: Box::new(guard),
100        }
101    }
102
103    /// A grant without tools, for backends that cannot use MCP.
104    pub fn none() -> Self {
105        Self::new(Vec::new(), Vec::new(), ())
106    }
107}
108
109impl fmt::Debug for RunGrant {
110    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
111        f.debug_struct("RunGrant")
112            .field("mcp", &self.mcp)
113            .field("env", &self.env)
114            .finish_non_exhaustive()
115    }
116}
117
118/// The bot's knowledge and memory (design ยง5).
119#[async_trait]
120pub trait KnowledgeProvider: Send + Sync {
121    /// Persona, catalog and profile for the system prompt.
122    async fn context(&self, who: &Requester) -> Result<KnowledgeContext, KnowledgeError>;
123
124    /// Issues tool access for one run, scoped to `who`.
125    async fn grant(&self, who: &Requester, run: &RunInfo<'_>) -> Result<RunGrant, KnowledgeError>;
126
127    /// Text search for backends without MCP: the most relevant excerpts for `query`, or an empty
128    /// string when nothing matches.
129    async fn excerpts(&self, who: &Requester, query: &str) -> Result<String, KnowledgeError>;
130}