Skip to content

Triggers ​

Implemented

The triggers block on a pipeline declares when it should run automatically — on file changes, on a cron schedule, or by explicit invocation only.

yaml
pipeline:
  name: ingest-inbox
  triggers:
    - type: file
      watch: inbox/*.txt
    - type: cron
      schedule: "0 9 * * 1-5"
      timezone: America/New_York
  steps:
    - name: process
      action: ai
      prompt: "Process the latest item"
  output: "{{ process.text }}"

triggers is optional. A pipeline without it behaves as if it had triggers: [manual] — it only runs when invoked explicitly.

When triggers actually fire ​

Triggers are armed only when the pipeline runs under jigspec app. They are ignored by jigspec run.

When the app starts, it does a one-shot scan of the working directory for *.pipe.yaml files (excluding node_modules/ and .jigspec/), parses each, and arms every file and cron trigger it finds. Edits to a pipeline's triggers: block require restarting the app to re-arm — there is no live YAML watcher in v1.

One-shot scan

Adding, removing, or editing triggers in a YAML file does not take effect until you restart jigspec app. The pipeline body itself (steps, prompts, etc.) is re-read on every fire, so step changes do not require a restart.

Manual, conversational, and ambient triggers do nothing at arm time — they exist as schema-level declarations only.

Trigger types ​

TypeStatusFires when
manualImplementedRun button or CLI invocation only — never automatic
fileImplementedA path matching watch is added, changed, or deleted
cronImplementedThe cron expression matches the current time
conversationalSchema-only(no runtime behavior in v1)
ambientSchema-only(no runtime behavior in v1)

file ​

Implemented

Watches a glob pattern relative to the project root. When a matching path is added, modified, or deleted, the pipeline fires with a debounce window collapsing rapid bursts into a single run.

yaml
triggers:
  - type: file
    watch: inbox/*.txt
    debounce_ms: 500
    concurrency: skip
FieldTypeDefaultDescription
watchstring (glob)requiredGlob pattern, relative to project root
debounce_msint > 01000Trailing-edge debounce in milliseconds
concurrencyskip | queue | allowskipBehavior when a fire arrives mid-run — see Concurrency

Path safety ​

watch patterns are confined to the project workspace. The validator rejects:

  • Absolute paths (anything starting with /)
  • Any segment equal to ..
yaml
# rejected at validate time
- type: file
  watch: /etc/hosts            # absolute — error
- type: file
  watch: ../../outside/*.txt   # parent traversal — error

Symlinks that point outside the workspace are silently ignored at watch time as a second layer of defense.

Glob examples ​

yaml
- type: file
  watch: inbox/*.txt           # files directly under inbox/
- type: file
  watch: src/**/*.ts           # any TS file under src/, recursively
- type: file
  watch: data/2026-*/report.md # interpolated date directory

Dotfiles are not matched by default (consistent with shell glob semantics). The watcher excludes node_modules/ and .jigspec/ from descent.

File path is not yet a step input

File events fire the pipeline with no input. The pipeline runs against its declared input block — if you need the changed path, plumb it in via a code step that reads the directory.

cron ​

Implemented

Schedules the pipeline against a cron expression. Backed by croner, so all of croner's syntax is available.

yaml
triggers:
  - type: cron
    schedule: "0 9 * * 1-5"
    timezone: America/New_York
    concurrency: skip
FieldTypeDefaultDescription
schedulestringrequired5-field cron, 6-field cron, or named preset
timezonestring (IANA)UTCIANA timezone, e.g. America/New_York
concurrencyskip | queue | allowskipBehavior when a tick arrives mid-run

Schedule syntax ​

yaml
- schedule: "0 9 * * 1-5"      # 5-field: 09:00 Mon–Fri
- schedule: "* * * * * *"      # 6-field: every second
- schedule: "@hourly"          # preset: top of every hour
- schedule: "@daily"           # preset: 00:00 every day

Named presets supported: @hourly, @daily, @weekly, @monthly, @yearly. The 6-field form gives second-level precision.

Timezone handling ​

Both invalid cron expressions and unknown IANA timezone strings are caught at arm time. The trigger logs a warning, stays disarmed, and the rest of the pipeline (and other triggers) keep running:

[TriggerManager] WARN: cron trigger skipped — invalid schedule/timezone
for "my-pipeline" (schedule: every-fifteen-minutes, timezone: UTC): ...

The Triggers panel surfaces a nextRunAt ISO timestamp computed at arm time and refreshed after every fire.

manual ​

Implemented

The default. A manual trigger arms no watcher and registers no schedule — the pipeline only runs when explicitly invoked (Run button, jigspec run, or programmatic call).

Two equivalent forms:

yaml
# explicit object
triggers:
  - type: manual

# bare-string shorthand
triggers: [manual]

The bare string is expanded to { type: "manual" } by the schema preprocessor before validation.

A pipeline that omits triggers: entirely behaves identically to one with [manual].

conversational / ambient ​

Schema-only

Both types parse and validate, but have no runtime behavior in v1. They are placeholders for future trigger sources (an MCP-driven chat trigger and a condition-watching ambient trigger).

yaml
triggers:
  - type: conversational
    description: "When the user asks for a daily summary"

  - type: ambient
    condition: "GitHub PR opened on jigspec/jigspec"

When the scanner encounters either, it emits a warning at startup and moves on. No watcher, no schedule, no fire.

Concurrency ​

When a trigger fires while a previous run from the same trigger is still in flight, concurrency decides what happens:

ModeBehavior
skip (default)The new fire is dropped. Use this for idempotent jobs where running once is enough.
queueAt most one pending fire is buffered. When the in-flight run completes, the buffered fire kicks off. Additional fires while one is already pending are coalesced into that single pending fire — no stacking.
allowEvery fire starts a new run immediately, in parallel. No state guard.

Each trigger gets its own concurrency state — a file and a cron trigger on the same pipeline do not block each other.

yaml
triggers:
  - type: cron
    schedule: "*/5 * * * *"
    concurrency: queue       # never lose a tick
  - type: file
    watch: inbox/*.json
    concurrency: allow       # process every drop in parallel

Triggers Panel ​

When running under jigspec app, the right-side Triggers panel lists every armed trigger across every pipeline in the workspace. For each entry it shows:

  • Pipeline name + trigger type
  • Pattern (glob for file, schedule for cron)
  • Concurrency mode
  • lastFiredAt and last runId (clickable — opens the run viewer)
  • nextRunAt for cron triggers

For full UI behavior see Using the App — Triggers Panel.

Limitations (v1) ​

  • One-shot scan. Triggers are armed once at jigspec app startup. Adding or editing a triggers: block requires restarting the app. The pipeline body (steps, prompts, code) is re-read on every fire and does not need a restart.
  • App-only. jigspec run ignores triggers entirely — it executes the requested pipeline once and exits. Triggers only fire under jigspec app.
  • Path safety enforced at validate time. Absolute paths and .. segments in watch patterns are rejected by the schema, not by the watcher — jigspec validate catches them before the app ever runs.
  • No file path injected as input. File triggers fire the pipeline against its declared input block; the changed path is not yet exposed as a built-in reference.
  • Conversational and ambient are schema stubs. They parse but do not arm.