ExecWarden Docs

Automation

Save repeatable agent and code work as durable workflows.

A workflow is a durable definition of what should happen. Manual, scheduled, and supported event-triggered runs execute today. Runs record code steps, waits, outputs, and access rules.

When to Make Work Repeatable

Start with a manual agent run, package action, or webhook setup. Save a workflow only after the useful task has a stable trigger, scoped capabilities, an approval point for risky writes, and evidence that someone can inspect after each run.

Good fitA repo review, incident check, report, branch maintenance task, or provider sync that should run again with the same authority boundary.
Not firstDo not start here when you are still discovering which system the agent should touch, which write is safe, or what a reviewer needs to see.
Event-driven workUse Webhook Events when an external system should start the work. Use this page for the saved run that consumes the event.
Security reviewUse Security Model to keep the first workflow posture small: one scoped read, one supervised write, one Activity record, and cleanup you can verify.

What a Workflow Stores

WorkflowThe saved repeatable job: name, definition, enabled state, outputs, and capability policy.
TriggerWhat starts it. Manual, schedule, GitHub webhook/GitHub App events, custom webhook receiver events, and declared package events work today; broader product-event publishers and richer trigger UI are follow-up work.
RunOne execution: status, trigger input snapshot, definition/policy snapshot, logs, approvals, artifacts, and linked session if an agent was started.
Capability postureThe workflow-level statement of which capabilities are Off, Ask first, or Allowed.

Create One From a Prompt

Open the Workflows page, describe the repeatable job in the authoring panel, and start the workflow-building agent. ExecWarden opens that agent with workflow admin capability and asks it to call workflow:validate before workflow:create or workflow:update. If the request is ready, the agent saves the workflow and links back to it; if credentials, package setup, or policy details are missing, it should draft the shape and explain what to review before the workflow is runnable. Open the saved workflow from the returned link or the Workflows page to inspect its definition, run it, and review the run evidence.

Advanced fallback: start a generic agent session, open Configure capabilities, enable Workflow authoring with Admin access, then ask for the workflow you want. That manual route uses the same workflow management functions, but the Workflows-page authoring panel is the primary entry point.

Create a manual workflow named Echo input. It should accept a JSON object, return it with an ok flag, and be enabled so I can run it from the Workflows page. Create a manual workflow that starts an agent to inspect my repo status, summarize open risks, and ask before making any GitHub write. Create a scheduled workflow named Daily readiness check. It should run every weekday at 09:00 Europe/Brussels time, inspect a JSON input trigger object, and return a concise ok/status result. Create a graph workflow with a JS step that checks a condition from input. If it is not ready, return a durable wait for 60 seconds; otherwise finish with a ready result. Create a workflow that subscribes to the existing webhook:incident-router receiver's incident.created event and starts an investigation agent with the event payload.

For webhooks, set up the receiver first from the Webhooks page or a webhook-capable agent, then create a workflow that subscribes to the receiver's emitted events. Webhook handlers publish workflow events; workflows do not subscribe to raw HTTP requests. See Webhook Events for the receiver and handler shape.

Current Executable Slice

Agents with workflow capability can create, run, and schedule workflows today. Schedules use five-field minute cron with optional timezone and fire through the gateway scheduler with one run per scheduled slot. Workflows can also subscribe to supported events by source/type, including events emitted by GitHub-preset or custom webhook receivers. A workflow can be a single bounded awaited wasm-js entrypoint, or a persisted graph of committed wasm-js steps. Each run freezes the workflow definition and policy it started with. Graph steps are the durable path: each step commits before the next one starts, and JS steps can pause by returning a durable wait directive instead of sleeping inside JavaScript. Long-running agent work follows the same model: start a hosted agent run, store its run id in wait output, then poll it after the workflow resumes.

Durability Guarantees and Limits

