Let your coding agent make every ordinary edit. Interlock reserves destructive writes, protected pushes, and releases for a trusted, policy-controlled broker.
$ curl -fsSL https://sh.opstk.dev/interlock | sh
> irm https://sh.opstk.dev/interlock/install.ps1 | iex
Prompts, AGENTS.md files, and skills guide an agent. But they only advise the agent. A capable agent can ignore them, forget them, or find another path. You learn about the problem too late. Interlock controls the effect itself. The guardrail is not a suggestion.
You write the rule in a skill, an AGENTS.md file, or a system prompt. The text only advises the agent. The agent decides whether it obeys. It can ignore the rule, forget it during a long run, or find another path.
# Repository rules — ⚠️ IMPORTANT, PLEASE READ
NEVER force-push main. Not ever. This is critical.
Do not run git push --force on main under any circumstances.
ALWAYS ask a human before you push main.
Wait for explicit approval — do not push on your own.
If you are unsure, STOP and ask first.
Please double-check every push. I really mean it 🙏
Like branch protection — but it lives in your repo, not on a host, and it guards every risky effect (delete, publish, more), for any agent.
You write the same rule using our typed libraries. It compiles to one canonical policy. The broker owns the effect. So the agents cannot bypass it. The decision stays the same on every run.
A normal source file, committed with your code — in the language you already use.
// fluent builder → canonical IR
il.Policy("repository-policy.v1").
Actor("agent").
Branch("main", "repo://branch/main").
Deny("deny-force-push-main").By("agent").
To(il.ForcePush).On("main").
Because("force-pushing main is not allowed").Add().
Allow("push-main").By("agent").
To(il.Push).On("main").
Requiring(il.HumanApproval("release-main")).
Because("pushing main needs human approval").Add()
// @operatorstack/interlock
policy("repository-policy.v1")
.actor("agent")
.branch("main", "repo://branch/main")
.deny("deny-force-push-main").by("agent")
.to(ForcePush).on("main")
.because("force-pushing main is not allowed")
.allow("push-main").by("agent")
.to(Push).on("main")
.requiring(humanApproval("release-main"))
.because("pushing main needs human approval")
.emit();
# interlock
(Policy("repository-policy.v1")
.actor("agent")
.branch("main", "repo://branch/main")
.deny("deny-force-push-main").by("agent")
.to(FORCE_PUSH).on("main")
.because("force-pushing main is not allowed")
.allow("push-main").by("agent")
.to(PUSH).on("main")
.requiring(human_approval("release-main"))
.because("pushing main needs human approval")
.emit())
// interlock crate
Policy::new("repository-policy.v1")
.actor("agent")
.branch("main", "repo://branch/main")
.deny("deny-force-push-main").by("agent")
.to(ForcePush).on("main")
.because("force-pushing main is not allowed")
.allow("push-main").by("agent")
.to(Push).on("main")
.requiring(human_approval("release-main"))
.because("pushing main needs human approval")
.emit()?;
The agent proposes; the broker publishes. That's the difference between hoping the agent follows the rules and knowing it can't skip the ones that matter.
Pick a starter. Interlock scaffolds a real policy file in your repo — then you own it and edit it like any code. Click a starter to preview its policy file →
$ interlock init --template main-branch
The others are infrastructure you run, or a list that stops watching after it says yes. Interlock is a file you already have. The coding agent proposes; the broker publishes.
Interlock keeps its authority at the effect boundary. The broker owns push and publish. Any agent can propose a change. But no agent can reach that authority. You do not change tools. You do not install a plugin. You do not reconfigure your agent. This works even with no hook.
Interlock talks to your coding agents over hooks, using the Pitot protocol — one line of config, no fork or plugin. Pitot on GitHub →
Many tools use hooks to control agents. For those tools, the hook is the guarantee. But an agent can bypass the hook. The hook also cannot see the writes that a shell command starts. For Interlock, the hook only shows the decision. The guarantee is the control of the effect.
Interlock does not exaggerate. This is what Interlock guarantees. This is also what it does not guarantee.
Interlock does not watch every keystroke or every file write. It controls the few effects that matter, such as push and publish. The agent cannot do these effects without the guardrail.
Interlock denies the effect if it finds a mismatch, such as wrong evidence or a changed policy. Your repository stays unchanged.
Interlock controls who can do an effect. It does not give cryptographic authenticity. It uses no MCP server.
"Interlock does not define whether work is correct, whether an artifact is safe to ship, or what 'released' means."
Laptop, coding agent, cloud sandbox, CI — the transport changes, the authority does not.
Install the CLI. Then pick a template. interlock init --template scaffolds a policy module. interlock compile builds the canonical IR. interlock doctor reports what your setup can build.
$ curl -fsSL https://sh.opstk.dev/interlock | sh
> irm https://sh.opstk.dev/interlock/install.ps1 | iex
$ go install github.com/operatorstack/interlock/cmd/interlock@latest
$ interlock doctor