Skip to content

Workflows & UI Extensions

Workflows define multi-step agent pipelines — sequences of phases that transform a task from specification to deployed code.

Workflows are markdown files with a structured format:

---
name: Widget Pipeline
description: Build, test, and deploy widgets end-to-end.
status: active
---
## Steps
### research
- skill: researching-codebase
- timeout_minutes: 15
### plan
- skill: planning-implementation
- timeout_minutes: 20
- depends_on: research
### build
- skill: implementing-code
- timeout_minutes: 60
- depends_on: plan
### test
- skill: iterating-on-tests
- timeout_minutes: 30
- depends_on: build
- gate: true
- max_retries: 2
### review
- skill: packaging-for-review
- timeout_minutes: 15
- depends_on: test
FieldTypeDescription
namestringDisplay name.
descriptionstringWhat the workflow does.
statusstringdraft, active, or archived.
FieldTypeDescription
skillstringThe skill ID to execute for this step.
timeout_minutesnumberMaximum time before the watchdog kills the step.
depends_onstringStep ID that must complete first.
gatebooleanIf true, step must pass before successors run.
max_retriesnumberHow many times to retry on failure.

Workflows can be started through:

  1. MCP tool: session_delegate({ workflow_id: "widget-pipeline", project: "/path/to/project" })
  2. Dashboard: Click “Run” on a workflow in the Workflows view
  3. Automations: Triggered on a schedule or event

Plugins can extend the Recursive dashboard with new views, panels, navigation items, and custom renderers.

A view is a full-page Svelte component that gets its own route in the dashboard:

{
"ui": {
"views": [
{
"id": "widgets",
"label": "Widgets",
"icon": "box",
"route": "widgets",
"component": "./ui/WidgetsView.svelte",
"detailPanel": "./ui/WidgetDetailPanel.svelte"
}
]
}
}

The component receives props from the Recursive shell:

<script>
let { selectedId = $bindable(null) } = $props();
</script>
<div class="widgets-view">
<!-- Your view content -->
</div>

Add entries to the sidebar navigation:

{
"ui": {
"nav": [
{
"route": "widgets",
"label": "Widgets",
"icon": "box",
"shortcut": "Mod+6",
"order": 60,
"surface": "sidebar.nav.widgets"
}
]
}
}

The order field controls position in the sidebar. Built-in items use 10-40; plugins should use 50+.

Add entries to the command palette (Cmd+K):

{
"ui": {
"commands": [
{
"id": "nav-widgets",
"label": "Go to Widgets",
"icon": "box",
"section": "Navigation",
"type": "navigate",
"route": "widgets",
"shortcut": "Mod+6",
"keywords": ["widget", "components"]
}
]
}
}

If your view needs reactive state, declare a store module:

{
"ui": {
"store": "./ui/widgets-store.ts",
"sseEvents": ["widgets"]
}
}

The store module should export initialization and update functions that the dashboard shell calls.

Custom renderers display rich UI for specific tool call results in the chat feed:

{
"ui": {
"chatRenderers": [
{
"kind": "widget_create",
"component": "./ui/WidgetCreateCard.svelte"
}
]
}
}

When an agent calls widget_create and the result appears in the chat, your component renders instead of the default JSON view.

  • Use CSS classes from src/app.css for consistent styling
  • Follow the 38px compact header pattern for view headers
  • Add tooltips to all icon-only buttons
  • Use SVG icons from the Recursive icon set (not emoji)
  • Ensure viewport-aware positioning for popovers and dropdowns