Restart and deploy recoveryWorkflow runs, step rows, wait rows, trigger input, and the run's definition/policy snapshot are stored in the gateway database. If the same database is available after restart or deploy, startup recovery and the recovery sweep resume running and waiting graph runs.
Committed step boundaryA succeeded graph step is not re-executed on recovery. A step that was running when the process stopped can run again from its saved input, so any external side effect inside a step must be idempotent.
Durable waitsGraph JS can return delayMs or until waits. Waits are persisted as workflow timer records and are resumed by startup recovery or the recovery sweep. Waits have a maximum delay and are resumed on a sweep cadence; they are not a precise wakeup SLA.
Manual runsManual invocation creates a run immediately for an enabled workflow, stores the provided input, and executes the definition/policy snapshot captured when the run row is created.
Scheduled runsSchedules use five-field cron plus optional timezone. The scheduler runs on startup and on its configured sweep interval, uses a bounded lookback window, and uses one idempotency key per trigger and scheduled slot. It starts at most the latest due slot per sweep, so long downtime can miss older slots instead of backfilling every missed minute.
Event idempotencyPublished events are de-duped by workspace, source, and idempotency key. Matching enabled event triggers queue one delivery per event/trigger pair, and the resulting run uses an event/trigger idempotency key so delivery retries do not create duplicate runs.
Event delivery retriesThe delivery sweep runs on startup and on its configured sweep interval, using a claim window, default sweep limit, and retry backoff policy. After the configured retry attempts are exhausted, the delivery stays failed until manually retried.
Trigger timingEvent trigger matching happens when the event is published. A trigger created later does not receive old events. If a workflow or trigger is disabled or deleted before a queued delivery starts, that delivery is skipped.
Hosted-agent pollingWorkflow durability comes from the start, wait, and poll pattern. Start the hosted agent run with a stable idempotency key, persist its run id in wait output, and poll it after resume. The workflow does not keep a JS process alive while the agent works.
Retention and auditWorkflow product rows persist according to the deployment database, backup, deletion, and log-retention policy. There is no fixed public retention SLA or immutable compliance archive implied by the workflow runtime.
Not guaranteedThis is not a Temporal-grade orchestration engine, exactly-once external side-effect system, arbitrary-duration timer service, broad SaaS workflow builder, or guarantee that user-authored JS cannot perform duplicate work after a crash.

Programming Model

Graph wasm-js stepRuns bounded JavaScript in the Wasm/QuickJS sandbox. The entrypoint may use short async/await work, reads ctx.input, can emit observations with ctx.output.append(value), and returns the step value. New graph steps should accept (ctx, workflow, gateway) so they can use helpers such as workflow.route(...), workflow.next(...), workflow.sleep(...), and scoped gateway.call(...).
JS stepRuns a bounded JS/WASM step with ctx.input, ctx.output.append(value), and explicit graph control through workflow.route, workflow.next, or durable waits.
Recovery boundaryGraph workflows recover from committed step boundaries and durable wait directives.

Durable Waits

Do not keep a JavaScript loop alive while waiting for a remote condition. Run a short check, return a wait directive, and let the workflow engine persist the pause. If next is omitted, the workflow resumes the same step. If the returned value includes output, that output becomes the next input when the wait resumes.

export async function main(ctx, workflow, gateway) { const ready = await remoteCondition(ctx.input); if (ready) { return workflow.next("finish", { ready: true }); } return workflow.sleep("60s", { output: { attempt: (ctx.input.attempt ?? 0) + 1 } }); }

The raw object form still works and is what the helpers compile to:

export async function main(ctx) { const ready = await remoteCondition(ctx.input); if (ready) { return { output: { ready: true }, next: "finish" }; } return { wait: { delayMs: 60_000 }, output: { attempt: (ctx.input.attempt ?? 0) + 1 } }; }
workflow.sleep(delay, { output?, route?, next? })Pause durably. Delay can be milliseconds or a string such as 30s, 5m, 2h, or 1d.
workflow.sleepUntil(iso, { output?, route?, next? })Pause until an ISO timestamp.
workflow.next(stepId, output)Send the graph to a named step id, or pass null to stop after the current step.
{ wait: { delayMs } }Pause durably for a bounded delay, then resume the workflow.
{ wait: { until } }Pause until an ISO timestamp.
DiagnosticsWaiting runs surface workflow_wait_pending or workflow_wait_due in workflow run diagnostics.

