AI Traits
Three core traits plus two Modality methods govern how types participate in AI generation. The traits live in modality_core with zero platform dependencies.
Describe
Section titled “Describe”Universal self-description as text. Used for prompt composition, CEL documentation, and UI display.
pub trait Describe { fn describe(&self) -> String;}Self-contained — every impl describes only itself. No type ever knows about its parents. The orchestrator concatenates ancestor descriptions when building context for child subagents. Tree awareness lives entirely in the orchestrator, never in the types themselves.
Example outputs
Section titled “Example outputs”| Type | Example describe() output |
|---|---|
PropertyType | "text (max 500 chars)" |
PropertySchema | "difficulty: one of easy, medium, hard -- How challenging" |
PropertyValue | "Photosynthesis" |
Property | "difficulty: hard (one of: easy, medium, hard)" |
ValidationRule | "value must be at most 500 characters" |
ComponentPlacement<P> | "text_box at (100, 200, 400, 300)" |
WhiteboardPosition | "x:100 y:200 w:400 h:300" |
GridPosition | "row 2, col 3" |
SlidePosition | "slide in section X, enabled, 5min" |
| Metadata (per-modality) | "canvas: 1920x1080" |
| Component | "TextBox: renders text with font, size, color" |
| Modality | "Whiteboard: canvas with positioned components" |
AI on Modality
Section titled “AI on Modality”There is no standalone Agent trait. Instead, Modality provides the AI surface via two default methods:
// On trait Modality:fn system_prompt(&self) -> String { String::new() }fn agent_config(&self) -> AgentConfig { AgentConfig::default() }pub struct AgentConfig { pub preamble: Option<String>, // static role instructions pub temperature: Option<f64>, // 0.0–2.0 pub max_tokens: Option<u64>, pub model: Option<String>, // override global model pub max_turns: u32, // multi-turn tool-call limit (default 50) pub validate: bool, // inject DoneTool/ValidateTool}Modalities override system_prompt() to provide context-rich prompts describing canvas, components, and current state. agent_config() sets defaults like temperature. The runner merges the static preamble with the dynamic system_prompt().
Tools come from CommandTool on the modality’s intent enum — not from a trait method. Each non-hidden intent variant becomes a rig tool via #[derive(CommandTool)].
Validate
Section titled “Validate”Validation rules that constrain property values and modality state.
pub trait Validate { fn rules(&self) -> Vec<ValidationRule>;
fn validate( &self, eval_cel: &dyn Fn(&str) -> Result<bool, String>, ) -> Vec<ValidationError> { // Default: evaluate Expression + Function rules. // Guideline rules are NOT checked. }}Takes a CEL evaluator closure, which avoids modality_core depending on the executor crate. The closure is built from cel_context() at the call site, using the same context shape used for formulas.
Rule types
Section titled “Rule types”Three ValidationRule variants with different enforcement strategies:
pub enum ValidationRule { Expression(String), // CEL expression -> bool. Hard gate. Guideline(String), // Natural language. Preamble only. Never checked. Function(fn(&PropertyValue) -> bool), // Rust native predicate. Hard gate.}| Variant | Evaluated when | Enforcement | AI-generatable |
|---|---|---|---|
Expression | done tool call | Hard gate — blocks completion | Yes |
Guideline | Never | Included in preamble via Describe | Yes |
Function | done tool call | Hard gate — blocks completion | No (hand-written) |
- Expression rules are CEL strings evaluated synchronously when the agent calls the
donetool. If any fail, errors are returned to the agent to fix. - Guideline rules are natural language injected into the system prompt. The agent follows them while generating. They are never evaluated programmatically.
- Function rules are Rust-native predicates evaluated at
donetime. Not describable to AI, so only used for invariants the agent cannot violate.
ValidationRule implements Describe — Expression and Guideline return their string content, Function returns "(native validation)".
Pending
Section titled “Pending”Determines whether something needs AI resolution.
pub trait Pending { fn is_pending(&self) -> bool;}The meaning of “pending” varies by type:
| Type | is_pending() returns true when |
|---|---|
Property | Required and has no value |
Component | Any required property is pending |
Modality | Props or placements are pending |
| Metadata (per-modality) | Any required metadata field is unset |
Optional or defaulted fields do not trigger generation, but may be filled during the same agent call if the agent chooses. The orchestrator uses is_pending() to decide whether to run a subagent at each level.
Trait coverage
Section titled “Trait coverage”| Type | Describe | Validate | Pending | AI methods |
|---|---|---|---|---|
PropertyType | x | |||
Property | x | x | x | |
PropertySchema | x | |||
PropertyValue | x | |||
ValidationRule | x | |||
ComponentPlacement<P> | x | |||
| Position types | x | |||
| Metadata (per-modality) | x | x | x | |
| Component | x | x | x | |
| Modality | x | x | x | system_prompt(), agent_config() |
Related
Section titled “Related”- Orchestrator — deterministic code that drives
Describe/Pendingchecks - Subagents — rig agents that use
system_prompt()andCommandTooltools - CEL Environment — the evaluator closure passed to
Validate::validate() - Bridge — how
CommandToolintent variants become rig tools - Modality —
system_prompt()andagent_config()methods