Skip to main content

phi_core/config/
schema.rs

1//! Config schema — the deserialization target for TOML/JSON/YAML config files.
2//!
3//! All structs use `#[serde(default)]` so omitted sections/fields get sensible defaults.
4//! String fields are used for enum-like values (e.g. `thinking_level = "high"`) —
5//! parsing to Rust enums happens in the builder.
6
7use serde::Deserialize;
8use std::collections::HashMap;
9
10// ── Top-level config ────────────────────────────────────────────────────────
11
12/// Top-level agent configuration. All sections are optional with defaults.
13#[derive(Debug, Deserialize, Default, Clone)]
14#[serde(default)]
15pub struct AgentConfig {
16    pub agent: AgentSection,
17    pub provider: ProviderSection,
18    pub session: SessionSection,
19    pub tools: ToolsSection,
20    pub skills: SkillsSection,
21    pub sub_agents: SubAgentsSection,
22    /// Phase 2 WASM — callback references stored as strings.
23    pub callbacks: CallbacksSection,
24    /// Phase 2 WASM — hook references stored as strings.
25    pub hooks: HooksSection,
26    pub compaction: CompactionSection,
27    pub execution: ExecutionSection,
28    /// System prompt strategy templates (G6).
29    pub system_prompt_strategy: SystemPromptStrategySection,
30    /// System prompt instances — content for strategy templates (G6).
31    pub system_prompt: SystemPromptSection,
32    /// Default workspace directory for all agents.
33    pub default_workspace: Option<String>,
34}
35
36// ── Agent section ───────────────────────────────────────────────────────────
37
38/// Agent-level configuration. `system_prompt` here overrides the profile's.
39#[derive(Debug, Deserialize, Default, Clone)]
40#[serde(default)]
41pub struct AgentSection {
42    /// Agent-level system prompt override. When set, takes precedence over
43    /// `profile.system_prompt`.
44    pub system_prompt: Option<String>,
45    /// The agent's profile blueprint.
46    pub profile: ProfileSection,
47    /// Named agent instances (for multi-agent configs).
48    pub instances: Vec<AgentInstanceSection>,
49    /// Agent-level workspace directory override.
50    pub workspace: Option<String>,
51}
52
53/// Profile section — the reusable agent blueprint.
54/// Maps to `AgentProfile` in the builder. Multiple agent instances can share
55/// the same profile.
56#[derive(Debug, Deserialize, Default, Clone)]
57#[serde(default)]
58pub struct ProfileSection {
59    /// Unique profile identifier. Auto-generated if omitted.
60    pub profile_id: Option<String>,
61    /// Human-readable name.
62    pub name: Option<String>,
63    /// Description of the profile's purpose.
64    pub description: Option<String>,
65    /// Default system prompt for agents using this profile.
66    pub system_prompt: Option<String>,
67    /// Thinking level: "off", "minimal", "low", "medium", "high".
68    pub thinking_level: Option<String>,
69    /// Temperature for LLM calls.
70    pub temperature: Option<f32>,
71    /// Max output tokens.
72    pub max_tokens: Option<u32>,
73    /// Stable config identity for loop_id generation.
74    pub config_id: Option<String>,
75    /// Skill names loaded via SkillSet from SKILL.md files (NOT tools).
76    pub skills: Vec<String>,
77    /// Reference to a compaction instance via `{{...}}` protocol.
78    pub compaction: Option<String>,
79    /// Named profile instances (variations of this profile blueprint).
80    /// Each instance overrides specific fields from the profile defaults.
81    pub instances: Vec<ProfileInstanceSection>,
82}
83
84/// A named profile instance — a variation of the profile blueprint.
85///
86/// The `id` field uses the `{{...}}` reference protocol:
87/// - `{{%name%}}` — no recreation if exists
88/// - `{{name}}` — recreate
89/// - `{{#system_id#}}` — literal system ID
90///
91/// Fields set here override the parent `ProfileSection` defaults.
92#[derive(Debug, Deserialize, Default, Clone)]
93#[serde(default)]
94pub struct ProfileInstanceSection {
95    /// Instance ID using the `{{...}}` reference protocol.
96    pub id: String,
97    /// Description used for existence check queries (with `%` references).
98    pub description: Option<String>,
99    /// Override name.
100    pub name: Option<String>,
101    /// Override system prompt.
102    pub system_prompt: Option<String>,
103    /// Override thinking level.
104    pub thinking_level: Option<String>,
105    /// Override temperature.
106    pub temperature: Option<f32>,
107    /// Override max tokens.
108    pub max_tokens: Option<u32>,
109    /// Override config identity.
110    pub config_id: Option<String>,
111    /// Override skills.
112    pub skills: Vec<String>,
113}
114
115/// A named agent instance that can reference or override a profile.
116#[derive(Debug, Deserialize, Default, Clone)]
117#[serde(default)]
118pub struct AgentInstanceSection {
119    /// Instance name (for identification).
120    pub name: Option<String>,
121    /// Reference to a profile instance via `{{...}}` protocol
122    /// (e.g., `"{{agent_profile.coder}}"` or `"{{coder}}"`).
123    pub agent_profile: Option<String>,
124    /// Override profile for this instance (inline, not a reference).
125    pub profile: Option<ProfileSection>,
126    /// Override system prompt for this instance.
127    pub system_prompt: Option<String>,
128    /// Override provider reference for this instance (supports `{{...}}` protocol).
129    pub provider: Option<String>,
130    /// Per-instance workspace directory. Overrides `[agent].workspace` for this instance.
131    /// Used for `file:` resolution in system prompts and script callbacks.
132    pub workspace: Option<String>,
133}
134
135// ── Provider section ────────────────────────────────────────────────────────
136
137/// Provider configuration — model identity, API credentials, and protocol.
138#[derive(Debug, Deserialize, Default, Clone)]
139#[serde(default)]
140pub struct ProviderSection {
141    /// Model identifier sent to the API (e.g. "claude-sonnet-4-20250514", "gpt-4o").
142    pub model: Option<String>,
143    /// API key or credential. Supports `${ENV_VAR}` substitution.
144    pub api_key: Option<String>,
145    /// API protocol: "anthropic_messages", "openai_completions", "openai_responses",
146    /// "azure_openai_responses", "google_generative_ai", "google_vertex",
147    /// "bedrock_converse_stream".
148    pub api: Option<String>,
149    /// Base URL for API requests (without trailing slash).
150    /// Also accepted as `url` in config files.
151    #[serde(alias = "url")]
152    pub base_url: Option<String>,
153    /// Provider name (e.g. "anthropic", "openai", "xai").
154    pub provider: Option<String>,
155    /// Human-friendly model name.
156    pub name: Option<String>,
157    /// Whether this model supports reasoning/thinking.
158    pub reasoning: Option<bool>,
159    /// Context window size in tokens.
160    pub context_window: Option<u32>,
161    /// Default max output tokens.
162    pub max_tokens: Option<u32>,
163    /// Cost configuration.
164    pub cost: CostSection,
165    /// Additional headers to send with requests.
166    pub headers: HashMap<String, String>,
167    /// OpenAI-compat quirk flags.
168    pub compat: CompatSection,
169    /// Named provider instances (for multi-provider configs).
170    pub instances: Vec<ProviderInstance>,
171}
172
173/// Cost rates for token usage tracking.
174#[derive(Debug, Deserialize, Default, Clone)]
175#[serde(default)]
176pub struct CostSection {
177    pub input_per_million: Option<f64>,
178    pub output_per_million: Option<f64>,
179    pub cache_read_per_million: Option<f64>,
180    pub cache_write_per_million: Option<f64>,
181}
182
183/// OpenAI-compat quirk flags.
184#[derive(Debug, Deserialize, Default, Clone)]
185#[serde(default)]
186pub struct CompatSection {
187    pub auth_style: Option<String>,
188    pub reasoning_format: Option<String>,
189    pub max_tokens_field: Option<String>,
190    pub supports_streaming: Option<bool>,
191    pub supports_system_message: Option<bool>,
192}
193
194/// A named provider instance with overrides.
195///
196/// The `id` field uses the `{{...}}` reference protocol for cross-referencing.
197#[derive(Debug, Deserialize, Default, Clone)]
198#[serde(default)]
199pub struct ProviderInstance {
200    /// Instance ID using the `{{...}}` reference protocol.
201    pub id: Option<String>,
202    /// Display name (kept for backward compat; `id` is preferred for references).
203    pub name: Option<String>,
204    /// Description used for existence check queries (with `%` references).
205    pub description: Option<String>,
206    pub model: Option<String>,
207    pub api_key: Option<String>,
208    pub api: Option<String>,
209    /// Base URL. Also accepted as `url` in config files.
210    #[serde(alias = "url")]
211    pub base_url: Option<String>,
212    pub provider: Option<String>,
213}
214
215// ── Session section ─────────────────────────────────────────────────────────
216
217/// Session-level configuration.
218#[derive(Debug, Deserialize, Default, Clone)]
219#[serde(default)]
220pub struct SessionSection {
221    /// Session scope: "ephemeral" or "persistent".
222    pub scope: Option<String>,
223}
224
225// ── Tools section ───────────────────────────────────────────────────────────
226
227/// Tool configuration.
228#[derive(Debug, Deserialize, Default, Clone)]
229#[serde(default)]
230pub struct ToolsSection {
231    /// List of enabled tool names (resolved by the caller's tool registry).
232    pub enabled: Vec<String>,
233    /// Tool execution strategy: "sequential", "parallel", "batched".
234    pub tool_strategy: Option<String>,
235    /// Batch size for "batched" strategy.
236    pub batch_size: Option<usize>,
237    /// Named tool instances with overrides.
238    pub instances: Vec<ToolInstance>,
239}
240
241/// A named tool instance with configuration overrides.
242#[derive(Debug, Deserialize, Default, Clone)]
243#[serde(default)]
244pub struct ToolInstance {
245    pub name: Option<String>,
246    pub enabled: Option<bool>,
247    pub config: HashMap<String, serde_json::Value>,
248}
249
250// ── Skills section ──────────────────────────────────────────────────────────
251
252/// Skills configuration.
253#[derive(Debug, Deserialize, Default, Clone)]
254#[serde(default)]
255pub struct SkillsSection {
256    /// Skill directory paths to load SKILL.md files from.
257    pub paths: Vec<String>,
258}
259
260// ── Sub-agents section ──────────────────────────────────────────────────────
261
262/// Sub-agent configuration.
263#[derive(Debug, Deserialize, Default, Clone)]
264#[serde(default)]
265pub struct SubAgentsSection {
266    /// Named sub-agent definitions.
267    pub instances: Vec<SubAgentInstance>,
268}
269
270/// A sub-agent instance.
271#[derive(Debug, Deserialize, Default, Clone)]
272#[serde(default)]
273pub struct SubAgentInstance {
274    /// Instance ID using the `{{...}}` reference protocol.
275    pub id: Option<String>,
276    pub name: Option<String>,
277    pub description: Option<String>,
278    pub system_prompt: Option<String>,
279    /// Provider reference (supports `{{...}}` protocol).
280    pub provider: Option<String>,
281    pub model: Option<String>,
282    pub max_turns: Option<usize>,
283    pub tools: Vec<String>,
284}
285
286// ── Callbacks & Hooks (Phase 2 WASM) ────────────────────────────────────────
287
288/// Callback references — Phase 2 WASM plugin loading.
289/// In Phase 1, these are stored as strings but not acted upon.
290#[derive(Debug, Deserialize, Default, Clone)]
291#[serde(default)]
292pub struct CallbacksSection {
293    pub before_loop: Option<String>,
294    pub after_loop: Option<String>,
295    pub before_turn: Option<String>,
296    pub after_turn: Option<String>,
297    pub before_tool_execution: Option<String>,
298    pub after_tool_execution: Option<String>,
299    pub before_compaction_start: Option<String>,
300    pub after_compaction_end: Option<String>,
301}
302
303/// Hook references — Phase 2 WASM plugin loading.
304/// In Phase 1, these are stored as strings but not acted upon.
305#[derive(Debug, Deserialize, Default, Clone)]
306#[serde(default)]
307pub struct HooksSection {
308    pub convert_to_llm: Option<String>,
309    pub transform_context: Option<String>,
310}
311
312// ── Compaction section (G5 — unified config) ────────────────────────────────
313
314/// Compaction configuration — unifies context management settings (G5).
315#[derive(Debug, Deserialize, Default, Clone)]
316#[serde(default)]
317pub struct CompactionSection {
318    /// Maximum context tokens (the model's context window).
319    pub max_context_tokens: Option<usize>,
320    /// Tokens reserved for the system prompt.
321    pub system_prompt_tokens: Option<usize>,
322    /// Fraction of max_context_tokens below which headroom is measured.
323    pub compact_at_pct: Option<f64>,
324    /// Minimum remaining headroom fraction before compaction fires.
325    pub compact_budget_threshold_pct: Option<f64>,
326    /// Turns to keep verbatim from the start.
327    pub keep_first_turns: Option<usize>,
328    /// Minimum turns to keep from the end.
329    pub keep_recent_turns: Option<usize>,
330    /// Token budget for the summarised middle section.
331    pub max_summary_tokens: Option<usize>,
332    /// Max lines per tool output in the keep_recent section.
333    pub tool_output_max_lines: Option<usize>,
334    /// Focus message to guide compaction summarization.
335    pub focus_message: Option<String>,
336    /// Named compaction instances with `{{...}}` ID protocol.
337    pub instances: Vec<CompactionInstanceSection>,
338}
339
340/// A named compaction instance — a variation of the compaction defaults.
341///
342/// The `id` field uses the `{{...}}` reference protocol.
343/// Fields set here override the parent `CompactionSection` defaults.
344#[derive(Debug, Deserialize, Default, Clone)]
345#[serde(default)]
346pub struct CompactionInstanceSection {
347    /// Instance ID using `{{...}}` reference protocol.
348    pub id: String,
349    /// Description for existence check queries.
350    pub description: Option<String>,
351    pub max_context_tokens: Option<usize>,
352    pub system_prompt_tokens: Option<usize>,
353    pub compact_at_pct: Option<f64>,
354    pub compact_budget_threshold_pct: Option<f64>,
355    pub keep_first_turns: Option<usize>,
356    pub keep_recent_turns: Option<usize>,
357    pub max_summary_tokens: Option<usize>,
358    pub tool_output_max_lines: Option<usize>,
359    pub focus_message: Option<String>,
360}
361
362// ── Execution section ───────────────────────────────────────────────────────
363
364/// Execution limits and related configuration.
365#[derive(Debug, Deserialize, Default, Clone)]
366#[serde(default)]
367pub struct ExecutionSection {
368    /// Maximum number of LLM turns.
369    pub max_turns: Option<usize>,
370    /// Maximum total tokens consumed across all turns.
371    pub max_total_tokens: Option<usize>,
372    /// Maximum wall-clock duration in seconds.
373    pub max_duration_secs: Option<u64>,
374    /// Maximum cumulative dollar cost.
375    pub max_cost: Option<f64>,
376    /// Retry configuration.
377    pub retry: RetrySection,
378    /// Cache configuration.
379    pub cache: CacheSection,
380}
381
382/// Retry configuration for transient provider errors.
383#[derive(Debug, Deserialize, Default, Clone)]
384#[serde(default)]
385pub struct RetrySection {
386    /// Maximum retry attempts (0 = no retries).
387    pub max_retries: Option<usize>,
388    /// Initial delay before first retry in milliseconds.
389    pub initial_delay_ms: Option<u64>,
390    /// Backoff multiplier applied each attempt.
391    pub backoff_multiplier: Option<f64>,
392    /// Maximum delay cap in milliseconds.
393    pub max_delay_ms: Option<u64>,
394}
395
396/// Cache configuration.
397#[derive(Debug, Deserialize, Default, Clone)]
398#[serde(default)]
399pub struct CacheSection {
400    /// Master switch — false disables all caching hints.
401    pub enabled: Option<bool>,
402    /// Cache strategy: "auto", "disabled", or a manual config.
403    pub strategy: Option<String>,
404}
405
406// ── System Prompt Strategy section (G6) ─────────────────────────────────
407
408/// System prompt strategy configuration.
409#[derive(Debug, Deserialize, Default, Clone)]
410#[serde(default)]
411pub struct SystemPromptStrategySection {
412    /// Named strategy instances (structure templates).
413    pub instances: Vec<StrategyInstanceSection>,
414}
415
416/// A system prompt strategy instance — defines block structure.
417#[derive(Debug, Deserialize, Default, Clone)]
418#[serde(default)]
419pub struct StrategyInstanceSection {
420    /// Instance ID using `{{...}}` reference protocol.
421    pub id: String,
422    /// Description for existence check queries.
423    pub description: Option<String>,
424    /// Block definitions (name, order, max_length).
425    pub blocks: Vec<StrategyBlockSection>,
426}
427
428/// A block definition within a strategy template.
429#[derive(Debug, Deserialize, Default, Clone)]
430#[serde(default)]
431pub struct StrategyBlockSection {
432    /// Block name (e.g., "identity", "instructions", "constraints").
433    pub name: String,
434    /// Assembly order — lower appears first.
435    pub order: Option<u32>,
436    /// Maximum character budget for this block.
437    pub max_length: Option<usize>,
438}
439
440// ── System Prompt section (G6) ──────────────────────────────────────────
441
442/// System prompt instances configuration.
443#[derive(Debug, Deserialize, Default, Clone)]
444#[serde(default)]
445pub struct SystemPromptSection {
446    /// Named prompt instances (content for strategy templates).
447    pub instances: Vec<PromptInstanceSection>,
448}
449
450/// A system prompt instance — fills content into a strategy's blocks.
451#[derive(Debug, Deserialize, Default, Clone)]
452#[serde(default)]
453pub struct PromptInstanceSection {
454    /// Instance ID using `{{...}}` reference protocol.
455    pub id: String,
456    /// Description for existence check queries.
457    pub description: Option<String>,
458    /// References a strategy instance by `{{...}}` id.
459    #[serde(rename = "type")]
460    pub strategy_type: Option<String>,
461    /// Block content: keys are block names, values are text or "file:path".
462    /// Uses `#[serde(flatten)]` to capture all unknown fields as block content.
463    #[serde(flatten)]
464    pub blocks: HashMap<String, serde_json::Value>,
465}