BREAKING CHANGES: - Relationship syntax now requires blocks for all participants - Removed self/other perspective blocks from relationships - Replaced 'guard' keyword with 'if' for behavior tree decorators Language Features: - Add tree-sitter grammar with improved if/condition disambiguation - Add comprehensive tutorial and reference documentation - Add SBIR v0.2.0 binary format specification - Add resource linking system for behaviors and schedules - Add year-long schedule patterns (day, season, recurrence) - Add behavior tree enhancements (named nodes, decorators) Documentation: - Complete tutorial series (9 chapters) with baker family examples - Complete reference documentation for all language features - SBIR v0.2.0 specification with binary format details - Added locations and institutions documentation Examples: - Convert all examples to baker family scenario - Add comprehensive working examples Tooling: - Zed extension with LSP integration - Tree-sitter grammar for syntax highlighting - Build scripts and development tools Version Updates: - Main package: 0.1.0 → 0.2.0 - Tree-sitter grammar: 0.1.0 → 0.2.0 - Zed extension: 0.1.0 → 0.2.0 - Storybook editor: 0.1.0 → 0.2.0
26 KiB
Resource Linking System Design
Author: Resource Linking Architect
Date: 2026-02-12
Status: Ready for Checkpoint 1 Review
Version: 0.2
Keyword Decision: uses (approved by Sienna)
Executive Summary
This document proposes a unified uses keyword system for associating behaviors and schedules with characters and institutions in the Storybook DSL. The design enables:
- Characters using one or more behaviors
- Characters using one or more schedules
- Institutions using behaviors (for institutional operations)
- Institutions using schedules (operating hours, seasonal variations)
- Conditional/contextual selection of linked resources
- Priority-based behavior selection at runtime
1. Design Goals
Primary Goals
- Unified Syntax: Single
useskeyword for both behaviors and schedules - Simple Default Case: Most common use case should be simple one-liner
- Powerful When Needed: Support complex multi-link scenarios with conditions
- Clear Semantics: Unambiguous about which behavior/schedule applies when
- Backward Compatible: Existing .sb files continue to parse and work
Non-Goals
- Not for Relationships: Relationship linking remains separate (already exists)
- Not Inline Definitions: Can only link to named behaviors/schedules, not define inline
- Not Dynamic Composition: Links are static at author-time, selection is runtime
2. Syntax Design
2.1 Simple Single Link
The most common case: a character has one primary behavior and one schedule.
character Martha: Human {
age: 34
uses behavior: WorkAtBakery
uses schedule: BakerSchedule
}
AST Representation:
// Add to Character struct in ast.rs
pub struct Character {
pub name: String,
pub species: Option<String>,
pub fields: Vec<Field>,
pub template: Option<Vec<String>>,
pub behavior_links: Vec<BehaviorLink>, // NEW
pub schedule_links: Vec<ScheduleLink>, // NEW
pub span: Span,
}
2.2 Multiple Links with Priorities
Characters may have multiple behaviors that activate based on context:
character Alice: Human {
age: 7
uses behaviors: [
{ tree: HandleUrgentNeeds, priority: critical }
{ tree: CuriousExplorer, priority: normal }
{ tree: Idle, priority: low }
]
}
Semantics:
- Higher priority behaviors preempt lower priority ones
- Within same priority, declaration order determines evaluation
critical>high>normal>low
2.3 Conditional Links
Links can have when conditions for context-based selection:
character Alice: Human {
uses behaviors: [
{ tree: HandleUrgentNeeds, priority: critical }
{ tree: GiantBehavior, when: current_size == huge }
{ tree: TinyBehavior, when: current_size == tiny }
{ tree: NormalExploring, default: true }
]
uses schedules: [
{ schedule: SleepSchedule, when: emotional_state == exhausted }
{ schedule: AdventureSchedule, default: true }
]
}
Condition Evaluation:
- Conditions use the existing expression language (see design.md §5)
default: truemeans "use this if no condition matches"- Only one
defaultallowed per link type - Runtime evaluates conditions top-to-bottom
2.4 Institution Links
Institutions can link to behaviors and schedules:
institution Bakery {
type: commercial
uses behavior: BakeryOperations
uses schedule: BakeryHours
}
Multiple Schedules for Seasons:
institution Bakery {
uses schedules: [
{ schedule: SummerHours, when: season == summer }
{ schedule: WinterHours, when: season == winter }
{ schedule: StandardHours, default: true }
]
}
2.5 Template Inheritance
Templates can define default links that characters inherit:
template WonderlandCreature {
uses behavior: WonderlandBehavior
uses schedule: WonderlandSchedule
}
character CheshireCat: Cat from WonderlandCreature {
// Inherits WonderlandBehavior and WonderlandSchedule
// Can override:
uses behavior: CheshireBehavior // Replaces WonderlandBehavior
}
Override Semantics:
- If character defines
uses behavior:, it replaces template's behavior link entirely - If character defines
uses behaviors: [...], it replaces template's behavior links - No merging—it's full replacement (consistent with current template override system)
3. AST Design
3.1 New AST Types
// In src/syntax/ast.rs
/// A link to a behavior tree
#[derive(Debug, Clone, PartialEq)]
pub struct BehaviorLink {
pub tree: Vec<String>, // Qualified path to behavior
pub priority: Option<String>, // critical, high, normal, low
pub condition: Option<Expr>, // when clause
pub is_default: bool, // default: true
pub span: Span,
}
/// A link to a schedule
#[derive(Debug, Clone, PartialEq)]
pub struct ScheduleLink {
pub schedule: Vec<String>, // Qualified path to schedule
pub condition: Option<Expr>, // when clause
pub is_default: bool, // default: true
pub span: Span,
}
// Priority levels (could be enum or validated string)
pub enum Priority {
Critical,
High,
Normal,
Low,
}
3.2 Modified AST Structs
// Character gains link fields
pub struct Character {
pub name: String,
pub species: Option<String>,
pub fields: Vec<Field>,
pub template: Option<Vec<String>>,
pub behavior_links: Vec<BehaviorLink>, // NEW
pub schedule_links: Vec<ScheduleLink>, // NEW
pub span: Span,
}
// Institution gains link fields
pub struct Institution {
pub name: String,
pub fields: Vec<Field>,
pub behavior_links: Vec<BehaviorLink>, // NEW
pub schedule_links: Vec<ScheduleLink>, // NEW
pub span: Span,
}
// Template can also have links
pub struct Template {
pub name: String,
pub fields: Vec<Field>,
pub includes: Vec<Vec<String>>,
pub behavior_links: Vec<BehaviorLink>, // NEW
pub schedule_links: Vec<ScheduleLink>, // NEW
pub span: Span,
}
4. Parser Design (LALRPOP)
4.1 Grammar Productions
// In parser.lalrpop
// Character definition with optional links
pub Character: Character = {
"character" <name:Ident> <species:SpeciesClause?> <template:TemplateClause?> "{"
<items:CharacterItem*>
"}" => {
let mut fields = vec![];
let mut behavior_links = vec![];
let mut schedule_links = vec![];
for item in items {
match item {
CharacterItem::Field(f) => fields.push(f),
CharacterItem::BehaviorLink(bl) => behavior_links.extend(bl),
CharacterItem::ScheduleLink(sl) => schedule_links.extend(sl),
}
}
Character { name, species, fields, template, behavior_links, schedule_links, span }
}
};
CharacterItem: CharacterItem = {
<Field> => CharacterItem::Field(<>),
<BehaviorLinkStmt> => CharacterItem::BehaviorLink(<>),
<ScheduleLinkStmt> => CharacterItem::ScheduleLink(<>),
};
// Behavior link statement
BehaviorLinkStmt: Vec<BehaviorLink> = {
// Single link: uses behavior: BehaviorName
"uses" "behavior" ":" <path:QualifiedPath> => {
vec![BehaviorLink {
tree: path,
priority: None,
condition: None,
is_default: false,
span,
}]
},
// Multiple links: uses behaviors: [...]
"uses" "behaviors" ":" "[" <links:Comma<BehaviorLinkSpec>> "]" => links,
};
BehaviorLinkSpec: BehaviorLink = {
// { tree: Name, priority: normal, when: condition, default: true }
"{" <fields:Comma<BehaviorLinkField>> "}" => {
let mut tree = None;
let mut priority = None;
let mut condition = None;
let mut is_default = false;
for field in fields {
match field {
BehaviorLinkField::Tree(path) => tree = Some(path),
BehaviorLinkField::Priority(p) => priority = Some(p),
BehaviorLinkField::Condition(c) => condition = Some(c),
BehaviorLinkField::Default => is_default = true,
}
}
BehaviorLink {
tree: tree.expect("tree field required"),
priority,
condition,
is_default,
span,
}
},
};
BehaviorLinkField: BehaviorLinkField = {
"tree" ":" <QualifiedPath> => BehaviorLinkField::Tree(<>),
"priority" ":" <Ident> => BehaviorLinkField::Priority(<>),
"when" ":" <Expr> => BehaviorLinkField::Condition(<>),
"default" ":" "true" => BehaviorLinkField::Default,
};
// Schedule link statement (parallel structure)
ScheduleLinkStmt: Vec<ScheduleLink> = {
"uses" "schedule" ":" <path:QualifiedPath> => {
vec![ScheduleLink {
schedule: path,
condition: None,
is_default: false,
span,
}]
},
"uses" "schedules" ":" "[" <links:Comma<ScheduleLinkSpec>> "]" => links,
};
ScheduleLinkSpec: ScheduleLink = {
"{" <fields:Comma<ScheduleLinkField>> "}" => {
let mut schedule = None;
let mut condition = None;
let mut is_default = false;
for field in fields {
match field {
ScheduleLinkField::Schedule(path) => schedule = Some(path),
ScheduleLinkField::Condition(c) => condition = Some(c),
ScheduleLinkField::Default => is_default = true,
}
}
ScheduleLink {
schedule: schedule.expect("schedule field required"),
condition,
is_default,
span,
}
},
};
ScheduleLinkField: ScheduleLinkField = {
"schedule" ":" <QualifiedPath> => ScheduleLinkField::Schedule(<>),
"when" ":" <Expr> => ScheduleLinkField::Condition(<>),
"default" ":" "true" => ScheduleLinkField::Default,
};
5. Resolution & Validation
5.1 Name Resolution
During the resolution pass (Pass 3 in design.md §4.7), the resolver must:
- Resolve Behavior Paths: Each
tree: BehaviorNamemust reference a validbehaviordeclaration - Resolve Schedule Paths: Each
schedule: ScheduleNamemust reference a validscheduledeclaration - Validate Priorities: Priority values must be one of {critical, high, normal, low}
- Validate Conditions: Expressions in
whenclauses must be valid and type-check
Error Examples:
error: unresolved behavior reference
┌─ characters/alice.sb:12:23
│
12 │ uses behavior: CuriousExporer
│ ^^^^^^^^^^^^^^ no behavior named `CuriousExporer` exists
│
= help: did you mean `CuriousExplorer`? (defined in behaviors/alice_behaviors.sb)
5.2 Semantic Validation
- At Most One Default: Each link array can have at most one
default: true - Priority + Default Conflicts: If
default: true, priority should belowor omitted - Condition Completeness: Warn if conditions are not exhaustive (no default + gaps in conditions)
Warning Example:
warning: conditions may not cover all cases
┌─ characters/alice.sb:8:5
│
8 │ uses behaviors: [
9 │ { tree: GiantBehavior, when: current_size == huge }
10 │ { tree: TinyBehavior, when: current_size == tiny }
11 │ ]
│
= note: no default behavior specified and conditions don't cover all size values
= help: add a default behavior: { tree: NormalBehavior, default: true }
5.3 Template Merge Logic
When a character uses from Template, behavior and schedule links are merged:
// Pseudocode for merge logic
fn merge_character_with_template(char: Character, template: Template) -> ResolvedCharacter {
let behavior_links = if !char.behavior_links.is_empty() {
char.behavior_links // Character overrides completely
} else {
template.behavior_links // Inherit from template
};
let schedule_links = if !char.schedule_links.is_empty() {
char.schedule_links
} else {
template.schedule_links
};
// ... merge fields, etc.
}
Key Rule: If character defines ANY behavior links, template's behavior links are ignored entirely. Same for schedules. This is all-or-nothing replacement, not merging. (no, i like merging. idk how we'll handle conflicts, but i want to support that kinda composition)
6. Resolved Type Representation
6.1 Resolved Link Types
// In src/types.rs
/// Resolved behavior link with all references resolved
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedBehaviorLink {
pub tree_name: String, // Fully qualified behavior name
pub priority: Priority, // Resolved to enum
pub condition: Option<Expr>, // Validated expression
pub is_default: bool,
}
/// Resolved schedule link
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedScheduleLink {
pub schedule_name: String, // Fully qualified schedule name
pub condition: Option<Expr>,
pub is_default: bool,
}
#[derive(Debug, Clone, PartialEq, PartialOrd)]
pub enum Priority {
Critical = 3,
High = 2,
Normal = 1,
Low = 0,
}
6.2 Updated Resolved Structs
pub struct ResolvedCharacter {
pub name: String,
pub species: Option<String>,
pub fields: HashMap<String, Value>,
pub prose_blocks: HashMap<String, ProseBlock>,
pub behavior_links: Vec<ResolvedBehaviorLink>, // NEW
pub schedule_links: Vec<ResolvedScheduleLink>, // NEW
pub span: Span,
}
pub struct ResolvedInstitution {
pub name: String,
pub fields: HashMap<String, Value>,
pub behavior_links: Vec<ResolvedBehaviorLink>, // NEW
pub schedule_links: Vec<ResolvedScheduleLink>, // NEW
pub span: Span,
}
7. SBIR Representation Proposal
7.1 CHARACTERS Section Extension
Currently, the CHARACTERS section (called ENTITIES in some specs) stores character data. We extend it:
CHARACTERS Section:
- count: u32
- characters: [Character...]
Character:
- name: String
- species: Option<String>
- fields: Map<String, Value>
- behavior_links: [BehaviorLink...] <-- NEW
- schedule_links: [ScheduleLink...] <-- NEW
BehaviorLink:
- behavior_id: u32 (index into BEHAVIORS section)
- priority: u8 (0=low, 1=normal, 2=high, 3=critical)
- condition: Option<Expression>
- is_default: bool
ScheduleLink:
- schedule_id: u32 (index into SCHEDULES section)
- condition: Option<Expression>
- is_default: bool
Expression:
- (Existing expression bytecode format from design.md §5)
7.2 INSTITUTIONS Section Extension
INSTITUTIONS Section:
- count: u32
- institutions: [Institution...]
Institution:
- name: String
- fields: Map<String, Value>
- behavior_links: [BehaviorLink...] <-- NEW
- schedule_links: [ScheduleLink...] <-- NEW
7.3 BEHAVIORS and SCHEDULES Sections
These sections remain unchanged—they define the behavior trees and schedules. Links reference them by index.
Index Resolution:
- During compilation, behavior/schedule names are resolved to their index in the respective section
- At runtime, the engine uses the index to look up the behavior/schedule definition
8. Runtime Link Resolution Algorithm
8.1 Behavior Selection
When the engine needs to select a behavior for a character:
fn select_behavior(character: &Character, context: &RuntimeContext) -> Option<&Behavior> {
let mut candidates: Vec<_> = character.behavior_links
.iter()
.filter(|link| {
// Evaluate condition if present
link.condition.is_none() || evaluate_condition(&link.condition, context)
})
.collect();
if candidates.is_empty() {
return None;
}
// Sort by priority (descending)
candidates.sort_by(|a, b| b.priority.cmp(&a.priority));
// Return highest priority candidate
// (If multiple same priority, declaration order is preserved by stable sort)
let selected = candidates[0];
Some(get_behavior_by_id(selected.behavior_id))
}
Key Properties:
- Priority-based selection: higher priority wins
- Conditions filter candidates before priority sorting
default: trueonly matters if no conditions match (it's implicitlywhen: true)- Deterministic: same context always yields same behavior
8.2 Schedule Selection
fn select_schedule(entity: &Entity, context: &RuntimeContext) -> Option<&Schedule> {
for link in &entity.schedule_links {
if link.is_default {
continue; // Skip default, check it last
}
if link.condition.is_none() || evaluate_condition(&link.condition, context) {
return Some(get_schedule_by_id(link.schedule_id));
}
}
// No conditions matched, use default if present
entity.schedule_links
.iter()
.find(|link| link.is_default)
.map(|link| get_schedule_by_id(link.schedule_id))
}
Key Properties:
- First-match semantics: first condition that evaluates to true wins
- Default is fallback: only used if no condition matches
- Order matters: earlier links are checked first
9. Examples
9.1 Simple Character with Behavior and Schedule
behavior BakerBehavior {
> {
check_oven
serve_customers
clean_workspace
}
}
schedule BakerSchedule {
block work { 5:00 - 13:00 }
block lunch { 13:00 - 14:00 }
block home { 14:00 - 22:00 }
block sleep { 22:00 - 5:00 }
}
character Martha: Human {
age: 34
occupation: baker
uses behavior: BakerBehavior
uses schedule: BakerSchedule
}
9.2 Character with Multiple Context-Dependent Behaviors
character Alice: Human {
age: 7
current_size: normal
emotional_state: curious
uses behaviors: [
{ tree: PanicBehavior, priority: critical, when: emotional_state == frightened }
{ tree: GiantBehavior, when: current_size == huge }
{ tree: TinyBehavior, when: current_size == tiny }
{ tree: BraveBehavior, when: emotional_state == brave }
{ tree: CuriousExplorer, default: true }
]
uses schedules: [
{ schedule: SleepingSchedule, when: emotional_state == exhausted }
{ schedule: AdventureSchedule, default: true }
]
}
9.3 Institution with Seasonal Schedules
schedule SummerHours {
block open { 6:00 - 20:00 }
block closed { 20:00 - 6:00 }
}
schedule WinterHours {
block open { 7:00 - 18:00 }
block closed { 18:00 - 7:00 }
}
institution Bakery {
type: commercial
uses behavior: BakeryOperations
uses schedules: [
{ schedule: SummerHours, when: season == summer }
{ schedule: WinterHours, when: season == winter }
]
}
9.4 Template with Default Links
behavior WonderlandBehavior {
> {
speak_nonsense
violate_logic
}
}
schedule WonderlandSchedule {
block awake { 0:00 - 24:00 } // Always awake in dreams
}
template WonderlandCreature {
uses behavior: WonderlandBehavior
uses schedule: WonderlandSchedule
}
character CheshireCat: Cat from WonderlandCreature {
// Inherits WonderlandBehavior and WonderlandSchedule
can_vanish: true
}
character Alice: Human from WonderlandCreature {
// Overrides behavior but keeps schedule
uses behavior: CuriousExplorer
}
10. Open Questions for User Review (Checkpoint 1)
Question 1: Priority vs. Declaration Order
Current Design: Priority determines order, then declaration order breaks ties.
Alternative: Remove priority, use only declaration order (simpler but less expressive).
Recommendation: Keep priority. It's more explicit and handles common use cases like "urgent needs always trump routine activities." (i guess priority is fine)
Question 2: Condition Syntax Sugar
Current Design: Full condition expressions.
Alternative: Add syntactic sugar for common patterns:
uses behaviors: [
{ tree: GiantBehavior, when: current_size == huge }
// vs.
{ tree: GiantBehavior, when current_size: huge } // shorter
]
Recommendation: Start with full expressions, add sugar if usage reveals patterns. (use == or is, support both like python does.)
Question 3: Schedule-Behavior Integration
Current Design: Behaviors and schedules are separate links. Schedule determines WHEN, behavior determines WHAT.
Alternative: Allow schedules to specify behaviors inline:
schedule WorkSchedule {
block work { 9:00 - 17:00, behavior: WorkBehavior }
}
Recommendation: Defer inline behaviors to schedule system design (Task #8). Keep linking separate for now. (yeah that's fine)
Question 4: Link Override Semantics
Current Design: If character defines any behavior links, template's links are completely replaced.
Alternative: Merge character and template links (character links come first, then template links).
Recommendation: Keep replacement semantics. It's clearer and matches existing override system. (sure? i don't remember the nuances tbh)
Question 5: Multiple Defaults
Current Design: At most one default: true per link type.
Alternative: Allow multiple defaults with priority order.
Recommendation: Keep single default. Multiple defaults creates ambiguity. (single default makes sense?)
11. Implementation Plan (for Task #6)
Phase 1: AST Extension (Week 1)
- Add
BehaviorLinkandScheduleLinkstructs toast.rs - Add link fields to
Character,Institution,Template - Update
Declarationenum if needed
Phase 2: Parser Implementation (Week 1-2)
- Implement
BehaviorLinkStmtandScheduleLinkStmtgrammar - Implement
BehaviorLinkSpecandScheduleLinkSpecparsing - Add link parsing to character, institution, template productions
- Write parser tests for all link variations
Phase 3: Resolution (Week 2)
- Implement behavior/schedule name resolution in
resolve/names.rs - Add priority validation
- Add condition expression validation
- Implement template merge logic for links in
resolve/merge.rs - Write resolution tests
Phase 4: Resolved Types (Week 2)
- Add
ResolvedBehaviorLinkandResolvedScheduleLinktotypes.rs - Update
ResolvedCharacterandResolvedInstitution - Implement conversion in
resolve/convert.rs - Write conversion tests
Phase 5: Validation & Diagnostics (Week 3)
- Implement semantic validation (single default, etc.)
- Add helpful error messages with fuzzy matching
- Add warnings for incomplete condition coverage
- Write validation tests
Phase 6: Integration & Documentation (Week 3)
- Update examples to use new linking syntax
- Update language documentation
- Run full test suite
- Create migration examples (if backward compatibility breaks)
Total Estimate: 3 weeks implementation after design approval.
12. Success Criteria
Must Have
- Unified
useskeyword for behaviors and schedules - Single-link syntax works (
uses behavior: Name) - Multi-link syntax works (
uses behaviors: [...]) - Conditions supported (
when: expression) - Priorities supported for behaviors
- Default fallback supported (
default: true) - Template inheritance works
- Character linking works
- Institution linking works
- Parser produces correct AST
- Resolver validates references
- Clear error messages
- SBIR format defined
Should Have
- Warning for incomplete condition coverage
- Examples for all use cases
- Migration guide if needed
- Runtime selection algorithm specification
- Performance characteristics documented
Nice to Have
- Visual editor support design
- Auto-completion for behavior/schedule names
- Link refactoring tools
13. Risks & Mitigation
| Risk | Likelihood | Impact | Mitigation |
|---|---|---|---|
| Syntax conflicts with relationship linking | Low | Medium | Different syntax context—parser can disambiguate |
| Complex condition expressions hard to debug | Medium | Medium | Good error messages, warnings for non-exhaustive conditions |
| Priority system confusing for users | Medium | Low | Clear documentation, examples, default priority=normal |
| Template override semantics unclear | Medium | Medium | Explicit documentation, validation warnings |
| SBIR encoding inefficient | Low | Low | Use indices for references, compress expressions |
| Runtime selection too slow | Low | Medium | Profile early, cache selections if needed |
Appendix A: Comparison with Relationship Linking
Relationship Linking (existing):
- Top-level
relationshipdeclarations - Participants within relationships
- Bidirectional by nature
self/otherblocks for asymmetry
Resource Linking (this design):
- Links within character/institution definitions
- References to external behaviors/schedules
- Unidirectional (character → behavior)
- Conditions/priorities for selection
These are complementary systems serving different purposes. No conflict.
Appendix B: Grammar Sketch (Full)
// Simplified grammar showing link integration
Character: Character = {
"character" <name:Ident> <species:SpeciesClause?> <template:TemplateClause?>
"{" <items:CharacterItem*> "}" => // ... construct Character
};
CharacterItem = {
Field,
BehaviorLinkStmt,
ScheduleLinkStmt,
ProseBlock,
};
BehaviorLinkStmt: Vec<BehaviorLink> = {
"uses" "behavior" ":" <QualifiedPath> => // single link
"uses" "behaviors" ":" "[" <Comma<BehaviorLinkSpec>> "]" => // multi link
};
BehaviorLinkSpec: BehaviorLink = {
"{" <Comma<BehaviorLinkField>> "}" => // parse fields into BehaviorLink
};
BehaviorLinkField = {
"tree" ":" QualifiedPath,
"priority" ":" Ident,
"when" ":" Expr,
"default" ":" "true",
};
// Parallel structure for schedules...
End of Design Document
Next Step: Present to user (Sienna) for Checkpoint 1 review and approval.