Skip to main content

supercode_interchange/orchestration/
profile.rs

1//! The home and its profiles (§2.1): the root folder IS the `default`
2//! profile; `profiles/<name>/` are the named ones.
3
4use std::collections::BTreeMap;
5use std::path::PathBuf;
6
7use schemars::JsonSchema;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10
11use super::{Access, ChannelConfig, Fire, Job, Obligation, Route, WebhookSubscription, WorkerSpec};
12use crate::ontology::{Binding, SurfaceKey};
13
14/// The persona: `AGENTS.md` at the profile root, content hash first.
15#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
16pub struct PersonaRef {
17    /// Always `AGENTS.md` in our folder (`SOUL.md` in a Hermes home).
18    pub path: String,
19    /// The text, when loaded.
20    #[serde(default)]
21    pub text: Option<String>,
22    /// Hex sha256 of the text.
23    pub sha256: String,
24}
25
26/// Which conversations share one session (Hermes `group_sessions_per_user`,
27/// `thread_sessions_per_user`).
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, JsonSchema, Default)]
29#[serde(rename_all = "snake_case")]
30pub enum ExpiryScope {
31    /// One session per chat.
32    #[default]
33    PerChat,
34    /// One per user inside a group.
35    PerUserInGroup,
36    /// One per thread.
37    PerThread,
38}
39
40/// When a conversation's session ends on its own (`docs/session-lifecycle.md`).
41#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
42pub struct ExpiryPolicy {
43    /// Idle minutes before the session ends; 1440 by default.
44    #[serde(default = "default_idle_minutes")]
45    pub idle_minutes: u32,
46    /// Local hour of the daily reset; `None` = off.
47    #[serde(default)]
48    pub daily_reset_hour: Option<u8>,
49    /// Session scope.
50    #[serde(default)]
51    pub scope: ExpiryScope,
52}
53
54fn default_idle_minutes() -> u32 {
55    1440
56}
57
58impl Default for ExpiryPolicy {
59    fn default() -> Self {
60        Self {
61            idle_minutes: 1440,
62            daily_reset_hour: None,
63            scope: ExpiryScope::PerChat,
64        }
65    }
66}
67
68/// What a profile carries that the model does not interpret.
69#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
70pub struct ProfileResidue {
71    /// `config.yaml` keys that are the source harness's, verbatim.
72    #[serde(default)]
73    pub config: BTreeMap<String, Value>,
74    /// Unmodeled files, by path relative to the profile dir; copied verbatim.
75    #[serde(default)]
76    pub files: Vec<String>,
77}
78
79/// One profile: a folder with `AGENTS.md` at its root.
80#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
81pub struct Profile {
82    /// `default` for the root, else the folder name.
83    pub name: String,
84    /// The folder.
85    pub dir: PathBuf,
86    /// O — which harness runs it.
87    #[serde(default)]
88    pub worker: Option<WorkerSpec>,
89    /// O — `AGENTS.md`.
90    #[serde(default)]
91    pub persona: Option<PersonaRef>,
92    /// H — `config.yaml` `platforms:` blocks.
93    #[serde(default)]
94    pub channels: BTreeMap<String, ChannelConfig>,
95    /// H — `gateway.profile_routes`.
96    #[serde(default)]
97    pub routes: Vec<Route>,
98    /// H — idle / daily / scope.
99    #[serde(default)]
100    pub expiry: ExpiryPolicy,
101    /// H — `/sethome`.
102    #[serde(default)]
103    pub home: Option<SurfaceKey>,
104    /// H — `cron/jobs.json`.
105    #[serde(default)]
106    pub jobs: BTreeMap<String, Job>,
107    /// H — `webhook_subscriptions.json`.
108    #[serde(default)]
109    pub subscriptions: BTreeMap<String, WebhookSubscription>,
110    /// H — allowlists and pairing.
111    #[serde(default)]
112    pub access: Access,
113    /// O — the conversations, keyed by their slot (the surface key string for
114    /// a live one, `<key>#<started_at>[#n]` for ended ones).
115    #[serde(default)]
116    pub bindings: BTreeMap<String, Binding>,
117    /// H — `cron/executions.db`.
118    #[serde(default)]
119    pub fires: Vec<Fire>,
120    /// H — `delivery_obligations`.
121    #[serde(default)]
122    pub obligations: Vec<Obligation>,
123    /// What the model does not interpret.
124    #[serde(default)]
125    pub residue: ProfileResidue,
126}
127
128/// The whole home: the persisted half of `OrchestratorState` (§2.1); the
129/// runtime half is never written and is not part of the wire.
130#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
131pub struct Orchestration {
132    /// The home folder.
133    pub root: PathBuf,
134    /// `default` is the root folder; the rest are `profiles/<name>/`.
135    pub profiles: BTreeMap<String, Profile>,
136}