Trigger Recipes from an Agent (HTTP)
The two HTTP endpoints on Omniscio's localhost CLI Control server that let an agent fire a flagged recipe without a human approval click: discovering triggerable recipes, firing one with parameters, the error codes and cooldown rules, and how the per-recipe Agent triggering flag and cost cap work.
What it is
Omniscio exposes two HTTP endpoints on its localhost CLI Control server (127.0.0.1:19519) that let an AI agent — Claude Code, the in-app chat panel, or any tool holding the Omniscio bearer token — fire a multi-step recipe workflow without a human-approval click in the inbox. The user controls which recipes are agent-fireable through a per-recipe Agent triggering card in the Recipe Editor: open a recipe, scroll to the "Agent triggering" section, flip the toggle on, optionally set a cost cap in dollars, save. From that moment forward, any agent presenting the bearer token can run that recipe via POST /recipes/run.
This is intentionally different from the rest of CLI Control's mutation endpoints (cron jobs, automation rules, settings patches, session lifecycle), which queue a row in the inbox and refuse to fire until the user approves each instance. Recipes use per-recipe pre-approval instead — the user grants permission once when they flag the recipe, and agents act inside those permissions until the user toggles it off.
The two endpoints are:
POST /recipes/run— fire a flagged recipe. Returns arunIdand theomniscio://orchestration/run/<runId>deep-link the user can open to watch progress. A 30-second cooldown per(recipeId, projectId)tuple stops accidental retry-loop double-fires; pass a stableIdempotency-Keyheader to make a retry safe beyond that window (returns the original run, no second paid spawn).GET /recipes/triggerable— list the recipes the user has flagged, with parameter schemas, scope, home project id, and cost cap. Read-only and counts toward zero rate-limit budget.
Where to find it
How to use it
1. Discover what's available
TOKEN=$(<~/.amc/cli-token)
curl -s -H "Authorization: Bearer $TOKEN" \
http://127.0.0.1:19519/recipes/triggerable
Response:
{
"ok": true,
"recipes": [
{
"id": "8f3c9c2e-...",
"name": "Audit Pipeline (5-prompt test)",
"description": "Run a fixed audit pipeline against a project.",
"scope": "project",
"homeProjectId": "proj_abc123",
"parameters": [
{
"name": "audit_prompts",
"label": "Audit prompts (newline-separated)",
"type": "text",
"required": true,
"default": "..."
}
],
"costCapUsd": 5
}
]
}
If the array is empty, no recipes are flagged. Tell the user to open Omniscio → Recipes → pick a recipe → "Agent triggering" card → enable.
2. Fire a recipe
curl -s -X POST http://127.0.0.1:19519/recipes/run \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"recipeName": "Audit Pipeline (5-prompt test)",
"parameters": { "audit_prompts": "step1\nstep2\nstep3" }
}'
Response (HTTP 200):
{
"ok": true,
"data": {
"runId": "run_xyz789",
"dashboardUrl": "omniscio://orchestration/run/run_xyz789"
}
}
Body fields:
- At least one of
recipeIdorrecipeNameis required. PreferrecipeId(canonical, survives renames).recipeNamematches case- and whitespace-insensitively across global and every project's.claude/recipes/directory. projectName(optional) — overrides the recipe'shomeProjectId. Use it when the user says "run X on project Y". Resolution is case-insensitive on the project name. If the recipe has nohomeProjectIdand the body omitsprojectName, the call returns 400.parameters(optional object) — keys must match the recipe's declared parameternames. Required parameters without adefaultMUST be present. Extra keys are stripped silently.clientRequestId(optional) — an idempotency key. Send the SAME value (as theX-Client-Request-Idheader or this body field; the header wins if both are present) when you retry the same logical trigger, and Omniscio returns the original run instead of spawning a second paid run. Unlike the 30s cooldown, this is safe across the full retry horizon (a 10-minute window) — use it whenever a network layer or load balancer might re-send a timed-outPOST. Without it, retries are guarded only by the best-effort cooldown below.
3. Poll status (optional)
The trigger is fire-and-forget — the HTTP call returns as soon as the engine accepts the dispatch. To follow the run, open the dashboardUrl (an omniscio:// deep link Omniscio handles natively) or hit GET /status for a coarse summary.
Pre-approval — how the user opts in
Recipes default to agent-fireable = OFF. The toggle lives on the Recipe Editor's Agent triggering card (it sits below the parameters card and above the supervisors section). Flipping it on writes agentTriggerable: { enabled: true, costCapUsd?: <number> } into the recipe's JSON and saves it. The flag is per-recipe — turning it on for one recipe does not auto-flip others.
The cost cap field is optional. If the recipe already declares a limits.costCapUsd, that wins. If it doesn't, the agent-trigger cap is copied into limits.costCapUsd for this run only — every agent-triggered run inherits the cap, the engine's existing cost-recovery logic kills the run if the cap is hit. Setting agentTriggerable.costCapUsd to 0 means "fire freely without a per-run cap"; setting it to a positive number means "stop the run at this dollar amount".
The toggle stays put until the user flips it off. There is no quota on triggers per recipe (yet) — the only throttle is the 30-second per-tuple cooldown.
The agent-trigger flag is orthogonal to the authoring approval gate. A recipe saved via the CLI authoring path (external AI → POST /recipe/save) lands as approvalStatus: 'pending' and the agent-trigger endpoint still refuses to dispatch it — even with agentTriggerable.enabled: true — until the user approves it in the Omniscio inbox. Two separate decisions: (1) "is this recipe authorized to exist?" (the authoring approval gate, blocking until inbox approval) and (2) "is this recipe authorized to be agent-fired without a per-run inbox click?" (the agentTriggerable toggle). Both must pass for POST /recipes/run to succeed. See recipes-cli-authoring.md for the authoring gate.
How it behaves
Error codes
| Status | Meaning | Recover |
|---|---|---|
| 400 | Two cases. (a) Validation error — body missing both recipeId and recipeName, missing required parameter, or projectName could not be resolved. (b) error: "Recipe is pending approval..." or "Recipe was rejected..." — the matched recipe has approvalStatus: 'pending' or 'rejected' and is awaiting authoring approval in the Omniscio inbox. The agentTriggerable.enabled: true flag does NOT bypass this gate. |
(a) Read the error field, fix the body, retry. (b) Tell the user to approve the recipe in the Omniscio inbox (or revise + re-approve a rejected recipe). Do NOT auto-retry — authoring approval is a human-in-the-loop step. See recipes-cli-authoring.md. |
| 401 | error: "unauthorized" — Bearer token missing or invalid. The dispatcher authenticates every non-GET method before the handler runs, so this is identical to every other Omniscio CLI mutation's auth-failure response. As of L05-F10 the whole CLI surface is uniform — a missing/invalid token is always 401, never 403. |
Tell the user to regenerate the token in Omniscio Settings → CLI Control. The auto-delivered file at ~/.amc/cli-token is the source of truth. |
| 403 | error: "Recipe '...' is not authorized for agent triggering" — the matched recipe does NOT have agentTriggerable.enabled: true. Authenticated-but-denied: the token is valid; the recipe just isn't flagged. (A missing or invalid token returns 401 unauthorized at the dispatcher instead — see the 401 row above.) |
Tell the user "That recipe isn't flagged for agent triggering — open the Recipe Editor → Agent triggering card → enable, then retry." Do NOT retry without the user flipping it. |
| 404 | No recipe matched the given recipeId or recipeName (searched global and every project's .claude/recipes/ directory). |
Re-list with GET /recipes/triggerable — the recipe may have been deleted, renamed, or the flag turned off. |
| 409 | The orchestration engine is already running another top-level recipe — engine enforces a singleton lock so it will not run two recipes in parallel. | Tell the user "Another recipe is currently running — wait for it to finish, then retry." Don't auto-retry on a fixed schedule; the wait is unbounded. |
| 429 | Either the shared 10-mutations/minute limit is hit, OR the per-(recipeId, projectId) cooldown is active. Cooldown response includes retryAfterSeconds. |
If retryAfterSeconds is present, wait that long and retry. Otherwise wait 60 seconds for the rate-limit bucket to refill. Do NOT bypass the cooldown by varying request shape — it is keyed on the canonical tuple, server-side. |
| 500 | Engine spawn failed (process error, file-store error, etc.). | Surface the error field to the user and tell them to check Omniscio's logs (Settings → Debug Log Viewer). |
Cooldown rules
- Keyed on
(recipeId, projectId). Same recipe on a different project is a different tuple and is allowed. - Window: 30 seconds. There is no flag to change this.
- Triggered on every successful dispatch. Engine-busy 409s do NOT consume the cooldown — the user can immediately retry once the busy run finishes.
- In-memory only. An Omniscio restart resets every cooldown.
The cooldown exists to absorb agent retry loops. If a Claude Code session sees a transient failure and retries the same POST /recipes/run immediately, the second call is rejected with 429 + retryAfterSeconds: <0..30> so it can back off cleanly. Without the cooldown, a misbehaving agent could fire dozens of expensive runs in seconds.
The cooldown is a best-effort timestamp window, not true idempotency — a retry that arrives more than 30 seconds later (a slow client, or a load balancer re-sending a timed-out POST) passes the expired cooldown and would double-fire. To make such a retry safe, send a stable Idempotency-Key (X-Client-Request-Id) header: a repeat with the same key returns the original run for up to 10 minutes instead of spawning a second paid run. See the clientRequestId body field above.
Cost cap
If the recipe carries agentTriggerable.costCapUsd > 0 AND no limits.costCapUsd, the agent-trigger cap is copied into limits.costCapUsd for the run before dispatch. If the recipe already has a limits.costCapUsd, that wins (lowering at the recipe level is intentional). The engine's existing cost-cap recovery logic kills any step whose cumulative spend exceeds the cap and surfaces a "cost cap hit" notification.
This makes the agent-trigger flag a budget surface: the user can flag a recipe as agent-fireable AND cap each run at $5 in the same UI card, without authoring a separate limits block.
For agents
How it works
The HTTP route in src/main/services/cli/cli-server.ts is a thin wrapper over the IPC handler at src/main/ipc/recipe-handlers.ts (handleRecipeTriggerViaCli). Both share the same validation order, the same in-memory cooldown Map<string, number> keyed on <recipeId>:<projectId>, and the same dispatch into the recipe orchestration engine. Error mapping happens in the HTTP layer so callers always get matching status codes regardless of whether they came in over IPC or HTTP.
Recipe lookup mirrors GET /recipes/triggerable — it calls listAllRecipeConfigs(projectPaths) against the global directory plus every registered project's .claude/recipes/ directory and dedupes by id (global wins on collision). This is critical for project-scoped recipes: a recipe authored in <projectPath>/.claude/recipes/ is reachable for triggering as long as the project is registered in Omniscio. (Earlier builds searched only the global directory and broke project-scoped triggering — fixed in commit af794f9.)
Every successful trigger logs a recipe.agent_triggered feature event with metadata { recipeId, recipeName, source, projectId }, where source is either 'cli' (HTTP route) or 'ipc' (in-app chat panel). The same event surface is used by the analytics dashboard.
The flag itself lives in the recipe JSON under agentTriggerable: { enabled, costCapUsd? }. The Recipe Editor's "Agent triggering" card binds to that key — see src/renderer/src/features/recipes/RecipeEditor.tsx (search for "Agent triggering"). Flipping the toggle persists through the standard recipe save flow with no separate IPC channel needed.
Controlling a run you started
An agent that started a run can also pause it, resume it, stop it, or dismiss its card:
POST /recipe/runs/:runId/pause·/resume·/stop·/dismiss— the four run-control verbs. Stop ends the spend.
Only the run you started. A scoped agent token is confined to a run whose recorded creator is
its own session; any other run — the user's, a cron's, another agent's, or one with no recorded
creator at all — answers 403 not authorized to control this run. Because a run can only be
started by an agent through POST /recipes/run, which requires the recipe to be flagged, this also
means an agent can never control a run from a recipe the user has not marked agent-triggerable. The
full-trust token (~/.amc/cli-token) is unaffected and can control any run.
POST /recipe/runs/:runId/acknowledge is deliberately NOT open to an agent token — clearing an
incomplete run out of the inbox stays a human action.
Related
- use-recipes.md — building, running, and scheduling recipes (the human-driven flow)
- recipes-cli-authoring.md — authoring a recipe by asking an external AI (lands as
pendinguntil inbox approval) - cli-control.md — full CLI Control Server endpoint reference
- omniscio-control skill — the same endpoints documented for in-conversation external-AI use
Last verified 2026-10-03