What is Synkro?A package manager for agents, with Rules for using your software.Getting startedQuickstartLet your agent set up Synkro, create your first Rule, and verify an action.Getting startedSetup checklistCheck your account, installation, repository, and first agent decision.SetupInstall and verifyInstall the CLI, sign in, and connect your agent.SetupSet up your agentConnect your agent and verify its first tool call.SetupVerify a decisionFind the recorded decision for an agent action.SetupYour first decisionRead your first agent decision and test a Rule.SetupHow Synkro worksUnderstand how Synkro applies Rules and records agent decisions.Core conceptsRules and ControlsChoose repository Rules for actions and Controls for access restrictions.Core conceptsRule outcomesUnderstand when an action continues, needs approval, or must be changed.Core conceptsRulesAdd, test, update, and remove the Rules used by your repository.GuidesEventsFind recorded agent actions and decisions with the Synkro CLI.GuidesApprove MCP toolsChoose project approvals and package defaults for MCP tools.GuidesConsentReview and approve an agent action before it runs.GuidesFeedbackReport a Synkro bug or suggest an improvement with diagnostic context.GuidesBilling and usageAdd funds and check your organization's balance and usage.GuidesGitHubConnect GitHub to discover Rules from private repositories.IntegrationsCLI commandsFind the current commands for setup, Rules, events, account, and recovery.ReferenceSupported agentsFind the installed Synkro files and the checks that apply to each agent.ReferenceTroubleshootingFix setup, sign-in, missing decisions, Rule, and approval problems.Reference
Documentation
Guides

Rules

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

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:

synkro init

This creates:

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

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

To add one:

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:

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 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.

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:

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:

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 and verify a governed action before calling the Rule active.

Dismiss a finding

If a suggestion is not useful or is already covered:

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:

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:

synkro lock
synkro check --deterministic-only

Check that the report has no failures.

With enforcement active, ask your agent to run:

printf synkro-rule-demo

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

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

If the marker runs instead, the Rule did not block it. Check Troubleshooting.

Semantic Rules

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

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. 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 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.

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.

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.

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.

Update or remove a Rule

Edit or remove the relevant Rule file, then run:

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 to confirm the new Rules were used.

Update a dependency

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.