TermAl

Feature Brief: Orchestration

Backlog source: docs/bugs.md

Related:

Status

Implemented for template authoring, project-scoped launches, runtime instances, pause/resume/stop controls, and on-completion transition delivery.

The template editor is a visual canvas. Session cards define agent, optional model override, input mode, auto-approval behavior, and instructions. Transitions connect card anchors and define trigger, result mode, and prompt template. Runtime instances create ordinary sessions and drive follow-up prompts when source sessions become prompt-ready.

Delegation waits intentionally reuse the same runtime idea without requiring a visible template. When a parent asks TermAl to wait for delegated child sessions, those children behave like completion sources and the parent behaves like the destination session. A wait with mode="all" mirrors a Consolidate node: the parent receives one synthesized prompt after all watched delegations are terminal. A wait with mode="any" mirrors ordinary queued transition delivery: the parent resumes when the first watched delegation is terminal.

Unlike orchestration templates, delegation waits are ad hoc and parent-owned. The parent session can yield after scheduling the wait; TermAl persists the wait, exposes it through state/SSE for UI visibility, and re-activates the parent by queueing the synthesized resume prompt when the fan-in condition is satisfied.

The problem

TermAl can already run many agent sessions in parallel, but the developer still does the routing: read a session’s reply, decide who should act next, copy the result into the next prompt, and keep switching tabs. That is coordination work, not product work.

The missing piece is orchestration: a reusable graph of sessions and transitions that lets TermAl move work forward automatically once a session finishes its turn and is ready for the next prompt.

Core model

An orchestration template is a graph:

There is no special orchestrator node in the data model. If the workflow needs a lead, coordinator, or reviewer session, that is just another regular session on the canvas.

Example graph:

Planner -> Builder -> Reviewer
   ^                    |
   |--------------------|

In that graph:

All three are ordinary sessions with ordinary prompts and ordinary histories.

Template definition

Templates are design-time only. They define reusable session slots, transition rules, and canvas layout. They do not contain runtime status, live session IDs, or pending work.

{
  "name": "Feature Delivery",
  "description": "Implement, review, and loop until ready.",
  "sessions": [
    {
      "id": "planner",
      "name": "Planner",
      "agent": "Claude",
      "model": "claude-sonnet-4-5",
      "instructions": "Plan the work and decide who should act next.",
      "autoApprove": false,
      "position": { "x": 640, "y": 120 }
    },
    {
      "id": "builder",
      "name": "Builder",
      "agent": "Codex",
      "model": "gpt-5",
      "instructions": "Implement the requested changes.",
      "autoApprove": true,
      "position": { "x": 220, "y": 420 }
    },
    {
      "id": "reviewer",
      "name": "Reviewer",
      "agent": "Claude",
      "instructions": "Review the changes and report issues.",
      "autoApprove": false,
      "position": { "x": 980, "y": 420 }
    }
  ],
  "transitions": [
    {
      "id": "planner-to-builder",
      "fromSessionId": "planner",
      "toSessionId": "builder",
      "trigger": "onCompletion",
      "resultMode": "lastResponse",
      "promptTemplate": "Use this plan and implement it:\n\n"
    },
    {
      "id": "builder-to-reviewer",
      "fromSessionId": "builder",
      "toSessionId": "reviewer",
      "trigger": "onCompletion",
      "resultMode": "summaryAndLastResponse",
      "promptTemplate": "Review this implementation:\n\n"
    },
    {
      "id": "reviewer-to-planner",
      "fromSessionId": "reviewer",
      "toSessionId": "planner",
      "trigger": "onCompletion",
      "resultMode": "lastResponse",
      "promptTemplate": "Reviewer finished. Decide the next action:\n\n"
    }
  ]
}

Templates persist to ~/.termal/orchestrators.json.

Transition semantics

Trigger

Phase 1 supports a single trigger:

enum TransitionTrigger {
    OnCompletion,
}

OnCompletion means:

Transitions do not fire while a session is still active, waiting on approval, or waiting on user input.

Cyclic graphs are supported. Templates can represent both one-shot flows and intentionally long-running loops such as planner-reviewer-fixer cycles.

Result processing

A transition defines how the completed session’s output is transformed before it is sent to the destination.

enum TransitionResultMode {
    None,
    LastResponse,
    Summary,
    SummaryAndLastResponse,
}

Prompt shaping

Each transition has a promptTemplate. The backend renders it using the processed result and then queues that rendered prompt into the destination session.

At minimum the backend should support:

Example:

Builder finished its turn.

Use this implementation result and decide whether to approve or request changes:


Lifecycle

1. Template design

The developer designs a reusable session graph on a canvas:

2. Instantiation

The developer instantiates a template for a selected project.

