Skip to main content

4take 🎯

Python License: BUSL-1.1 PyPI CI Status Linted with Ruff

Multi-Model AI Council for Code Review.
Four free models from four vendors review your code in one command, and their findings are ranked by how many raised each one.

On Greptile's own 50-PR bug benchmark, a council of free models found 37 of 50 bugs:
level with Greptile (41), ahead of Copilot and CodeRabbit, whose reviews date from July 2025. How it was measured

⚡ Quickstart • 🤖 AI Agent & MCP Integration • ✨ Features • 📏 Benchmark • 🏗️ Architecture • 💻 CLI Recipes • ⚙️ Configuration • 📊 OpenRouter Limits Guide

What is free, what is paid. 4take is source-available under the Business Source License 1.1; the license text decides.

Free to use A person using it for their own work, paid or unpaid. A solo developer working alone, as long as it does not run on a client's machines or accounts and no client requires or pays for it. Volunteer projects whose code is public under an OSI-approved license (details).
Paid Organizations: companies, public bodies, universities and non-profits, after a 30-day evaluation. Commercial license: takelicensing@gmail.com.
Models Only OpenRouter's free (:free) models. Free has limits: roughly 4 to 8 reviews a day on an OpenRouter account that has never been topped up, roughly 80 to 160 once it has (limits).

⚡ Quickstart

1. Install

# Recommended (Global tool)
uv tool install 4take
# or
pipx install 4take
# or
pip install 4take

4take needs Python 3.10 or newer. The python3 that comes with macOS is 3.9, and pip there fails with No matching distribution found for 4take. uv tool install 4take fetches a suitable Python by itself, whatever the system has; to get uv, run curl -LsSf https://astral.sh/uv/install.sh | sh or brew install uv.

Installing asks for nothing: pip and uv cannot prompt during an install. The key comes next, in step 2 or on your first review in a terminal.

2. Set Your OpenRouter API Key

Get a free key at OpenRouter, then save it once:

4take --set-key

It asks for the key without echoing it, checks it with OpenRouter and saves it to ~/.config/4take/config.json, readable only by you. The CLI and the MCP server both read it from there, so an MCP client needs no env block. Skip this step and the first review you run in a terminal asks for the key the same way, then carries on; without a terminal (CI, an agent, the MCP server) a missing key is an error that names --set-key.

Or set it for the current shell instead:

export OPENROUTER_API_KEY="sk-or-v1-..."

The environment variable wins over a key in the config file.

3. Run Review

# Review a single file
cat main.py | 4take

# Review your recent git changes
git diff HEAD~1 | 4take

# Review staged changes before commit
git diff --cached | 4take

🤖 AI Coding Agents & MCP Integration

4take works with AI coding agents (Claude Code, Cursor, Antigravity IDE, Windsurf, Cline, Roo Code, Codex) as an MCP server or a CLI: the agent sends its code to the council and gets the findings back ranked by agreement, to show the user.

An agent tends to keep the findings most reviewers agree on and drop the rest, which loses much of what the council catches (measured). So the MCP payload opens with every finding and its agreement, for the agent to show you.

                    ┌──────────────────────┐
                    │   AI coding agent    │
                    └──────────┬───────────┘
                               │ 1. sends its code or diff
                               ▼
                    ┌──────────────────────┐
                    │    4take council     │
                    └──────────┬───────────┘
          ┌────────────┬───────┴────┬────────────┐
          ▼            ▼            ▼            ▼
     reviewer 1   reviewer 2   reviewer 3   reviewer 4
     (vendor A)   (vendor B)   (vendor C)   (vendor D)
          └────────────┴───────┬────┴────────────┘
                               │ 2. findings grouped and ranked
                               ▼    by how many raised each one
                    ┌──────────────────────┐
                    │   report_for_user    │
                    │  e.g. "3/4 HIGH ..." │
                    └──────────┬───────────┘
                               │ 3. the agent shows every line and
                               ▼    checks each against the code
                             user

Option A: FastMCP Server (4take-mcp)

4take includes a native FastMCP server with real-time streaming progress notifications.

It ships as an optional extra, because its transport stack (starlette, uvicorn, pyjwt, cryptography) triples the size of an install that most CLI users never run a server from:

pip install '4take[mcp]'        # or: uv tool install '4take[mcp]'

The server reads the key 4take --set-key saved (step 2), so none of the configs below needs an env block. The server cannot ask for a key: run 4take --set-key in a terminal first. If you do give the client an env block with OPENROUTER_API_KEY, put your real key in it, because the environment wins over the saved key.

Claude Desktop Configuration (claude_desktop_config.json)
{
  "mcpServers": {
    "4take": {
      "command": "uvx",
      "args": ["--from", "4take[mcp]", "4take-mcp"]
    }
  }
}
Cursor IDE (.cursor/mcp.json)
{
  "mcpServers": {
    "4take": {
      "command": "4take-mcp"
    }
  }
}
Antigravity / VS Code / Roo Code / Cline (mcp_config.json)
{
  "mcpServers": {
    "4take": {
      "command": "uvx",
      "args": ["--from", "4take[mcp]", "4take-mcp"]
    }
  }
}

