Skip to content

Examples

Daniel Baldwin edited this page Sep 17, 2026 · 5 revisions

Examples

Worked trigger recipes in the connectors schema, roughly in order of depth. All assume connectors named gh (github), slack-ops (slack), timer (cron), and hoff (web), steps named fixer/planner, and the defaults from config.example.yaml. Start with Quickstart if these are your first triggers.

Autonomous conflict fixer with lifecycle hooks

- on: gh.merge_conflict
  steps:
    - { id: fix, type: agent, name: fixer,
        prompt: "Resolve the conflict on {{.repo}}#{{.pr}} against {{.base}}, verify, push." }
  hooks:
    - { at: start, uses: slack-ops.post, options: { text: "conflict on {{.repo}}#{{.pr}} — on it" } }
    - { at: done,  uses: slack-ops.post, options: { text: "resolved {{.repo}}#{{.pr}}" } }
    - { at: fail,  uses: slack-ops.post, options: { text: "couldn't fix {{.repo}}#{{.pr}}: {{.error}}" } }

Release announcement (cross-boundary one-liner)

- on: gh.release
  options: { include_prereleases: false }
  steps:
    - { id: announce, uses: slack-ops.post,
        options: { channel: "#releases", text: "released {{.tag_name}}: {{.url}}" } }

One agent per PR comment burst (Grouping)

- on: gh.new_comment
  filter: { not_comment_author: ["ci-bot"] }
  group: { key: "{{.repo}}#{{.pr}}", window: 15s }
  steps:
    - id: handle
      type: agent
      name: fixer
      prompt: |
        Address the comments on {{.repo}}#{{.pr}}:
        {{range .group.events}}- {{.comment_body}}
        {{end}}
    - { id: ack, uses: gh.comment,
        options: { repo: "{{.repo}}", number: "{{.pr}}", body: "Addressed {{.group.count}} comment(s)." } }

Alarm webhook → reshape in js → act

js is an engine plugin — conductor init fetches it (Code-Steps). Swap in use: cli, command: [sh] to stay on the builtin.

- on: hooks.cloudwatch
  steps:
    - { id: shape, use: js,
        code: "return { sev: ctx.body.detail.severity || 'low', name: ctx.body.detail.alarmName }" }
    - { id: page, if: "{{.shape.sev}} == high", uses: slack-ops.post,
        options: { text: "ALARM {{.shape.name}} — {{.url}}" } }

Incident: research, then page or log

- on: oncall.incident
  filter: { event_types: [incident.triggered] }
  steps:
    - { id: dig, type: agent, name: planner, checkout: none,
        prompt: "Research {{.pagerduty.title}} ({{.pagerduty.url}}); return severity + summary.",
        output_schema: { type: object, required: [sev, summary],
                         properties: { sev: { enum: [low, high] }, summary: { type: string } } } }
    - { id: page, if: "{{.dig.sev}} == high", uses: slack-ops.post,
        options: { channel: "#outage", text: "{{.dig.summary}}" } }
    - { id: log, if: "{{.dig.sev}} != high", uses: slack-ops.post,
        options: { text: "incident (low): {{.dig.summary}}" } }

Review triage → draft → human ask → submit (Hand-offs)

- on: gh.review_requested
  filter: { not_branch: ["release/*"] }
  options: { reviewer: { logins: [your-login] } }
  steps:
    - { id: a, call: assess-and-post, with: { repo: "{{.repo}}", pr: "{{.pr}}" } }
    - { id: draft, if: "{{.a.decision}} == auto", type: agent, name: planner,
        checkout: none, prompt: "Draft the review for {{.repo}}#{{.pr}}." }
    - { id: review, if: "{{.a.decision}} == auto", uses: hoff.ask,
        options: { prompt: "Submit this review?", draft: "{{.draft.text}}", timeout: 2h } }
    - { id: submit, if: "{{.review.action}} == approve", uses: gh.submit_review,
        options: { repo: "{{.repo}}", pr: "{{.pr}}", event: COMMENT, body: "{{.review.text}}" } }

Act once per incident id — durable state across runs (Configuration)

stores:
  state: { type: boltdb }

triggers:
  - name: new-incidents
    on: [ oncall.incident ]
    steps:
      - { id: gate, uses: kv.get, options: { store: state, namespace: pagerduty, key: last-seen, default: "" } }
      - if: "{{ .pagerduty.id }} != {{ .gate.value }}"
        uses: slack-ops.post
        options: { channel: "#outages", text: "New incident {{ .pagerduty.id }}" }
      - { uses: kv.set, options: { store: state, namespace: pagerduty, key: last-seen, value: "{{ .pagerduty.id }}" } }

The value survives restarts (bbolt, fsync on commit). Read it inline elsewhere with {{ kv "state" "pagerduty" "last-seen" }}; SQL stores work the same way through sql.query/sql.exec with bound args:.

Nightly remote deploy over SSH (Hosts)

A type: command step with host: runs on the named SSH target and outputs {stdout, stderr, exit_code} — a non-zero exit fails the step, so the done/fail hooks are the report:

