Tinkler is a local repository agent built with LangGraph. It inspects a codebase in short decision loops, chooses one tool call at a time, updates its working context from the result, and only edits files through apply_patch when writes are allowed.
Untitled.design.mp4
The repo currently contains three user-facing surfaces:
agent/: the core LangGraph agent runtimetinkler_cli/: a read-only analysis CLItinkler_backend/: a FastAPI backend with JSON and SSE endpointstinkler_desktop/: a minimal Electron desktop shell for the backend
Tinkler is designed for repository inspection first and repository editing second.
- The core loop is
context -> prompt -> model action -> tool -> context update -> repeat - The model returns exactly one structured action per turn
- Tool calls are bounded and explicit
- Read-only analysis runs disable
apply_patch - Writable runs allow in-loop patch application instead of deferred file staging
This makes the behavior easier to test and reason about than a loose "agent with tools" setup.
The graph is defined in agent/graph.py and is composed of small nodes with explicit state updates:
START
-> init_turn
-> build_turn_context
-> build_prompt_and_tools
-> model_step
-> route_model_output
-> shell_command
-> read_file
-> list_dir
-> search_files
-> apply_patch
-> finalize_turn
tool node
-> append_tool_result
-> maybe_update_repo_state
-> build_turn_context
-> ...
finalize_turn -> END
Core pieces:
agent/state.py: typed state, action, message, and tool result shapesagent/service.py: high-level runtime helpers such asrun_agent,run_analysis, anditer_agent_eventsagent/nodes/: graph nodes for prompt building, model execution, routing, loop updates, and finalizationagent/prompts/: system prompt and turn-context constructionagent/tools/: repo-safe tool implementationsagent/policies/: routing and loop guard policies
The agent currently exposes five internal tools:
shell_command: bounded shell execution for exploration and testingread_file: file reads without shelling outlist_dir: bounded directory inspectionsearch_files: content or filename searchapply_patch: Codex-style patch application for writes
Tool schemas are declared in agent/prompts/system_prompt.py. Routing from model output into those tools is handled in agent/nodes/route_model_output.py.
The loop is intentionally constrained.
max_turnsstops the run after a configured number of model decisionsallow_writes=Falseremovesapply_patchfrom the allowed tool setagent/policies/loop_guard.pycan stop the run based on state or repeated actionsroute_model_outputfinalizes immediately onfinal_answer, missing model output, or loop guard stop conditions
The current loop guard threshold for identical tool requests is very high, so max_turns is the main practical brake unless you lower that policy.
Tinkler/
βββ agent/
β βββ actions/
β βββ nodes/
β βββ policies/
β βββ prompts/
β βββ tools/
β βββ graph.py
β βββ service.py
β βββ state.py
βββ tinkler_cli/
βββ tinkler_backend/
βββ tinkler_desktop/
βββ tests/
βββ dummy_repo/
βββ smoke_repos/
βββ pyproject.toml
βββ README.md
Supporting directories:
tests/: unit tests for the agent loop, CLI behavior, backend API, and event streamingdummy_repo/: a small Python repo used as a realistic target during developmentsmoke_repos/: fixture repositories for search and layout scenarios
- Python 3.11+
- An OpenAI API key in
OPENAI_API_KEY - Node.js if you want to run the Electron desktop app
Python dependencies are declared in pyproject.toml:
langgraphlangchain-openaifastapiuvicorn
python3 -m venv .venv
source .venv/bin/activate
python3 -m pip install -e .
export OPENAI_API_KEY=your_key_hereOptional environment variables:
OPENAI_MODEL: overrides the default model, which is currentlygpt-4o-miniAGENT_LOG_LEVEL: logging level for the core agentTINKLER_BACKEND_HOST: backend bind host, default127.0.0.1TINKLER_BACKEND_PORT: backend bind port, default8000TINKLER_BACKEND_URL: desktop app target URL, defaulthttp://127.0.0.1:8000
The direct Python entrypoint is python3 -m agent.
Writable run:
python3 -m agent "Add a short contributing guide to the repository root" --cwd .Useful flags:
--cwd: repo root to inspect--max-turns: maximum agent turns, default30--model: model name--log-level: Python log level
This entrypoint requires OPENAI_API_KEY and runs with allow_writes=True.
The installed CLI command is tinkler. It is built for read-only repository analysis.
Run it interactively:
tinkler --cwd .Run a single request:
tinkler "Explain the architecture of this repo" --cwd .Write the analysis to a file:
tinkler "Summarize the backend API" --cwd . --output reports/backend.mdUseful flags:
--trace: append the tool trace to the plain-text response--json: emit structured JSON output--max-turns: maximum analysis turns--model: OpenAI model name
Under the hood the CLI calls run_analysis(...), which wraps the request in read-only instructions and disables apply_patch.
Start the backend:
tinkler-backendor:
python3 -m tinkler_backendRoutes:
GET /healthPOST /api/v1/agent/runsPOST /api/v1/agent/runs/stream
Example request:
curl -X POST http://127.0.0.1:8000/api/v1/agent/runs \
-H "Content-Type: application/json" \
-d '{
"cwd": ".",
"request": "Explain this repository",
"max_turns": 20,
"allow_writes": false
}'/api/v1/agent/runs/stream returns server-sent events produced by iter_agent_events(...). The event stream includes:
run.startedloop.progressmodel.actiontool.resultrun.completedrun.failed
tinkler_desktop/ is a minimal Electron wrapper around the backend.
Install and run:
npm install --prefix tinkler_desktop
npm --prefix tinkler_desktop startThe desktop app will try to connect to an existing backend first. If none is running, it attempts to start one with:
.venv/bin/python -m tinkler_backendIf .venv/bin/python does not exist, it falls back to python3.
The codebase is small and intentionally split by responsibility.
- Graph wiring lives in
agent/graph.py - High-level orchestration lives in
agent/service.py - Tool implementations are plain Python functions in
agent/tools/ - FastAPI integration is isolated under
tinkler_backend/ - The Electron shell is isolated under
tinkler_desktop/
This separation makes it straightforward to test the graph without running a real model or web server.
Run the Python test suite:
python3 -m unittest discover -s tests -p "test_*.py"Run desktop checks:
npm --prefix tinkler_desktop run check
npm --prefix tinkler_desktop testCurrent test coverage focuses on:
- agent loop behavior
- parser normalization and fallback behavior
- in-loop
apply_patch - read-only analysis safeguards
- backend route registration and SSE formatting
- event stream emission
- desktop backend-client parsing
The repo is already useful as a compact reference implementation for:
- a LangGraph-based repo agent
- read-only analysis vs writable execution modes
- streaming agent progress over SSE
- wrapping a local Python backend in Electron
It is still intentionally minimal. The strongest next steps would likely be richer tool policies, stronger loop-guard heuristics, and broader integration tests across the CLI, backend, and desktop surfaces.