MCP Tool Exposed:

  • get_4take_opinion(code: str, force: bool = False, context_files: list[str] | None = None, context: str | None = None, auto_context: bool = False) -> str: Queries 4 models in parallel and returns structured JSON with model critiques, latencies, and status. context_files are paths the server reads from disk as reference context; context is the same as text; auto_context lets the server choose it for a diff. The payload opens with report_for_user, lines the tool description asks the agent to show you as they are: one per finding, most-agreed first, with how many reviewers raised it (- 4/4 CRITICAL `search_users` — SQL injection), under a header giving the measured calibration. The agent is told to act on findings three or more reviewers raised and to check those one or two raised against the code before accepting or rejecting them, so a single reviewer's find reaches you instead of being dropped unseen. The payload then carries the same consensus list as the CLI, plus status (ok / degraded / error), distinct_models and min_responses. degraded means fewer independent reviewers answered than were asked for — not that a warning was logged.

Option B: CLI Subprocess for Agents (JSON Output)

AI agents can pipe code directly into 4take via shell commands and receive clean, parseable JSON:

cat modified_file.py | 4take --format json --accept-risk --quiet

JSON Output Schema

{
  "version": "0.2.6",
  "timestamp": "2026-08-16T14:20:00Z",
  "config": {"models_requested": 12, "min_responses": 4, "timeout_seconds": 600, "strict": false},
  "summary": {
    "task_hash": "25d297f773c3",
    "responses_received": 4,
    "distinct_models": 4,
    "responses_failed": 0,
    "total_latency_ms": 3200,
    "context": {"files": ["src/mailer.py", "src/users.py (lines 40-90)"], "tokens": 812, "dropped": []}
  },
  "warning": null,
  "error": null,
  "consensus": [
    {
      "agreement": 3,
      "severity": "CRITICAL",
      "category": "INJECTION",
      "symbol": "search_users",
      "title": "SQL injection via the term parameter",
      "models": ["cohere/north-mini-code:free", "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
                 "poolside/laguna-s-2.1:free"],
      "severities": {"CRITICAL": 2, "WARNING": 1},
      "disputed": true
    }
  ],
  "responses": [
    {
      "model": "openrouter/nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
      "served_model": "nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free",
      "served_provider": "NVIDIA",
      "content": "### Code Review\n1. Found SQL Injection on line 8...",
      "finish_reason": "stop",
      "truncated": false,
      "duplicate_of": null,
      "latency_ms": 1150
    },
    {
      "model": "openrouter/poolside/laguna-s-2.1:free",
      "served_model": "poolside/laguna-s-2.1:free",
      "served_provider": "Poolside",
      "content": "### Security & Performance\n1. Use parameterized query for user_id...",
      "finish_reason": "stop",
      "truncated": false,
      "duplicate_of": null,
      "latency_ms": 1420
    }
  ],
  "errors": []
}

consensus is the list to read first: findings already grouped across models and ranked by agreement, the number of independent reviewers who raised each one. Grouping is lexical, so an agreement: 1 entry is not dismissed — it is a finding the others did not word the same way, or did not see. On the eval corpus an entry all four reviewers raised held a labelled defect 96% of the time, one raised by two or three 60%, and one raised by a single reviewer 41%: unanimous agreement is strong evidence, and anything less is a lead to verify.

category is the defect class the reviewers declared — one of INJECTION, CONCURRENCY, RESOURCE_LEAK, BOUNDS, ERROR_HANDLING, AUTH_CRYPTO, LOGIC, STRUCTURE, SIMPLICITY — and UNKNOWN when none did. It is read from the reviews, never guessed from their wording. It is a label for reading, not evidence: two reviewers filing the same defect choose the same category only about half the time, so grouping ignores it.

--strict narrows consensus to findings more than one reviewer raised, plus any CRITICAL finding however few raised it. Agents should normally leave it off: the single-reviewer entries are the ones they are asked to audit.

A council seats one model per vendor: two models from one vendor share training data, so their agreement is weaker evidence. A second answer from a seated vendor — or the same model again, as a router sometimes serves — stays in responses with duplicate_of naming the seat it shadows, and does not count towards agreement. It never takes a seat, even when no other vendor answers in time: the council then reports fewer seats (exit code 2, and warning says how many), and no spare is launched from a vendor that already holds one.

A review lasts as long as the council waits for its seats. Most free models reason before they answer, and each gets up to 32768 tokens to do it in, so a short file is usually reviewed in about a minute, while a large diff can take up to --timeout (600 s). On a large diff some models reason without ever reaching an answer and lose their seat at the per-model timeout, and only three or four free vendors reliably finish one, so the council often seats fewer than four reviewers (exit code 2). Measured on 8 real diffs of 3.5–29 KB, each run twice on 8 October: 8 of the 16 councils seated four reviewers, 5 seated three and 3 seated one or two; the median council took about 8½ minutes, and 6 ran to the 600 s limit. On the 28 short snippets of the eval corpus the median was about 50 s, and 27 of 28 councils seated four.