- on: timer.nightly-tidy
  steps:
    - { id: deploy, type: command, host: build-box, command: [make, -C, /srv/app, deploy] }
  hooks:
    - { at: done, uses: slack-ops.post, options: { text: "deploy ok: {{.deploy.stdout}}" } }
    - { at: fail, uses: slack-ops.post, options: { text: "deploy FAILED: {{.error}}" } }

(A run: sh, host: build-box code step is the same idea for inline scripts; its outputs are the script's stdout — a non-zero exit is a step error, not an exit_code output. For exit-code-as-data, use a type: command connector whose run verb returns it: uses: build-box.run.)

Fan one check across services (for_each)

for_each: runs a step once per element with {{.item}}/{{.index}} in scope (parallel: true fans out, bounded); at runtime the step's collected outputs land under {{.<id>.items}} / {{.<id>.count}}. Here against a type: command connector (build-box), whose run verb returns exit codes as data:

- on: timer.rate-check
  steps:
    - { id: services, run: js, code: "return { value: ['api', 'web', 'worker'] }" }
    - id: probe
      for_each: "{{.services.value}}"
      parallel: true
      uses: build-box.run
      options: { command: "systemctl is-active {{.item}}" }
    - { id: report, uses: slack-ops.post,
        options: { text: "checked {{ len .services.value }} services" } }

(Note: on a for_each verb step, validate today checks later references against the verb's own output schema, so read the source list — as above — rather than {{.probe.count}}; for_each over a code step has no schema and either read passes.)

One live agent per PR — session affinity (Steps)

Every event on a PR — comments, review changes, failing checks — reaches the same live agent as a follow-up, so it keeps the whole conversation. group: above batches one burst; session: extends the one-run-per-key idea across the PR's life. end_on: [ gh._closed ] evicts the session when the PR closes or merges (github reports both as the one internal close signal; it is eviction-only — you cannot trigger on: it):

x-templates:
  pr-agent: &pr-agent
    type: agent
    memory: true                             # inject the run's context memories
    session:
      key: "{{.repo}}#{{.pr}}"
      idle_ttl: 12h
      end_on: [ gh._closed ]

triggers:
  - on: [ gh.new_comment, gh.changes_requested, gh.failing_checks ]
    steps:
      - <<: *pr-agent
        prompt: "New activity ({{.kind}}) on {{.repo}}#{{.pr}} — continue where you left off."
    hooks:
      - { at: done, uses: memory.remember,
          options: { text: "handled {{.kind}} on {{.repo}}#{{.pr}}", scope: repo } }

(The memory.remember hook needs a top-level memory: section — Memory.)

Alert on conductor itself (Notifications)

- name: act-now
  on: [ conductor.escalate, conductor.failed, conductor.needs_input ]
  steps:
    - { uses: slack-ops.post, options: { text: "conductor {{.message}}" } }

Agent-driven: consult the catalog, run the fit (Workflows + Policy)

Requires a policy.agent_authored block — without one, plans are rejected:

- on: gh.issue_matched
  filter: { label_any: [auto] }
  steps:
    - id: triage
      type: agent
      name: planner
      checkout: none
      prompt: |
        Goal: handle "{{.title}}". Consult the workflow catalog; if a
        workflow fits, run it and say why. Reply with a ```plan block, e.g.
        - uses: workflow.run
          options: { name: assess-and-post, with: { repo: "{{.repo}}", pr: "{{.pr}}" }, reason: "..." }

Related: Quickstart · Workflows · Connectors · Code-Steps · Grouping · Hand-offs · Configuration

Gate the fix, approve the diff, team up on epics (Gates + Teams)

The agent-quality layer end to end: a gated fixer whose proposed diff is approved before it pushes (with a per-profile spend cap and outcome-tuned guidance), and a planner/workers/critic team for issues labeled epic.

checks:
  test: { type: command, command: ["make", "test"] }

x-templates:
  fixer: &fixer { type: agent, workspace: worktree, outcome_feedback: true }

# A team's roles are step REFERENCES, so the steps they name live in a
# workflow — here one nothing calls directly.
workflows:
  roles:
    steps:
      - { id: architect, type: agent, model: claude-opus-5, prompt: "…" }
      - { id: reviewer,  type: agent, prompt: "…" }

triggers:
  - on: gh.failing_checks
    steps:
      - { <<: *fixer, id: fix, prompt: "Fix the failing checks on {{.repo}}#{{.pr}}.",
          gate: { run: [ test ], max_revisions: 2 } }
      - { id: ok, uses: slack-ops.ask,
          options: { to: dm, user: U0123ABCD, prompt: "Gate passed. Apply?\n{{.fix.diff}}" } }
      - { id: push, if: "{{.ok.action}} == approve", type: command,
          command: ["git", "-C", "{{.fix.workdir}}", "push"] }

  - on: gh.issue_matched
    filter: { label_any: [epic] }
    steps:
      - id: feature
        prompt: "Implement the feature in {{.url}}."
        team: { planner: roles/architect, worker: roles/architect,
                critic: roles/reviewer, max_workers: 4 }

Watch it live with conductor watch, inspect or retry afterwards with conductor runs (Runs); merges/reverts feed back per agent (Outcomes, Cost-Accounting).

Clone this wiki locally