docs blog library
github

Unattended runs

Before scheduling or deploying an unattended agent, restrict its tools, set budgets, and decide how it should handle approvals.

1. Cut the toolset first

Deny tools the job does not need. For example, deny execute_command to remove shell access.

Use deniedTools rather than omission: tools is additive and leaving something out withholds nothing. A CI reviewer should hold reads, greps, and nothing that writes.

2. Pick the lowest policy that does the job

read-only for anything that only inspects, keeps todos, or delegates. low-risk for jobs that write memory or set reminders and triggers. high-risk only where the job’s whole purpose is to change something, and then only with the toolset already cut to that one thing.

For specific commands, use autoApprovedCommands; see approvals.

3. Limit time and spending

Set maxCostUSD, maxTokens, maxDurationMs, and maxIterations. Cost and token caps are checked between iterations, so pick numbers with headroom. maxDurationMs and --timeout are hard walls: they stop running commands and model calls where they are. See budgets.

4. Decide what happens at a gate

Default behaviour is to decline gated calls and report them, which is usually right: the run finishes and tells you what it could not do.

Use --park when the job needs your decision to continue. It saves the run and exits 2. Inspect it with jazz runs show <id>, then approve with jazz runs approve <id> or reject with jazz runs reject <id>. Configure a notify target to receive its approval request.

Egress after untrusted input

An inbox digest at read-only reads mail somebody else wrote. Without a further rule, the next call could put anything the agent knows into a URL of that person’s choosing.

HTTP requests and page fetches run automatically by default, including unattended runs after untrusted content. An optional global network.httpApproval URL array requires one-call approval outside its grants, or parking when --park is enabled. Risk tiers and session grants cannot override that list. See HTTP approvals.

For other tools, once a run has read external content (a web page, an API response, a search result, an MCP result, a peer’s answer, or the output of any shell or custom command), other egress tools stop auto-approving under read-only, low-risk or an unset policy. They prompt where somebody can answer, park under --park, and are declined otherwise, like any other gated call. The state is kept for the whole run, shared with its sub-agents, and restored on resume from the labelled results already in the conversation.

Two exceptions stay automatic: a configured destination, or a URL the run received rather than composed:

  • web_search, which only reaches the search provider you configured;
  • a plain GET (read_pdf by URL) of a URL that already appears in your messages or in content the run read. Following a link from a page or a search result tells its author nothing new.

Jazz cannot tell what a command read, so any execute_command run marks the run: after one, other egress at read-only or low-risk needs approval even if the command was ls. A job that mixes shell and network work should do its fetching first or expect to park.

high-risk (and true) approve egress as before, and an explicit autoApprovedTools entry still counts. If an unattended job needs to post somewhere after reading mail, name that tool in autoApprovedTools for the job, or accept that it parks.

5. Give every external conversation a stable identity

A bridge or webhook that reuses one conversation key across senders mixes people’s history together. Derive the key from the platform’s own id, and make sure it cannot collide.

6. Isolate the host account

Jazz’s controls bound what the model chooses to do. The operating system bounds what is reachable when that fails, and only the second one holds if a prompt injection succeeds.

Run unattended agents as a dedicated OS user, or in a container, with credentials scoped to that job and nothing else. The Telegram and Discord bridges do this per conversation when they run as root in the Linux container, giving each one its own Unix user and Jazz home, so one person’s agent cannot read another’s mail credentials. The other bridges do not.

7. Run it in the foreground first, under the real policy

jazz workflow run my-job --auto-approve

This uses the workflow’s unattended approval policy, as the scheduler does.

machine-readable: /docs/security/unattended-runs.md · /llms.txt · /llms-full.txt