Durable Agent Runs

Workflows should treat hosted agents as long-running jobs. Start the agent once with a stable idempotency key, return a durable wait, then poll the run after resume. This keeps the workflow recoverable and lets the next step branch from the agent result.

export async function main(ctx, workflow, gateway) { const requestId = String(ctx.input.requestId); if (!ctx.input.agentRunId) { const started = await gateway.call("gateway:agent_runs.start", { idempotencyKey: `workflow-agent-${requestId}`, title: "Review report", prompt: `Review ${ctx.input.reportName} and return launch risks.` }); if (!started.ok) throw new Error(started.error); return workflow.sleep("60s", { output: { ...ctx.input, agentRunId: started.data.run.id, attempt: 1 } }); } const fetched = await gateway.call("gateway:agent_runs.get", { runId: ctx.input.agentRunId }); if (!fetched.ok) throw new Error(fetched.error); const run = fetched.data.run; if (run.status === "succeeded") { return workflow.next("decide", { agentRunId: run.id, summary: run.result?.message }); } if (run.status === "failed" || run.status === "cancelled") { return workflow.next("handle_failure", { agentRunId: run.id, status: run.status, error: run.error }); } return workflow.sleep("60s", { output: { ...ctx.input, attempt: (ctx.input.attempt ?? 0) + 1 } }); }
CapabilityThe start/poll step needs agentRuns run access; a separate poll-only step can use agentRuns read access.
IdempotencyDerive the key from trigger input, event id, or another durable request id. Do not use randomness or current time for the same intended agent run.
ContextAgent runs started by one workflow step can be polled by a later step in the same workflow run even though each step uses a fresh short-lived key.

Human Input Pauses

Workflows can also pause when a package-backed action asks for human input. Approval-shaped pauses appear in the existing approvals surfaces and can satisfy approval policy for the selected followup action. Generic requests use the broader user-input request API for values, choices, or explicit resume signals. In both cases, the run keeps the request, answer, and followup context with the workflow history instead of requiring the agent to invent its own out-of-band checkpoint.

Declared Branches

Branching should be visible in the saved graph, not hidden inside JavaScript. Add routes to a step, then return a route label from the JS result. When a step declares routes, it must return one of those route labels; otherwise the run fails instead of silently taking an invisible default path. The Workflows page can then render the real branch structure before a run starts. Prefer workflow.route(label, output) for new graph steps; the raw { route, output } shape remains supported.

{ "id": "review", "kind": "wasm-js", "source": "export function main(ctx, workflow, gateway) { return workflow.route(ctx.input.ok ? 'approved' : 'needs_changes', ctx.input); }", "entrypoint": "main", "routes": [ { "label": "approved", "nextStepId": "publish" }, { "label": "needs_changes", "nextStepId": "revise" } ] }

Example Workflow Shapes

Keep PRs current

A manual workflow run starts an agent that rebases branches, runs tests, and shows the branch update before pushing within the branch policy.

Fix red builds

A manual or GitHub/custom webhook event-triggered workflow starts an investigation with repo and CI context.

Poll remote readiness

A JS step checks an external condition, returns a durable wait when it is not ready, then resumes later without keeping a process alive.

Review comments loop

A manually run workflow reads review comments, proposes edits, and asks before writing.

Nightly report

A scheduled workflow produces a report artifact without write authority, with one run per scheduled slot.

Need an external event to start it?Use Webhook Events to verify and normalize the provider callback before the workflow runs.
Need a package action in the run?Use Capability Packages and grant only the package actions this workflow needs.
Need to review writes?Use Approvals & Policy for exact-call review before risky workflow actions execute.