SyndaiSign inStart free

Run policy

Every field of policy_config: how a run checks its work, what it may spend, which harness it uses, and when it waits for you.

policy_config is a JSON object you can send on POST /runs. It sets how one run works. Leave out a key and the run uses its default. An unknown key fails with INVALID_POLICY (HTTP 400).

You can also save defaults so you don't send the same policy on every run. See Saved defaults.

Top-level fields

fieldtypedefaultwhat it sets
validationobjectsee belowhow the run checks its change
reviewsobjectsee belowwhich review gates the run must pass
budgetobjectsee belowrepair tries and the spend limit
egressobjectmanaged_defaultnetwork access for the sandbox
artifactsobjectstandardhow long evidence files are kept
harnessobjectnonewhich coding agent and model run the change
subscription_usageobjectsee belowuse of a subscription past its usage limit
usage_failoverobjectsee belowwhat runs when your subscriptions are used up
toolsobjectnonebuilt-in tools and your own tools for the run
autonomyenumautowhat happens once the change passes its checks
spec_reviewenumnoneif the run waits for you to approve its plan
unattendedboolfalsenever pause the run to ask a question
context_research_enabledbooltruea short web search for current practice before the plan
auth_modeenumapi_keywho pays for the AI: credits or a subscription
subscription_credential_idUUIDnonethe subscription a run uses
subscription_providerenumnoneclaude_max or chatgpt

validation

fieldtypedefaultwhat it does
profileenumautoA stack hint: auto, python, nextjs, vite, flutter, react_native, native_ios or native_android. Syndai still detects stacks from your files. A hint only adds to what it finds.
levelenumstandardsmoke, standard or strict. See Validation.
base_freshnessenumas_of_baselatest_main counts CI only when the pull request sits on the current tip of the base branch. With as_of_base, CI still counts after the base branch moves, as long as it did not move in a file the change touches.
bootstrap_when_toollessboolfalsetrue: when your repo has no checks Syndai can run, the agent adds a small test setup so the change can be checked. false: the run stops for lack of checks.
template_treeslist of strings[]Folders that hold templates your repo ships, not code it builds. Syndai never builds or tests inside them. Each entry is a plain path like tools/scaffold. No leading or trailing slash, no . or .., at most 50.
inputsmap of string to list{}What each command folder reads, as path prefixes: {"api": ["api/", "shared/"]}. . is the whole repo. A check is skipped when the change touches none of its inputs. A passing check is reused when its inputs, command and setup did not change. No globs. See syndai.yaml.
timeout_secondsint600Time limit for a check that runs on GitHub Actions and has no limit of its own. More than zero.
github_poll_attemptsint60How many times Syndai polls a GitHub Actions check. More than zero.
github_poll_interval_secondsnumber5.0Seconds between those polls. At least 2.0.

You never send commands. Syndai finds them in your repo. These keys are refused with INVALID_POLICY: commands, execution_environment, github_workflow_id, github_result_artifact_name and artifact_upload.

reviews

fieldtypedefaultwhat it does
required_gateslist[]Gates the run must pass. Allowed: planning_review, adversarial_review, security_review and preflight.
hitl_on_exhaustionbooltrueWhen the run uses up its repair tries, it waits for you instead of failing. A run with no one to answer never waits. false turns this off.

Every run passes security_review, preflight and planning_review, whatever you send. So adversarial_review is the one gate you can add. Names that act, such as merge, push, deploy or create_pr, are refused. See Review gates.

budget

fieldtypedefaultwhat it does
retry_budgetint3How many times the run may fix a failed check before it stops. 0 means no repair tries.
max_budget_usdnumbernoneThe most this run may spend, in US dollars. At least 5.
branch_strategyenumbranch_only_never_mergebranch_only_never_merge or branch_then_pr. Neither one lands a change. Landing is set by autonomy.

egress

fieldtypedefaultwhat it does
profileenummanaged_defaultmanaged_default gives the sandbox normal network access. offline blocks all outbound traffic.

An offline run can't call your own MCP servers, since they need the network.

artifacts

fieldtypedefaultwhat it does
retention_profileenumstandardHow long evidence files (screenshots, logs, reports) are kept. standard is up to 30 days and extended is up to 90. pinned keeps them until you delete them.

harness

fieldtypedefaultwhat it does
kindenumnoneThe coding agent: pi, opencode, claude_code or codex_cli.
modelstringnoneThe model for that agent. It must be on the agent's allowed list. The error lists the allowed models.
effortenumnonelow, medium, high, xhigh or max.

model needs kind. On credits (auth_mode: api_key) you can pick pi, opencode or claude_code. On a subscription you can pick claude_code (Claude Max) or codex_cli (ChatGPT). Leave out auth_mode and Syndai picks it for you. claude_code uses your Claude subscription when you connected one, or credits when you did not. codex_cli needs a connected ChatGPT subscription.

