Execution lifecycle
Every skill in Ultron goes through the same engine. It acquires a concurrency slot, resolves the model, builds session context from the user profile and memories, assembles the tool catalog filtered by connected integrations, runs the tool-calling loop with parallel tool execution, streams progress to the caller, and writes a memory entry plus an activity log on completion.
Overview
- Entry
runSkillMission(params)insrc/lib/skills/engine.ts- Concurrency cap
- 2 parallel skill runs per process
- Model timeout
- 180 seconds per request
- Retry policy
- Up to 2 retries on 429, backoff 2s then 4s
- Default tier
- Allegro
- Tool execution
- Parallel by default, up to N tool-use blocks per turn
- Progress
- Streamed through onProgress callback
- Auto memory
- save_memory called on completion summary
- Activity feed
- log_activity rows for every tool start and end
The engine is a single function with auxiliary helpers, not a class. Skills do not have hooks they can register or extend; the engine has the runtime authority. This is the price of the closed catalog: every skill behaves the same under load, every skill respects the same concurrency limit, and every skill gets the same progress callback shape. The body of the SKILL.md changes; the surrounding execution does not.
Entry point
One function, six parameters. The caller passes the skill id, the user objective, the user id, the workspace code, optional conversation context, and an optional progress callback.
The chat handler calls this function once per run_skill tool use. The objective is the user message that triggered the skill, possibly rewritten by the router. The conversation context is a compressed summary of the surrounding chat so the skill has a sense of what the user has been doing. The progress callback is how the activity sidebar and the chat surface learn what the skill is doing in real time.
Concurrency slot
The first thing the engine does is wait for a slot. A semaphore caps parallel skill runs at two per host process.
Model resolution
The skill declares a tier as a string. The engine resolves it to a versioned model id.
MODEL_MAP. Every running skill picks up the new id on its next request.Session context
Before the loop starts, the engine builds a context block that sits at the top of the system prompt. It pulls user profile data and relevant memories.
Two reads happen here. First, the user profile is loaded from the profile cache (preferences, company, role, brand voice). Second, the memory layer runs hybrid retrieval: it combines meaning-based search with keyword search, then a small step picks the handful most relevant entries. The merged result lands in the system prompt under a USER CONTEXT heading the skill body can reference.
search_memory as a first move is re-doing work the engine already did. Skills should treat the context block as authoritative for what the user has on file.Tool catalog assembly
The catalog is built once at the start of the run and frozen for the duration of the loop. It is the manifest tools, unioned with the system tools, filtered by the connection gate.
The tool-calling loop
With model, prompt, and tools ready, the engine starts the tool-calling loop against the model. Each iteration is one turn.
- 01Send the message arrayThe system prompt plus the user objective plus any tool results from prior turns. Tool catalog attached.
- 02Inspect stop reasonThe model returns
end_turn,tool_use,max_tokens, orstop_sequence. Onlytool_usecontinues the loop. - 03Execute every tool-use blockIf the response contains N tool-use blocks, dispatch them in parallel. Collect the results in order.
- 04Append tool resultsOne
tool-role message per tool call. The order matches the original tool_use_id sequence. - 05Decrement turn budgetIf
maxTurnsis reached, force a final answer with a short follow-up prompt and exit. - 06Repeat or finalizeNext iteration if stop reason was
tool_use. Otherwise persist the final assistant message and exit.
Parallel tool execution
The engine fans out concurrent tool calls when the model returns multiple tool-use blocks in one response. This is the fast path for things like multi-search.
Most turns return a single tool call but it is common for the model to ask for several searches at once. The engine dispatches them in parallel through Promise.all against the tool executors, then assembles the results back into the message array in the same order the model requested them. Tool result order matters: the model reads results by their tool_use_id, not by array position, but a stable ordering helps the next turn read clean.
Progress streaming
The caller passes an onProgress callback. The engine fires it on every state change inside the loop.
| Event type | Fired when | Payload |
|---|---|---|
| status | On skill start, model switch, finalization | Short message |
| turn | At the top of each loop iteration | Turn number, tool count |
| tool_start | Before each tool executor runs | Tool name, args summary |
| tool_end | After each tool executor returns | Tool name, duration, error? |
| error | On unrecoverable failure | Phase, message |
The chat surface renders these events into the agent activity sidebar. Tool start and tool end events drive the running status pills shown next to each in-flight skill. The status events feed the short summary line at the top of an active session.
Rate limit retries
The engine wraps every model call in a retry helper that backs off on 429.
Side effects on completion
When the loop ends with a final answer, the engine writes two records before returning to the caller.
- 01save_memoryA summary entry is written to
agent_memoriestagged with the skill id and the objective. Subsequent skills retrieve it through the hybrid memory path. - 02log_activityA row in the activity feed marking the skill as complete with elapsed time, turn count, and result length.
- 03releaseSlotThe semaphore counter decrements and the next queued skill wakes up.
- 04Return MissionResultThe caller receives the final assistant text, an array of canvas spec ids (if any were emitted), and a metrics block.
Error paths
A skill can fail in three places. Each surfaces differently.
| Failure | When | What the user sees |
|---|---|---|
| Tool error | An executor throws or returns a tool error result | The model receives the error as a tool result and decides what to do. The skill usually retries or asks the user. |
| Loop exhaustion | maxTurns reached without end_turn | The engine forces a final answer prompt. The result is whatever the model could synthesize from the partial work. |
| Hard error | The model provider returns a non-recoverable status or the network times out | An error progress event fires. The chat surface shows a retry affordance. The conversation state goes to failed. |
File map
Glossary
- Mission
- One full execution of a skill from acquireSlot to releaseSlot.
- Slot
- A unit of the concurrency semaphore. Two slots per host process.
- Tier resolution
- Mapping the manifest model string to a versioned model id through MODEL_MAP.
- Session context
- The block of profile data plus retrieved memories injected into the system prompt before the loop starts.
- Allowed tool
- A tool that survived both the manifest gate and the connection gate. Only allowed tools serialize to the model.
- Progress event
- One callback fire from the engine to the caller carrying status, turn, tool_start, tool_end, or error.
- Mission result
- The return value of runSkillMission. Contains the final text, canvas spec ids, and a metrics block.