Ready-to-Use Agent Configuration Files:


✨ Features

  • 🚀 One Command: Pipe a file or a diff into 4take; four free models review it in parallel.
  • 🧠 Auto-Discovery Ranked on Evidence: Fetches and filters the free models on OpenRouter, then ranks them by the success rate and median latency they have actually delivered on your machine. The name-based heuristic (parameter count, context window) survives only as the cold-start prior for models you have not run yet.
  • 🔎 Four Review Axes: Correctness, security, structure and simplicity — not just defects. Every structural finding has to name its cost, so the breadth does not become a flood of taste.
  • 🤝 Consensus Grouping: Findings several models raised independently are grouped and ranked by how many raised them, so the cross-referencing is done for you rather than left in four parallel reviews.
  • 🔄 Fault-Tolerant Dynamic Rotation: A rate-limited model (HTTP 429) is retried with exponential backoff; one that keeps failing gives its seat to the next model in a pre-ranked pool of up to 12.
  • 🆓 Free Models Only: Requests go only to OpenRouter's free (:free) models, and a paid model is rejected before anything is sent. Free has limits: roughly 4 to 8 reviews a day on an account that has never been topped up, roughly 80 to 160 once it has (details).
  • 🛡️ Secret Redaction: Automatically scrubs API keys, passwords, and authorization tokens before sending payloads over the wire. The code itself still goes to third-party free endpoints (privacy).
  • 🚦 Cross-Process Rate Limiter: Shared file-locked token bucket ensures concurrent terminal sessions or background agents never trigger rate limit bans.
  • 🎨 Terminal and JSON Output: Markdown rendered in the terminal, or --format json for scripts and CI.

📏 Against the Paid Reviewers: Greptile's Benchmark

Greptile published a benchmark of 50 pull requests from five open-source projects (Cal.com, Discourse, Grafana, Keycloak, Sentry), each with one labelled bug, together with the comments five commercial AI reviewers left on them in late July 2025. Fifteen months later 4take reviewed the same 50 diffs using only OpenRouter's free models (commit 2f07956, 8 October 2026). Its reviews were graded blind, against a rubric and an analysis written down before the run.

