drivers.local

drivers.local

Regeneration with a local model in short sessions (spotregen local).

The protocol of spotregen claude without Claude Code and without one long agent session. A deterministic driver splits the work into fresh model sessions. Each session receives only the context of its step, has its own output and time budget, and leaves its result in a file; no conversation history is carried from one session to the next.

  1. Setup (no model): the unit’s regeneration bundle is built in process; the files named by --allow are read from the repository.
  2. Generate: prompt + bundle + allowed files -> the whole module in one code block.
  3. Score (no model): the module is copied into a detached git worktree of HEAD in which the original was deleted, and judged by the unit’s evaluations.
  4. Repair (at most --attempts - 1 times): bundle + last module + the failing evaluation output -> a corrected module; then step 2 again.
  5. Report: bundle + final module -> the places where the bundle was ambiguous, silent or contradictory. The unit tests and the dependents inside the repository (declared code consumers imported, the original’s docstring examples run) judge the final module once.

The model is served by LM Studio on localhost; requests go to its OpenAI-compatible /v1/completions endpoint with standard-library HTTP. The driver renders the prompt itself (--template chatml for Qwen, --template mistral for Devstral), so for a reasoning model it can prefill an empty <think></think> block, which is what the model’s chat template does for enable_thinking=false (--reasoning off, the default); --reasoning none omits the block for a model without a reasoning mode. --repair-seed step gives the repair of attempt n the seed plus n, so that a repair does not replay its input under the same seed. The model is offered no tools: it sees only what the driver puts into the prompt, so the generator is memoryless by construction, independent of a harness refusing file access. Any endpoint other than localhost is refused, and the remote endpoints of the driver’s own sockets are sampled with lsof during the run and recorded; the command refuses to run where lsof is missing.

run.toml records model, backend, commit, hashes, every session (tokens, seconds, finish reason) and all three scores; sessions.jsonl holds every prompt and reply. --canary sends the confinement prompt of the Claude runs instead and stops. This command is non-deterministic and never belongs in make guard.

Usage::

spotregen local core.calc --model my-model \
    --allow src/samplepkg/core/helpers.py --out /tmp/local

Classes

Name Description
Egress Samples the remote endpoints of this process’s internet sockets with lsof.
Session One fresh request to the local server; nothing is kept between sessions.

Egress

drivers.local.Egress(period=0.25)

Samples the remote endpoints of this process’s internet sockets with lsof.

Attributes

Name Type Description
period float Seconds between samples.
remotes set[str] Every remote host:port seen so far.
samples int Number of samples taken.
stop threading.Event Set to end the sampling.

Examples

from spotregen.drivers.local import Egress

egress = Egress(period=0.5)
print(egress.period, egress.samples, sorted(egress.remotes))
0.5 0 []

Methods

Name Description
run Sample until stop is set.
run
drivers.local.Egress.run()

Sample until stop is set.

Session

drivers.local.Session(args, log)

One fresh request to the local server; nothing is kept between sessions.

Calling a session sends one /v1/completions request, prints a progress line, appends the request and the reply to the log and returns the generated text. A network error or a timeout yields an empty text and a finish_reason that starts with error.

Attributes

Name Type Description
args argparse.Namespace The parsed options (endpoint, model, sampling).
log Path The sessions.jsonl file.
records list[dict[str, object]] One record per call, for run.toml.

Examples

import argparse
from pathlib import Path
from tempfile import TemporaryDirectory

from spotregen.drivers.local import Session

args = argparse.Namespace(endpoint="http://127.0.0.1:9", model="m")
with TemporaryDirectory() as tmp:
    session = Session(args, Path(tmp) / "sessions.jsonl")
    print(session.records)
[]

Functions

Name Description
add_arguments Register the options of spotregen local on parser.
canary_prompt The confinement test prompt, built from the paths of this repository.
chatml The prompt as the model’s chat template renders it, ready for generation.
code_block The module in text: from the first opening fence to the last closing fence.
mistral The prompt as the Devstral Small 2 chat template renders it, for generation.
run_local Regenerate one unit with a local model in short sessions and score the result.

add_arguments

drivers.local.add_arguments(parser)

Register the options of spotregen local on parser.

Parameters

Name Type Description Default
parser argparse.ArgumentParser The parser of the subcommand. required

Examples

import argparse

from spotregen.drivers.local import add_arguments

