# Session Steps
{{< tier level="sovereign" note="Session steps launch agents and keep follow-up work inside the same inspectable workflow run." >}}

A `session` step supports three actions:

| Action | Use it to |
| --- | --- |
| `launch_task` | Launch an agent with a Valdr task |
| `launch_prompt` | Launch an agent from a prompt without requiring a task |
| `input` | Send a follow-up message to an existing session |

## Fields by action

Valdr supplies the run actor and a unique request ID. It can also supply `taskKey` from a task-scoped run. You author the fields that choose the work and the agent:

| Action | Required authored fields | Common optional fields |
| --- | --- | --- |
| `launch_task` | `taskKey` unless the run is task-scoped, `agentHandle`, `launcherConfigKey` | `role`, `additionalInstructions`, `capabilityKeys`, `worktree`, `provider`, `config`, `maxRuntimeSeconds` |
| `launch_prompt` | `prompt`, `launcherConfigKey` | `contextRef`, `role`, `worktree`, `provider`, `config`, `maxRuntimeSeconds` |
| `input` | `sessionUlid`, `prompt` | `expectAgentHandle`, `expectTaskKey` |

The selected launcher preset provides the provider configuration. If you also set `provider`, it must match that preset.

## Launch a task agent

```yaml
- key: launch_executor
  name: Launch Executor
  kind: session
  session:
    action: launch_task
    taskKey: "${workflow.inputs.taskKey}"
    agentHandle: "${workflow.inputs.executorHandle}"
    launcherConfigKey: "${workflow.inputs.launcherConfigKey}"
    role: executor
  waitsFor:
    kind: workflow_input
    expected: [executor_completed, executor_blocked]
  outputs:
    sessionUlid: "$.normalized.session.sessionUlid"
    outcome: "$.normalized.input.outcome"
```

Provide a task key, registered agent handle, and launcher preset. A task-scoped run can supply the task key automatically.

The declared `sessionUlid` lets later steps wait for the session, launch a reviewer against its work, or send a follow-up turn.

## Launch a prompt-only agent

```yaml
- key: summarize_risk
  name: Summarize Risk
  kind: session
  session:
    action: launch_prompt
    prompt: "Summarize the release risk for ${workflow.inputs.releaseName}."
    launcherConfigKey: coder-codex
    contextRef: "release:${workflow.inputs.releaseName}"
  waitsFor:
    kind: workflow_input
    expected: [summary_ready]
  gates:
    - key: summary
      kind: session_output
      source:
        event: final_agent_message
      extract: { format: json, path: $.outcome }
      outcomes: { summary_ready: summary_ready }
      onInvalid: { action: block }
  outputs:
    sessionUlid: "$.normalized.session.sessionUlid"
    outcome: "$.normalized.input.outcome"
```

`prompt` and `launcherConfigKey` are required. Use `contextRef` when the session should join other taskless work under the same local context. This example expects the agent's final message to contain JSON with `"outcome": "summary_ready"`.

## Interpret structured session output

A session-output `gate` maps a field from the final agent message into the step's expected outcome. Put the gate on a start-and-wait Session or Review step, as above, or on a Session `input` step. Only one session-output gate is supported per wait.

For a start-and-wait launch, omit `source.sessionUlid` or bind it to that step's own session. For a Session `input` gate, `source.sessionUlid` is required and must identify the session receiving the turn. `onInvalid.action` may be `block`, `keep_waiting`, or `default`; `default` also requires a `defaultOutcome` listed under `waitsFor.expected`. Use `payload.includeParsed` or `payload.includeSession` only when downstream work needs that data.

Launch steps should choose an explicit completion mode. Add `waitsFor` when the step owns completion, or set `detached: true` and join that exact turn with a later [`await_condition`](../await-condition/) step. For `launch_task` only, use `run: false` when the workflow should create the task session without dispatching work. Prompt-only launches must run when created.

| Mode | Configuration | Use it when |
| --- | --- | --- |
| Start and wait | Add `waitsFor` | This step should wait for and own the agent outcome |
| Continue after dispatch | Set `detached: true` | Other work can proceed before a later exact-turn join |
| Create session only | Set `run: false` on `launch_task` | A later step will send the first turn |

## Continue an existing session

```yaml
- key: request_revision
  name: Request Revision
  kind: session
  needs: [launch_executor]
  session:
    action: input
    sessionUlid: "${steps.launch_executor.outputs.sessionUlid}"
    expectAgentHandle: "${workflow.inputs.executorHandle}"
    expectTaskKey: "${workflow.inputs.taskKey}"
    prompt: "Apply the latest review feedback and report when the revision is ready."
  waitsFor:
    kind: workflow_input
    expected: [revision_ready]
```

Use `input` when the same agent should preserve its existing conversation and worktree context. `expectAgentHandle` and `expectTaskKey` make Valdr verify that the target session belongs to the intended agent and task before sending the message. Declare the wait, gate, and outputs on the `input` step so it owns that follow-up turn before the workflow advances. A separate `await_condition` around exactly one non-detached Session `input` step is rejected as redundant.

## Results to map

Launch actions expose the session identity under `$.normalized.session`. Map `sessionUlid` whenever another step needs to inspect, review, or continue that session. A start-and-wait launch also exposes the accepted outcome under `$.normalized.input.outcome`; a detached launch exposes `turnClientRequestId` for an exact-turn join.

## Builder controls

The session card changes its fields when you choose an action. It offers current agents, launcher presets, workflow inputs, and outputs from upstream steps, then lets you map the results needed later.

## Failure and recovery

If a launch cannot find the selected agent or launcher preset, the run blocks with a visible error. Restore or register the referenced agent or preset and use **Retry step** when Valdr offers it. To change the binding, update the definition and start a new run; a Session retry retains the run's frozen binding.

## Next step

Add an [Await-Condition Step](../await-condition/) when later work must wait for the session's result, or add a [Review Step](../review/) to evaluate its implementation.