subscription_usage

fieldtypedefaultwhat it does
allow_extra_usageboolfalsetrue lets a run use your subscription even when its usage is near the limit or can't be read. Your provider may then charge for extra usage.

usage_failover

fieldtypedefaultwhat it does
backup_chainlist or nullnullWhat runs when every subscription you connected is used up. null uses the default: your other subscription if it has room, then pi. [] means no backup: the run waits and goes on when a usage window resets. A list is the agents to try in order, from pi, opencode, claude_code and codex_cli. At most 3, no repeats.
api_fundingenum or nullnullWho pays when a subscription run falls back to an API agent. credits pays from workspace credits, never from your own OpenRouter key. null and workspace_default use the workspace's normal lane.

tools

fieldtypedefaultwhat it does
builtinsenumallall gives the run Syndai's built-in tools. none gives it none of them. Your own tools still work.
disabled_builtin_capabilitieslist[]Built-in tools to take away from all. Ignored with none. At most 100, no repeats.
custom_tool_idslist or nullnullYour own tools (skills and MCP servers) for this run, as UUIDs. null gives the run every tool you turned on. A list, even an empty one, gives it only those. At most 50, no repeats.

Leave out tools and the run gets all built-in tools and every tool you turned on. The run calls your MCP servers through Syndai. Your server's URL and secret never enter the sandbox.

autonomy

What happens once the change passes its checks. Values: auto, gated, merge and push.

  • auto opens the pull request.
  • gated stops before the pull request. You see the change and pick Approve to open it, or Request changes to send notes for another try.
  • merge opens the pull request, then squash merges it when every gate is green. It holds if a gate fails. The security review and preflight must pass. The base branch must not have moved in a file the change touches. CI must pass too.
  • push runs the same gates. Then it moves the default branch up to the checked commit. If the base branch moved, it squash merges the pull request. push is for the API only.

A held change waits on the run with Merge anyway, Retry and Close. Only a person signed in at app.syndai.ai can pick Merge anyway. A key gets HUMAN_APPROVAL_REQUIRED. A per-run merge or push counts only when it comes from an API key, an owner or admin, or a user's own run. Some runs come from a request someone else wrote, like a site visitor, a member or a Sentry alert. Those always hold before they land.

spec_review

If the run waits for you to approve its plan before it builds. Values: auto, timed and hold.

  • Not set: the run builds at once. A large plan waits like timed.
  • auto never waits, even for a large plan.
  • timed waits for you, then builds on its own if you don't answer in time.
  • hold waits until you approve. It never builds on its own. If no one answers, the run ends without building.

A run with no one to answer never waits. That covers scheduled runs and unattended runs.

unattended

true means no one will answer questions. When the agent is unsure, it goes on with its best guess and says so in the pull request. It does not pause. Pauses for a sign-in or a usage limit still happen.

context_research_enabled

true (the default) runs a short web search for current best practice before the plan. The search is built from your task text, with known secret patterns taken out first. Send false if your task text must never reach the search provider.

Subscriptions

  • auth_mode: api_key (the default) or subscription.
  • subscription_credential_id: the subscription you connected.
  • subscription_provider: claude_max or chatgpt. It must match the harness: claude_max goes with claude_code, and chatgpt goes with codex_cli.

Send subscription_credential_id and subscription_provider only with auth_mode: subscription. A subscription agent needs one of the two.

Rules

A policy that breaks one of these fails with INVALID_POLICY (HTTP 400). detail names the field.

  1. No unknown keys, at any depth.
  2. These keys are refused by name: webhook_url, mobile_validation, web_validation, backend_validation, policy_config_override, internet_access, network_allow_list, allow_hosts and allow_cidrs. Set up webhooks with the webhooks API instead.
  3. timeout_seconds and github_poll_attempts are more than zero.
  4. github_poll_interval_seconds is at least 2.0.
  5. retry_budget is zero or more.
  6. max_budget_usd is at least 5.
  7. The harness, model and subscription fields agree, as above.

A null value clears the key back to its default.

Saved defaults

Save a policy once and every new run starts from it:

  • GET /policy/schema returns this schema as JSON Schema.
  • PUT /policy/defaults saves a default. Add coding_repository_id to save one for a single project.
  • GET /policy/defaults reads the current default, and GET /policy/defaults/history lists past ones.

Layers apply in this order, and the last one wins for each key:

  1. the workspace default
  2. the project default
  3. the repo's syndai.yaml
  4. the run's own policy_config

syndai.yaml is read after the run's sandbox starts. So it can't change egress.profile, tools.builtins, tools.disabled_builtin_capabilities, budget.branch_strategy, budget.max_budget_usd or autonomy. Set those in a saved default or on the run.

A new workspace starts with this default: spec_review: auto, autonomy: merge, unattended: true, and usage_failover set to backup_chain: null and api_funding: credits. It is an ordinary saved default, so you can change it.