# Rules

> Add, test, update, and remove the Rules used by your repository.

Source: https://docs.synkro.sh/guides/rules

Rules are TOML files that check agent actions. A package contains `synkro-package.toml` and a `rules/` directory.

Deterministic Rules match exact conditions, such as a tool name or command pattern. Semantic Rules assess an action and its context against requirements written in natural language.

Your project's `synkro.toml` lists its packages. `synkro.lock` records the versions Synkro resolved.

## Set up the project

In a Git repository without `synkro.toml`, run:

```sh
synkro init
```

This creates:

```text
synkro.toml
.synkro/policy/
  synkro-package.toml
  rules/
```

Review the dependencies copied from your user setup. If the project already has `synkro.toml`, keep it and use the package paths it declares.

Put Rule files directly in the package's `rules/` directory.

## Ask your agent to manage a Rule

```text
Use manage-synkro-rules to show me this repository's Rules.
```

To add one:

```text
Use manage-synkro-rules to propose a Rule for this requirement.
Show me the file and examples before applying it.
```

If the skill is missing, run `synkro install` and start a new agent session. You can also edit the files directly.

The skill shows the proposed change and waits for your approval before creating,
changing, disabling, or deleting a Rule. After applying it, the agent runs
`synkro lock` and `synkro check`, then reports what passed, failed, or could not
be checked.

## Find suggestions from review feedback

Ask your agent:

```text
Use discover-historical-synkro-rule-opportunities to review accepted feedback
from merged PRs in OWNER/REPO over the last 30 days. Compare it with this
project's Rules and show me useful suggestions. Do not activate any Rules.
```

Replace `OWNER/REPO` with a GitHub repository. Public scans use a signed-in GitHub CLI.
For private repositories, [connect GitHub](/integrations/github) and tell your agent
to use `--connected`. Synkro's discovery commands collect comments, reviews and
resolved threads; the agent checks which feedback describes a useful Rule for
your current code. Suggestions retain their accepted review evidence.

In the candidate evidence-assessment update, each citation is checked separately
against the recommendation and the scan's active Rules. Supported citations count
toward independent PR support; unrelated, contradictory, uncertain and unchecked
citations remain visible without increasing that count. A conflict is a flag for
human review. Assessment uses normal metered model usage when available; an
unavailable assessment never becomes positive support. A later scan can retry
unchecked evidence. None of these checks activates a Rule or replaces your approval.

