Move configuration to a YAML file, and make the tuning knobs configurable
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 51s

The thresholds Part H says to tune on the real inference hardware were
hardcoded, so every adjustment would have been a PR. They now live in a
`tuning:` section of a YAML config file, which also replaces flat
environment variables as the home for structure (#32).

- The file (NISSE_CONFIG, default ./nisse-dev.yaml) carries structure
  and holds no secrets: credentials are ${VAR} references resolved from
  the environment, and a reference to an unset variable is an error
- The environment still wins: every existing NISSE_* variable overrides
  its key, and the file is optional, so .env and CI work unchanged
- Validation stays fail-fast and reports every problem at once; unknown
  keys are errors, so a misspelt knob fails at startup instead of
  silently doing nothing
- The config file joins the state store and .env in the check that
  refuses to start when they sit inside the workspace clone
- Tuning covers the executor's turn and failure limits, the consult
  budget, the delegation caps, the command timeout and the tool output
  limit, which Workspace now takes as a setting; the defaults are the
  values the agent already ran with
- llm.max_output_tokens reaches both LLM clients from the config

docs/Tuning-Guide.md explains the knobs for someone learning local
model tuning: how weights, quantization and the KV cache spend VRAM,
what each knob does and what goes wrong at either extreme, and an order
to tune in. nisse-dev.example.yaml documents every key and is checked
by a test so it cannot drift.

Closes #32

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dongarandClaude Opus 5.5 committed 2026-09-23 23:14:07 +00:00
1 parent 4a9354ae3b
commit df449c7e34
12 files changed
+994 -67

No files matched your search

+54
View File
@@ -286,3 +286,57 @@ def test_delegation_records_what_the_alias_resolved_to(
agent = build_offline(tmp_path, origin, monkeypatch)
assert agent.orchestrator._delegate._backing_model == "anthropic/claude-opus-5"
agent.stop()
def test_tuning_reaches_every_component(
tmp_path: Path, origin: Path, monkeypatch
) -> None:
from dataclasses import replace
from nisse_dev.config import (
DelegationTuning,
ExecutorTuning,
ExpertTuning,
ToolsTuning,
Tuning,
)
config = replace(
config_for(tmp_path),
max_output_tokens=8192,
tuning=Tuning(
executor=ExecutorTuning(max_turns=40, failure_limit=3),
expert=ExpertTuning(consult_budget=5),
delegation=DelegationTuning(
max_turns=12, max_tokens=50_000, wall_clock_s=60
),
tools=ToolsTuning(command_timeout_s=300, max_output_chars=2000),
),
)
GitRepo(tmp_path).clone(str(origin), config.workspace)
monkeypatch.setattr(
"nisse_dev.agent.GiteaClient.whoami", lambda self: "nisse-dev-testing"
)
monkeypatch.setattr(
"nisse_dev.agent.ModelCatalog.discover",
classmethod(lambda cls, *a, **kw: TEST_CATALOG),
)
monkeypatch.setattr("nisse_dev.agent.MatrixTransport", lambda cfg: FakeTransport())
monkeypatch.setattr(
"nisse_dev.agent.authenticated_url",
lambda base, repo, user, token: str(origin),
)
agent = build_agent(config)
executor = agent.orchestrator._executor
delegation = agent.orchestrator._delegate
assert (executor._max_turns, executor._failure_limit) == (40, 3)
assert executor._expert._budget == 5
assert delegation._caps.turns == 12
assert delegation._caps.tokens == 50_000
assert delegation._caps.wall_clock_s == 60
assert executor._workspace._timeout_s == 300
assert executor._workspace._max_output_chars == 2000
assert executor._client._max_tokens == 8192
assert delegation._client._max_tokens == 8192
agent.stop()