python-vibe

ask, test, fix, add

GitHub

Architecture

The folders in this repository are listed on Folders. This page is only src/harness/.

src/harness/ is ordered bottom-up. A module may import a layer strictly below it, never one above or beside it. That rule is enforced by tests/whole/test_architecture.py, so a refactor that rots fails the merge gate rather than the next reader.

cli.py       commands                    run, serve, mcp, editors
server.py    HTTP on 127.0.0.1           /v1/ask, /v1/chat/completions
mcp_stdio    editor child process        JSON-RPC on stdin/stdout
observe/     what a run leaves behind    trace_record, report_md, eval_gate
locate.py    find the symbol before acting
act/         intent becomes a change     parse, tools, gate, patch_fix, code
skillkit/    the skill kit               catalog, target, refuse_change, refuse_finish
scan/        facts about a tree          project_brief, repo_map, layout
guard/       what ships, what is refused python_vibe, run, types
editor_kit   copy drop-in editor files
task.py      what the user asked for     (leaf)
paths.py     where this repo is on disk  (leaf)
model/       talking to weights          engine, ollama_generate, openai_compat
ship/        git and PR helpers          git_ship

Read a layer top-down and you learn what the harness does. Read it bottom-up and you learn what it refuses. The kit skills skillkit/ loads are listed on Skills.

What a run remembers

memory/ is one component with one job: decide what a request carries.

It was a bare list on the generate function. Every turn appended the prompt and the reply, nothing was ever removed, and the request grew by about 130 tokens a turn on top of an opening usually over a thousand. Nobody decided where that stopped: the harness sent no context size, so Ollama applied its own default of 4096 tokens — for weights that accept 131072 — and dropped the oldest messages once a run passed it.

The oldest message is the opening, which carries the file the harness located and the instruction about it. A long run lost exactly the part the harness had done work to assemble, and nothing said so.

Conversation decides instead. The opening is kept for the whole run. Recent turns are kept, because that is where the run is. What goes is the middle, which is where a model has already been told four times that it used the wrong verb, and it is counted rather than silently dropped. The context size is now stated in the request.

It belongs to the harness, not the model package: what is worth remembering is a harness decision. make_generate is handed something that answers messages(prompt) and never imports it.

Three rings

An agent is a harness around a model. That is the shape the code keeps.

Three rings: agent, harness, model The model sits inside the harness, and the harness inside what a person talks to. Each ring names the parts of this project that live in it. agent what a person or an editor talks to cli · server · mcp_stdio · editor_kit harness the loop, the tools, the guards, the skills agent · act · locate · scan · skillkit · ship · observe · guard model the code that talks to a model, and nothing else engine · ollama · mlx · openai · route nearly all the behaviour is the middle ring

Nearly all of the behaviour is the middle ring, which is the point of the project: what the model gets wrong, the harness catches.

The outer ring does not reach into the inner one. The command line and the server used to import harness.model directly, so the model package could not change shape without changing them. They go through the harness now, and a test refuses the direct import.

openai_api used to sit in the model package. It knows what an OpenAI-style chat request looks like and nothing about weights, so it belongs beside the server that speaks that format. The model package is now only the code that talks to a model, and a test keeps it that way.

What one run does before it loads a model:

What one run does before it loads a model Four questions are asked in order. Each one can finish the run on its own. Only what survives all four reaches the model. is the task clear enough to start from? ask, and stop is reading the file the whole job? answer, and stop can the harness make the change itself? write it, 0.1s, no model is there a typo only a person can settle? ask what was meant what is left is what the model is for Three of the four cases that pass every single time never reach the bottom box.

One place asks for a generator, agent/loop.py, and a test checks that too. If a second appeared, there would be two answers to “which model is this run using”.

Why task.py is the bottom

Every layer needs to know whether the user asked a question or asked for a change. Before, whichever module needed a predicate first owned it, so project_brief, skills, and style imported each other in a circle, broken only by function-local imports that hid the cycle from every reader.

Pulling the predicates into one leaf removed all three cycles. One rule holds the set together: a question is never a write — every looks_like_* writer predicate returns False for a question.

Why guard/ cannot import act/

guard/ is the safety boundary. If it could import a layer that writes files, a refusal could be routed around by whatever it imported. The rule is a test (test_the_guard_layer_cannot_write), not a convention.

Why nothing counts parents[N]

A module that resolves the repo root by counting parent directories breaks silently the moment it moves into a layer. harness/paths.py resolves it once; test_no_module_counts_its_own_depth keeps it that way.

The same check, pointed at your project

Action: layout runs harness/scan/layout.py against the tree in front of the agent and reports the same four things, worst first, then names one move:

Finding What it means
cycle two modules import each other; neither can be read alone
flat one package holding many modules with no grouping
god one module far larger than its neighbours
no-tests code with no test_*.py anywhere
PYTHONPATH=src python3.13 scripts/run/agent.py --project /path/to/your/app \
  --skill readable-layout "why is this project hard to follow?"

One move per turn is deliberate. Handed four findings an 8B rewrites the tree; handed one it does the one.