parser = argparse.ArgumentParser(prog="spotregen local")
add_arguments(parser)
args = parser.parse_args(["core.calc", "--model", "m", "--template", "mistral"])
print(args.unit, args.model, args.template, args.endpoint)
core.calc m mistral http://127.0.0.1:1234

canary_prompt

drivers.local.canary_prompt(root, unit)

The confinement test prompt, built from the paths of this repository.

The canary asks the model to attempt seven actions it must not be able to do (or, for the last two, may do) and to report what happened. No path is hard-coded: the unit’s module comes from root and unit, the settings file from the home directory, the listing from the repository’s parent.

Parameters

Name Type Description Default
root Path Repository root. required
unit Unit The unit whose module the model must not be able to read. required

Returns

Name Type Description
str str The prompt text.

Examples

from pathlib import Path
from tempfile import TemporaryDirectory

from spotregen import load_spec, make_sample_repo
from spotregen.drivers.local import canary_prompt

with TemporaryDirectory() as tmp:
    root = make_sample_repo(Path(tmp) / "demo")
    unit = load_spec(root).units["samplepkg.core.calc"]
    lines = canary_prompt(root, unit).splitlines()
    print(lines[2].replace(str(root), "<root>"))
fail, and do not retry:

chatml

drivers.local.chatml(system, user, reasoning)

The prompt as the model’s chat template renders it, ready for generation.

reasoning="off" opens the assistant turn with an empty think block, the rendering of enable_thinking=false in the Qwen3.x chat templates. "on" leaves the reasoning mode to the model. "none" is for a model without a reasoning mode, such as Qwen3-Coder, whose template has no think block at all.

Parameters

Name Type Description Default
system str The system message. required
user str The user message. required
reasoning str "off", "on" or "none". required

Returns

Name Type Description
str str The rendered prompt.

Examples

from spotregen.drivers.local import chatml

print(repr(chatml("S", "U", "off")))
print(repr(chatml("S", "U", "none")))
'<|im_start|>system\nS<|im_end|>\n<|im_start|>user\nU<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n'
'<|im_start|>system\nS<|im_end|>\n<|im_start|>user\nU<|im_end|>\n<|im_start|>assistant\n'

code_block

drivers.local.code_block(text)

The module in text: from the first opening fence to the last closing fence.

The answer is one code block, but the module’s docstrings contain fenced Examples blocks of their own, so the outermost fences delimit the module, not the first or last pair.

Parameters

Name Type Description Default
text str The model’s answer. required

Returns

Name Type Description
str | None str | None: The module source, or None if there is no complete fence pair.

Examples

from spotregen.drivers.local import code_block

answer = "Here:\n```python\nX = 1\n```\n"
print(repr(code_block(answer)))
print(code_block("no fence"))
'X = 1\n'
None

mistral

drivers.local.mistral(system, user, reasoning)

The prompt as the Devstral Small 2 chat template renders it, for generation.

The tokenizer adds no begin-of-sequence token, so the prompt starts with <s> as the template does. The template has no reasoning block; reasoning must be "none".

Parameters

Name Type Description Default
system str The system message. required
user str The user message. required
reasoning str Must be "none". required

Returns

Name Type Description
str str The rendered prompt.

Raises

Name Type Description
ConfigurationError If reasoning is not "none".

Examples

from spotregen.drivers.local import mistral

print(mistral("S", "U", "none"))
<s>[SYSTEM_PROMPT]S[/SYSTEM_PROMPT][INST]U[/INST]

run_local

drivers.local.run_local(spec, unit, args)

Regenerate one unit with a local model in short sessions and score the result.

Parameters

Name Type Description Default
spec RegenSpec The loaded spec; its root is the repository. required
unit Unit The unit to regenerate. required
args argparse.Namespace The parsed options, see add_arguments. required

Returns

Name Type Description
int int 0 if the last attempt’s evaluations pass (and at least one test ran) and the dependents pass, 1 otherwise. With --canary: 0 after the canary session.

Raises

Name Type Description
ConfigurationError If the endpoint is not local or an --allow file lies outside the repository.
ExternalToolError If lsof or git is missing or the endpoint does not answer GET /v1/models.

Examples

import argparse

from spotregen.drivers.local import add_arguments

parser = argparse.ArgumentParser(prog="spotregen local")
add_arguments(parser)
print(parser.parse_args(["core.calc", "--model", "m", "--canary"]).canary)
True