reviewer benchmark standard, 50 PRs strict, 42 verified labels
4take, free models 37 (74%) 30 (71%)
Greptile 41 32
Bugbot 29 25
GitHub Copilot 26 19
CodeRabbit 22 16
Graphite 3 2
  • Level with Greptile, not ahead of it. Neither gap is statistically significant (exact McNemar test, paired by PR: p = 0.42 and 0.77). 4take is significantly ahead of Copilot and CodeRabbit (p ≤ 0.02), and ahead of Bugbot without significance.
  • What counts. Benchmark standard credits a finding at the right place with a related concern, as the benchmark credited the commercial tools; their figures in that column are the ones Greptile published in its table. Strict credits only a finding that names the labelled bug, on the 42 labels an audit did not show to be wrong; the commercial tools' strict figures come from a blind re-grade of their published comments.
  • Who graded. Two Claude models (Opus and Sonnet), each in an isolated session that held only the review packets and the rubric, without knowing which run a packet came from. They agreed on 95 of 100 packets (Cohen's κ 0.88); a third settled the disputed seats.
  • What it measures, and what it does not. Recall of one labelled bug per PR. A council also says a lot more: a median of 17 findings and 8 consensus entries per PR, which the benchmark neither credits nor penalises. Read them by agreement (next section).
  • What it is not. One recording of a stochastic free pool, built and graded by the author of 4take. A strong signal, not proof. The pool has moved on since: two of the four most-seated models of that run (apodex-1.1-mini, ling-3.0-flash-sante) have left the free tier, so the same councils cannot be seated again.
  • Fifteen months apart, on public data. The commercial reviews were written in July 2025 and 4take's in October 2026, and models improved in between: those tools would probably score higher today. The PRs and the benchmark's bug descriptions have been public since 2025, so a model trained later may have seen them, and nothing here rules that out. Both effects favour 4take.
  • Where it is weakest. Large Java diffs: Keycloak 5 of 10. On two of them, and on one Sentry diff, only one free vendor reasoned through to an answer. A council took a median of six minutes on these diffs and up to the 600 s timeout.

The pre-registrations, the graders' instructions, every departure from the plan and the results are in docs/benchmark. The diffs are other projects' code, so the recordings and the graded packets are not.


🧪 Read the Council Yourself

Four free models found more than a commercial model alone. A pre-registered test on the same 50 PRs, in October 2026, gave Gemini 3.8 Flash (thinking high) each diff alone, and again with the council's findings as notes; three runs each, graded blind:

bugs found, of 50
the council's findings, read directly 37
Gemini 3.8 Flash alone 24
Gemini 3.8 Flash given the council's findings 26

The council caught 15 bugs the model missed, and the model 2 that the council missed (exact McNemar p ≈ 0.002). This comparison was not pre-registered, and it credits the council's best reviewer per PR against the model's majority of three runs. Both sides were recorded in the same month on the same diffs, so the time gap of the commercial comparison does not arise here, and any exposure of the PRs in training data applies to both.

An agent that reads the findings keeps only part of them. Given the council's findings as notes, the model caught 26, not 37; against the model alone that is no difference shown (p = 0.69). In an exploratory breakdown it took up 3 of the 4 bugs only the council caught that three or four reviewers shared, and none of the 11 that one or two raised: it kept the consensus and dropped the long tail.

So 4take puts every finding in front of you. Over MCP the user sees only what the agent relays, so the payload opens with report_for_user: one line per finding with its agreement (3/4), severity, symbol and title. The tool description asks the agent to show those lines, to act on agreement 3 or more, and to check agreement 1 or 2 against the code instead of dropping it. The CLI prints the same ranking to your terminal.

Agreement tells you where to look first. On the eval corpus, over three recordings with four reviewers seated, a finding all four raised held a real, labelled defect 96% of the time; one raised by two or three held one 60% of the time; one raised by a single reviewer 41%. A single reviewer's finding is a pointer to check, not a verdict, and four in ten of them are real.


🔎 What the Council Reviews

The code you pipe in was, more often than not, written by a capable model. Syntax errors and typos are already gone, and a linter has effectively already run. So the reviewers are asked for the four things a linter cannot see, in this order:

Axis What it catches
Correctness Wrong results, unhandled edge cases, broken invariants, races, resource leaks, error paths that swallow a failure.
Security Injection, path traversal, missing authorization, unsafe deserialization, secrets in code or logs, non-constant-time secret comparison.
Structure Responsibility in the wrong layer, a boundary that leaks, coupling that makes one behavioural change require edits in several places, logic duplicated where it will drift.
Simplicity Indirection with one implementation, a config knob nothing sets, a hand-rolled version of what the stdlib already provides, control flow that can be halved.

The last two axes are the reason the reviews are worth reading on code that already works, and they are also the easy way to ruin a review tool. A structural finding is only reported when the model can name its cost — the change that becomes expensive, the two places that will drift apart, the code that disappears once it is fixed. "Consider extracting a helper" is not a finding. An empty review is a valid result.

4take reviews code. A design question or a plan piped in is refused before any request goes out, because the reviewers are told their input is code and would all answer NO_ISSUES_FOUND. Pass --force-review to send one anyway.


🧭 Giving the Council Context

A diff shows what changed, not what depends on it. A function that now returns None where it used to raise, a route added without the check its neighbours make, a helper that already exists under another name: none of these is visible in the changed lines alone. Sending the whole repository is no answer either. It multiplies every request by the number of reviewers, and a change buried in its surroundings is reviewed worse, not better.

So 4take takes a few related files as reference context, which the reviewers read but do not review:

git diff -W | 4take --context-file src/mailer.py --context-file src/users.py:40-90
  • Send what the change touches: each changed function in full (git diff -W includes it in the diff), its direct callers, the functions it calls, the types, schemas and config it relies on, related tests. Most relevant first.
  • A file the diff itself changes can be sent too, when the reviewers need the rest of it (the lock every other method takes). It is read as it stands after the change and marked that way, so the reviewers know the changed code in it is the code under review, not a second copy. The smallest free models sometimes report it as defined twice anyway.
  • path:START-END sends only those lines. Context is capped at 16000 tokens by default (--context-budget, or context_budget_tokens in the config); a file that does not fit is left out whole and named, never cut.
  • Findings stay on the code under review. A defect the change causes in a caller is reported at the changed symbol, with the caller named.
  • Context is redacted like the input, and read only from under the working directory (or FOURTAKE_CONTEXT_ROOT). .env files, keys, certificates, .git/ and .ssh/ are refused.
  • --dry-run prints exactly what would be sent, after redaction, and contacts nothing.
  • Context is read from disk as it is now: to review gh pr diff 42, check out the PR branch first.

Letting 4take choose

git diff -W | 4take --auto-context

With a diff, --auto-context (MCP: auto_context=true) finds the context itself, by name:

  • code that still uses a definition or a string value the change removes (a renamed function, an event name, a cache key);
  • callers of the functions the change edits, in files that import their module;
  • the rest of a class the change edits, outside the diff (the lock every other method takes);
  • where the functions, types and constants the new lines use are defined, and the module they read settings from.

It is lexical, names and indentation rather than a parser, so it works the same in every language and is wrong the way grep is wrong. A name found in more than 25 files is taken for a word and not followed; docs, lock files, saved diffs, data files, docstrings, comments and imports never count as a use. It sends at most 8 excerpts, after any --context-file, within the same budget, and --dry-run shows what it chose. The JSON summary's context.auto and the text report say why each excerpt was picked. A hunk is found in the file where it now stands, so an unstaged edit above it does not throw the selection off; a file the diff does not describe (a diff of two other revisions) is left out rather than excerpted at the wrong lines.

Measured on the 14 cross-file cases of evals/corpus_ctx, put in one repository so that every other case's files are distractors. Offline, every diff selects all of the context chosen for it by hand and nothing from the other 13 cases. Recorded three times, the councils then named 12 of 12 cross-file defects in every recording, as with hand-picked context; the clean controls drew 3 to 8 findings against 4 to 9, and the median council took 39 s against 38 s (details). The corpus's context is a few small files that are easy to find by name, so this shows auto context matching the hand-picked choice on the cases it was built for, not on a large repository. It stays opt-in: it sends more of your code to the providers than the diff you piped.

A review without context is sent exactly the prompt it was sent before, so the measurements below still describe it.


🏗️ How It Works

                           ┌───────────────────┐
                           │   Host / Caller   │  (Developer, Claude Code, Antigravity, Cursor)
                           └─────────┬─────────┘
                                     │ pipe code / stdin
                                     ▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ 4take Core Engine                                                           │
│                                                                             │
│  1. Safety Sanitizer      — Redact secrets & sensitive credentials          │
│  2. Token Estimator       — tiktoken context verification                   │
│  3. Model Auto-Discovery  — Heuristic prior, then measured success rate     │
│  4. Free-Model Guard      — Hard check: only :free models, paid refused     │
│  5. Token Bucket Limiter  — Shared cross-process rate governor              │
│  6. Async Parallel Pool   — Query N models concurrently                     │
│  7. Dynamic Rotation      — Auto-swap failing models on HTTP 429 / errors   │
└─────────────────────────────────────────────────────────────────────────────┘
                                     │
                                     ▼
                      ┌─────────────────────────────┐
                      │   OpenRouter API Gateway    │
                      └──────────────┬──────────────┘
                                     │
           ┌─────────────────────────┼─────────────────────────┐
           ▼                         ▼                         ▼
   NVIDIA Nemotron              Poolside Laguna           Cohere North
   (120B / 550B:free)           (S / XS:free)             (mini-code:free)
                                     │
                                     ▼
                   ┌───────────────────────────────────┐
                   │  Formatted Markdown / JSON Output │
                   └───────────────────────────────────┘

💻 CLI Usage & Recipes

1. Review Staged Git Changes Before Committing

git diff --cached | 4take

2. Review a Specific GitHub Pull Request

gh pr diff 42 | 4take

3. Use Custom Free Models from OpenRouter

4take --models="nvidia/nemotron-3-nano-omni-30b-a3b-reasoning:free,poolside/laguna-xs-2.1:free,cohere/north-mini-code:free,dots-studio/dots-3-note-preview:free"

4. Output as JSON for Automation

cat src/engine.py | 4take --format json --accept-risk > review.json

5. Save Report to a Markdown File

cat complex_algorithm.py | 4take --output-file review_report.md

6. Set Minimum Required Responses

# Succeed if at least 2 models respond
cat script.py | 4take --min-responses 2

7. Review a Diff Together with the Code It Touches

# Callers and types are read as reference context; only the diff is reviewed
git diff -W | 4take --context-file src/mailer.py --context-file src/models.py:10-60

# Or let 4take find them
git diff -W | 4take --auto-context

# See exactly what would be sent, without sending it
git diff -W | 4take --dry-run --context-file src/mailer.py

⚙️ CLI Options Reference

Option Env Var Config Key Default Description
--models — default_models ["auto"] Comma-separated list of free OpenRouter models (or auto).
--min-responses — min_responses 4 Minimum successful responses required.
--timeout — timeout 600 HTTP request timeout in seconds.
--format — — text Output format: text (rendered Markdown) or json.
--output-file — — — Write output to specified file path.
--quiet — — False Disable live progress spinners and status logs.
--accept-risk FOURTAKE_ACCEPT_RISK=1 — False Silence external LLM privacy warning banner.
--force-review — — False Review the input even though it does not look like code.
--context-file FOURTAKE_CONTEXT_ROOT — — Related code the reviewers read but do not review, PATH[:START-END], repeatable, most relevant first. Read only under the working directory, or under FOURTAKE_CONTEXT_ROOT.
--auto-context — — off With a diff: also send what the change breaks or relies on, chosen by name, after any --context-file. See Letting 4take choose.
--context-budget — context_budget_tokens 16000 Most reference context one review may carry, in tokens. Files that do not fit are left out whole and named.
--dry-run — — False Print exactly what would be sent, after redaction, and exit without contacting any model.
--strict — — False Report only findings more than one reviewer raised, plus any CRITICAL one. Text output also omits the individual reviews and says how many findings it hid; errors are always shown.
--no-strip-secrets — — False Disable automatic redaction of API keys/passwords.
--show-redactions — — False List which secret patterns were redacted before sending.
--init-config — — — Create template config at ~/.config/4take/config.json.
--set-key — — — Ask for the OpenRouter API key, check it and save it to the config file.
--per-model-timeout — — half of --timeout, min 60 Abandon one model after N seconds and rotate in a replacement.
--no-cache — — False Bypass the local 24h SHA-256 review cache.
--clear-cache — — — Purge ~/.cache/4take/reviews/ and exit.
--no-telemetry — telemetry False Do not record model latency and success rate locally.
--clear-telemetry — — — Delete ~/.cache/4take/stats.json and exit.
--version — — — Print 4take version and exit.

🚦 Exit Codes

stdout carries only the payload — every log, spinner and warning goes to stderr, so 4take --format json | jq and json.loads(stdout) are safe.

Code Meaning
0 The council met --min-responses.
2 Partial result: at least one review, fewer than --min-responses. See warning.
1 Total failure: no model responded. See error.

⚙️ Configuration File

Generate a starting configuration file:

4take --init-config

This creates ~/.config/4take/config.json:

{
  "default_models": [
    "auto"
  ],
  "providers": {
    "openrouter": {
      "api_key": "YOUR_OPENROUTER_API_KEY_HERE"
    }
  },
  "rpm_limit": 10,
  "min_responses": 4,
  "timeout": 600,
  "provider_concurrency": {
    "openrouter": 4
  }
}

📊 OpenRouter Free Tier & Rate Limits

By default, 4take dynamically discovers and uses OpenRouter's top free models (:free). A review is not four requests. It launches six models at once, four seats and two spares, so that one slow model cannot stall it, and every retry after a 429 and every replacement for a failed model is one more request. On recorded runs a review took 6 requests when the pool was quiet and a median of 11 to 12 on a busy day, when about half of them failed:

  • An account that has never been topped up: OpenRouter limits free-model use to 50 requests per day: about 4 reviews on a busy day, up to 8 on a quiet one.
  • An account that has been topped up with $10 or more at least once: the limit rises to 1,000 requests per day: about 80 reviews on a busy day, up to 160 on a quiet one.

OpenRouter sets these limits and can change them. They are why 4take is free to run but not unlimited.

When the day's allowance is spent, 4take says so and stops. Before a review it reads the counter from your key (GET /api/v1/key, which costs no quota), and a 429 during a review makes it read the counter again. With none left it launches nothing more, waits only for requests OpenRouter had already accepted, and says how many requests were used, when the limit resets (00:00 UTC, with the hour in your own time zone and how long that is away) and what raises it, instead of waiting out the provider's 429s for ten minutes. The same sentence is the error of the JSON output and of the MCP result, or a warning when some answers came in first. If the counter cannot be read, the review goes ahead as before.

If a model hits an HTTP 429 rate limit, 4take retries it up to eight times with exponential backoff and jitter, then rotates in a candidate from its 12-model backup pool. A model whose provider refused every request of the last 72 hours is left out of the pool until those refusals age out. The 429s come from the free pool's global load, not from your own usage, so a quiet hour is the best way to get full councils.


🛡️ Privacy & Security

  • Automatic Secret Redaction: Matches and sanitizes API keys, auth tokens, and credentials from code snippets before sending. Known key formats (OpenAI/OpenRouter, Stripe, GitHub, GitLab, Slack, Google, AWS, Groq, Telegram, JWTs, PEM keys, credentials in connection strings) are redacted wherever they appear. A value assigned to a secret-named key is redacted when it is quoted, or where config puts an unquoted one: at the end of its line, or right after a tight KEY=. A value code computes — token = await fetchToken(), password: string — is left alone, so the council reviews the code as written. It is a safety net, not a guarantee: --dry-run shows exactly what would be sent.
  • Privacy Warning Banner: Alerts users when code leaves local machine (silence with --accept-risk or export FOURTAKE_ACCEPT_RISK=1).
  • Free endpoints are run by third parties. Your code goes to OpenRouter and from there to whichever provider serves the free model. Providers set their own policies, and some may retain prompts or train on them. OpenRouter has account privacy settings for this, separate for free and paid models: check them, and the provider's terms, before you send code you cannot share. --dry-run shows exactly what would be sent. On first use tiktoken may also download its tokenizer vocabulary from its default host; no code is sent there.
  • Context Length Safeguard: Estimates input size with tiktoken and checks it against each model's real context window from the OpenRouter catalog, reserving room for the reply.
  • Reference Context Guard: Context files are read only from under the project root, after symlinks are resolved. Credential files, .git/, .ssh/, binary and oversized files are refused, and everything sent is redacted. In the MCP server the paths come from an agent, so a server started from / or the home directory reads no paths unless FOURTAKE_CONTEXT_ROOT names the project.

🛠️ Development & Testing

# Clone the repository
git clone https://github.com/aleksbuss/4take.git
cd 4take

# Install dependencies
uv sync --all-extras --dev

# Run test suite
uv run pytest

Measuring the council

The claim that several models find more than one does is measured, not asserted. evals/ holds 28 snippets with 37 labelled defects — SQL injection, TOCTOU, off-by-one, resource leaks, unchecked None, races, traversal — plus two deliberately clean controls.

The labels cover defects only, so the eval measures the correctness and security axes and says nothing about structure or simplicity. The clean controls are clean of defects; now that the reviewers are asked for structure too, a finding raised on one may be legitimate. That column is still the check on a model rewarded for reporting everything, but it has to be read rather than counted, and it is expected to be non-zero.

uv run python evals/score.py          # scores the checked-in recordings, offline and free
uv run python evals/record.py         # re-records them against live models (the only network step)

The report gives council recall against the best single model that saw the same corpus, per-model label precision, noise on the clean controls, the pairwise agreement matrix, how often a defect several models found lands in one consensus entry, how often two findings one model wrote separately share an entry, and how often an entry at each agreement holds a labelled defect. The scoring runs in CI against the recorded fixtures, so it stays hermetic and costs nothing.

Free models are stochastic, so one recording of the corpus is one draw. It is recorded three times — evals/recordings (scored in CI), evals/runs/2 and evals/runs/3 — and uv run python evals/score.py --runs evals/recordings evals/runs/2 evals/runs/3 prints the spread:

measurement range over 3 recordings
council recall 97–100%
best single-model recall 91–94%
lift from the council +4 to +6 points
findings raised on the two clean controls 1–7
agreeing models landing in one consensus entry 56–69%
... or in at least one entry that holds all of them 81–94%
pairs of one model's own findings sharing an entry 22–42%

The lift is real and modest, and it held in every recording. Recall alone is easy to game — a model that reports everything scores well on it — which is what the label-precision and clean-control columns are there to expose.

The grouping rows are the weak point, and the first two reward merging: an entry that swallows a whole snippet groups every defect in it. The third row is their check, because a model that writes two findings mostly means two defects. Grouping was at 74% while the reviewers were asked for defects alone; asking them for structure and simplicity too widened the vocabulary they write in, and grouping is lexical, so two models can raise one finding in different words and still land in two entries. It also used to depend on the order the models answered in: shuffling one recording's responses moved it anywhere from 44% to 69%. It is now single linkage, which no order can change, with the threshold raised against the chaining single linkage brings. Pooled over the three recordings, 61% of agreed defects land in one entry, as before (62%), and 31% of one model's pairs of findings share an entry, against 44%. A closed category vocabulary was tried as the signal that does not dilute with wording and carried little: reviewers filing one labelled defect chose the same category in only 51–59% of pairs. Used as a veto it split real agreement; used to lower the threshold it merged distinct defects about as often as agreeing ones. So read an agreement: 1 entry on your own judgement rather than as a weak signal.

What an agreement value is worth, pooled over the three recordings, in which every council seated four reviewers (score.py --runs prints this table):

agreement entries holding a labelled defect
4, every reviewer 96% (68 of 71)
3 58% (11 of 19)
2 62% (16 of 26)
1 41% (50 of 123)

Only unanimous agreement is strong evidence. Two or three reviewers agreeing is a lead to verify: those rows are small, cannot be told apart, and about four in ten of their entries held no labelled defect. A single reviewer's finding is right often enough to be judged rather than dismissed. Every share is a floor, because the snippets hold defects nobody labelled.

What reference context adds

The corpus above is self-contained snippets, so it cannot say anything about defects that live between files. evals/corpus_ctx holds 12 of those: a function that now returns None to callers that dereference it, a unit change a watchdog still compares in seconds, an event renamed under its subscribers, a helper that runs its arguments through a shell. It also holds three defects visible in the diff itself, to check that context does not cost them, and two clean controls where the diff looks suspect alone and the context shows it is fine. Each case is recorded twice, without and with its context, back to back, and once more with the context --auto-context chooses from a repository holding every case's files:

uv run python evals/record.py --corpus ctx   # 28 councils, roughly 180 requests of free-tier quota
uv run python evals/record.py --corpus ctx --mode auto   # 14 councils, context chosen by --auto-context
uv run python evals/score.py --ctx           # cross-file and local recall, noise and time, side by side

Each cross-file defect is counted twice. It is flagged by a finding that warns about it in any words, since a review of the diff alone can infer that callers may break, and named only by a finding that names what the context holds, such as the caller a change breaks; a test keeps every naming keyword out of the reviewed diff. The flagged row is the fair comparison of how many breaks each mode caught, and the gap between the two rows is what the context adds beyond inference. The report also counts findings the reviewers located in the context, which the prompt forbids, and separates a right finding filed under the caller from noise.

The corpus has been recorded three times: evals/recordings_ctx, evals/runs_ctx/2 and evals/runs_ctx/3. score.py --ctx given all three directories prints the table below:

Without context With context Auto context
Cross-file defects flagged 5–7 of 12 12 of 12 in every recording 12 of 12 in every recording
… naming the affected code 0–1 of 12 12 of 12 in every recording 12 of 12 in every recording
Local defects found 2–3 of 3 3 of 3 2–3 of 3
Findings on the two clean controls 4–6 4–9 3–8
Median council time 31 s 38 s 39 s

Read it with its limits:

  • Written for this feature. Every case's context is exactly the right few files. So it shows that the mechanism works, not how much it adds to real changes, where the chairman has to choose those files.
  • The matcher was corrected after the first recording and frozen before the other two. The corrections are listed under _about in evals/labels_ctx.json. Before them the first recording read 7/12 → 10/12 flagged and 3/12 → 10/12 named.
  • The matcher still errs in favour of the review without context. Read by hand, each of the two later recordings credits that review with one defect it did not flag: a "duplicate check" in one, a cache-miss finding in the other. The 2/3 local result is the matcher's miss, not the reviewers': they found the defect and located it elsewhere. The one naming without context, in recordings 2 and 3, is a guess: "if run_git uses shell=True".
  • Noise on clean code went up with context, not down. The controls were built to provoke "no input validation" and "no lock" findings that the context disproves. The context removed most of those. But when it repeats the file after the change, reviewers take the changed method for a second definition of itself: 2 to 5 such findings per recording, most of the rise.
  • Marking that file helped the stronger reviewers only. A context file the diff changes is now headed as the file after the change. Across three further recordings of the two cases where this applies (evals/runs_ctx/overlap), duplicate findings fell from 8 to 5.
    • Every remaining one came from the smallest model seated, nemotron-3-nano-omni-30b, which made the claim in two of the three.
    • north-mini-code made it before the mark, and was seated twice after it without doing so.
    • Such a finding comes from one reviewer, so its agreement is 1.
  • Auto context was recorded days after the other two columns, from a different free pool, so the columns are three separate draws rather than one pool read three ways.
    • Its context is the hand-picked files in 12 cases. In the two where that file is the changed one, it sends the file without the lines the diff shows.
    • There, the "method defined twice" claims went from 8 across the hand-picked recordings to 0. But nemotron-3-nano-omni-30b, which made 7 of the 8, was never seated in that case under auto context. north-mini-code made the claim once with the whole file, and sat once without it and did not. A hint, not a result.
    • Its 2 of 3 local defects is a council that seated one reviewer: six models timed out at once and two were rate-limited.

📄 License

4take is source-available under the Business Source License 1.1. The license text decides; this is what it says in plain words:

  • Free for you. A person using 4take for their own work, paid or unpaid, may use it in full, at no cost. So may a self-employed developer who works alone (no employees, no subcontractors, no company with staff under their control), in their own name or through a company only they own: on software they own, and for clients, as long as it does not run on the client's machines or accounts and the client does not ask for it, require it or pay for it.
  • Free for volunteer projects, in their CI and accounts: public code under an OSI-approved license, not owned or controlled by any organization other than a non-profit that stewards open source, and not sold or the basis of anything sold by its owners, its maintainers or an organization paying people to maintain it. Donations, sponsorship and grants do not make a project commercial. Volunteers contributing in their own time are covered as individuals.
  • Organizations need a commercial license: companies, public bodies, universities and other non-profits. That covers anyone using 4take in their work for the organization, and any use the organization sets up, requests, requires, pays for or runs (in its CI, on its servers, under its accounts), even when a contractor does the running. Reviewing any software with 4take is production use, on a laptop or in CI alike. An organization may evaluate 4take for 30 consecutive days, once for its whole group of companies, and anyone may test and develop 4take itself.
  • No hosted service. Offering 4take or what it does to others as a hosted or managed service is not covered by any of the free grants.
  • Every version converts to the Apache License 2.0 four years after it is first published.

For a commercial license, write to takelicensing@gmail.com. The code is open to read, change and redistribute under the same terms. A pull request can only be merged once its author agrees that the contribution may be distributed under this license and under commercial ones.

Metadata

Release files for 4take 0.2.6

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for 4take 0.2.6
File Size Uploaded Provenance Actions
4take-0.2.6.tar.gz 303.9 kB With provenance Details for 4take-0.2.6.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for 4take 0.2.6
File Interpreter ABI Platform Actions
4take-0.2.6-py3-none-any.whl Python 3 none any Details for 4take-0.2.6-py3-none-any.whl

Total release size: 430.7 kB

Release files / 4take-0.2.6.tar.gz

Download URL 4take-0.2.6.tar.gz
Size 303.9 kB
Tags Source
SHA-256 checksum
How to use checksums
4aa37ca8ea75c4b8d0e3f7ce09045e9dfbc9bb9a1414d209d395bef2ba089655
BLAKE2b-256 checksum
How to use checksums
43f38d65566bd1a3f4c46676ebf9669bdae9123e7324911feef9c6b7159daee3
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Release files / 4take-0.2.6-py3-none-any.whl

Download URL 4take-0.2.6-py3-none-any.whl
Size 126.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1525b2a2cfacf3106b2083c06a5b8a3d2f00712f925df6faf4fa1983a546babb
BLAKE2b-256 checksum
How to use checksums
0bd1c3f751a363190340fad0d0e6f2d936df0617c7fc3fa1584c3f8bbb6116bf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

Security for 4take 0.2.6

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for 4take-0.2.6.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 27, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page