Skip to main content
Glama

understudy

Let Claude Code hand routine coding tasks to a local model, with your tests as the gate and Claude as the reviewer.

understudy is an MCP server. It gives Claude Code (or any MCP client) a delegate tool: Claude describes a small, well-defined change, a local model running on your machine does the work in an isolated git worktree, and your project's tests are run after every attempt. When the tests pass, Claude gets back the diff to review, and nothing reaches your code until Claude (and you) accept it.

The point is to spend your Claude usage on the hard parts (planning, tricky code, review) and let a free local model do the typing for the routine ones.

flowchart LR
    C[Claude Code] -- "delegate(task, test_command)" --> U[understudy]
    U --> W[isolated git worktree]
    W --> M[local model<br/>via OpenCode + Ollama]
    M --> T{tests pass?}
    T -- "no: errors fed back<br/>(up to N rounds)" --> M
    T -- yes --> R[diff + test output<br/>+ warnings]
    R --> C
    C -- "accept(id)" --> G[one commit on your branch]
    C -- "revise(id, feedback)" --> M
    C -- "discard(id)" --> X[thrown away]

Why

  • Save quota. The local model writes the code. Claude only reads a diff, instead of reading files, writing code and re-running tests itself.

  • Safe by construction. All work happens in a separate git worktree on its own branch. Your working tree is never touched until accept, and a merge conflict is rolled back automatically.

  • Tests are a hard gate. Failing test output is fed straight back to the local model, for free, until the tests pass or the round limit is reached.

  • Claude stays the reviewer. The Claude you're already talking to reviews the result, so no second Claude session is needed. understudy also flags the classic small-model mistakes: when existing tests or functions were deleted, it says so, because "tests pass" can just mean "the failing test was removed".

  • A failed revision never loses a passing version. If revise breaks the tests, the change is rolled back to the last version that passed.

Related MCP server: cc-delegate

