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}