The backend:

  1. creates all sessions from the template
  2. records the template snapshot in the orchestration instance
  3. starts the runtime instance and drives transition delivery as sessions finish

3. Runtime loop

At runtime the loop is edge-driven:

Session A becomes prompt-ready after replying
  -> backend identifies matching outgoing transitions
  -> backend builds transition payload(s)
  -> backend renders prompt template(s)
  -> backend queues prompt(s) into destination session(s)

If a session has multiple outgoing transitions, multiple destination sessions may be prompted.

Data model

Template

struct OrchestratorTemplate {
    id: String,
    name: String,
    description: String,
    sessions: Vec<OrchestratorSessionTemplate>,
    transitions: Vec<OrchestratorTemplateTransition>,
    created_at: DateTime<Utc>,
    updated_at: DateTime<Utc>,
}

struct OrchestratorSessionTemplate {
    id: String,
    name: String,
    agent: Agent,
    model: Option<String>,
    instructions: String,
    auto_approve: bool,
    position: CanvasPoint,
}

struct OrchestratorTemplateTransition {
    id: String,
    from_session_id: String,
    to_session_id: String,
    trigger: TransitionTrigger,
    result_mode: TransitionResultMode,
    prompt_template: Option<String>,
}

struct CanvasPoint {
    x: f64,
    y: f64,
}

Constraints:

Runtime instance

This is the shape the runtime should move toward once orchestration execution is implemented:

struct OrchestratorInstance {
    id: String,
    template_id: String,
    template_snapshot: OrchestratorTemplate,
    status: OrchestratorStatus,
    session_instances: Vec<OrchestratorSessionInstance>,
    pending_transitions: Vec<PendingTransition>,
    created_at: DateTime<Utc>,
    completed_at: Option<DateTime<Utc>>,
}

struct OrchestratorSessionInstance {
    template_session_id: String,
    session_id: String,
    last_completion_revision: Option<u64>,
    last_delivered_completion_revision: Option<u64>,
}

struct PendingTransition {
    id: String,
    transition_id: String,
    source_session_id: String,
    destination_session_id: String,
    completion_revision: u64,
    rendered_prompt: String,
    created_at: DateTime<Utc>,
}

enum OrchestratorStatus {
    Running,
    Paused,
    Completed,
    Stopped,
}

Backend behavior

Status hook

The runtime hook is not “generic commit happened.” It is the explicit session lifecycle edge:

active -> idle after reply

That edge is what creates transition work.

Delivery model

When a source session completes a turn:

  1. find all transitions whose from_session_id matches the completed session
  2. build the transition result according to result_mode
  3. render the destination prompt from prompt_template
  4. persist a PendingTransition
  5. queue the prompt into the destination session
  6. mark that completion revision as delivered

Persisting undelivered transitions is important for restart safety.

Restart behavior

Running orchestrations survive restart.

On boot:

This keeps transition delivery deterministic and avoids duplicate prompts after restart.

API

Template management

Phase 1 template management API:

GET    /api/orchestrators/templates
POST   /api/orchestrators/templates
GET    /api/orchestrators/templates/{id}
PUT    /api/orchestrators/templates/{id}
DELETE /api/orchestrators/templates/{id}

The path can stay /api/orchestrators/... even though the template model no longer contains a special orchestrator node.

Runtime orchestration

Later phases can add:

GET    /api/orchestrators
POST   /api/orchestrators
GET    /api/orchestrators/{id}
POST   /api/orchestrators/{id}/pause
POST   /api/orchestrators/{id}/resume
POST   /api/orchestrators/{id}/stop

UI

Template library

The control panel and settings should let the developer:

Canvas editor

The canvas is a graph editor:

There is no visually special orchestrator card by default. If the developer wants a central coordinator session, they create one explicitly and place it where they want on the canvas.

Design decisions

D1: No special orchestrator node

Resolved. The orchestration graph contains only regular sessions. A “main” or “planner” role is just another session in sessions.

D2: Transition trigger meaning

Resolved. OnCompletion means the source agent replied and the session became prompt-ready again.

D3: Transition ownership

Resolved. A transition belongs to the edge, not the node. It defines how a completed result is processed and how the destination prompt is built.

D4: Session creation

Resolved. Transitions do not create sessions. They route work only between sessions that already exist in the orchestration instance.

D5: Summary generation

Resolved. Summary-based result modes use a fresh summarizer session if and when those modes are implemented. The summary is not generated by asking the source session to summarize itself.

Implementation phases

Phase 1: Template design and management

Phase 2: Runtime graph instantiation

Phase 3: Transition execution

Phase 4: Summary modes and polish

Testing plan

Backend

Frontend

Integration

Acceptance criteria