Requirements

  • Python 3.10+ and git

  • Ollama with a coding model pulled

  • OpenCode, configured with your Ollama models (understudy drives the model through OpenCode's file-editing tools). Any other command-line coding agent can be used instead; see Using another builder.

Setup

1. Give your model enough context

Ollama runs models with a 4,096-token context by default, which is too small for a coding agent: the model loses its instructions and stops without making changes. Create a variant with a larger context window (this doesn't download anything and leaves the original model untouched):

curl http://localhost:11434/api/create -d '{"model": "devstral-small-2:24b-16k", "from": "devstral-small-2:24b", "parameters": {"num_ctx": 16384}}'

Alternatively, set OLLAMA_CONTEXT_LENGTH=16384 in the environment of the Ollama server.

2. Add the model to OpenCode

In ~/.config/opencode/opencode.json, list the model under your Ollama provider:

{
  "provider": {
    "ollama": {
      "npm": "@ai-sdk/openai-compatible",
      "options": { "baseURL": "http://localhost:11434/v1" },
      "models": {
        "devstral-small-2:24b-16k": { "name": "Devstral Small 2 (16k)" }
      }
    }
  }
}

3. Install understudy and add it to Claude Code

pipx install git+https://github.com/Barry-Lenhart/understudy.git
claude mcp add understudy --scope user \
  -e UNDERSTUDY_MODEL=ollama/devstral-small-2:24b-16k \
  -- understudy-mcp

A delegation can take several minutes on local hardware. Claude Code's default MCP tool timeout is generous, but if you have set MCP_TOOL_TIMEOUT yourself, make sure it allows for that (e.g. 1800000, 30 minutes in ms).

Then, in any git project, ask Claude something like:

Use understudy to add an is_palindrome(text) function to textutil.py with a test. Run the tests with pytest -q.

Tools

Tool

What it does

delegate(task, test_command="", repo_path="")

Creates a worktree, has the local model do the task, and runs test_command after each attempt (failures go back to the model). Returns the diff, test output, any warnings, and an id.

revise(id, feedback)

Sends review feedback; the model tries again and the tests re-run. If every attempt breaks the tests, the previous passing version is kept.

accept(id, commit_message="")

Squash-merges the change into your current branch as one commit and cleans up. Refuses if you have uncommitted changes.

discard(id)

Deletes the worktree and branch.

status()

Lists open delegations.

Result statuses

Status

Meaning

tests_passed

The change passes the tests. Review the diff and warnings.

tests_failing

Still failing after the maximum number of rounds.

built_untested

No test_command was given. Review extra carefully.

no_changes

The model didn't change anything.

builder_failed

The model/tool errored before making changes (see builder_output).

revision_made_no_changes

A revise didn't change anything.

revision_failed

A revise broke the tests, so it was rolled back to the previous passing version.

Test commands

test_command is a shell command run inside the worktree. Worktrees don't contain your virtualenv, so use {repo} to point at the main checkout, e.g. {repo}/.venv/bin/python -m pytest -q. The path is also available as $UNDERSTUDY_REPO.

Configuration

All settings are environment variables (set them with -e in claude mcp add).

Variable

Default

UNDERSTUDY_MODEL

ollama/qwen3:8b

Model passed to OpenCode (provider/model).

UNDERSTUDY_MAX_ROUNDS

3

Build → test attempts per call.

UNDERSTUDY_BUILDER_TIMEOUT

900

Seconds per model run.

UNDERSTUDY_TEST_TIMEOUT

600

Seconds per test run.

UNDERSTUDY_WORKTREE_DIR

~/.cache/understudy/worktrees

Where worktrees are created. Must be outside .git.

UNDERSTUDY_BUILDER

opencode

opencode or command.

UNDERSTUDY_BUILDER_COMMAND

Command for the command builder.

UNDERSTUDY_MAX_DIFF_CHARS

20000

Longer diffs are truncated in results.

Using another builder

Set UNDERSTUDY_BUILDER=command and UNDERSTUDY_BUILDER_COMMAND to any command that edits files in the current directory. {prompt_file} is replaced with a file containing the instructions and {workdir} with the worktree path. For example, with aider:

UNDERSTUDY_BUILDER_COMMAND='aider --model ollama_chat/devstral-small-2:24b-16k --yes --no-auto-commits --message-file {prompt_file}'

Choosing a model

From testing on a consumer GPU:

Model (16k context)

Typical small task

Notes

devstral-small-2:24b

~100 s

Clean, targeted edits; follows review feedback. Recommended.

qwen3:8b

~20–30 s

Much faster, but edits are sloppier and it often ignores feedback.

Local models are only as good as they are: understudy's job is to make them safe to use (isolation, test gate, warnings, rollback) and to keep Claude in the reviewer's seat. Give them small, specific tasks.

How it works

  • Each delegation gets a branch understudy/<id> and a worktree under ~/.cache/understudy/worktrees/. Bookkeeping lives in .git/understudy/, so it never shows up in git status.

  • The model's changes are committed in the worktree before tests run, and anything the test run leaves behind is cleaned up, so build artifacts never end up in the diff.

  • accept squash-merges the branch as a single commit authored by you.

Development

git clone https://github.com/Barry-Lenhart/understudy.git && cd understudy
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/pytest
.venv/bin/ruff check . && .venv/bin/ruff format --check .

The tests use a fake builder, so they need neither Ollama nor OpenCode.

License

MIT

Available Tools

5 tools
acceptA

Merge a delegation into the current branch as a single commit (the task text is used as the message unless commit_message is given), then clean up its worktree. Refuses if the working tree has uncommitted changes.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
repo_pathNo
commit_messageNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the sparse annotations (destructiveHint=false), the description discloses several meaningful behaviors: the merge is squashed to a single commit, the task text is the default message, the delegation worktree is cleaned up afterward, and the call refuses under dirty-tree conditions. These add real operational context the annotations do not provide. The word 'clean up' sits near the line of contradicting destructiveHint=false, but since content is preserved via the merge, it is not a hard contradiction.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two compact sentences carry the core action first, the commit-message nuance second, and the critical refusal guard last. Every clause earns its place with no filler or restatement of the tool's name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation-style operation with no output schema, the description covers the core behavior, a parameter default, a side effect (worktree cleanup), and a precondition guard. The main gaps are the semantics of id and repo_path, and what 'refuses' looks like in practice, but an agent can reasonably invoke the tool knowing it must supply a delegation id.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description must compensate. It does explain commit_message's role (overrides task text as the message) and implies it is optional. But the required id parameter is never described as the delegation identifier, and repo_path's purpose is left entirely unexplained. Partial compensation only.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description names a specific verb ('Merge'), a specific resource ('a delegation'), and a precise outcome: a single commit into the current branch plus worktree cleanup. This clearly distinguishes it from sibling tools like delegate, revise, discard, and status, since only accept performs the merge-and-cleanup operation.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The workflow context (delegation lifecycle) makes the intended use implied, and the refusal condition ('Refuses if the working tree has uncommitted changes') gives a concrete when-not-to-call signal. However, it never names alternatives or explains when to prefer discard or revise over accept, leaving sibling differentiation to inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

delegateA

Hand a coding task to the local model.

The model edits an isolated git worktree; test_command (a shell command, e.g. "pytest -q") is run after each attempt and failures are fed back to the model, up to UNDERSTUDY_MAX_ROUNDS times. "{repo}" in the test command is replaced with the repository path, e.g. "{repo}/.venv/bin/pytest -q".

Returns the diff and test output for you to review, plus an id for accept/revise/discard. repo_path defaults to the current directory.

ParametersJSON Schema
NameRequiredDescriptionDefault
taskYes
repo_pathNo
test_commandNo

TDQS

A4.1/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description goes beyond the annotations by explaining the iterative test-feedback loop, the replacement of '{repo}' in the test command, and the fact that it returns a diff and test output. It adds value about the execution model that the annotations don't convey. It doesn't contradict the annotations (destructiveHint=false), which is fine. However, it doesn't mention potential side effects or cleanup of the worktree, but the isolated worktree implies safety.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is well-structured and efficient: a one-sentence purpose, a clear explanation of the workflow, specific details on the test_command semantics, and a concise summary of the return value. Every sentence adds necessary information without fluff. The most important details (the loop, the replacement) are front-loaded.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 3 parameters (one required), no output schema, and no sibling descriptions, the description is quite complete. It explains the main behaviors and parameters. It falls short of a 5 because it doesn't mention the maximum number of rounds explicitly (only the variable name), or whether the repository is cleaned up after. But these are minor given the complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must explain parameters. It explains test_command in detail (shell command, placeholder replacement, example usage) and mentions repo_path defaults to current directory. The task parameter is obvious from the purpose. This compensates well for the lack of schema descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool's purpose: to hand a coding task to a local model for execution in an isolated git worktree. It specifies the verb 'hand' and the resource 'coding task', and differentiates itself from siblings by mentioning the workflow (editing, testing, feedback loop) that leads to review actions. However, it doesn't explicitly name sibling tools like 'revise' or 'accept', so differentiation is implicit rather than direct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the workflow: the tool runs a test command after each attempt, feeds failures back to the model, and up to a certain number of rounds. It mentions the returned id for accept/revise/discard, which implies when to use this tool (to delegate a task) versus siblings (to act on the result). It doesn't explicitly say when NOT to use it, but the context is clear enough for an agent to decide.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

discardA
Destructive

Throw away a delegation's worktree and branch without merging.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
repo_pathNo

TDQS

A3.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already declare destructiveHint=true, and the description adds specificity about what gets destroyed: the delegation's worktree and branch. This is useful context beyond the annotation, though it does not mention irreversibility or potential side effects in detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, tightly worded sentence with no filler. It front-loads the action and immediately communicates the key distinction from merging, making it easy to parse.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

While the tool is simple and the destructive annotation covers the main risk, the description leaves parameter semantics undocumented and provides no guidance on how to supply 'id' or 'repo_path'. For a destructive operation, more context about required inputs and effects would be expected.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

With 0% schema description coverage, the description carries the full burden for parameter meaning, but it only indirectly suggests that 'id' identifies a delegation. It does not clarify 'repo_path' or confirm how the parameters are used, leaving the agent to guess about the second parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Throw away'), a specific resource ('a delegation's worktree and branch'), and a key constraint ('without merging'). This clearly distinguishes it from the sibling tool 'accept', which would merge the delegation's changes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies the tool is used when you want to discard a delegation's work rather than merge it, but it does not explicitly state when to use this tool versus alternatives like 'revise' or 'accept'. There is no direct guidance on when not to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

reviseA

Send review feedback on a delegation; the local model tries again and the tests are re-run. Returns the updated diff and test output.

ParametersJSON Schema
NameRequiredDescriptionDefault
idYes
feedbackYes
repo_pathNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the annotations (destructiveHint=false), the description discloses meaningful behavior: the model retries, tests are re-run, and the tool returns the updated diff and test output. This adds useful context about what happens when the tool is invoked, though it does not mention any side effects on existing delegation state.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no fluff. It front-loads the action and effect, then states the return value, making it quick for an agent to parse and act on.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the main workflow and return value, but leaves repo_path undefined and does not explain when to use this tool versus accept/discard. For a 3-parameter tool with no output schema and minimal annotations, these are meaningful gaps that could hinder correct invocation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate, but it only loosely implies that 'id' refers to a delegation and 'feedback' is the review feedback. The 'repo_path' parameter is entirely unexplained, leaving an agent without enough information to populate all parameters correctly.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific action ('Send review feedback on a delegation'), identifies the resource (delegation), and explains the effect (the local model tries again and tests are re-run). This clearly distinguishes it from sibling tools like accept, discard, and status, which handle different outcomes.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context for when to use the tool: when review feedback should be sent to trigger another attempt. It does not explicitly name alternatives or exclusions, but the behavior is distinct enough from accept/discard/status that an agent can infer the appropriate use case.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

statusA
Read-only

List delegations in this repository that have not been accepted or discarded.

ParametersJSON Schema
NameRequiredDescriptionDefault
repo_pathNo

Output Schema

ParametersJSON Schema
NameRequiredDescription
resultYes

TDQS

A4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint=true, so the safe read-only nature is covered. The description adds the useful scoping constraint of pending delegations but does not detail return format or pagination; for a simple read-only listing this is acceptable.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

A single front-loaded sentence with no filler. The verb, resource, and filter are immediately clear, and every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a one-parameter, read-only listing tool with a defined output schema and a clear description of what is returned, no additional invocation-critical information is missing. The tool is simple enough that this description is self-contained.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not explain repo_path, its expected format, or why it is optional. The parameter name and default provide some clue, but the description does not fully compensate for the missing schema detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States the specific verb 'List', the resource 'delegations', and the scope 'in this repository' with a clear status filter: 'not been accepted or discarded.' This distinguishes it from sibling tools like accept and discard.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description clearly implies this tool is for viewing pending delegations before accepting or discarding them. It does not explicitly name alternatives or exclusions, but the status filter gives sufficient usage context.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedaccept
    • First observeddelegate
    • First observeddiscard
    • First observedrevise
    • First observedstatus

TDQS

A4.2/5.0

Scored across 5 tools

Disambiguation5/5

Each tool maps to a distinct phase of the delegation lifecycle: delegate starts, revise updates, accept finalizes, discard cancels, and status inspects. There is no overlap or ambiguity between responsibilities.

Naming Consistency5/5

All tool names are single-word imperative verbs (delegate, revise, accept, discard, status), forming a predictable pattern. No mixed conventions or stylistic deviations.

Tool Count5/5

Five tools is the ideal scope for a delegation workflow: create, iterate, merge, cancel, and list. Each tool earns its place without redundancy or bloat.

Completeness5/5

The lifecycle is fully covered: delegate creates a task, revise handles iterative feedback, accept and discard resolve it, and status provides visibility. No essential operation is missing.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    B
    maintenance
    Delegate coding tasks to external AI coding agents in isolated git worktrees with independent verification, enabling any MCP client to orchestrate multi-agent workflows.
    5
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Delegates heavy development tasks from a supervisor to an autonomous worker on a cheaper model via MCP, with isolated git worktrees and provider-agnostic support.
    3
    1
    MIT
  • A
    license
    Not graded
    quality
    C
    maintenance
    An MCP server that lets ChatGPT or any MCP client securely delegate coding tasks to a local Claude Code instance, with git checkpointing, approval gates, and structured results. Supports code review, test running, and rollback via simple tool calls.
    8 npm
    MIT
  • A
    license
    A
    quality
    A
    maintenance
    Enables delegating complete coding tasks to a local OpenAI-compatible model through MCP, using bounded Git worktrees and compact receipts so conversation history is not carried.
    4
    MIT