Base URL: http://localhost:8000/api
Method
Path
Request Body
Response
Description
POST
/sessions
--
{ session_id: string }
Create a new build session
GET
/sessions/:id
--
BuildSession
Get full session state
POST
/sessions/:id/start
{ spec: NuggetSpec, workspace_path?: string, workspace_json?: object }
{ status: "started" }
Start build with a NuggetSpec
POST
/sessions/:id/stop
--
{ status: "stopped" }
Cancel a running build
GET
/sessions/:id/tasks
--
Task[]
List all tasks in session
GET
/sessions/:id/git
--
CommitInfo[]
Get commit history
GET
/sessions/:id/tests
--
Test results object
Get test outcomes
GET
/sessions/:id/export
--
application/zip
Export nugget directory as zip
POST
/sessions/:id/chat
{ message: string }
{ status: "processing" }
Post-build iterative chat (requires session in done state, response streamed via WS)
POST
/sessions/:id/checkpoint
{ checkpoint_id: string, response: "approve"|"reject"|"choice", choice_id?: string, comment?: string }
{ status: "ok" }
Respond to a HITL checkpoint (resumes blocked execution)
POST
/sessions/:id/gate
{ approved?: boolean, feedback?: string }
{ status: "ok" }
Respond to a human gate
POST
/sessions/:id/question
{ task_id: string, answers: Record<string, any> }
{ status: "ok" }
Answer an agent question
Method
Path
Request Body
Response
Description
POST
/skills/run
{ plan: SkillPlan, allSkills?: SkillSpec[] }
{ session_id: string }
Start standalone skill execution. allSkills needed for invoke_skill steps.
POST
/skills/:sessionId/answer
{ step_id: string, answers: Record<string, any> }
{ status: "ok" }
Answer a skill's ask_user question. answers keyed by step header.
Method
Path
Request Body
Response
Description
GET
/hardware/detect
--
{ detected: boolean, port?: string, board_type?: string }
Detect connected ESP32
POST
/hardware/flash/:id
--
{ success: boolean, message: string }
Flash session output to board
Method
Path
Request Body
Response
Description
POST
/workspace/save
{ workspace_path, workspace_json?, skills?, rules?, portals? }
{ status: "saved" }
Save design files to directory
POST
/workspace/load
{ workspace_path }
{ workspace, skills, rules, portals }
Load design files from directory
Method
Path
Response
Description
GET
/devices
DeviceManifest[]
List device plugin manifests (block definitions, deploy config)
Base URL: http://localhost:8000/api/sessions/:sessionId/meetings
Method
Path
Request Body
Response
Description
GET
/sessions/:id/meetings
--
MeetingSession[]
List all meetings for a session
GET
/sessions/:id/meetings/:mid
--
MeetingSession
Get meeting details
POST
/sessions/:id/meetings/:mid/accept
--
MeetingSession
Accept a meeting invite (must be in invited state)
POST
/sessions/:id/meetings/:mid/decline
--
MeetingSession
Decline a meeting invite (must be in invited state)
POST
/sessions/:id/meetings/:mid/message
{ content: string }
MeetingMessage
Send a message from the kid in an active meeting
POST
/sessions/:id/meetings/:mid/outcome
{ outcomeType: string, data: object }
Outcome object
Save meeting outcome data
POST
/sessions/:id/meetings/:mid/materialize
{ canvasType: string, data: object }
{ files: string[], primaryFile?: string }
Materialize canvas into workspace files
POST
/sessions/:id/meetings/:mid/end
--
MeetingSession
End an active meeting
POST
/sessions/:id/meetings/start
{ meetingTypeId: string }
{ meetingId: string }
Start kid-initiated meeting
Base URL: http://localhost:8000/api/sessions/:sessionId/planning
Method
Path
Request Body
Response
Description
POST
/sessions/:id/planning/start
{ idea: string, canvasContext?: CanvasContext }
{ planId: string }
Start a planning session (events via WS)
POST
/sessions/:id/planning/answer
{ optionValue: string }
{ plan: PlanState }
Submit structured widget answer (instant, no API call)
POST
/sessions/:id/planning/message
{ text: string }
202 Accepted
Submit free-text answer (response streamed via WS)
POST
/sessions/:id/planning/generate
--
202 Accepted
Generate canvas blocks from finalized plan
GET
/sessions/:id/planning
--
{ plan, status, conversationHistory }
Get current planning state (used for reconnection)
Base URL: http://localhost:8000/v1
All endpoints except POST /v1/agents and GET /v1/agents/:id/heartbeat require the x-api-key header set to the API key returned during provisioning.
Method
Path
Request Body
Response
Description
POST
/v1/agents
NuggetSpec
{ agent_id, api_key, runtime_url, agent_name, greeting }
Provision a new agent (no auth required)
PUT
/v1/agents/:id
NuggetSpec
{ status: "updated", agent_id }
Update agent config
DELETE
/v1/agents/:id
--
{ status: "deleted", agent_id }
Deprovision agent (cleans up sessions, usage, backpack, study, gaps)
POST
/v1/agents/:id/turn/text
{ text: string, session_id?: string }
{ response, session_id, input_tokens, output_tokens }
Send a text conversation turn
POST
/v1/agents/:id/turn/audio
Audio file (multipart)
{ transcript, response_text, audio_base64, audio_format, session_id, usage }
Audio conversation turn via OpenAI STT/TTS (x-api-key auth, 501 without OPENAI_API_KEY)
GET
/v1/agents/:id/history
--
{ agent_id, sessions: Array<{ session_id, turn_count, created_at }> }
List conversation sessions for agent
GET
/v1/agents/:id/history?session_id=X&limit=N
--
{ session_id, turns: ConversationTurn[] }
Get turn history for a specific session
GET
/v1/agents/:id/heartbeat
--
{ status: "online", agent_id, agent_name, session_count, total_input_tokens, total_output_tokens }
Agent health check (no auth required)
GET
/v1/agents/:id/gaps
--
{ agent_id, gaps: GapEntry[] }
List detected knowledge gaps
POST
/v1/agents/:id/backpack
{ title: string, content: string, source_type?: string, uri?: string }
{ source_id, agent_id }
Add a source to the knowledge backpack
GET
/v1/agents/:id/backpack
--
{ agent_id, sources: BackpackSource[] }
List all backpack sources
DELETE
/v1/agents/:id/backpack/:sourceId
--
{ status: "removed", source_id }
Remove a backpack source
POST
/v1/agents/:id/backpack/search
{ query: string, limit?: number }
{ agent_id, results: SearchResult[] }
Search the knowledge backpack
PUT
/v1/agents/:id/study
{ enabled?: boolean, style?: string, difficulty?: string, quiz_frequency?: number }
{ status: "enabled"|"disabled", agent_id }
Enable or disable study mode
GET
/v1/agents/:id/study
--
{ agent_id, config: StudyModeConfig, progress: StudyProgress }
Get study mode config and progress
POST
/v1/agents/:id/study/quiz
--
QuizQuestion
Generate a quiz question from backpack content
POST
/v1/agents/:id/study/answer
{ question_id: string, answer: number }
{ correct: boolean, question_id }
Submit a quiz answer
WS
/v1/agents/:id/stream?api_key=KEY
--
Streaming conversation turn
WebSocket endpoint for streaming conversation turns
Base URL: http://localhost:8000/api/spec-graph
Method
Path
Request Body
Response
Description
POST
/api/spec-graph
{ workspace_path: string }
{ graph_id }
Create a new spec graph
GET
/api/spec-graph/:id
--
{ graph: SpecGraph }
Get full graph (nodes + edges)
DELETE
/api/spec-graph/:id
--
{ status: "deleted" }
Delete a graph
POST
/api/spec-graph/:id/nodes
{ spec: NuggetSpec, label: string }
{ node_id }
Add a nugget node to the graph
GET
/api/spec-graph/:id/nodes
--
{ nodes: SpecGraphNode[] }
List all nodes
GET
/api/spec-graph/:id/nodes/:nid
--
{ node: SpecGraphNode }
Get a single node
DELETE
/api/spec-graph/:id/nodes/:nid
--
{ status: "removed" }
Remove a node and its edges
POST
/api/spec-graph/:id/edges
{ from_id: string, to_id: string, relationship: EdgeRelationship, description?: string }
{ status: "added" }
Add a directed edge between nodes
DELETE
/api/spec-graph/:id/edges
{ from_id: string, to_id: string }
{ status: "removed" }
Remove an edge
GET
/api/spec-graph/:id/neighbors/:nid
--
{ incoming: SpecGraphEdge[], outgoing: SpecGraphEdge[] }
Get incoming and outgoing neighbors of a node
POST
/api/spec-graph/:id/compose
{ node_ids: string[], system_level?: string, session_id?: string }
ComposeResult
Compose selected nodes into a merged NuggetSpec
POST
/api/spec-graph/:id/impact
{ node_id: string }
ImpactResult
Detect cross-nugget impact of changing a node
GET
/api/spec-graph/:id/interfaces?node_ids=A,B,C
--
{ contracts: InterfaceContract[] }
Resolve interface contracts among nodes
EdgeRelationship : "depends_on" | "provides_to" | "shares_interface" | "composes_into"
Method
Path
Request Body
Response
Description
GET
/projects
--
ProjectSummary[]
List all saved projects under ~/Elisa/projects/
POST
/sessions/:id/restore
{ projectDir: string }
{ status: "restored", sessionId }
Restore a session from a persisted project directory
Method
Path
Request Body
Response
Description
POST
/internal/shutdown
--
{ status: "ok" }
Checkpoint all active sessions to disk (called by Electron on quit)
Method
Path
Response
Description
GET
/health
{ status: "ready"|"degraded", apiKey: "valid"|"invalid"|"missing"|"unchecked", apiKeyError?: string, agentSdk: "available"|"not_found" }
Health check
Connect to: ws://localhost:8000/ws/session/:sessionId
All events flow server to client as JSON with a type discriminator field.
Event
Payload
Description
session_started
{ session_id }
Session created
planning_started
--
Meta-planner decomposing spec
plan_ready
{ tasks: Task[], agents: Agent[], explanation: string, deployment_target?: string }
Task DAG ready for execution
workspace_created
{ nugget_dir: string }
Nugget workspace directory created
session_complete
{ summary }
Build finished
error
{ message, recoverable: boolean }
Error occurred
Event
Payload
Description
task_started
{ task_id, agent_name }
Agent began working on task
task_completed
{ task_id, summary }
Task finished successfully
task_failed
{ task_id, error, retry_count }
Task failed (may auto-retry)
Event
Payload
Description
agent_output
{ task_id, agent_name, content }
Streamed agent message chunk
agent_status
{ agent: Agent }
Agent status changed (idle/working/done/error/waiting)
agent_message
{ from, to, content }
Inter-agent communication
token_usage
{ agent_name, input_tokens, output_tokens, cost_usd }
Token consumption per agent
budget_warning
{ total_tokens, max_budget, cost_usd }
Token budget threshold reached
minion_state_change
{ agent_name, old_status, new_status }
Minion status transition
narrator_message
{ from, text, mood, related_task_id? }
Narrator commentary on build events
permission_auto_resolved
{ task_id, permission_type, decision, reason }
Agent permission auto-resolved by policy
Event
Payload
Description
commit_created
{ sha, message, agent_name, task_id, timestamp, files_changed }
Git commit created
test_expectations
{ task_id, tests: Array<{ name, description }> }
Test expectations generated for a task
test_result
{ test_name, passed: boolean, details }
Individual test outcome
test_phase_complete
{ passed, failed, total }
All tests finished
coverage_update
{ percentage, details?: CoverageReport }
Code coverage report
fix_started
{ bugReport }
Bug fix initiated
fix_task_completed
{ taskId, success }
Fix task done
fix_tests_completed
{ passed, failed, total }
Fix re-test results
Event
Payload
Description
skill_started
{ skill_id, skill_name }
Skill plan execution started
skill_step
{ skill_id, step_id, step_type, status }
Skill step started/completed/failed
skill_question
{ skill_id, step_id, questions: QuestionPayload[] }
Skill asking user a question
skill_output
{ skill_id, step_id, content }
Skill step produced output
skill_completed
{ skill_id, result }
Skill plan finished
skill_error
{ skill_id, message }
Skill plan failed
Event
Payload
Description
deploy_started
{ target }
Deploy phase started
deploy_progress
{ step, progress: number, device_role? }
Deploy progress (0-100)
deploy_checklist
{ rules: Array<{ name, prompt }> }
Pre-deploy rules checklist
deploy_complete
{ target, url? }
Deploy finished
flash_prompt
{ device_role, message }
Prompts user to connect device for flashing
flash_progress
{ device_role, step, progress: number }
Per-file flash progress (0-100)
flash_complete
{ device_role, success, message? }
Device flash finished
documentation_ready
{ file_path }
Generated documentation available
serial_data
{ line, timestamp }
ESP32 serial monitor output
Event
Payload
Description
human_gate
{ task_id, question, context }
Build paused, awaiting user approval
user_question
{ task_id, questions: QuestionPayload[] }
Agent asking user a question
teaching_moment
{ concept, headline, explanation, tell_me_more?, related_concepts? }
Learning moment surfaced
QuestionPayload :
{
question: string ;
header: string ;
options: Array < { label : string ; description : string } > ;
multiSelect: boolean ;
}
NarratorMessage moods : excited, encouraging, concerned, celebrating
Event
Payload
Description
decomposition_narrated
{ goal, subtasks: string[], explanation }
Narrated breakdown of the goal into subtasks
impact_estimate
{ estimated_tasks, complexity: 'simple'|'moderate'|'complex', heaviest_requirements: string[], requirement_details: Array<{ description, estimated_task_count, test_linked, weight, dependents }> }
Pre-execution complexity analysis
boundary_analysis
{ inputs: Array<{ name, type, source? }>, outputs: Array<{ name, type, source? }>, boundary_portals: string[] }
System boundary identification (inputs, outputs, portals)
system_health_update
{ tasks_done, tasks_total, tests_passing, tests_total, tokens_used, health_score }
Periodic health vital signs during execution
system_health_summary
{ health_score, grade: 'A'|'B'|'C'|'D'|'F', breakdown: { tasks_score, tests_score, corrections_score, budget_score } }
Post-execution health summary with grade
health_history
{ entries: Array<{ timestamp, goal, score, grade, breakdown: { tasks, tests, corrections, budget } }> }
Health-over-time trend data (Architect level)
traceability_update
{ requirement_id, test_id, status: 'untested'|'passing'|'failing' }
Individual requirement-test link status change
traceability_summary
{ coverage: number, requirements: Array<{ requirement_id, description, test_id?, test_name?, status: 'untested'|'passing'|'failing' }> }
Full requirement traceability coverage report
correction_cycle_started
{ task_id, attempt_number, failure_reason, max_attempts }
Correction cycle begun for a failed task
correction_cycle_progress
{ task_id, attempt_number, step: 'diagnosing'|'fixing'|'retesting' }
Progress within a correction cycle
convergence_update
{ task_id, attempts_so_far, tests_passing, tests_total, trend: 'improving'|'stalled'|'diverging', converged: boolean, attempts: Array<{ attempt_number, status, tests_passing?, tests_total? }> }
Feedback loop convergence tracking
Event
Payload
Description
composition_started
{ graph_id, node_ids: string[] }
Nugget composition process started
composition_impact
{ graph_id, changed_node_id, affected_nodes: Array<{ node_id, label, reason }>, severity }
Cross-nugget impact detected from a node change
Event
Payload
Description
meeting_invite
{ meetingId, meetingTypeId, agentName, title, description }
Agent proposes a meeting to the user
meeting_started
{ meetingId, meetingTypeId, agentName, canvasType }
Meeting session activated
meeting_message
{ meetingId, role: 'agent'|'kid', content }
Message in an active meeting
meeting_canvas_update
{ meetingId, canvasType, data }
Canvas state updated during meeting
meeting_outcome
{ meetingId, outcomeType, data }
Single outcome produced during meeting
meeting_ended
{ meetingId, outcomes: Array<{ type, data }> }
Meeting ended with collected outcomes
meeting_blocking_task
{ task_id, meeting_type_id }
Task blocked waiting for meeting
meeting_unblocking_task
{ task_id }
Task unblocked after meeting
Event
Payload
Description
planning_mode_started
{ sessionId }
Planning session initialized
planning_turn
{ message, streaming: boolean }
Claude's conversational message (streamed)
planning_question
{ question: QuestionWidget, plan_mutation_map }
Question widget + deterministic mutation map
planning_plan_updated
{ plan: PlanState }
Plan state changed after any answer
planning_ready
{ plan: PlanState, summary }
Plan meets readiness criteria
planning_canvas_generated
{ blocks: CanvasBlockSpec }
Canvas generation complete
planning_teaching
{ teaching: TeachingAnnotation }
Teaching annotation for current turn
planning_learning_summary
{ summary: LearningSummary }
End-of-conversation learning summary
planning_error
{ error }
Planning error
Agent Runtime WebSocket Events
Event
Payload
Description
audio_status
{ status: 'transcribing' | 'thinking' | 'speaking' }
Audio turn processing state for face animation
audio_response
{ transcript, response_text, audio_base64, audio_format, session_id, usage }
Completed audio turn result
Event
Payload
Description
hitl_checkpoint
{ checkpoint_id, checkpoint_type: 'visual'|'choice'|'progress', task_id, screenshot_base64?, choices?: Array<{ id, label, description }>, summary?, progress_pct, narrator_message? }
Mid-build checkpoint requires kid response (blocks execution)
hitl_checkpoint_response
{ checkpoint_id, response: 'approve'|'reject'|'choice', choice_id?, comment? }
Kid responded to checkpoint (execution resumes)
Event
Payload
Description
chat_processing
{ message }
Chat message received, agent is thinking
chat_agent_output
{ content }
Streamed agent output chunk during chat
chat_response
{ content, filesChanged: string[] }
Agent finished responding with summary and changed files
chat_tests_completed
{ passed, failed, total }
Test results after chat-driven code change
chat_preview_refresh
--
Files changed, frontend should reload preview
chat_error
{ message }
Chat processing error
Event
Payload
Description
context_flow
{ from_task_id, to_task_ids: string[], summary_preview }
Context passed from one task to its dependents
The narrator translates raw build events into kid-friendly commentary via Claude Haiku.
Narrator messages are triggered by these build events: task_started, task_completed, task_failed, agent_message, error, session_complete.
{
type: "narrator_message" ;
from: string ; // narrator character name
text: string ; // kid-friendly message (max 200 chars)
mood: string ; // "excited" | "encouraging" | "concerned" | "celebrating"
related_task_id?: string ;
}
NARRATOR_MODEL env var overrides the model (default: claude-haiku-4-5-20251001)
agent_output events are accumulated per task and translated after a 10-second silence window. This avoids flooding the UI with narrator messages during rapid agent output.
Max 1 narrator message per task per 15 seconds. Messages that would exceed this limit are silently dropped.
CLI portals validate commands against a strict allowlist before execution:
node, npx, python, python3, uvx, docker, deno, bun, bunx, gcloud, firebase
Any command not in this list is rejected with an error.
CliPortalAdapter.execute() uses execFile (not spawn with shell: true). This prevents shell injection because execFile bypasses the shell entirely -- arguments are passed directly to the executable without shell interpretation.
Serial portals are validated via board detection (USB VID:PID matching) before flash operations proceed. This ensures the target device is actually an ESP32 before attempting to write firmware.
The JSON structure produced by the block interpreter and sent to POST /sessions/:id/start.
interface NuggetSpec {
nugget : {
goal : string ; // What the user wants to build
description : string ; // Expanded description
type : string ; // "game" | "website" | "hardware" | "story" | "tool" | "general"
} ;
requirements ?: Array < {
type : string ; // "feature" | "constraint" | "when_then" | "data" | "timer"
description : string ;
} > ;
style ?: {
visual : string | null ; // "fun_colorful" | "clean_simple" | "dark_techy" | "nature" | "space"
personality : string | null ;
} ;
agents ?: Array < {
name : string ;
role : string ; // "builder" | "tester" | "reviewer" | "custom"
persona : string ;
allowed_paths ?: string [ ] ;
restricted_paths ?: string [ ] ;
} > ;
deployment ?: {
target : string ; // "preview" | "web" | "esp32" | "both"
auto_flash : boolean ;
} ;
workflow ?: {
review_enabled : boolean ;
testing_enabled : boolean ;
human_gates : string [ ] ;
flow_hints ?: Array < { type : "sequential" | "parallel" ; descriptions : string [ ] } > ;
iteration_conditions ?: string [ ] ;
behavioral_tests ?: Array < { when : string ; then : string } > ; // Proof blocks (PRD-003)
} ;
skills ?: Array < {
id : string ;
name : string ;
prompt : string ;
category : string ; // "agent" | "feature" | "style" | "composite"
} > ;
rules ?: Array < {
id : string ;
name : string ;
prompt : string ;
trigger : string ; // "always" | "on_task_complete" | "on_test_fail" | "before_deploy"
} > ;
portals ?: Array < {
id : string ;
name : string ;
description : string ;
mechanism : string ; // "mcp" | "cli" | "serial"
capabilities ?: Array < { id : string ; name : string ; kind : string ; description : string } > ;
interactions ?: Array < { type : "tell" | "when" | "ask" ; capabilityId : string ; params ?: Record < string , unknown > } > ;
mcpConfig ?: { command : string ; args ?: string [ ] ; env ?: Record < string , string > } ;
cliConfig ?: { command : string ; args ?: string [ ] } ;
} > ;
devices ?: Array < {
pluginId : string ; // Device plugin ID (e.g., "heltec-sensor-node")
instanceId : string ; // Unique block instance ID
fields : Record < string , unknown > ; // User-configured field values from Blockly blocks
} > ;
permissions ?: {
auto_approve_workspace_writes ?: boolean ;
auto_approve_safe_commands ?: boolean ;
allow_network ?: boolean ;
escalation_threshold ?: number ; // 1-10
} ;
runtime ?: {
agent_name : string ; // Display name for the deployed agent
voice ?: string ; // Agent voice preference (e.g., "coral", "sage")
greeting ?: string ; // Initial greeting message
display_theme ?: string ; // Display theme for BOX-3 (e.g., "dark", "light", "nature")
fallback_response ?: string ; // Response when agent cannot answer
} ;
knowledge ?: {
backpack_sources ?: Array < { title : string ; content : string ; source_type ?: string ; uri ?: string } > ;
study_mode ?: {
enabled ?: boolean ;
style ?: string ; // Quiz style (e.g., "spaced-repetition")
difficulty ?: string ; // Difficulty level (e.g., "easy", "medium", "hard")
quiz_frequency ?: number ; // Frequency of quizzes
} ;
} ;
composition ?: {
provides ?: Array < { name : string ; type : string } > ; // Interfaces this nugget provides
requires ?: Array < { name : string ; type : string } > ; // Interfaces this nugget requires
parent_graph_id ?: string ; // Parent spec graph ID for composed builds
} ;
meeting_team ?: Array < string > ; // Opt-in agent team member IDs (e.g., "media-agent", "web-design-agent")
}
type TaskStatus = "pending" | "in_progress" | "done" | "failed" ;
type AgentRole = "builder" | "tester" | "reviewer" | "custom" ;
type AgentStatus = "idle" | "working" | "done" | "error" | "waiting" ;
type SessionState = "idle" | "planning" | "executing" | "testing" | "deploying" | "reviewing" | "done" ;
Note: reviewing is a transient state during human gate pauses, not a separate pipeline phase.
Defines a reusable skill. Simple skills have a prompt. Composite skills additionally have a workspace (Blockly JSON for the flow editor).
interface SkillSpec {
id : string ; // Unique skill ID (max 200 chars)
name : string ; // Display name (max 200 chars)
prompt : string ; // Prompt template, supports {{key}} variables (max 5000 chars)
category : string ; // "agent" | "feature" | "style" | "composite"
workspace ?: Record < string , unknown > ; // Blockly workspace JSON (composite skills only)
}
Sent to POST /api/skills/run. Represents a sequence of steps to execute.
interface SkillPlan {
skillId : string ; // ID of the skill being executed (max 200 chars, optional)
skillName : string ; // Display name (max 200 chars)
steps : SkillStep [ ] ; // Ordered steps (max 50)
}
SkillStep (discriminated union on type)
All steps share id: string (max 200 chars). The 6 step types:
Type
Fields
Description
ask_user
question (max 2000), header (max 200), options: string[] (max 50 items), storeAs
Pauses execution, presents choice to user. Answer stored in context under storeAs.
branch
contextKey, matchValue (max 500), thenSteps: SkillStep[] (max 50, recursive)
Runs thenSteps only if context[contextKey] === matchValue. No else.
invoke_skill
skillId, storeAs
Calls another skill. Cycle detection (max depth 10). Result stored under storeAs.
run_agent
prompt (max 5000), storeAs
Spawns a Claude agent. Prompt supports {{key}} templates. Result stored under storeAs.
set_context
key, value (max 5000)
Sets a context variable. Value supports {{key}} templates.
output
template (max 5000)
Produces final skill output. Template supports {{key}} syntax.
When {{key}} is resolved:
Check current skill's context entries
Walk parent contexts (for nested invoke_skill calls)
Return empty string if not found
interface SkillContext {
entries : Record < string , string | string [ ] > ;
parentContext ?: SkillContext ;
}