Use `discover-synkro-rule-opportunities` for later scans from the saved
checkpoint, or `investigate-synkro-findings` to compare saved suggestions.
These skills use the [CLI discovery commands](/reference/cli#synkro-discover).

After a successful direct `gh pr create`, Synkro's supported post-tool hooks
ask the agent to run the incremental scan. It looks at feedback on earlier
merged PRs, not the PR just opened. The first scan covers 30 days by default;
later scans start where the last completed scan ended. Interrupted scans resume.

Automatic detection requires a recorded tool return with one PR URL and a
recognized successful command result in Claude Code, Codex or Cursor. For
wrappers, compound commands, browser-created PRs or Antigravity, ask the agent
to run the incremental skill explicitly. The scan is a follow-up, not a merge
gate, and no Rule is activated automatically.

## Turn a finding into a Rule

Ask your agent to run:

```sh
synkro discover propose FINDING_ID --json
```

Replace `FINDING_ID` with an ID from `synkro discover findings OWNER/REPO`.
The command returns the finding, its evidence and the current rule-management
instructions. It does not create or activate a Rule.

Your agent checks whether the suggestion still applies and whether an existing
Rule covers it, then shows you the proposed file and examples. After you approve
the exact proposal, the agent writes it and runs:

```sh
synkro lock
synkro check --rule RULE_ID --json
```

`RULE_ID` is the ID in the new Rule, not the finding ID.
The approved file keeps the finding ID and PR links as comments;
the saved finding is not automatically marked as promoted. Follow the normal
[publication steps](#update-or-remove-a-rule) and verify a governed action before
calling the Rule active.

## Dismiss a finding

If a suggestion is not useful or is already covered:

```sh
synkro discover dismiss FINDING_ID --reason "Already covered by our parser rule"
```

It disappears from the normal list and later reassessments. Its evidence stays
available with `synkro discover finding FINDING_ID` or
`synkro discover findings OWNER/REPO --include-dismissed`. Existing Rules are
unchanged.

## Test a Rule

This Rule blocks the exact command `printf synkro-rule-demo`. In a project created with `synkro init`, save it as `.synkro/policy/rules/block-demo-marker.toml`:

```toml
schema_version = 3
id = "block-demo-marker"
title = "Block the tutorial marker command"
description = "Stop the exact marker command used to test Synkro."
mode = "block"
severity = "low"
remediation = "Use a command other than the tutorial marker."
evaluation = { kind = "deterministic", predicate = { kind = "command_invocation_regex", pattern = "^printf synkro-rule-demo$" } }

[[examples]]
expected = "violation"
input = { kind = "command", command = "printf synkro-rule-demo" }

[[examples]]
expected = "violation"
input = { kind = "command", command = "printf synkro-rule-demo; true" }

[[examples]]
expected = "ok"
input = { kind = "command", command = "printf ordinary-message" }

[[examples]]
expected = "ok"
input = { kind = "command", command = "echo synkro-rule-demo" }
```

This pattern matches the marker command, not every use of `printf`. Synkro checks command lists as separate invocations.

Validate the files and test the examples:

```sh
synkro lock
synkro check --deterministic-only
```

Check that the report has no failures.

With enforcement active, ask your agent to run:

```sh
printf synkro-rule-demo
```

The agent should receive a block naming `block-demo-marker`. Check the record:

```sh
synkro events --type verdict --limit 5 --details
```

If the marker runs instead, the Rule did not block it. Check [Troubleshooting](/troubleshooting#a-rule-does-not-take-effect).

## Semantic Rules

Use `evaluation = { kind = "semantic" }` when a Rule needs judgment rather than a fixed pattern. For example:

```toml
schema_version = 3
id = "preserve-fetch-errors"
title = "Do not turn a failed fetch into an empty result"
description = "Code fetching remote items must keep a failed request distinct from a successful empty result."
mode = "block"
severity = "high"
remediation = "Return or raise the error so the caller can handle it."
evaluation = { kind = "semantic" }

[[examples]]
expected = "violation"
input = { kind = "content", path = "src/client.ts", content = "try { return await fetchItems(); } catch { return []; }", coverage = "fragment" }

[[examples]]
expected = "violation"
input = { kind = "content", path = "src/client.ts", content = "return response.ok ? response.items : [];", coverage = "fragment" }

[[examples]]
expected = "ok"
input = { kind = "content", path = "src/client.ts", content = "if (!response.ok) throw new Error('Request failed'); return response.items;", coverage = "fragment" }
```

Save it as a separate file in the package's `rules/` directory. Run `synkro lock`, then `synkro check` to test the semantic examples. `synkro lock` alone does not test them.

## Rule format

A Rule needs a version, ID, title, description, mode, severity, evaluation, and examples. A `fix` Rule also requires remediation.


For new Rules with a CLI and daemon that support v3, use
[`schema_version = 3`](https://docs.synkro.sh/schemas/rules/v3/rule.schema.json).
V3 includes the v2 execution-fact predicates and adds workspace examples. V1
and v2 remain readable; they are not the recommended starting point for a new
Rule. Preserve an existing file's version unless the approved change needs a
newer one. The project and package manifests still use version 1.

[V4](https://docs.synkro.sh/schemas/rules/v4/rule.schema.json) is supported by
rc.24. It requires both the CLI and running service to support v4; confirm both
before publishing a v4 Rule. V4 adds `mcp_call` examples and permits one violation plus one nearby allowed
case (2–8 examples total). V1–v3 require two violations and one allowed case
(3–8 total). Prefer 2–4 distinct violations when the predicate has them. For a
singleton exact match, one positive and meaningful near misses in v4 are enough;
do not broaden the predicate or invent a second violating value just to pad the
count. V3's narrow `argument` example can express only the matching key/value:
its required repeated positive is a format limitation, not independent coverage.

`expected = "violation"` means the predicate matches, regardless of the Rule's
mode. `expected = "ok"` means it does not. TOML strings are quoted; literal
single-quoted strings preserve regex backslashes. `evaluation` contains one
predicate, not an invented `all`, `and`, `conditions`, or `args` format.

### Exact argument value on the native hook (v3)

Codex displays `exec_command` with `cmd`, but its native hook reports `Bash`
and `tool_input.command`. The key below is therefore `command`. Matching is
exact, including whitespace; a shell list containing the marker is a near miss
for this predicate. Use `command_invocation_regex` when the intent is to find a
command inside a shell list instead. This complete v3 file intentionally repeats
its one representable positive to meet the frozen v3 minimum.

```toml
schema_version = 3
id = "exact-demo-command"
title = "Block the exact demo command"
description = "Refuse only a command argument equal to printf synkro-rule-demo."
mode = "block"
severity = "low"
evaluation = { kind = "deterministic", predicate = { kind = "argument_value", key_path = "command", value = "printf synkro-rule-demo" } }

[[examples]]
expected = "violation"
input = { kind = "argument", key_path = "command", value = "printf synkro-rule-demo" }
note = "The singleton matching value."

[[examples]]
expected = "violation"
input = { kind = "argument", key_path = "command", value = "printf synkro-rule-demo" }
note = "Required repeat for v3; adds no distinct coverage."

[[examples]]
expected = "ok"
input = { kind = "argument", key_path = "command", value = "printf synkro-rule-demo; true" }

[[examples]]
expected = "ok"
input = { kind = "argument", key_path = "cmd", value = "printf synkro-rule-demo" }
note = "This model-facing key is not the native Codex shell key."
```

An `argument` value is a scalar: string, number, boolean, or null in JSON
(TOML cannot spell null). Nested argument facts use paths such as `message.to[0]`.
The predicate is not implicitly scoped to Bash or an MCP server: it matches the
same normalized key/value on any applicable tool. Verify the intended scope.

### Workspace writes (v3)

`workspace_path` supplies the path, access (`read`, `write`, or `unknown`) and
`outside_workspace` boolean. These are authored facts, not a request to inspect
the filesystem. A filename by itself does not establish workspace membership.

```toml
schema_version = 3
id = "outside-workspace-writes"
title = "Keep writes inside the workspace"
description = "Refuse writes whose reported path is outside the workspace."
mode = "block"
severity = "high"
evaluation = { kind = "deterministic", predicate = { kind = "workspace_boundary", access = "write" } }

[[examples]]
expected = "violation"
input = { kind = "workspace_path", path = "/outside/one.txt", access = "write", outside_workspace = true }

[[examples]]
expected = "violation"
input = { kind = "workspace_path", path = "/outside/two.txt", access = "write", outside_workspace = true }

[[examples]]
expected = "ok"
input = { kind = "workspace_path", path = "src/main.rs", access = "write", outside_workspace = false }

[[examples]]
expected = "ok"
input = { kind = "workspace_path", path = "/outside/one.txt", access = "read", outside_workspace = true }
```

### MCP identity and arguments together (v4)

V3 `mcp_tool` examples carry only `server`/`tool`; `argument` examples carry only
one key/value. Neither can stand in for both. V4 `mcp_call` carries the explicit
coordinates and a complete `arguments` object, including nested arrays/objects.
Use observed server/tool names and arguments; the following `gmail`/`send_email`
coordinates are illustrative and must be replaced with the actual connector's.
Do not send an email to test this fixture: `synkro check` grades its data.

```toml
schema_version = 4
id = "confirm-external-email"
title = "Confirm email to external recipients"
description = "Match gmail/send_email calls with a recipient outside example.org. Internal-only messages and draft creation do not match."
mode = "ask"
severity = "high"
evaluation = { kind = "semantic" }

[[examples]]
expected = "violation"
input = { kind = "mcp_call", server = "gmail", tool = "send_email", arguments = { to = ["person@external.invalid"], subject = "Project update", body = "Please review the update." } }

[[examples]]
expected = "violation"
input = { kind = "mcp_call", server = "gmail", tool = "send_email", arguments = { to = ["team@example.org", "person@external.invalid"], subject = "Mixed recipients", body = "Please review." } }

[[examples]]
expected = "ok"
input = { kind = "mcp_call", server = "gmail", tool = "send_email", arguments = { to = ["team@example.org"], subject = "Internal update", body = "Please review." } }

[[examples]]
expected = "ok"
input = { kind = "mcp_call", server = "gmail", tool = "create_draft", arguments = { to = ["person@external.invalid"], subject = "Unsent draft", body = "Please review." } }
```

`mcp_call` also supports deterministic `argument_value`, `mcp_tool`, `tool_name`,
and `max_payload_bytes` examples. Supplying both coordinates and arguments does
not turn a single predicate into an AND: `argument_value` still checks only its
key/value, while `mcp_tool` checks only its server/tool. A semantic Rule can
evaluate their relationship from the complete normalized evidence.

## Match the fields Synkro receives

Use the recorded hook, not just the agent's tool-call display, when choosing an
argument key or tool name. Read a known hook with
`synkro events --id EVENT_ID --details --json` from your signed-in CLI.

For Codex shell calls, the agent supplies `exec_command` with `cmd`, but the hook
reports `Bash` with `tool_input.command`. An `argument_value` Rule must use
`key_path = "command"`, not `"cmd"`. For command patterns, use
`command_invocation_regex` as in the tutorial above.

For Codex subagents:

- To match every reported launch, use `tool_name` with `exact = "spawn_agent"`.
- To match a specific profile, use `agent_invocation` with that profile's name.
  It only matches when the hook reports the profile, such as `agent_type`.
- `task_name` labels a task. It does not identify an agent profile. A launch
  without a reported profile can still match the `spawn_agent` tool-name Rule.

After checking the examples, run an action through your agent and inspect
the recorded Rule match. This checks the Rule against the fields your agent
actually supplies. See [agent
coverage](/reference/supported-agents#which-actions-are-checked).

## Update or remove a Rule

Edit or remove the relevant Rule file, then run:

```sh
synkro lock
synkro check --deterministic-only
```

Run `synkro check` too if you changed semantic examples. Review and commit the Rule files, package declarations, and lockfile together.

After locking, start a new agent session to publish changed project Rules before testing. The next SessionStart or PreToolUse hook uploads them. If validation fails, the previous valid Rules stay active. Fix the error and retry. See [Verify a decision](/onboarding/verify-events) to confirm the new Rules were used.

## Update a dependency

```sh
synkro lock update DEPENDENCY_NAME
```

Replace the name with a dependency key from `synkro.toml`. Omit it to update all dependencies.

Named Git branches or tags are resolved again. A full commit pin moves to the remote's latest commit. Review the changes before committing; updating one dependency leaves the others pinned.

Local path packages use the files on disk. Agent requests do not fetch Git dependency updates.

## Project scope

`synkro init` copies your user defaults into a new project. Later user-file edits do not change existing projects.

To turn off Synkro for a project, put `enabled = false` at the top of `synkro.toml`, before `[dependencies]` or any other table.
