Singh Labs ← all tools

interlock

what an agent may change

An agent can fix your broken pipeline. Interlock decides what it may fix alone, what needs a person, and what it must never touch.

The problem

Without it

Your build goes red at 2am. The agent reads the log, decides the fix is a tweak to the deploy workflow, and opens a PR. It is well written and it explains itself. At 8:40am someone merges it because it looked plausible — and because the other forty agent PRs that month were fine.

With interlock

REFUSE  .github/workflows/deploy.yml is denied by scope.deny

Same agent, same patch. The difference is that somebody wrote down, in advance and in a reviewed file, that nothing automatic touches deploys.

Branch protection and CODEOWNERS guard the merge. Nothing guards the moment a machine decided something was worth changing. For a human contributor that is fine — they had a reason you can ask about. For an agent that opened forty PRs this week, "looks plausible" is the entire review.

What you get

Install

  1. Install it

    Node 18+. Install from GitHub — the bare name interlock on npm belongs to an unrelated package, so always go through the repo.

    npm install -g github:manpreet171/interlock

    Prefer not to install anything? Every command below also works as npx github:manpreet171/interlock <command>.

  2. Scaffold the policy

    interlock init
     wrote interlock.json   ← review this in a PR like any other code
     created .interlock/    ← the ledger lands here. Commit it.
  3. Set the owner, then check it

    The linter refuses a placeholder owner on purpose. Exit 1 makes it CI-friendly.

    interlock lint
     interlock.json is valid
      mode normal · 1 auto · 7 hold · 6 refuse · rules 0a20347304ad
  4. Point it at a build that actually broke

    gh run view 4471 --log-failed > run.log
    interlock gate run.log --repo acme/api --run 4471
    missing-python-module  263819a52f2e
      evidence  ModuleNotFoundError: No module named 'requests'
      proposal  add "requests" to requirements.txt
    
      AUTO  class "missing-python-module" is pre-cleared and the change is in scope
    
    --- a/requirements.txt
    +++ b/requirements.txt
    @@ -1,3 +1,4 @@
     flask==3.0.0
     gunicorn==21.2.0
     pytest==8.0.0
    +requests
  5. Wire it into your workflow

    One step, only on failure. Exit 75 means held, not broken.

    - name: interlock
      if: failure()
      run: |
        gh run view "$GITHUB_RUN_ID" --log-failed > run.log
        npx github:manpreet171/interlock gate run.log > fix.diff
  6. Run a week in shadow mode first

    Set "mode": "shadow" and it records every verdict while blocking nothing. A week of evidence with nothing at stake, then switch to normal.

The part you cannot fake

Every failure gets a fingerprint — a hash of the error once timestamps, run ids, runner paths and commit hashes are stripped out. The same break always hashes the same; different breaks never collide. So an approval is bound to one exact failure, not to "this once" and not to "forever".

HOLD   set-output-deprecated  1c59f09f5474   → a person decides
 cleared by Manpreet Singh · warrant valid 14 days

three days later — new run id, new timestamps, new runner path
AUTO   warrant h-627fdffd — cleared by Manpreet Singh (use 1/5)

And because the fingerprint comes from the log, the thing asking for permission cannot forge it.

Source & docs

Full documentation, the four pillars, the decision order and a LangGraph reference agent live in the repository. MIT licensed.

github.com/manpreet171/interlock
zero dependencies node 18+ 35 tests passing MIT