Guide 01 · Workflow

Commit review by a second model

A git hook that stops the commit unless a separate read-only agent, on a different model, approves the staged diff.

Related exhibit:
02 · Review by another model
Checked against the source:
Nov 4, 2026
Tools:
OMP 18.14.1 · Git

What it is for

Before every commit, the hook takes the staged diff and hands it to a second agent. That agent may only read, uses a different model from the one that writes the code, and ends its review with a verdict. Only an exact VERDICT: approve on the last line lets the commit through. A different verdict, any other last line or an error stops it.

What you need

Other agent CLIs have their own hook and permission systems; we have not tested them.

Steps

  1. Add a model role for the review

    In OMP's config.yml, add a commit-review role. Without it the hook stops every commit.

    adapted from · OMP config.yml
    modelRoles:
      commit-review: provider-a/review-model
  2. Write the reviewer

    The hook uses the body below the frontmatter as the reviewer's system prompt. The reviewer gets three tools: read, grep and glob.

    adapted from · .omp/agents/commit-reviewer.md (K-B)
    ---
    name: commit-reviewer
    description: Read-only review of the staged diff before a commit. Ends with a machine-readable verdict.
    tools: read, grep, glob
    model: "@commit-review"
    ---
    
    # Commit Reviewer
    
    ## Input
    
    - A path to a file with the staged diff (git diff --cached). The diff is the only subject of the review.
    
    You only read: read, grep, glob. Read surrounding code only where a finding cannot be confirmed or ruled out without it. You do not fix code.
    
    ## Checklist
    
    Go through all of it; do not stop at the first finding.
    
    - Repo rules: follow the rules in the repo's context files (AGENTS.md, CLAUDE.md). Breaking a hard repo rule is Critical.
    - Secrets: a key, token, password or credential in the diff is always Critical.
    - Correctness and edge cases: null, off-by-one, empty collections, missing error handling for I/O and external calls.
    - Security: input validation at trust boundaries, parameterized queries, authorization before an action, error messages without internal details.
    - Destruction: deleted files, mass rewrites, removed data or configuration. Check that they are intended and replaced.
    - Consistency: a changed link, path or name without updating the places that refer to it (check with grep).
    - Maintainability: clear names, no needless complexity. Do not comment on formatting or lint.
    
    Each finding: file:line + severity + why + recommendation. Phrase an unclear intent as a question (Info).
    
    Severity: Critical blocks the commit. Warning should be fixed but does not block. Info is a question or a nice-to-have.
    
    ## Output
    
        ## Commit Review
        ### Critical
        ### Warning
        ### Info
        ### Summary
        X critical, Y warning, Z info. One sentence.
    
        VERDICT: approve
    
    Leave out sections without findings. The last non-empty line is exactly one of:
    
    - `VERDICT: approve` — no Critical finding.
    - `VERDICT: changes` — at least one Critical finding.
    
    Nothing after the verdict. The hook treats any other last line as a rejection.

    Translated from Czech and shortened.

  3. Isolate the reviewer from your global setup

    The hook passes this file to omp with --config. It turns off three things the reviewer would otherwise inherit: your user context file, the advisor (a second model that sees tool results, so it would see the diff) and memory, which would add notes from other sessions to the prompt. In the rest of our setup, memory is OMP's Mnemopi backend with retain, recall and reflect (exhibit 04 · Memory across sessions); in the gate it is "off".

    The fallback chain is keyed by the exact model id of the role, not by the role name. default: [] and the trailing [] end the chain: in our setup (OMP, Oct 2026), once a chain ran out, omp went on with the chain of the last model it tried.

    adapted from · hooks/commit-review.yml (K-B)
    # OMP settings overlay used only by hooks/pre-commit (passed with --config).
    # The user context file (your global rules) would reach the reviewer as extra instructions.
    disabledExtensions:
      - context-file:user:AGENTS.md
    # The advisor sees tool results, so it would see the diff.
    advisor:
      enabled: false
    # Memory would add notes from other sessions to the reviewer's prompt.
    memory:
      backend: "off"
    # Key = the exact model id of the commit-review role, not the role name.
    # An empty list ends the chain: omp fails and the commit stops.
    retry:
      fallbackChains:
        default: []
        provider-a/review-model:
          - provider-b/second-model
        provider-b/second-model: []

    Model ids replaced; the real chain has three models.

  4. Add the hook

    The hook writes the staged diff to a temporary file. An empty diff passes without a review. A missing reviewer or overlay stops the commit, and so does every outcome except the exact approve line.

    adapted from · hooks/pre-commit (K-B)
    #!/bin/sh
    # pre-commit: commit review by the OMP agent .omp/agents/commit-reviewer.md
    # Activate: git config core.hooksPath hooks
    # Passes only when the last line is exactly "VERDICT: approve"; any error stops the commit.
    # Model: role @commit-review (override: COMMIT_REVIEW_MODEL=<model|@role>).
    # Fallbacks: only the chain in hooks/commit-review.yml, never the default chain.
    # Skip the review for one commit: git commit --no-verify
    set -u
    
    root=$(git rev-parse --show-toplevel) || exit 1
    agent="$root/.omp/agents/commit-reviewer.md"
    overlay="$root/hooks/commit-review.yml"
    model="${COMMIT_REVIEW_MODEL:-@commit-review}"
    
    if [ ! -f "$agent" ]; then
      echo "commit-review: $agent is missing, commit stopped" >&2
      exit 1
    fi
    if [ ! -f "$overlay" ]; then
      echo "commit-review: $overlay is missing, commit stopped" >&2
      exit 1
    fi
    
    tmpdir=$(mktemp -d "${TMPDIR:-/tmp}/commit-review.XXXXXX") || exit 1
    trap 'rm -rf "$tmpdir"' EXIT HUP INT TERM
    
    if ! git diff --cached --no-color --no-ext-diff > "$tmpdir/staged.diff"; then
      echo "commit-review: git diff --cached failed, commit stopped" >&2
      exit 1
    fi
    [ -s "$tmpdir/staged.diff" ] || exit 0
    
    # The agent body without frontmatter is the reviewer's system prompt.
    awk 'NR == 1 && /^---[[:space:]]*$/ { fm = 1; next }
         fm && /^---[[:space:]]*$/      { fm = 0; next }
         !fm' "$agent" > "$tmpdir/prompt.md" || exit 1
    
    # Git Bash on Windows: hand omp native paths. Without cygpath the path passes unchanged.
    native() { cygpath -m "$1" 2>/dev/null || printf '%s\n' "$1"; }
    
    out=$(omp -p --no-session --no-skills \
      --model "$model" \
      --config "$(native "$overlay")" \
      --tools read,grep,glob \
      --max-time 10m \
      --system-prompt "$(native "$tmpdir/prompt.md")" \
      "Review the staged diff in the file $(native "$tmpdir/staged.diff"). Repo root: $(native "$root").")
    status=$?
    
    printf '%s\n' "$out" >&2
    
    if [ "$status" -ne 0 ]; then
      echo "commit-review: omp exited with code $status, commit stopped" >&2
      exit 1
    fi
    
    verdict=$(printf '%s\n' "$out" | tr -d '\r' | awk 'NF { last = $0 } END { print last }')
    case "$verdict" in
      "VERDICT: approve")
        exit 0 ;;
      "VERDICT: changes")
        echo "commit-review: changes requested, commit stopped (skip: git commit --no-verify)" >&2
        exit 1 ;;
      *)
        echo "commit-review: no valid verdict, commit stopped" >&2
        exit 1 ;;
    esac

    Messages translated from Czech; the Windows note is reworded.

  5. Activate it

    Git runs hooks from hooks/ only after core.hooksPath points there. The hook must keep LF line endings, because a CRLF line ending breaks the shebang, so set the line-ending rule before you stage the hook. Git also runs only an executable hook: on Linux and macOS that is the file mode, and the index entry must be 100755 so every clone gets it.

    adapted from · K-B setup
    printf 'hooks/* text eol=lf\n' >> .gitattributes
    chmod +x hooks/pre-commit
    git add .gitattributes hooks/pre-commit
    git update-index --chmod=+x hooks/pre-commit
    git config core.hooksPath hooks
  6. Decide who may skip it

    git commit --no-verify skips the review for one commit. Who may use it is the repo owner's decision, not the agent's.

How to check it works

  1. git ls-files -s hooks/pre-commit starts with 100755.
  2. Stage a harmless change and commit. The review prints in the terminal, and with no Critical finding the last line is VERDICT: approve and the commit goes through.
  3. Plant a finding: add DB_PASSWORD = "correct-horse-battery-staple" to a file, stage it and commit. Expect VERDICT: changes and commit stopped. Then unstage and delete the line.
  4. Break the model on purpose: COMMIT_REVIEW_MODEL=does-not-exist/none git commit -m test. omp exits with an error and the hook prints omp exited with code …, commit stopped.

What to watch out for

Sources

← All guides