> ## Documentation Index
> Fetch the complete documentation index at: https://www.shipfox.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Listening Jobs (Coming Soon)

> Listening jobs wait on events and react repeatedly inside a single workflow run — an agent handling each batch of PR comments until the PR merges. Coming soon.

<Warning>
  **Coming soon.** Listening jobs are under active development. The `listening`
  schema below is accepted and validated today — you can author it and it will
  parse — but Shipfox does **not execute listening jobs yet**: a listening job
  currently acts as a scheduling boundary, and jobs that depend on it stay
  blocked. This page documents the authored shape and what it will do, so you can
  follow along; details may still evolve before release.
</Warning>

Today a workflow is a one-shot DAG: a trigger creates a run, every job runs exactly
once in `needs` order, and the run ends when all jobs end. A **listening job** — a
job driven by events — changes that: it *listens* for events and runs **one
execution per event batch**, inside the same run, until a resolution condition is
met. `needs` still gates when the job activates; from then on, events gate each
execution.

## What you'll build with it

Two driving use cases shape the design:

**Agentic PR-review loop.** Job A opens a pull request with an agent. Job B
(`needs: [A]`) listens for new review comments and runs an agent on each batch —
answering reviewers, applying requested changes — until the PR is closed or merged.

```yaml theme={null}
# Illustrative — listening jobs and the PR events they need are coming soon.
jobs:
  open_pr:
    steps:
      - model: claude-opus-4-8
        prompt: Implement the fix described in the run context and open a PR.
      - run: gh pr create --fill
  address_reviews:
    needs: [open_pr]
    listening:
      on:
        - source: github_acme
          event: pull_request_review_comment.created
      until:
        - source: github_acme
          event: pull_request.closed
      batch:
        debounce: "30s"
    steps:
      - model: claude-opus-4-8
        prompt: >
          New review comments arrived on the PR this run opened. Address each one:
          reply, or apply the requested change and push.
```

**Issue-watcher loop.** A job listens for new comments on an issue and keeps the
issue or its spec document up to date — until the issue is closed.

```yaml theme={null}
# Illustrative — same caveat.
jobs:
  watch_issue:
    listening:
      on:
        - source: github_acme
          event: issue_comment.created
      until:
        - source: github_acme
          event: issues.closed
    steps:
      - model: claude-opus-4-8
        prompt: Fold the new issue comments into the spec and update the issue body.
```

<Note>
  These sketches assume PR-comment and issue events from the GitHub integration,
  which today delivers `push` — richer event ingestion ships alongside listening
  jobs.
</Note>

## The model

A run is a DAG of jobs; a job has one or more **executions**; an execution runs the
steps. A one-shot job has exactly one execution. A listening job gets **one
execution per event batch**:

* **`needs` gates the job** — the listener activates only when its dependencies
  succeed.
* **`on` events gate each execution** — every matching event (batch) starts one.
* Executions **serialize** per job; events arriving during an execution coalesce
  into the next batch (tune with `batch.debounce`, `batch.max_size`,
  `batch.max_wait`).
* The job **resolves** when an `until` event arrives, the `timeout` elapses, or
  `max_executions` is reached.

## The `listening` shape

```yaml theme={null}
jobs:
  address_reviews:
    needs: [open_pr]
    listening:
      on:                      # required — events that start an execution
        - source: github_acme
          event: pull_request_review_comment.created
      until:                   # optional — events that resolve the job
        - source: github_acme
          event: pull_request.closed
      timeout: "24h"           # optional — max wall-clock listening time
      max_executions: 50       # optional — cap on executions
      batch:                   # optional — event coalescing
        debounce: "30s"
        max_size: 10
        max_wait: "5m"
      on_resolve: finish       # 'finish' (default) or 'cancel'
    steps:
      - run: ./react.sh
```

<ParamField path="listening.on" type="trigger[]" required>
  The events the job listens for. Each entry uses the same `{source, event,
      filter?, with?}` shape as a workflow [trigger](/concepts/triggers) — one
  event-selector shape, used at run level (`triggers:`) and at job level
  (`on`/`until`).
</ParamField>

<ParamField path="listening.until" type="trigger[]">
  Events that resolve the listening job.
</ParamField>

<ParamField path="listening.timeout" type="string">
  Maximum wall-clock time to keep listening before the job resolves.
</ParamField>

<ParamField path="listening.max_executions" type="number">
  Caps how many executions the job runs before it resolves.
</ParamField>

<ParamField path="listening.batch" type="object">
  Coalesces bursts of events: `debounce` (quiet window before an execution
  starts), `max_size` (events per batch), and `max_wait` (longest a batch waits
  before flushing).
</ParamField>

<ParamField path="listening.on_resolve" type="'finish' | 'cancel'">
  What happens when `until` fires: `finish` completes the job normally (default),
  `cancel` cancels it.
</ParamField>

## Related pages

<CardGroup cols={2}>
  <Card title="Triggers" icon="bolt" href="/concepts/triggers">
    The trigger shape reused by `on` and `until`.
  </Card>

  <Card title="Integrations" icon="plug">
    The sources that emit the events a listening job reacts to.
  </Card>
</CardGroup>
