Compare commits
55
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8467b8747f | ||
|
|
277bff6fcd | ||
|
|
25fd57351d | ||
|
|
f2c48d8b16 | ||
|
|
f881a18ca4 | ||
|
|
a77c84c128 | ||
|
|
d49435d4e5 | ||
|
|
f41cbb00cf | ||
|
|
4e45dfe06a | ||
|
|
a716d5ec8d | ||
|
|
1cc8df8dab | ||
|
|
82f171c2f5 | ||
|
|
3cb70c0656 | ||
|
|
8a8d8dc27f | ||
|
|
0cccfe1e94 | ||
|
|
50b46821d7 | ||
|
|
b2bbf70b7b | ||
|
|
b934c46714 | ||
|
|
389b71a5d1 | ||
|
|
494715efa9 | ||
|
|
31851b7396 | ||
|
|
e1034a2cb7 | ||
|
|
58b439088f | ||
|
|
15d69799bf | ||
|
|
df449c7e34 | ||
|
|
4a9354ae3b | ||
|
|
abb7fa0216 | ||
|
|
d2fe3d5642 | ||
|
|
019fa24517 | ||
|
|
a91252cb77 | ||
|
|
5b163f0c2e | ||
|
|
854bddc680 | ||
|
|
b8df5d2066 | ||
|
|
182216296d | ||
|
|
2c9a082e5b | ||
|
|
889615e3e2 | ||
|
|
3dbfbc8cf0 | ||
|
|
cdc3881deb | ||
|
|
31bef59151 | ||
|
|
b0d7adb70d | ||
|
|
e38adf40ca | ||
|
|
231b406393 | ||
|
|
8cfa116aa1 | ||
|
|
8cfb45aed3 | ||
|
|
249bcb10fc | ||
|
|
ed2f5dfc7f | ||
|
|
1da94d2581 | ||
|
|
dfa574aeec | ||
|
|
2fb25b8195 | ||
|
|
6331fcafd9 | ||
|
|
ce436262f4 | ||
|
|
608a3a5bfb | ||
|
|
2b7651fb37 | ||
|
|
dd22eda528 | ||
|
|
f2ba932744 |
No files matched your search
+15
-34
@@ -1,36 +1,17 @@
|
|||||||
# Copy to .env (chmod 600). Real environment variables win over this file.
|
# Secrets only. Copy to .env (chmod 600); it is gitignored, so never commit
|
||||||
# .env is gitignored; never commit real values.
|
# real values. Real environment variables win over this file.
|
||||||
|
#
|
||||||
|
# Everything else lives in the YAML config (see nisse-dev.example.yaml),
|
||||||
|
# which refers to these by name, e.g. `token: ${NISSE_LLM_TOKEN}`. The live
|
||||||
|
# integration tests use the same names, from tests/integration/nisse-test.yaml.
|
||||||
|
|
||||||
# --- LLM access (A5): the LiteLLM proxy is the single LLM destination ---
|
# llm.token: the LiteLLM virtual key (A5)
|
||||||
NISSE_LLM_BASE_URL=http://litellm.example:4000
|
NISSE_LLM_TOKEN=replace-me
|
||||||
NISSE_LLM_TOKEN=sk-your-litellm-virtual-key
|
# matrix.token: the Matrix access token for the agent's account (A1)
|
||||||
|
NISSE_MATRIX_TOKEN=replace-me
|
||||||
|
# gitea.token: the Gitea service account's scoped token (B2)
|
||||||
|
NISSE_GITEA_TOKEN=replace-me
|
||||||
|
|
||||||
# --- Role mapping (A11): roles resolve to concrete model strings ---
|
# Where the config file is, if not ./nisse-dev.yaml. Where this file is, if
|
||||||
NISSE_ROLE_EXECUTOR=anthropic/claude-haiku-4-5-20251001
|
# not ./.env, is NISSE_ENV, which has to be set in the real environment.
|
||||||
NISSE_ROLE_PLANNER=anthropic/claude-opus-5
|
# NISSE_CONFIG=~/nisse-demo-workspace/nisse-dev.yaml
|
||||||
NISSE_ROLE_DELEGATE=anthropic/claude-opus-5
|
|
||||||
NISSE_ROLE_CONSULTANT=anthropic/claude-opus-5
|
|
||||||
|
|
||||||
# Opt-in for the live planning test: a model the virtual key can reach that
|
|
||||||
# holds the tool-call contract. Planning escalates unconditionally (A12), and
|
|
||||||
# small local models do not hold the contract reliably — llama3.2 returns
|
|
||||||
# truncated tool arguments, Haiku-class and up are solid.
|
|
||||||
NISSE_LIVE_PLANNER_MODEL=claude-haiku-4-5
|
|
||||||
|
|
||||||
# --- Matrix transport (A1), used by nisse-dev's MatrixTransport ---
|
|
||||||
NISSE_MATRIX_HOMESERVER=https://matrix.example
|
|
||||||
NISSE_MATRIX_USER_ID=@nisse-dev:example
|
|
||||||
NISSE_MATRIX_TOKEN=syt_your_matrix_access_token
|
|
||||||
NISSE_MATRIX_ROOM_ID=!roomid:example
|
|
||||||
# Dedicated room for the live integration test; never the production room.
|
|
||||||
NISSE_MATRIX_TEST_ROOM_ID=!testroomid:example
|
|
||||||
NISSE_MATRIX_OPERATORS=@you:example
|
|
||||||
|
|
||||||
# --- Gitea (B2): service account for branches, PRs, and comment intake ---
|
|
||||||
NISSE_GITEA_URL=https://gitea.example
|
|
||||||
NISSE_GITEA_TOKEN=your-scoped-service-account-token
|
|
||||||
# Sandbox repo for live tests; never the real project repo.
|
|
||||||
NISSE_GITEA_TEST_REPO=owner/sandbox-repo
|
|
||||||
# Gitea logins allowed to drive the agent via PR comments (A4 per-channel
|
|
||||||
# allowlist; a Matrix MXID and a Gitea login are different names).
|
|
||||||
NISSE_GITEA_OPERATORS=your-gitea-login
|
|
||||||
+3
-11
@@ -34,19 +34,11 @@ jobs:
|
|||||||
run: uv run pytest -m "not integration"
|
run: uv run pytest -m "not integration"
|
||||||
|
|
||||||
- name: Integration tests
|
- name: Integration tests
|
||||||
# Live-path credentials come from repo Actions secrets; the tests
|
# Everything but the secrets is in tests/integration/nisse-test.yaml,
|
||||||
# skip-guard on these, so a missing secret or unreachable service
|
# which refers to these by name. A missing secret fails the live
|
||||||
# degrades to "skipped", never a failure on unrelated work.
|
# tests that need it; it is never quietly turned into a skip.
|
||||||
env:
|
env:
|
||||||
NISSE_LLM_BASE_URL: ${{ secrets.NISSE_LLM_BASE_URL }}
|
|
||||||
NISSE_LLM_TOKEN: ${{ secrets.NISSE_LLM_TOKEN }}
|
NISSE_LLM_TOKEN: ${{ secrets.NISSE_LLM_TOKEN }}
|
||||||
NISSE_ROLE_EXECUTOR: ${{ secrets.NISSE_ROLE_EXECUTOR }}
|
|
||||||
NISSE_LIVE_PLANNER_MODEL: ${{ secrets.NISSE_LIVE_PLANNER_MODEL }}
|
|
||||||
NISSE_MATRIX_HOMESERVER: ${{ secrets.NISSE_MATRIX_HOMESERVER }}
|
|
||||||
NISSE_MATRIX_USER_ID: ${{ secrets.NISSE_MATRIX_USER_ID }}
|
|
||||||
NISSE_MATRIX_TOKEN: ${{ secrets.NISSE_MATRIX_TOKEN }}
|
NISSE_MATRIX_TOKEN: ${{ secrets.NISSE_MATRIX_TOKEN }}
|
||||||
NISSE_MATRIX_TEST_ROOM_ID: ${{ secrets.NISSE_MATRIX_TEST_ROOM_ID }}
|
|
||||||
NISSE_GITEA_URL: ${{ secrets.NISSE_GITEA_URL }}
|
|
||||||
NISSE_GITEA_TOKEN: ${{ secrets.NISSE_GITEA_TOKEN }}
|
NISSE_GITEA_TOKEN: ${{ secrets.NISSE_GITEA_TOKEN }}
|
||||||
NISSE_GITEA_TEST_REPO: ${{ secrets.NISSE_GITEA_TEST_REPO }}
|
|
||||||
run: uv run pytest -m integration -rs
|
run: uv run pytest -m integration -rs
|
||||||
@@ -6,3 +6,4 @@ __pycache__/
|
|||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
.ruff_cache/
|
.ruff_cache/
|
||||||
dist/
|
dist/
|
||||||
|
nisse-dev.yaml
|
||||||
Whitespace-only changes.
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/qwen3-14b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Fixed total_value to multiply unit price by quantity. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5328, "tokens_out": 1492, "trimmed_requests": 0, "ts": "2026-09-25T01:03:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 46.4}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\nImportError while importing test module '/tmp/tmpr1q1pe0r/work/.bench_check/test_hidden.py'.\nHint: make sure your test modules/packages have valid Python names.\nTraceback:\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n.bench_check/test_hidden.py:4: in <module>\n from inventory import Item, low_stock, total_value\nE ImportError: cannot import name 'Item' from 'inventory' (/tmp/tmpr1q1pe0r/work/inventory.py)\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.10s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n______________________ ERROR collecting test_inventory.py ______________________\nImportError while importing test module '/tmp/tmpr1q1pe0r/work/test_inventory.py'.\nHint: make sure your test modules/packages have valid Python names.\nTraceback:\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], packa", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 2560, "tokens_out": 1136, "trimmed_requests": 0, "ts": "2026-09-25T01:03:49+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 24.3}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Fixed the total_value function to multiply unit price by quantity, passing all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5318, "tokens_out": 1623, "trimmed_requests": 0, "ts": "2026-09-25T01:04:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 34.5}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n.bench_check/test_hidden.py:4: in <module>\n from inventory import Item, low_stock, total_value\ninventory.py:1: in <module>\n def total_value(items: list[Item]) -> float:\n ^^^^\nE NameError: name 'Item' is not defined\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py - NameError: name 'Item' is not defined\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.11s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n______________________ ERROR collecting test_inventory.py ______________________\ntest_inventory.py:1: in <module>\n from inventory import Item, low_stock, total_value\ninventory.py:1: in <module>\n def total_value(items: list[Item]) -> float:\n ^^^^\nE NameError: name 'Item' is not defined\n=========================== short ", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 2565, "tokens_out": 958, "trimmed_requests": 0, "ts": "2026-09-25T01:04:45+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 20.6}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n.bench_check/test_hidden.py:4: in <module>\n from inventory import Item, low_stock, total_value\ninventory.py:1: in <module>\n def total_value(items: list[Item]) -> float:\n ^^^^\nE NameError: name 'Item' is not defined\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py - NameError: name 'Item' is not defined\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.09s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n______________________ ERROR collecting test_inventory.py ______________________\ntest_inventory.py:1: in <module>\n from inventory import Item, low_stock, total_value\ninventory.py:1: in <module>\n def total_value(items: list[Item]) -> float:\n ^^^^\nE NameError: name 'Item' is not defined\n=========================== short ", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 2565, "tokens_out": 1051, "trimmed_requests": 0, "ts": "2026-09-25T01:05:07+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 22.6}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpq8iz1j8z/work/rates.py\", line 56\nE [... 1445 characters omitted ...]\nE ^^^^^^^^\nE SyntaxError: invalid syntax. Perhaps you forgot a comma?\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 13616, "tokens_out": 3186, "trimmed_requests": 0, "ts": "2026-09-25T01:06:21+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 72.8}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": ": 'R271'\nFAILED .bench_check/test_hidden.py::test_rate[272] - KeyError: 'R272'\nFAILED .bench_check/test_hidden.py::test_rate[273] - KeyError: 'R273'\nFAILED .bench_check/test_hidden.py::test_rate[274] - KeyError: 'R274'\nFAILED .bench_check/test_hidden.py::test_rate[275] - KeyError: 'R275'\nFAILED .bench_check/test_hidden.py::test_rate[276] - KeyError: 'R276'\nFAILED .bench_check/test_hidden.py::test_rate[277] - KeyError: 'R277'\nFAILED .bench_check/test_hidden.py::test_rate[278] - KeyError: 'R278'\nFAILED .bench_check/test_hidden.py::test_rate[279] - KeyError: 'R279'\nFAILED .bench_check/test_hidden.py::test_rate[280] - KeyError: 'R280'\nFAILED .bench_check/test_hidden.py::test_rate[281] - KeyError: 'R281'\nFAILED .bench_check/test_hidden.py::test_rate[282] - KeyError: 'R282'\nFAILED .bench_check/test_hidden.py::test_rate[283] - KeyError: 'R283'\nFAILED .bench_check/test_hidden.py::test_rate[284] - KeyError: 'R284'\nFAILED .bench_check/test_hidden.py::test_rate[285] - KeyError: 'R285'\nFAILED .bench_check/test_hidden.py::test_rate[286] - KeyError: 'R286'\nFAILED .bench_check/test_hidden.py::test_rate[287] - KeyError: 'R287'\nFAILED .bench_check/test_hidden.py::test_rate[288] - KeyError: 'R288'\nFAILED .bench_check/test_hidden.py::test_rate[289] - KeyError: 'R289'\nFAILED .bench_check/test_hidden.py::test_rate[290] - KeyError: 'R290'\nFAILED .bench_check/test_hidden.py::test_rate[291] - KeyError: 'R291'\nFAILED .bench_check/test_hidden.py::test_rate[292] - KeyError: 'R292'\nFAILED .bench_check/test_hidden.py::test_rate[293] - KeyError: 'R293'\nFAILED .bench_check/test_hidden.py::test_rate[294] - KeyError: 'R294'\nFAILED .bench_check/test_hidden.py::test_rate[295] - KeyError: 'R295'\nFAILED .bench_check/test_hidden.py::test_rate[296] - KeyError: 'R296'\nFAILED .bench_check/test_hidden.py::test_rate[297] - KeyError: 'R297'\nFAILED .bench_check/test_hidden.py::test_rate[298] - KeyError: 'R298'\nFAILED .bench_check/test_hidden.py::test_rate[299] - KeyError: 'R299'\n247 failed, 54 passed in 0.49s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n.....................................................FFFFFFFFFFFFFFFFFFF [ 24%]\nFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [ 48%]\nFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [ 72%]\nFFFFFFFFFFFFFFFFFFFFF.FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF [ 96%]\nFFFFFFFFFFFF [100%]\n=================================== FAILU", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 12726, "tokens_out": 2366, "trimmed_requests": 0, "ts": "2026-09-25T01:07:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 54.4}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmphac71897/work/rates.py\", line 56\nE [... 1445 characters omitted ...]\nE ^^^^^^^^\nE SyntaxError: invalid syntax. Perhaps you forgot a comma?\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.15s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 13614, "tokens_out": 3000, "trimmed_requests": 0, "ts": "2026-09-25T01:08:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 69.2}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmp0vs0w4kr/work/rates.py\", line 56\nE [... 1445 characters omitted ...]\nE ^^^^^^^^\nE SyntaxError: invalid syntax. Perhaps you forgot a comma?\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 13607, "tokens_out": 3223, "trimmed_requests": 0, "ts": "2026-09-25T01:09:40+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 73.8}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmp_pxm0ldr/work/rates.py\", line 56\nE [... 1445 characters omitted ...]\nE ^^^^^^^^\nE SyntaxError: invalid syntax. Perhaps you forgot a comma?\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.15s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 13609, "tokens_out": 3180, "trimmed_requests": 0, "ts": "2026-09-25T01:10:53+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 73.0}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n..FF [100%]\n=================================== FAILURES ===================================\n_________________________ test_cli_prints_the_greeting _________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x777eb60e3e90>\n\n def test_cli_prints_the_greeting(capsys) -> None:\n> assert main([\"Bob\"])\nE AssertionError: assert 0\nE + wh", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 13753, "tokens_out": 5755, "trimmed_requests": 0, "ts": "2026-09-25T01:12:55+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 121.2}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added --shout option to greeter, updated core and cli code, added tests, and verified with pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 21373, "tokens_out": 9952, "trimmed_requests": 0, "ts": "2026-09-25T01:16:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 13, "wall_s": 210.0}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n..FF [100%]\n=================================== FAILURES ===================================\n_________________________ test_cli_prints_the_greeting _________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x7a2788fdbe30>\n\n def test_cli_prints_the_greeting(capsys) -> None:\n> assert main([\"Bob\"])\nE AssertionError: assert 0\nE + wh", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9432, "tokens_out": 6691, "trimmed_requests": 0, "ts": "2026-09-25T01:18:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 142.1}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added --shout option to greeter, updated core and cli code, added tests, and verified with pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9104, "tokens_out": 3626, "trimmed_requests": 0, "ts": "2026-09-25T01:20:03+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 75.8}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n..FF [100%]\n=================================== FAILURES ===================================\n_________________________ test_cli_prints_the_greeting _________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x76eac82e3da0>\n\n def test_cli_prints_the_greeting(capsys) -> None:\n> assert main([\"Bob\"])\nE AssertionError: assert 0\nE + wh", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9382, "tokens_out": 7755, "trimmed_requests": 0, "ts": "2026-09-25T01:22:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 164.5}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added slugify function and passed all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 1769, "tokens_out": 1300, "trimmed_requests": 0, "ts": "2026-09-25T01:23:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 27.7}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added slugify function with tests; all tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 1782, "tokens_out": 1239, "trimmed_requests": 0, "ts": "2026-09-25T01:23:43+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 26.2}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added slugify function and passed all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3627, "tokens_out": 1332, "trimmed_requests": 0, "ts": "2026-09-25T01:24:11+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 28.1}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added slugify function and passed all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 734, "tokens_out": 653, "trimmed_requests": 0, "ts": "2026-09-25T01:24:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 1, "wall_s": 13.9}
|
||||||
|
{"backing": "openai/qwen3-14b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-coder", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11028, Qwen3-14B Q4_K_M, q8 KV, 32k", "outcome": "completed", "outcome_note": "Added slugify function and passed all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 733, "tokens_out": 523, "trimmed_requests": 0, "ts": "2026-09-25T01:24:37+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 1, "wall_s": 11.3}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1578, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-25T01:00:41+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 63.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1576, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-25T01:00:43+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1576, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-25T01:00:44+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1574, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-25T01:00:46+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1578, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-25T01:00:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpl4mdto5g/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 11000, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-25T01:00:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpsqvx_94y/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 11000, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-25T01:00:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpe3eakwxj/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 11013, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-25T01:01:01+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmprlm2camm/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 10987, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-25T01:01:06+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpvzw7f5tn/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 11000, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-25T01:01:11+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n...F [100%]\n=================================== FAILURES ===================================\n_________________________ test_cli_prints_the_greeting _________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x713c88ce3c20>\n\n def test_cli_prints_the_greeting(capsys) -> None:\n> assert main([\"Bob\"])\nE AssertionError: assert 0\nE + wh", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 13039, "tokens_out": 566, "trimmed_requests": 0, "ts": "2026-09-25T01:01:19+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 8.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n..FF [100%]\n=================================== FAILURES ===================================\n________________________________ test_cli_shout ________________________________\n\n def test_cli_shout() -> None:\n> from pytest import capsys\nE ImportError: cannot import name 'capsys' from 'pytest' (/root/nisse/.venv/lib/python3.12/site-packages/pytest/__init__.py)", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11243, "tokens_out": 715, "trimmed_requests": 0, "ts": "2026-09-25T01:01:29+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 9.9}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:4: in <module>\n from greeter.cli import main\nE File \"/tmp/tmp3f09u7jk/work/greeter/cli.py\", line 11\nE print(greet(args.name, shout=args.--shout))\nE ^\nE SyntaxError: invalid syntax\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.14s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n\nstderr:\nTraceback (most recent call last):\n File \"<frozen runpy>\", line 198, in _run_module_as_main\n File \"<frozen runpy>\", line 88, in _run_code\n File \"/tmp/tmp3f09u7jk/work/greeter/__main__.py\", line 1, in <module>\n from greeter.cli import main\n File \"/tmp/tmp3f09u7jk/work/greeter/cli.py\", line 11\n print(greet(args.name, shout=args.--shout))\n ^\nSyntaxError: invalid syntax\n", "prose_turns": 0, "provider": "openai", "success": false, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9358, "tokens_out": 450, "trimmed_requests": 0, "ts": "2026-09-25T01:01:56+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 26.5}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10784, "tokens_out": 549, "trimmed_requests": 0, "ts": "2026-09-25T01:02:04+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 7.5}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": " stderr arguments may not be used '\n 'with capture_output.')\n kwargs['stdout'] = PIPE\n kwargs['stderr'] = PIPE\n \n with Popen(*popenargs, **kwargs) as process:\n try:\n stdout, stderr = process.communicate(input, timeout=timeout)\n except TimeoutExpired as exc:\n process.kill()\n if _mswindows:\n # Windows accumulates the output in a single blocking\n # read() call run on child threads, with the timeout\n # being done in a join() on those threads. communicate()\n # _after_ kill() is required to collect that and add it\n # to the exception.\n exc.stdout, exc.stderr = process.communicate()\n else:\n # POSIX _communicate already populated the output so\n # far into the TimeoutExpired exception.\n process.wait()\n raise\n except: # Including KeyboardInterrupt, communicate handled that.\n process.kill()\n # We don't call process.wait() as .__exit__ does that for us.\n raise\n retcode = process.poll()\n if check and retcode:\n> raise CalledProcessError(retcode, process.args,\n output=stdout, stderr=stderr)\nE subprocess.CalledProcessError: Command '['/root/nisse/.venv/bin/python', '-m', 'greeter', '--shout', 'Bob']' returned non-zero exit status 2.\n\n/usr/lib/python3.12/subprocess.py:571: CalledProcessError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_cli_default - SystemExit: 2\nFAILED .bench_check/test_hidden.py::test_cli_shout - SystemExit: 2\nFAILED .bench_check/test_hidden.py::test_module_entry_point - subprocess.Call...\n3 failed, 2 passed in 0.19s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n..F [100%]\n=================================== FAILURES ===================================\n________________________________ test_cli_shout ________________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x763f352e3980>\n\n def test_cli_shout(capsys) -> None:\n> assert main([\"--shout\", \"Bob\"]) == 0\n ^^^^^^^^^^^^^^^^^^^^^^^^\n\ntest", "prose_turns": 0, "provider": "openai", "success": false, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 13124, "tokens_out": 589, "trimmed_requests": 0, "ts": "2026-09-25T01:02:13+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 8.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": " (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'leading-and-trailing'\nE \nE - leading-and-trailing\n\n.bench_check/test_hidden.py:19: AssertionError\n_____________________ test_slugify[MiXeD CaSe-mixed-case] ______________________\n\ntext = 'MiXeD CaSe', expected = 'mixed-case'\n\n @pytest.mark.parametrize(\n (\"text\", \"expected\"),\n [\n (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'mixed-case'\nE \nE - mixed-case\n\n.bench_check/test_hidden.py:19: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_slugify[Hello, World!-hello-world]\nFAILED .bench_check/test_hidden.py::test_slugify[ Nisse Agent -nisse-agent]\nFAILED .bench_check/test_hidden.py::test_slugify[a--b-a-b] - AssertionError: ...\nFAILED .bench_check/test_hidden.py::test_slugify[Python 3.12 Release-python-3-12-release]\nFAILED .bench_check/test_hidden.py::test_slugify[--leading and trailing---leading-and-trailing]\nFAILED .bench_check/test_hidden.py::test_slugify[MiXeD CaSe-mixed-case] - Ass...\n6 failed, 2 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\nE AssertionError: assert '' == 'hello-world'\nE \nE - hello-world\n\ntest_slugify.py:6: AssertionEr", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4942, "tokens_out": 377, "trimmed_requests": 0, "ts": "2026-09-25T01:02:19+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.5}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\n ^^^^^^^\nE NameError: name 'slugify' is not defined\n\ntest_slugify.py:4: NameError\n===============", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3688, "tokens_out": 255, "trimmed_requests": 0, "ts": "2026-09-25T01:02:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 3.9}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\n ^^^^^^^\nE NameError: name 'slugify' is not defined\n\ntest_slugify.py:4: NameError\n===============", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3696, "tokens_out": 255, "trimmed_requests": 0, "ts": "2026-09-25T01:02:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 3.9}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": " (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'leading-and-trailing'\nE \nE - leading-and-trailing\n\n.bench_check/test_hidden.py:19: AssertionError\n_____________________ test_slugify[MiXeD CaSe-mixed-case] ______________________\n\ntext = 'MiXeD CaSe', expected = 'mixed-case'\n\n @pytest.mark.parametrize(\n (\"text\", \"expected\"),\n [\n (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'mixed-case'\nE \nE - mixed-case\n\n.bench_check/test_hidden.py:19: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_slugify[Hello, World!-hello-world]\nFAILED .bench_check/test_hidden.py::test_slugify[ Nisse Agent -nisse-agent]\nFAILED .bench_check/test_hidden.py::test_slugify[a--b-a-b] - AssertionError: ...\nFAILED .bench_check/test_hidden.py::test_slugify[Python 3.12 Release-python-3-12-release]\nFAILED .bench_check/test_hidden.py::test_slugify[--leading and trailing---leading-and-trailing]\nFAILED .bench_check/test_hidden.py::test_slugify[MiXeD CaSe-mixed-case] - Ass...\n6 failed, 2 passed in 0.03s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\nE AssertionError: assert '' == 'hello-world'\nE \nE - hello-world\n\ntest_slugify.py:6: AssertionEr", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4936, "tokens_out": 377, "trimmed_requests": 0, "ts": "2026-09-25T01:02:33+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.5}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "50b4682", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.29, Qwen3-8B-AWQ, fp8 KV, 32k", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\n ^^^^^^^\nE NameError: name 'slugify' is not defined\n\ntest_slugify.py:4: NameError\n===============", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3686, "tokens_out": 251, "trimmed_requests": 0, "ts": "2026-09-25T01:02:37+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 3.9}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to multiply `unit_price` by `quantity` for each item. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4909, "tokens_out": 853, "trimmed_requests": 0, "ts": "2026-09-27T17:32:39+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 11.1}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply `unit_price` by `quantity` for each item. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5927, "tokens_out": 832, "trimmed_requests": 0, "ts": "2026-09-27T17:32:51+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 11.9}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the bug in `total_value` where it was only summing the unit price instead of multiplying by the quantity.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5927, "tokens_out": 623, "trimmed_requests": 0, "ts": "2026-09-27T17:33:01+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 9.8}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply each item's unit price by its quantity.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5651, "tokens_out": 749, "trimmed_requests": 0, "ts": "2026-09-27T17:33:13+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 11.0}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply `unit_price` by `quantity` for each item.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5927, "tokens_out": 704, "trimmed_requests": 0, "ts": "2026-09-27T17:33:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 10.6}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py from 0.21 to 0.12.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 18498, "tokens_out": 443, "trimmed_requests": 0, "ts": "2026-09-27T17:33:33+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 8.9}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in `rates.py`. The rate was 0.21, but it should have been 0.12 as per the formula (NNN mod 25) / 100. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 43881, "tokens_out": 8995, "trimmed_requests": 0, "ts": "2026-09-27T17:35:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 110.0}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in rates.py. The expected rate was 0.12 (237 mod 25 / 100), but it was 0.21. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 45997, "tokens_out": 9229, "trimmed_requests": 0, "ts": "2026-09-27T17:37:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 112.2}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in rates.py, changing it from 0.21 to 0.12 as required by the test.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 43881, "tokens_out": 8879, "trimmed_requests": 0, "ts": "2026-09-27T17:39:04+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 107.8}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region R237 in rates.py. The rate was 0.21, but it should have been 0.12 (calculated as (237 mod 25) / 100 = 12 / 100 = 0.12). All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 46010, "tokens_out": 9357, "trimmed_requests": 0, "ts": "2026-09-27T17:40:58+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 113.5}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "..... [100%]\n5 passed in 0.05s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a `--shout` option to the greeter. Updated `greeter/core.py` to support shouting, `greeter/cli.py` to include the flag, and added corresponding tests to `tests/test_greeter.py`. Verified the functionality via tests and manual command execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14116, "tokens_out": 1686, "trimmed_requests": 0, "ts": "2026-09-27T17:41:21+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 22.1}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter. Updated `greeter/core.py` to support the `shout` parameter, `greeter/cli.py` to handle the `--shout` flag, and updated `tests/test_greeter.py` with new test cases. Verified the functionality with both tests and a manual check of the CLI.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14068, "tokens_out": 1463, "trimmed_requests": 0, "ts": "2026-09-27T17:41:41+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 19.5}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a `--shout` option to the greeter. Updated `greeter/core.py` to support the `shout` parameter, `greeter/cli.py` to include the `--shout` flag, and added corresponding tests in `tests/test_greeter.py`. Verified that `python3 -m greeter --shout Bob` prints `HELLO, BOB!`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 19838, "tokens_out": 4424, "trimmed_requests": 0, "ts": "2026-09-27T17:42:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 15, "wall_s": 53.7}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter. Updated `greeter/core.py` to support the `shout` parameter, `greeter/cli.py` to include the `--shout` flag, and added tests to `tests/test_greeter.py`. Verified that the greeting is correctly shouted in uppercase when the flag is provided.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 12000, "tokens_out": 1523, "trimmed_requests": 0, "ts": "2026-09-27T17:42:55+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 11, "wall_s": 20.0}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added the `--shout` option to the greeter. Updated `greeter/core.py` to support shouting, `greeter/cli.py` to include the CLI flag, and added corresponding tests in `tests/test_greeter.py`. Verified the changes with tests and manual command execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14049, "tokens_out": 1697, "trimmed_requests": 0, "ts": "2026-09-27T17:43:17+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 22.1}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created `slugify.py` with the `slugify` function, created `test_slugify.py` with passing tests, and verified everything.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3283, "tokens_out": 451, "trimmed_requests": 0, "ts": "2026-09-27T17:43:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 6.3}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with the slugify function, created test_slugify.py with pytest tests, and verified all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3259, "tokens_out": 458, "trimmed_requests": 0, "ts": "2026-09-27T17:43:31+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 7.0}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with the slugify function, created test_slugify.py with required test cases, and verified that tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3215, "tokens_out": 672, "trimmed_requests": 0, "ts": "2026-09-27T17:43:41+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 9.1}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "I created `slugify.py` with the `slugify` function, added `test_slugify.py` with the required test cases, and verified that all tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3219, "tokens_out": 539, "trimmed_requests": 0, "ts": "2026-09-27T17:43:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 7.7}
|
||||||
|
{"backing": "openai/gemma-4-12b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-12b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with the slugify function, added test_slugify.py, and verified that all tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3223, "tokens_out": 629, "trimmed_requests": 0, "ts": "2026-09-27T17:43:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 8.7}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed `total_value` in `inventory.py` to multiply `unit_price` by `quantity`. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4904, "tokens_out": 372, "trimmed_requests": 0, "ts": "2026-09-27T17:53:38+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.1}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply each item's `unit_price` by its `quantity`. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4919, "tokens_out": 320, "trimmed_requests": 0, "ts": "2026-09-27T17:53:42+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.1}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply each item's unit price by its quantity. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4924, "tokens_out": 320, "trimmed_requests": 0, "ts": "2026-09-27T17:53:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.1}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to multiply `unit_price` by `quantity` for each item, and verified with tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4909, "tokens_out": 410, "trimmed_requests": 0, "ts": "2026-09-27T17:53:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.8}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed `total_value` in `inventory.py` to multiply `unit_price` by `quantity`. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 4909, "tokens_out": 401, "trimmed_requests": 0, "ts": "2026-09-27T17:53:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in rates.py. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 26136, "tokens_out": 4367, "trimmed_requests": 0, "ts": "2026-09-27T17:54:34+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 36.6}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect tax rate for region code R237 in `rates.py` from 0.21 to 0.12, which was (237 mod 25) / 100. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 26136, "tokens_out": 4494, "trimmed_requests": 0, "ts": "2026-09-27T17:55:12+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 37.5}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect tax rate for region R237 in rates.py. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 26118, "tokens_out": 4464, "trimmed_requests": 0, "ts": "2026-09-27T17:55:50+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 37.2}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in rates.py. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 26118, "tokens_out": 4362, "trimmed_requests": 0, "ts": "2026-09-27T17:56:26+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 36.5}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py to be 0.12, which matches (237 mod 25) / 100. Verified with all tests passing.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 26118, "tokens_out": 4490, "trimmed_requests": 0, "ts": "2026-09-27T17:57:04+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 37.4}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added `--shout` option to `greeter` CLI. Updated `greet` to support shouting, added `--shout` flag to CLI, and added tests for both. All tests passed and CLI output verified.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11250, "tokens_out": 1056, "trimmed_requests": 0, "ts": "2026-09-27T17:57:15+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 10.3}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. \n- Updated greeter/core.py to support shout argument.\n- Updated greeter/cli.py to support --shout flag.\n- Added tests for shout functionality in tests/test_greeter.py.\n- Verified with pytest and manual execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10805, "tokens_out": 1137, "trimmed_requests": 0, "ts": "2026-09-27T17:57:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 11.4}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter.\n1. Modified `greeter/core.py` to support `shout` parameter in `greet`.\n2. Modified `greeter/cli.py` to add `--shout` flag and pass it to `greet`.\n3. Added tests in `tests/test_greeter.py` for both `greet` with `shout=True` and CLI with `--shout`.\n4. Verified with `pytest` and manual CLI execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11222, "tokens_out": 1321, "trimmed_requests": 0, "ts": "2026-09-27T17:57:40+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 12.7}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter. \n1. Updated `greeter/core.py` to support `shout` parameter.\n2. Updated `greeter/cli.py` to include `--shout` flag.\n3. Added tests for shout functionality in `tests/test_greeter.py`.\n4. Verified with pytest and manual execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11237, "tokens_out": 1249, "trimmed_requests": 0, "ts": "2026-09-27T17:57:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 12.2}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter CLI and implemented shouting logic in the core. Added tests for both core logic and CLI output.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11242, "tokens_out": 1001, "trimmed_requests": 0, "ts": "2026-09-27T17:58:03+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 10.4}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify function and tests. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3308, "tokens_out": 964, "trimmed_requests": 0, "ts": "2026-09-27T17:58:12+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 9.2}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify function in slugify.py and verified it with tests in test_slugify.py.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3433, "tokens_out": 1136, "trimmed_requests": 0, "ts": "2026-09-27T17:58:22+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 9.6}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added `slugify` function in `slugify.py` and verified with tests in `test_slugify.py`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3302, "tokens_out": 479, "trimmed_requests": 0, "ts": "2026-09-27T17:58:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify function and corresponding tests. All tests passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3312, "tokens_out": 797, "trimmed_requests": 0, "ts": "2026-09-27T17:58:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 7.0}
|
||||||
|
{"backing": "openai/gemma-4-26b-a4b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-26b-a4b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added `slugify` function in `slugify.py` and verified it with tests in `test_slugify.py`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3293, "tokens_out": 867, "trimmed_requests": 0, "ts": "2026-09-27T17:58:43+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 7.7}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the bug in `total_value` in `inventory.py` where it was summing only the unit prices instead of multiplying unit price by quantity for each item. Verified the fix with `pytest`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5927, "tokens_out": 610, "trimmed_requests": 0, "ts": "2026-09-27T18:10:31+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 18.0}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply unit price by quantity for each item. Verified the fix with pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5933, "tokens_out": 810, "trimmed_requests": 0, "ts": "2026-09-27T18:10:54+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 22.5}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the bug in `total_value` in `inventory.py` where only the unit price was being added instead of unit price times quantity. Verified the fix by running pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5939, "tokens_out": 561, "trimmed_requests": 0, "ts": "2026-09-27T18:11:11+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 16.6}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the bug in `inventory.py` where `total_value` only added the unit price instead of multiplying it by the quantity. Verified the fix with pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5927, "tokens_out": 598, "trimmed_requests": 0, "ts": "2026-09-27T18:11:29+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 17.5}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the bug in `inventory.py` where `total_value` was only summing unit prices instead of multiplying each unit price by its quantity. Verified the fix with `pytest`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5933, "tokens_out": 491, "trimmed_requests": 0, "ts": "2026-09-27T18:11:44+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 15.0}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region R237 in rates.py, changing it from 0.21 to 0.12 to match the required formula (237 mod 25) / 100. Verified with pytest.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 32146, "tokens_out": 1025, "trimmed_requests": 0, "ts": "2026-09-27T18:12:17+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 32.4}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py, changing it from 0.21 to 0.12. Verified that all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 32124, "tokens_out": 735, "trimmed_requests": 0, "ts": "2026-09-27T18:12:42+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 24.8}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py from 0.21 to 0.12, as required by the test suite (237 mod 25 / 100 = 12/100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 17749, "tokens_out": 489, "trimmed_requests": 0, "ts": "2026-09-27T18:12:59+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 16.8}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Corrected the rate for region R237 in rates.py from 0.21 to 0.12.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 17758, "tokens_out": 548, "trimmed_requests": 0, "ts": "2026-09-27T18:13:18+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 18.3}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region code R237 in rates.py. Changed its value from 0.21 to 0.12. Verified that all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 40513, "tokens_out": 4946, "trimmed_requests": 0, "ts": "2026-09-27T18:15:36+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 137.5}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a `--shout` option to the greeter. Modified `greeter/core.py` to support shouting and `greeter/cli.py` to handle the new flag. Added tests to verify both the core logic and the CLI interface.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9821, "tokens_out": 652, "trimmed_requests": 0, "ts": "2026-09-27T18:15:56+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 20.0}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter. Updated core.py to support shouting, cli.py to handle the flag, and added tests to verify both the core logic and the CLI behavior.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9854, "tokens_out": 633, "trimmed_requests": 0, "ts": "2026-09-27T18:16:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 19.6}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter, allowing greetings to be returned in upper case. Updated core logic, CLI argument parsing, and added corresponding tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 11959, "tokens_out": 1071, "trimmed_requests": 0, "ts": "2026-09-27T18:17:42+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 11, "wall_s": 85.2}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added `--shout` option to the greeter. Updated `greeter/core.py` to support shouting, `greeter/cli.py` to accept the flag, and added tests in `tests/test_greeter.py`. Verified with pytest and manual execution.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9821, "tokens_out": 626, "trimmed_requests": 0, "ts": "2026-09-27T18:18:02+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 19.8}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added `--shout` option to greeter. Updated `greeter/core.py` to handle the `shout` argument and `greeter/cli.py` to accept the flag. Added tests to `tests/test_greeter.py` and verified they pass and that the CLI works as expected.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9809, "tokens_out": 705, "trimmed_requests": 0, "ts": "2026-09-27T18:18:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 21.4}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Implemented slugify function in slugify.py and verified it with pytest tests in test_slugify.py.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3358, "tokens_out": 348, "trimmed_requests": 0, "ts": "2026-09-27T18:18:34+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 10.5}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a `slugify` function in `slugify.py` and verified it with tests in `test_slugify.py`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3242, "tokens_out": 438, "trimmed_requests": 0, "ts": "2026-09-27T18:18:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 12.3}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with the requested slugify function and verified it with pytest tests in test_slugify.py.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3234, "tokens_out": 493, "trimmed_requests": 0, "ts": "2026-09-27T18:19:01+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 13.6}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Implemented a `slugify` function in `slugify.py` that lowercases text, replaces non-alphanumeric sequences with a single hyphen, and strips leading/trailing hyphens. Added and passed tests in `test_slugify.py`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3234, "tokens_out": 461, "trimmed_requests": 0, "ts": "2026-09-27T18:19:14+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 12.8}
|
||||||
|
{"backing": "openai/gemma-4-31b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "gemma-4-31b", "model_info": {"max_input_tokens": 49152, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, q4_0, q8_0 KV, 48k, temp 1.0 top-p 0.95 top-k 64, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Implemented a `slugify` function in `slugify.py` and verified it with tests in `test_slugify.py`. All four example cases passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3298, "tokens_out": 429, "trimmed_requests": 0, "ts": "2026-09-27T18:19:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 12.2}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item, passing all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 7709, "tokens_out": 868, "trimmed_requests": 0, "ts": "2026-09-27T17:17:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 9.3}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item, making tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6196, "tokens_out": 681, "trimmed_requests": 0, "ts": "2026-09-27T17:17:31+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 7.5}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item, making total_value compute correctly. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6358, "tokens_out": 752, "trimmed_requests": 0, "ts": "2026-09-27T17:17:39+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 8.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed `total_value` function in `inventory.py` to multiply unit price by quantity for each item, making the total value correct. Tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6335, "tokens_out": 881, "trimmed_requests": 0, "ts": "2026-09-27T17:17:49+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 9.3}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item, passing all tests.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 7614, "tokens_out": 721, "trimmed_requests": 0, "ts": "2026-09-27T17:17:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 7.9}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py from 0.21 to 0.12 (since 237 mod 25 = 12, 12/100 = 0.12). All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 23909, "tokens_out": 2327, "trimmed_requests": 0, "ts": "2026-09-27T17:18:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 25.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 from 0.21 to 0.12 (since 237 mod 25 = 12, i.e., 0.12). All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 33528, "tokens_out": 4836, "trimmed_requests": 0, "ts": "2026-09-27T17:19:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 52.4}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed the single wrong rate entry for region code R237. Changed from 0.21 to 0.12 (since 237 mod 25 = 12, 12/100 = 0.12). All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 31300, "tokens_out": 4765, "trimmed_requests": 0, "ts": "2026-09-27T17:20:08+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 51.3}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed the one wrong entry in the rate table: region code R237 had value 0.21, should be (237 mod 25) / 100 = 12 / 100 = 0.12. Changed the value in rates.py accordingly. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 33528, "tokens_out": 4596, "trimmed_requests": 0, "ts": "2026-09-27T17:20:59+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 50.2}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py. The correct rate should be (237 mod 25)/100 = 12/100 = 0.12, but it was incorrectly set to 0.21. Changed the value from 0.21 to 0.12. All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 23254, "tokens_out": 4940, "trimmed_requests": 0, "ts": "2026-09-27T17:21:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 53.0}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added --shout option to greeter: updated greet in core.py to accept shout=False, upper-cased output when True; added --shout flag in cli.py; added two tests (one for greet with shout=True, one for CLI with --shout). All tests pass and CLI works as expected.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 18519, "tokens_out": 1737, "trimmed_requests": 0, "ts": "2026-09-27T17:22:11+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 18.2}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Modified core.greet to accept shout=False and return uppercase when True. Modified cli.py to add --shout flag and pass it to greet. Added two tests (one for core.greet with shout=True and one for CLI with --shout). All tests pass and CLI works correctly.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 20265, "tokens_out": 2239, "trimmed_requests": 0, "ts": "2026-09-27T17:22:34+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 13, "wall_s": 22.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added --shout option to greeter: updated core.greet to accept shout=False and uppercase the name when True; updated cli.py to add --shout flag and pass it to greet; added two tests for shout behavior (one for core.greet, one for CLI via capsys); all tests pass and CLI prints 'HELLO, BOB!' when using --shout.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 25055, "tokens_out": 2097, "trimmed_requests": 0, "ts": "2026-09-27T17:22:56+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 14, "wall_s": 21.9}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added --shout option to greeter: core.greet now takes shout=False; cli adds --shout flag; tests added for both core and cli shout behavior; all tests pass and command works.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 18240, "tokens_out": 2766, "trimmed_requests": 0, "ts": "2026-09-27T17:23:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 27.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Successfully added --shout option to greeter. Modified core.py to accept shout=bool and return uppercase when True. Modified cli.py to add --shout flag and pass it to greet. Added two tests for greet(shout=True) and main([\"--shout\", \"Bob\"]). All tests pass and the CLI works as expected.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 21491, "tokens_out": 2173, "trimmed_requests": 0, "ts": "2026-09-27T17:23:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 13, "wall_s": 22.2}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Created slugify.py with slugify function that lowercases text, replaces runs of non-alnum with a single hyphen, and strips hyphens from both ends. Created test_slugify.py with pytest tests covering the four required examples and additional edge cases. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 10388, "tokens_out": 4091, "trimmed_requests": 0, "ts": "2026-09-27T17:24:28+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 40.5}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added slugify.py with slugify(text: str) -> str that lowercases, replaces non-alphanumeric runs with single hyphens, and strips hyphens from both ends. Created test_slugify.py with pytest tests covering the four examples, all passing.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5479, "tokens_out": 2510, "trimmed_requests": 0, "ts": "2026-09-27T17:24:53+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 24.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Created slugify.py with slugify function that lowercases, replaces runs of non-alphanumeric with hyphens, and strips hyphens from ends. Created test_slugify.py with four tests covering the examples. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5576, "tokens_out": 1993, "trimmed_requests": 0, "ts": "2026-09-27T17:25:13+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 19.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added slugify function in slugify.py that lowercases text, replaces non-alphanumeric runs with a single hyphen, and strips hyphens from ends. Created test_slugify.py with four tests covering examples, all passing.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5642, "tokens_out": 1941, "trimmed_requests": 0, "ts": "2026-09-27T17:25:32+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 19.3}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090", "outcome": "completed", "outcome_note": "Added slugify function in slugify.py that lowercases text, replaces runs of non-alphanumeric characters with a single hyphen, and strips hyphens from ends. Created test_slugify.py with pytest tests covering the four examples; all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5677, "tokens_out": 2688, "trimmed_requests": 0, "ts": "2026-09-27T17:25:59+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 26.6}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value function in inventory.py by multiplying unit_price by quantity for each item. Tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6191, "tokens_out": 532, "trimmed_requests": 0, "ts": "2026-09-27T18:20:29+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 6.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value function in inventory.py to multiply unit_price by quantity for each item, making it sum unit_price * quantity instead of just unit_price. Tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6191, "tokens_out": 776, "trimmed_requests": 0, "ts": "2026-09-27T18:20:38+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 8.4}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value function in inventory.py to multiply unit_price by quantity for each item, then summed. Both tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6191, "tokens_out": 539, "trimmed_requests": 0, "ts": "2026-09-27T18:20:44+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 6.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value function to multiply unit_price by quantity for each item, matching the docstring and test expectation. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6186, "tokens_out": 728, "trimmed_requests": 0, "ts": "2026-09-27T18:20:53+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 7.9}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item, matching test expectation. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 7648, "tokens_out": 629, "trimmed_requests": 0, "ts": "2026-09-27T18:21:00+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 7.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 from 0.21 to 0.12 (since 237 mod 25 = 12). All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 23270, "tokens_out": 4031, "trimmed_requests": 0, "ts": "2026-09-27T18:21:44+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 43.6}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 to (237 mod 25)/100 = 12/100 = 0.12.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 16630, "tokens_out": 1453, "trimmed_requests": 0, "ts": "2026-09-27T18:22:01+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 16.6}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the single wrong rate entry for region code R237 in rates.py. Changed value from 0.21 to 0.12 (since 237 mod 25 = 12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 23270, "tokens_out": 4556, "trimmed_requests": 0, "ts": "2026-09-27T18:22:51+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 49.2}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 in rates.py from 0.21 to 0.12 (since 237 mod 25 = 12, 12/100 = 0.12). All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 16735, "tokens_out": 1593, "trimmed_requests": 0, "ts": "2026-09-27T18:23:09+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 18.0}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region code R237 from 0.21 to 0.12 (since 237 mod 25 = 12, divided by 100). All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 23188, "tokens_out": 5076, "trimmed_requests": 0, "ts": "2026-09-27T18:24:05+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 55.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter: core.greet now accepts shout=bool=False and returns uppercase when True; cli.py adds --shout store_true flag and passes it to greet; tests added for both greet(shout=True) and main(['--shout', 'Bob']). All tests pass and CLI prints 'HELLO, BOB!' when --shout is used.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 24779, "tokens_out": 2576, "trimmed_requests": 0, "ts": "2026-09-27T18:24:32+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 14, "wall_s": 26.9}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Modified core.greet to accept shout=False and return uppercase when True. Added --shout flag to CLI and passed it to greet. Added two tests: one for greet with shout=True and one for CLI with --shout flag. All tests pass and CLI output matches expected.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14785, "tokens_out": 3312, "trimmed_requests": 0, "ts": "2026-09-27T18:25:05+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 33.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter: core.greet now accepts shout=bool default False and returns upper case when True; cli.py adds --shout flag and passes it; tests added for both greet and CLI; all tests pass and command prints correctly.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 18591, "tokens_out": 1921, "trimmed_requests": 0, "ts": "2026-09-27T18:25:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 19.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.05s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Updated greet function in core.py with shout parameter; updated cli.py to add --shout flag and pass it to greet; added two tests (one for greet with shout=True and one for CLI with --shout). All tests pass and CLI prints 'HELLO, BOB!' when invoked with --shout.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 18584, "tokens_out": 1434, "trimmed_requests": 0, "ts": "2026-09-27T18:25:41+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 12, "wall_s": 15.2}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter: core.greet now takes shout=False keyword, cli adds --shout flag, tests added for both core and CLI, all tests pass and CLI works correctly.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 20462, "tokens_out": 1703, "trimmed_requests": 0, "ts": "2026-09-27T18:25:59+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 13, "wall_s": 17.8}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Successfully implemented slugify function in slugify.py that lowercases text, replaces every run of non-alphanumeric characters (a-z, 0-9) with a single hyphen, and strips hyphens from both ends. Created test_slugify.py with pytest tests for the four required examples and ran tests; all pass. Verified edge cases.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 30463, "tokens_out": 24495, "trimmed_requests": 0, "ts": "2026-09-27T18:30:05+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 14, "wall_s": 245.6}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify function in slugify.py using regex substitution to replace non-alnum characters with hyphens and strip hyphens from ends. Created test_slugify.py with four tests covering the examples. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 8921, "tokens_out": 6467, "trimmed_requests": 0, "ts": "2026-09-27T18:31:09+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 63.9}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with the required function, test_slugify.py with pytest tests for the four examples, and all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 9089, "tokens_out": 3303, "trimmed_requests": 0, "ts": "2026-09-27T18:31:42+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 33.1}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with slugify function that lowercases text, replaces runs of non-alphanumeric characters with a single hyphen, and strips hyphens from both ends. Created test_slugify.py with pytest tests for the four examples. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5948, "tokens_out": 1905, "trimmed_requests": 0, "ts": "2026-09-27T18:32:02+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 19.0}
|
||||||
|
{"backing": "openai/granite-4.2-8b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "granite-4.2-8b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 128k, temp 1.0 top-p 0.95, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify function in slugify.py that lowercases text, replaces runs of non-alnum characters with a single hyphen, and strips hyphens from both ends. Created test_slugify.py with four tests covering the examples, and all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5551, "tokens_out": 2521, "trimmed_requests": 0, "ts": "2026-09-27T18:32:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 25.0}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1578, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-27T18:35:22+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1580, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-27T18:35:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1576, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-27T18:35:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1582, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-27T18:35:26+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "29.0 \u00b1 2.9e-05\nE \nE comparison failed\nE Obtained: 0.35\nE Expected: 29.0 \u00b1 2.9e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n________________________ test_total_value[items2-37.5] _________________________\n\nitems = [Item(name='gear', unit_price=12.5, quantity=3)], expected = 37.5\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 12.5 == 37.5 \u00b1 3.7e-05\nE \nE comparison failed\nE Obtained: 12.5\nE Expected: 37.5 \u00b1 3.7e-05\n\n.bench_check/test_hidden.py:22: AssertionError\n_________________________ test_total_value[items3-0.0] _________________________\n\nitems = [Item(name='empty', unit_price=9.99, quantity=0)], expected = 0.0\n\n @pytest.mark.parametrize(\n (\"items\", \"expected\"),\n [\n ([], 0.0),\n ([Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)], 29.0),\n ([Item(\"gear\", 12.5, 3)], 37.5),\n ([Item(\"empty\", 9.99, 0)], 0.0),\n ],\n )\n def test_total_value(items: list[Item], expected: float) -> None:\n> assert total_value(items) == pytest.approx(expected)\nE assert 9.99 == 0.0 \u00b1 1.0e-12\nE \nE comparison failed\nE Obtained: 9.99\nE Expected: 0.0 \u00b1 1.0e-12\n\n.bench_check/test_hidden.py:22: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_total_value[items1-29.0] - assert 0....\nFAILED .bench_check/test_hidden.py::test_total_value[items2-37.5] - assert 12...\nFAILED .bench_check/test_hidden.py::test_total_value[items3-0.0] - assert 9.9...\n3 failed, 3 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF. [100%]\n=================================== FAILURES ===================================\n______________________ test_total_value_counts_every_unit ______________________\n\n def test_total_value_counts_every_unit() -> None:\n items = [Item(\"bolt\", 0.25, 100), Item(\"nut\", 0.10, 40)]\n> assert total_value(items) == 29.0\nE AssertionError: assert 0.35 =", "prose_turns": 0, "provider": "openai", "success": false, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 1578, "tokens_out": 50, "trimmed_requests": 0, "ts": "2026-09-27T18:35:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 2, "wall_s": 1.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n......................F................................................. [ 95%]\n............. [100%]\n=================================== FAILURES ===================================\n________________________________ test_rate[237] ________________________________\n\nn = 237\n\n @pytest.mark.parametrize(\"n\", range(300))\n def test_rate(n: int) -> None:\n> assert RATES[f\"R{n:03d}\"] == pytest.approx(n % 25 / 100)\nE assert 0.21 == 0.12 \u00b1 1.2e-07\nE \nE comparison failed\nE Obtained: 0.21\nE Expected: 0.12 \u00b1 1.2e-07\n\n.bench_check/test_hidden.py:11: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_rate[237] - assert 0.21 == 0.12 \u00b1 1....\n1 failed, 300 passed in 0.21s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\n........................................................................ [ 24%]\n........................................................................ [ 48%]\n........................................................................ [ 72%]\n.....................F.................................................. [ 96%]\n............ [100%]\n=================================== FAILU", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 20975, "tokens_out": 4339, "trimmed_requests": 0, "ts": "2026-09-27T18:36:02+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 34.3}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmplpnklvun/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 10987, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-27T18:36:06+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 3.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmp240rbprt/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 10987, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-27T18:36:09+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 3.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmpagzonvyn/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 10974, "tokens_out": 183, "trimmed_requests": 0, "ts": "2026-09-27T18:36:12+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 3.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from rates import RATES\nE File \"/tmp/tmp9kctkv9f/work/rates.py\", line 1\nE \"R237\": 0.12,\nE IndentationError: unexpected indent\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 3, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n________________________ ERROR collecting test_rates.py ________________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 10976, "tokens_out": 181, "trimmed_requests": 0, "ts": "2026-09-27T18:36:16+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 3.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10766, "tokens_out": 549, "trimmed_requests": 0, "ts": "2026-09-27T18:36:21+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "F.FF. [100%]\n=================================== FAILURES ===================================\n__________________________ test_default_is_unchanged ___________________________\n\n def test_default_is_unchanged() -> None:\n> assert greet(\"Bob\") == \"Hello, Bob!\"\nE AssertionError: assert 'HELLO, Bob!' == 'Hello, Bob!'\nE \nE - Hello, Bob!\nE + HELLO, Bob!\n\n.bench_check/test_hidden.py:9: AssertionError\n_______________________________ test_cli_default _______________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x7b6d8abfca70>\n\n def test_cli_default(capsys) -> None:\n> assert main([\"Ada\"]) == 0\nE AssertionError: assert None == 0\nE + where None = main(['Ada'])\n\n.bench_check/test_hidden.py:17: AssertionError\n----------------------------- Captured stdout call -----------------------------\nHELLO, Ada!\n________________________________ test_cli_shout ________________________________\n\ncapsys = <_pytest.capture.CaptureFixture object at 0x7b6d8abbfb30>\n\n def test_cli_shout(capsys) -> None:\n> assert main([\"--shout\", \"Ada\"]) == 0\nE AssertionError: assert None == 0\nE + where None = main(['--shout', 'Ada'])\n\n.bench_check/test_hidden.py:22: AssertionError\n----------------------------- Captured stdout call -----------------------------\nHELLO, ADA!\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_default_is_unchanged - AssertionErro...\nFAILED .bench_check/test_hidden.py::test_cli_default - AssertionError: assert...\nFAILED .bench_check/test_hidden.py::test_cli_shout - AssertionError: assert N...\n3 failed, 2 passed in 0.05s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Tests pass.", "prose_turns": 0, "provider": "openai", "success": false, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 6758, "tokens_out": 474, "trimmed_requests": 0, "ts": "2026-09-27T18:36:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 4.0}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10775, "tokens_out": 549, "trimmed_requests": 0, "ts": "2026-09-27T18:36:30+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to greeter. Tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10775, "tokens_out": 549, "trimmed_requests": 0, "ts": "2026-09-27T18:36:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "t('stderr') is not None:\n raise ValueError('stdout and stderr arguments may not be used '\n 'with capture_output.')\n kwargs['stdout'] = PIPE\n kwargs['stderr'] = PIPE\n \n with Popen(*popenargs, **kwargs) as process:\n try:\n stdout, stderr = process.communicate(input, timeout=timeout)\n except TimeoutExpired as exc:\n process.kill()\n if _mswindows:\n # Windows accumulates the output in a single blocking\n # read() call run on child threads, with the timeout\n # being done in a join() on those threads. communicate()\n # _after_ kill() is required to collect that and add it\n # to the exception.\n exc.stdout, exc.stderr = process.communicate()\n else:\n # POSIX _communicate already populated the output so\n # far into the TimeoutExpired exception.\n process.wait()\n raise\n except: # Including KeyboardInterrupt, communicate handled that.\n process.kill()\n # We don't call process.wait() as .__exit__ does that for us.\n raise\n retcode = process.poll()\n if check and retcode:\n> raise CalledProcessError(retcode, process.args,\n output=stdout, stderr=stderr)\nE subprocess.CalledProcessError: Command '['/root/nisse/.venv/bin/python', '-m', 'greeter', '--shout', 'Bob']' returned non-zero exit status 2.\n\n/usr/lib/python3.12/subprocess.py:571: CalledProcessError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_cli_shout - SystemExit: 2\nFAILED .bench_check/test_hidden.py::test_module_entry_point - subprocess.Call...\n2 failed, 3 passed in 0.13s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "executor call failed: upstream returned 429: {\"error\":{\"message\":\"Rate limit exceeded for api_key: 8941a8ca890cd15add31e5bcb3204db273d130b7f62dede0557b3a11c1a2b52b. Limit type: requests. Current limit: 60, Remaining: 0. Limit resets at: 2026-09-27 18:37:56 UTC\",\"type\":\"throttling_error\",\"param\":null,\"code\":\"429\"}}", "prose_turns": 0, "provider": "openai", "success": false, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 1616, "tokens_out": 97, "trimmed_requests": 0, "ts": "2026-09-27T18:36:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 20.9}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": " (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'leading-and-trailing'\nE \nE - leading-and-trailing\n\n.bench_check/test_hidden.py:19: AssertionError\n_____________________ test_slugify[MiXeD CaSe-mixed-case] ______________________\n\ntext = 'MiXeD CaSe', expected = 'mixed-case'\n\n @pytest.mark.parametrize(\n (\"text\", \"expected\"),\n [\n (\"Hello, World!\", \"hello-world\"),\n (\" Nisse Agent \", \"nisse-agent\"),\n (\"a--b\", \"a-b\"),\n (\"\", \"\"),\n (\"Python 3.12 Release\", \"python-3-12-release\"),\n (\"--leading and trailing--\", \"leading-and-trailing\"),\n (\"MiXeD CaSe\", \"mixed-case\"),\n (\"!!!\", \"\"),\n ],\n )\n def test_slugify(text: str, expected: str) -> None:\n> assert slugify(text) == expected\nE AssertionError: assert '' == 'mixed-case'\nE \nE - mixed-case\n\n.bench_check/test_hidden.py:19: AssertionError\n=========================== short test summary info ============================\nFAILED .bench_check/test_hidden.py::test_slugify[Hello, World!-hello-world]\nFAILED .bench_check/test_hidden.py::test_slugify[ Nisse Agent -nisse-agent]\nFAILED .bench_check/test_hidden.py::test_slugify[a--b-a-b] - AssertionError: ...\nFAILED .bench_check/test_hidden.py::test_slugify[Python 3.12 Release-python-3-12-release]\nFAILED .bench_check/test_hidden.py::test_slugify[--leading and trailing---leading-and-trailing]\nFAILED .bench_check/test_hidden.py::test_slugify[MiXeD CaSe-mixed-case] - Ass...\n6 failed, 2 passed in 0.03s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\n ^^^^^^^\nE NameError: name 'slugify' is not defined\n\ntest_slugify.py:4: NameError\n===============", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3658, "tokens_out": 250, "trimmed_requests": 0, "ts": "2026-09-27T18:37:19+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 22.6}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from slugify import slugify\nE File \"/tmp/tmpj_2vxo2m/work/slugify.py\", line 1\nE def slugify(text: str) -> str:\\n import re\\n text = text.lower()\\n text = re.sub(r'[^a-z0-9]+', '-', text)\\n text = text.strip('-')\\n return text\nE ^\nE SyntaxError: unexpected character after line continuation character\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n_______________________ ERROR collecting test_slugify.py _______________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5808, "tokens_out": 331, "trimmed_requests": 0, "ts": "2026-09-27T18:37:23+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 3.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from slugify import slugify\nE File \"/tmp/tmpboff_f4_/work/slugify.py\", line 1\nE def slugify(text: str) -> str:\\n import re\\n text = text.lower()\\n text = re.sub(r'[^a-z0-9]+', '-', text)\\n return text.strip('-')\\n\nE ^\nE SyntaxError: unexpected character after line continuation character\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n_______________________ ERROR collecting test_slugify.py _______________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5825, "tokens_out": 380, "trimmed_requests": 0, "ts": "2026-09-27T18:37:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 3.8}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from slugify import slugify\nE File \"/tmp/tmpypm_nshh/work/slugify.py\", line 1\nE def slugify(text: str) -> str:\\n import re\\n text = text.lower()\\n text = re.sub(r'[^a-z0-9]+', '-', text)\\n return text.strip('-')\nE ^\nE SyntaxError: unexpected character after line continuation character\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.12s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 1\nstdout:\nF [100%]\n=================================== FAILURES ===================================\n_________________________________ test_slugify _________________________________\n\n def test_slugify():\n> assert slugify('Hello, World!') == 'hello-world'\n ^^^^^^^\nE NameError: name 'slugify' is not defined\n\ntest_slugify.py:5: NameError\n===============", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 5692, "tokens_out": 337, "trimmed_requests": 0, "ts": "2026-09-27T18:37:31+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 3.4}
|
||||||
|
{"backing": "openai/qwen3-8b-awq", "check_output": "\n==================================== ERRORS ====================================\n_________________ ERROR collecting .bench_check/test_hidden.py _________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__.py:90: in import_module\n return _bootstrap._gcd_import(name[level:], package, level)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n<frozen importlib._bootstrap>:1387: in _gcd_import\n ???\n<frozen importlib._bootstrap>:1360: in _find_and_load\n ???\n<frozen importlib._bootstrap>:1331: in _find_and_load_unlocked\n ???\n<frozen importlib._bootstrap>:935: in _load_unlocked\n ???\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/assertion/rewrite.py:188: in exec_module\n exec(co, module.__dict__)\n.bench_check/test_hidden.py:2: in <module>\n from slugify import slugify\nE File \"/tmp/tmp9oyadssz/work/slugify.py\", line 1\nE def slugify(text: str) -> str:\\n import re\\n text = text.lower()\\n text = re.sub(r'[^a-z0-9]+', '-', text)\\n text = text.strip('-')\\n return text\nE ^\nE SyntaxError: unexpected character after line continuation character\n=========================== short test summary info ============================\nERROR .bench_check/test_hidden.py\n!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!\n1 error in 0.13s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "local-fast", "model_info": {"max_input_tokens": 32768, "max_output_tokens": 8192, "supports_function_calling": true, "supports_tool_choice": true}, "note": "vLLM 0.30.0, AWQ, fp8 KV, 32k, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "2 consecutive command failures; last: exit code: 2\nstdout:\n\n==================================== ERRORS ====================================\n_______________________ ERROR collecting test_slugify.py _______________________\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/python.py:508: in importtestmodule\n mod = import_path(\n/root/nisse/.venv/lib/python3.12/site-packages/_pytest/pathlib.py:596: in import_path\n importlib.import_module(module_name)\n/usr/lib/python3.12/importlib/__init__", "prose_turns": 0, "provider": "openai", "success": false, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4136, "tokens_out": 269, "trimmed_requests": 0, "ts": "2026-09-27T18:37:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 3.0}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity per item; all tests now pass (2 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5132, "tokens_out": 310, "trimmed_requests": 0, "ts": "2026-09-27T17:44:29+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity per item. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6915, "tokens_out": 645, "trimmed_requests": 0, "ts": "2026-09-27T17:44:37+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 8.0}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity per item; all tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5132, "tokens_out": 306, "trimmed_requests": 0, "ts": "2026-09-27T17:44:42+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.3}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply unit_price by quantity for each item; both tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5132, "tokens_out": 308, "trimmed_requests": 0, "ts": "2026-09-27T17:44:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 4.5}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value() in inventory.py to multiply unit price by quantity instead of just summing unit prices. Both tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5316, "tokens_out": 414, "trimmed_requests": 0, "ts": "2026-09-27T17:44:53+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.6}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.21s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the R237 entry in rates.py (0.21 -> 0.12) and verified all 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 12453, "tokens_out": 202, "trimmed_requests": 0, "ts": "2026-09-27T17:44:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 4.2}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.21s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the wrong rate for region R237 (was 0.21, now 0.12 = 237 mod 25 / 100) in rates.py. All 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 25884, "tokens_out": 4404, "trimmed_requests": 0, "ts": "2026-09-27T17:45:43+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 45.4}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the one wrong rate entry in rates.py: R237 was 0.21, corrected to 0.12 (237 mod 25 = 12, /100). All 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 12475, "tokens_out": 230, "trimmed_requests": 0, "ts": "2026-09-27T17:45:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the single wrong rate entry in rates.py: R237 was 0.21 instead of (237 mod 25)/100 = 0.12. All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 25884, "tokens_out": 4403, "trimmed_requests": 0, "ts": "2026-09-27T17:46:34+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 45.7}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.21s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the wrong rate for R237 in rates.py (was 0.21, now 0.12 = 237 mod 25 / 100). All 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 25868, "tokens_out": 4400, "trimmed_requests": 0, "ts": "2026-09-27T17:47:20+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 45.5}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter: greet() takes shout: bool = False and uppercases the greeting when true; cli.py gained a --shout store_true flag passed to greet; tests added for both shout=True and the CLI with capsys. All 4 tests pass and `python -m greeter --shout Bob` prints HELLO, BOB!.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 8490, "tokens_out": 592, "trimmed_requests": 0, "ts": "2026-09-27T17:47:28+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 7.6}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter.\n\nChanges:\n- `greeter/core.py`: `greet` now takes `shout: bool = False`; when true it returns the greeting in uppercase.\n- `greeter/cli.py`: added a `--shout` `store_true` flag and passes it to `greet`.\n- `tests/test_greeter.py`: added `test_greet_shout` and `test_cli_prints_the_greeting_shout`.\n\nVerification: `python -m pytest -q` passes (4 passed), and `python -m greeter --shout Bob` prints `HELLO, BOB!`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10204, "tokens_out": 809, "trimmed_requests": 0, "ts": "2026-09-27T17:47:39+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 10.0}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter: greet() now accepts shout=True (returns uppercase), the CLI gained a --shout store_true flag, and two tests were added (greet shout + CLI capsys). All 4 tests pass and `python -m greeter --shout Bob` prints HELLO, BOB!.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 8490, "tokens_out": 585, "trimmed_requests": 0, "ts": "2026-09-27T17:47:46+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 7.3}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter: greet() now accepts shout=True (returns uppercase greeting), the CLI has a --shout flag passed through, and two new tests cover both. All 4 tests pass and `python -m greeter --shout Bob` prints HELLO, BOB!.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10964, "tokens_out": 708, "trimmed_requests": 0, "ts": "2026-09-27T17:47:56+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 9.0}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter.\n\n1. `greeter/core.py`: `greet` now takes `shout: bool = False`; when true it returns the greeting in upper case (`greet(\"Bob\", shout=True) == \"HELLO, BOB!\"`). Default behaviour unchanged.\n2. `greeter/cli.py`: added `parser.add_argument(\"--shout\", action=\"store_true\")` and passes `shout=args.shout` to `greet`.\n3. `tests/test_greeter.py`: added `test_greet_shout` and `test_cli_prints_the_greeting_shout` (uses capsys).\n4. `python -m pytest -q` \u2192 4 passed; `pyt", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 10198, "tokens_out": 848, "trimmed_requests": 0, "ts": "2026-09-27T17:48:06+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 10.2}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py with slugify() and test_slugify.py covering the four specified examples; all 4 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 2717, "tokens_out": 324, "trimmed_requests": 0, "ts": "2026-09-27T17:48:11+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.3}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py with slugify() and test_slugify.py covering all four examples; all 4 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 2651, "tokens_out": 277, "trimmed_requests": 0, "ts": "2026-09-27T17:48:15+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 3.7}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify() in slugify.py using a regex to collapse non-alphanumeric runs into single hyphens, plus test_slugify.py covering all four examples; all 4 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 7110, "tokens_out": 552, "trimmed_requests": 0, "ts": "2026-09-27T17:48:22+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 7.0}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py with a slugify() function and test_slugify.py covering the four required examples; pytest passes (4 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 2821, "tokens_out": 384, "trimmed_requests": 0, "ts": "2026-09-27T17:48:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.9}
|
||||||
|
{"backing": "openai/mimo-v2.6-9b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "mimo-v2.6-9b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q6_K, q8_0 KV, 256k, temp 0.6 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py with slugify() and test_slugify.py covering the four specified examples; all 4 pytest tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 2808, "tokens_out": 354, "trimmed_requests": 0, "ts": "2026-09-27T17:48:32+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.4}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed `total_value` in inventory.py: changed `total += item.unit_price` to `total += item.unit_price * item.quantity` so each item contributes its unit price times its quantity to the total inventory value. All 2 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6995, "tokens_out": 651, "trimmed_requests": 0, "ts": "2026-09-27T17:49:45+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.3}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` to correctly multiply each item's `unit_price` by its `quantity` when accumulating the total, instead of just adding `unit_price`. Both tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6728, "tokens_out": 630, "trimmed_requests": 0, "ts": "2026-09-27T17:49:50+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.3}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed `total_value` in inventory.py: the bug was that it only summed `item.unit_price` without multiplying by `item.quantity`. Changed `total += item.unit_price` to `total += item.unit_price * item.quantity`. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5219, "tokens_out": 642, "trimmed_requests": 0, "ts": "2026-09-27T17:49:56+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 5.2}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed `total_value` in `inventory.py` \u2014 changed `total += item.unit_price` to `total += item.unit_price * item.quantity` so each item contributes its unit price times its quantity. All 2 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6808, "tokens_out": 674, "trimmed_requests": 0, "ts": "2026-09-27T17:50:02+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.6}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the `total_value` function in `inventory.py` so that each item contributes `unit_price * quantity` to the total, rather than just `unit_price`. All tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6285, "tokens_out": 547, "trimmed_requests": 0, "ts": "2026-09-27T17:50:07+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 5.0}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the wrong rate entry for region R237 in rates.py: changed from 0.21 to 0.12 (237 mod 25 = 12, 12/100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 18886, "tokens_out": 580, "trimmed_requests": 0, "ts": "2026-09-27T17:50:13+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 6.0}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the wrong rate for region code R237 in rates.py, changing it from 0.21 to 0.12 (since 237 mod 25 = 12, and 12/100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 18904, "tokens_out": 625, "trimmed_requests": 0, "ts": "2026-09-27T17:50:20+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 6.4}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region R237 in rates.py: changed the value from 0.21 to 0.12 (since 237 mod 25 = 12, and the rate should be (NNN mod 25) / 100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 21349, "tokens_out": 718, "trimmed_requests": 0, "ts": "2026-09-27T17:50:28+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 7.2}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the rate for region R237 in rates.py: changed the value from 0.21 to 0.12 (237 mod 25 / 100 = 0.12). All 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 18717, "tokens_out": 676, "trimmed_requests": 0, "ts": "2026-09-27T17:50:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 6.8}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the incorrect rate for region R237 in rates.py, changing it from 0.21 to 0.12 (since 237 mod 25 = 12, and 12/100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 27343, "tokens_out": 4956, "trimmed_requests": 0, "ts": "2026-09-27T17:51:09+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 32.9}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added shout support to the greeter:\n1. `greeter/core.py`: Added `shout: bool = False` keyword argument to `greet()`, returning the greeting in uppercase when `shout=True`.\n2. `greeter/cli.py`: Added `--shout` flag (action=\"store_true\") and passed it to `greet()`.\n3. `tests/test_greeter.py`: Added two tests \u2014 one for `greet(\"Bob\", shout=True)` and one verifying `main([\"--shout\", \"Bob\"])` prints `HELLO, BOB!` via capsys.\nAll 4 tests pass and CLI verification succeeds.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 8616, "tokens_out": 1008, "trimmed_requests": 0, "ts": "2026-09-27T17:51:17+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 7.8}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "executor kept replying in prose instead of tools", "prose_turns": 2, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9357, "tokens_out": 748, "trimmed_requests": 0, "ts": "2026-09-27T17:51:24+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 6.7}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "executor kept replying in prose instead of tools", "prose_turns": 2, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9242, "tokens_out": 623, "trimmed_requests": 0, "ts": "2026-09-27T17:51:29+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 5.4}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a `--shout` option to the greeter. Modified `greeter/core.py` to accept a `shout` keyword argument that uppercases the greeting when True. Updated `greeter/cli.py` to add a `--shout` flag and pass it to `greet`. Added two tests to `tests/test_greeter.py`: one for `greet(\"Bob\", shout=True)` and one for `main([\"--shout\", \"Bob\"])` with capsys. All 4 tests pass, and `python -m greeter --shout Bob` prints `HELLO, BOB!`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 16065, "tokens_out": 1466, "trimmed_requests": 0, "ts": "2026-09-27T17:51:41+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 11.0}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "escalate", "outcome_note": "executor kept replying in prose instead of tools", "prose_turns": 2, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9418, "tokens_out": 681, "trimmed_requests": 0, "ts": "2026-09-27T17:51:47+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 5.9}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 complete: created slugify.py defining slugify(text) which lowercases, replaces non-alphanumeric runs with a single hyphen, and strips leading/trailing hyphens. Created test_slugify.py with 4 pytest tests covering all required examples, all passing.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4490, "tokens_out": 538, "trimmed_requests": 0, "ts": "2026-09-27T17:51:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 4.5}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created `slugify.py` with a `slugify(text: str) -> str` function that lowercases text, replaces non-alphanumeric character runs with a single hyphen, and strips leading/trailing hyphens. Created `test_slugify.py` with 4 pytest tests covering the required examples. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4538, "tokens_out": 631, "trimmed_requests": 0, "ts": "2026-09-27T17:51:57+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 5.1}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 complete: created slugify.py with a slugify(text) function that lowercases text, replaces non alphanumeric runs with a single hyphen, and strips leading/trailing hyphens. Created test_slugify.py covering all four examples, and all 4 pytest tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3410, "tokens_out": 546, "trimmed_requests": 0, "ts": "2026-09-27T17:52:02+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.3}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created `slugify.py` with a `slugify(text)` function that lowercases text, replaces non a-z/0-9 character runs with a single hyphen, and strips leading/trailing hyphens. Created `test_slugify.py` with 4 pytest tests covering the specified examples. All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3476, "tokens_out": 613, "trimmed_requests": 0, "ts": "2026-09-27T17:52:07+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.7}
|
||||||
|
{"backing": "openai/qwen3.6-35b-a3b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.6-35b-a3b", "model_info": {"max_input_tokens": 262144, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": false}, "note": "llama.cpp b11206, Q3_K_XL, q8_0 KV, 256k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Created slugify.py with a slugify() function that lowercases text, replaces non-alphanumeric runs with a single hyphen, and strips leading/trailing hyphens. Created test_slugify.py with 4 pytest tests covering the examples \"Hello, World!\" -> \"hello-world\", \" Nisse Agent \" -> \"nisse-agent\", \"a--b\" -> \"a-b\", and \"\" -> \"\". All tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3520, "tokens_out": 638, "trimmed_requests": 0, "ts": "2026-09-27T17:52:12+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 4.9}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply each item's unit price by its quantity; both tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 5275, "tokens_out": 439, "trimmed_requests": 0, "ts": "2026-09-27T18:01:40+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 11.8}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py to multiply each item's unit price by its quantity (was only summing unit prices). Both tests now pass (2 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6546, "tokens_out": 522, "trimmed_requests": 0, "ts": "2026-09-27T18:01:55+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 14.1}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py: it now accumulates item.unit_price * item.quantity instead of just item.unit_price. All 2 tests in test_inventory.py pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6575, "tokens_out": 557, "trimmed_requests": 0, "ts": "2026-09-27T18:02:10+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 15.0}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py: the loop added only item.unit_price, now it adds item.unit_price * item.quantity. Both tests pass (2 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6985, "tokens_out": 672, "trimmed_requests": 0, "ts": "2026-09-27T18:02:28+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 17.5}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "...... [100%]\n6 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed total_value in inventory.py so each item contributes unit_price * quantity instead of unit_price alone. All 2 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "fix-bug", "task_hash": "b78248aff00ddc39", "tokens_in": 6575, "tokens_out": 339, "trimmed_requests": 0, "ts": "2026-09-27T18:02:38+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 10.2}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the one wrong entry in rates.py: R237 changed from 0.21 to 0.12 (237 mod 25 = 12 \u2192 12/100). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 9841, "tokens_out": 525, "trimmed_requests": 0, "ts": "2026-09-27T18:02:54+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 15.0}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the one wrong entry in the rate table: `test_rate[R237]` was the only failing test, so in rates.py line 241 I changed \"R237\": 0.21 to \"R237\": 0.12 (237 mod 25 = 12 \u2192 0.12), leaving every other line untouched. All 300 tests now pass (`python -m pytest -q`).", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 13348, "tokens_out": 758, "trimmed_requests": 0, "ts": "2026-09-27T18:03:15+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 20.7}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the single wrong entry in rates.py: \"R237\": 0.21 \u2192 0.12 (= 237 % 25 / 100) on line 241. All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 12292, "tokens_out": 591, "trimmed_requests": 0, "ts": "2026-09-27T18:03:31+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 15.9}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 1, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Fixed the single wrong entry in rates.py: changed region code R237 from 0.21 to 0.12 (237 mod 25 / 100 = 0.12). All 300 tests now pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 18850, "tokens_out": 548, "trimmed_requests": 0, "ts": "2026-09-27T18:03:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 5, "wall_s": 15.9}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........................................................................ [ 23%]\n........................................................................ [ 47%]\n........................................................................ [ 71%]\n........................................................................ [ 95%]\n............. [100%]\n301 passed in 0.20s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 2, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 complete. Ran pytest: exactly one test failed (test_rate[R237], obtained 0.21 vs expected 0.12). Corrected line 241 of rates.py from \"R237\": 0.21 to \"R237\": 0.12 (237 % 25 / 100 = 0.12), leaving all other entries untouched. Re-ran pytest: all 300 tests pass.", "prose_turns": 0, "provider": "openai", "success": true, "task": "long-output", "task_hash": "8cfbf73fb6620613", "tokens_in": 29415, "tokens_out": 911, "trimmed_requests": 0, "ts": "2026-09-27T18:04:13+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 7, "wall_s": 24.8}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter: greet() in greeter/core.py now takes shout: bool = False and upper-cases the greeting when true; greeter/cli.py adds a --shout (store_true) flag passed through to greet; two new tests added (greet shout=True, and CLI --shout via capsys). `python -m pytest -q` passes (4/4) and `python -m greeter --shout Bob` prints \"HELLO, BOB!\".", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 9879, "tokens_out": 1015, "trimmed_requests": 0, "ts": "2026-09-27T18:04:37+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 24.3}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added the --shout option to the greeter: (1) greeter/core.py's greet now takes a `shout: bool = False` keyword argument and returns the whole greeting in upper case when true (greet(\"Bob\", shout=True) == \"HELLO, BOB!\"), with unchanged default behaviour; (2) greeter/cli.py adds a `--shout` flag (action=\"store_true\") and passes it to greet; (3) added two tests to tests/test_greeter.py \u2014 one for greet with shout=True and one calling main([\"--shout\", \"Bob\"]) and checking printed output via capsys; (", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 17632, "tokens_out": 1868, "trimmed_requests": 0, "ts": "2026-09-27T18:05:22+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 10, "wall_s": 44.0}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added a --shout option to the greeter: gave greet(name, shout=False) a shout keyword argument in greeter/core.py that upper-cases the greeting, added the --shout flag (store_true) in greeter/cli.py passing it through, and added two tests (greet with shout=True, and main([\"--shout\", \"Bob\"]) with capsys). python -m pytest -q passes (4 tests) and python -m greeter --shout Bob prints HELLO, BOB!.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 8690, "tokens_out": 1116, "trimmed_requests": 0, "ts": "2026-09-27T18:05:48+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 6, "wall_s": 26.5}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 done: added a `shout: bool = False` keyword argument to `greet` in `greeter/core.py` that returns the greeting in upper case, added a `--shout` store_true flag in `greeter/cli.py` passed through to `greet`, and added two tests (greet with shout=True, and main([\"--shout\", \"Bob\"]) checked via capsys). `python -m pytest -q` shows 4 passed, and `python -m greeter --shout Bob` prints `HELLO, BOB!`.", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14842, "tokens_out": 1534, "trimmed_requests": 0, "ts": "2026-09-27T18:06:25+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 9, "wall_s": 36.4}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "..... [100%]\n5 passed in 0.04s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added --shout option to the greeter per the approved plan: (1) greet(name, shout: bool = False) in greeter/core.py returns the upper-cased greeting when shout is true, default unchanged; (2) greeter/cli.py adds a --shout flag (action=\"store_true\") passed to greet; (3) tests/test_greeter.py gains test_greet_shout and test_cli_shout_prints_upper_case_greeting using capsys; (4) verified python -m pytest -q passes (4 passed) and python -m greeter --shout Bob prints HELLO, BOB! while plain python -m ", "prose_turns": 0, "provider": "openai", "success": true, "task": "multi-file", "task_hash": "76bc0dcf36a72599", "tokens_in": 14751, "tokens_out": 1805, "trimmed_requests": 0, "ts": "2026-09-27T18:07:08+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 8, "wall_s": 42.2}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 complete: created `slugify.py` with a `slugify(text)` function (lowercases, collapses non-alphanumeric runs to a single hyphen, strips edge hyphens) and `test_slugify.py` with pytest tests covering all four required examples. `python -m pytest -q test_slugify.py` passes (4 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 4395, "tokens_out": 544, "trimmed_requests": 0, "ts": "2026-09-27T18:07:21+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 4, "wall_s": 13.4}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 done: created slugify.py with slugify(text) (lowercases, collapses non-a-z0-9 runs to a single hyphen, strips edge hyphens) and test_slugify.py with pytest tests for the four examples; `python -m pytest -q test_slugify.py` passes (4 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3415, "tokens_out": 557, "trimmed_requests": 0, "ts": "2026-09-27T18:07:35+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 13.3}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py with slugify(text) (lowercase, collapse non a-z0-9 runs to single hyphen, strip hyphens), and test_slugify.py with pytest tests for all four examples. `python -m pytest -q test_slugify.py` passes: 4 passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3568, "tokens_out": 666, "trimmed_requests": 0, "ts": "2026-09-27T18:07:52+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 16.6}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Added slugify.py defining slugify(text) which lowercases, replaces runs of non-[a-z0-9] with a single hyphen, and strips hyphens from both ends; added test_slugify.py with pytest tests covering all four plan examples; `python -m pytest -q test_slugify.py` passes (4 passed).", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3542, "tokens_out": 906, "trimmed_requests": 0, "ts": "2026-09-27T18:08:14+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 21.9}
|
||||||
|
{"backing": "openai/qwen3.8-27b", "check_output": "........ [100%]\n8 passed in 0.01s\n", "commit": "8a8d8dc+dirty", "cost": 0.0, "failed_commands": 0, "fallbacks": [], "invented_tools": 0, "model": "qwen3.8-27b", "model_info": {"max_input_tokens": 131072, "max_output_tokens": 32768, "supports_function_calling": true, "supports_tool_choice": true}, "note": "llama.cpp b11206, Q4_K_XL, q8_0 KV, 128k, temp 1.0 top-p 0.95 top-k 20, RTX 4090, 350 W cap", "outcome": "completed", "outcome_note": "Milestone 1 done: created `slugify.py` with a `slugify` function (lowercases text, collapses non-alphanumeric runs into single hyphens, strips end hyphens) and `test_slugify.py` with pytest tests for all four required examples. `python -m pytest -q test_slugify.py` passes: 4 passed.", "prose_turns": 0, "provider": "openai", "success": true, "task": "write-function", "task_hash": "ec722fb0f389a6f4", "tokens_in": 3453, "tokens_out": 501, "trimmed_requests": 0, "ts": "2026-09-27T18:08:27+00:00", "tuning": {"context": {"keep_recent_turns": 6, "safety_margin": 0.1}, "delegation": {"max_tokens": 200000, "max_turns": 30, "wall_clock_s": 900.0}, "executor": {"failure_limit": 2, "max_turns": 24}, "expert": {"consult_budget": 3}, "tools": {"command_timeout_s": 120, "max_output_chars": 4000}}, "turns": 3, "wall_s": 13.0}
|
||||||
+523
-95
@@ -10,7 +10,10 @@ Gitea, Vikunja, Outline, LiteLLM) and heaviest on the **agent library** and the
|
|||||||
document and no longer exists separately: its A-series components appear as A10–A16,
|
document and no longer exists separately: its A-series components appear as A10–A16,
|
||||||
its network and credential revisions in A5 and B3, and its revised build order as
|
its network and credential revisions in A5 and B3, and its revised build order as
|
||||||
Part G. The inference-hardware question it raised has since been resolved in
|
Part G. The inference-hardware question it raised has since been resolved in
|
||||||
principle (Part H: a 4090-class host).
|
principle (Part H: a 4090-class host). The MCP Deployment amendment has likewise been
|
||||||
|
folded in: the every-agent-gets-a-VM rule, per-instance egress policy, and MCP server
|
||||||
|
placement now form Part F1–F3; its consequences appear in Part 0 (site identifiers),
|
||||||
|
A7, B3, C2, C3, C5, the Part F4 Watchtower carve-out, and Part H.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -32,6 +35,12 @@ same string across all of those: the VM, the container, the Caddy host, the Matr
|
|||||||
account, and the Bitwarden machine account for a given agent all share one name, so
|
account, and the Bitwarden machine account for a given agent all share one name, so
|
||||||
that grepping the infrastructure for `nisse-ops` finds everything.
|
that grepping the infrastructure for `nisse-ops` finds everything.
|
||||||
|
|
||||||
|
**Sites** are identified by their domain stem: `garrett-hall` (Arlington, hosts under
|
||||||
|
`garrett-hall.com`) and `hartman-hut` (Cross Plains, hosts under `hartman-hut.com`).
|
||||||
|
As with agent names, one string is used everywhere a site appears. For an MCP server
|
||||||
|
instance (F3), that means its container name, its Vaultwarden machine account, and
|
||||||
|
its MCP tool prefix.
|
||||||
|
|
||||||
The three artifacts live as three packages in **one repository, `nisse`** (decision
|
The three artifacts live as three packages in **one repository, `nisse`** (decision
|
||||||
recorded in Part H). Directory and package names carry the identity. If mirrored to
|
recorded in Part H). Directory and package names carry the identity. If mirrored to
|
||||||
GitHub, it goes under a personal account rather than an org — `github.com/nisse` is
|
GitHub, it goes under a personal account rather than an org — `github.com/nisse` is
|
||||||
@@ -55,6 +64,14 @@ Both are thin applications over a shared **`nisse-core`** library. The library i
|
|||||||
consumers that don't exist yet. Build the Dev Agent as if the library were internal,
|
consumers that don't exist yet. Build the Dev Agent as if the library were internal,
|
||||||
then lift the seams that proved real.
|
then lift the seams that proved real.
|
||||||
|
|
||||||
|
There is one deliberate, bounded exception: the **work model's identity scheme**
|
||||||
|
(0.2) is settled for both agents ahead of extraction. Identifiers end up in the state
|
||||||
|
schema, branch names, the operational log (A9), the transcript (A10), and external
|
||||||
|
records. Changing those after extraction would mean migrating two agents and their
|
||||||
|
stored state. Only identity is designed ahead. Behaviour is still built by each
|
||||||
|
agent as it needs it, so nothing in 0.2 requires the Dev Agent to implement duties,
|
||||||
|
deduplication, or concurrency.
|
||||||
|
|
||||||
The three artifacts live in **one repository** (decided — Part H), so the
|
The three artifacts live in **one repository** (decided — Part H), so the
|
||||||
extraction step in the build order is a package split within one tree, not a repo
|
extraction step in the build order is a package split within one tree, not a repo
|
||||||
split.
|
split.
|
||||||
@@ -77,6 +94,119 @@ split.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.2 The Work Model: Duties, Work Items, and Scopes
|
||||||
|
|
||||||
|
Every agent's work is organised into three kinds of unit. They define what state
|
||||||
|
belongs where, how work is identified, and what the operator is resuming when they
|
||||||
|
come back to something. Components throughout this document refer to them.
|
||||||
|
|
||||||
|
| Concept | What it is | Lifetime | Dev Agent | Sysadmin Agent |
|
||||||
|
|---|---|---|---|---|
|
||||||
|
| **Duty** | A standing responsibility that produces work items | Indefinite, recurring | None yet; the operator is the only source | Scheduled checks, log monitoring, alert intake |
|
||||||
|
| **Work item** | One effort with a lifecycle: opened, worked (often through gates), closed | Opened → closed | Building a feature through arch plan → milestones → PRs | Diagnosing one failure; answering one question |
|
||||||
|
| **Scope** | What a work item operates on, and the context that persists across work items there | Indefinite | A repo | A site and service, e.g. `garrett-hall` Proxmox |
|
||||||
|
|
||||||
|
### Duty
|
||||||
|
|
||||||
|
- **A duty produces work items. It is not one itself.** A run of a duty (one log
|
||||||
|
analysis, one scheduled check) is read-only and yields findings. A finding that
|
||||||
|
needs attention becomes a work item.
|
||||||
|
- **The operator is always an implicit source** of work items, for every agent.
|
||||||
|
- **Deduplication is a duty's responsibility.** When a later run finds the same
|
||||||
|
failure, it attaches to the existing open work item instead of opening a second
|
||||||
|
one. That needs a stable fingerprint per finding, checked against open work items.
|
||||||
|
This is the same idempotency concern A2/A3 address for side effects.
|
||||||
|
|
||||||
|
### Work item
|
||||||
|
|
||||||
|
- **The unit of state.** A work item owns its state-machine position, gates and plan
|
||||||
|
revisions, pending questions, pinned role mapping (A11), and its transcript (A10).
|
||||||
|
- **Identity is minted locally.** Every work item gets a local ID when it opens.
|
||||||
|
This is the `task_id` of A9: a work item's ID and its correlation ID are one value.
|
||||||
|
- **An external record is optional, and linked.** A work item *may* be tied to a
|
||||||
|
durable external record: a Gitea issue for the Dev Agent, a Vikunja ticket for the
|
||||||
|
Sysadmin Agent. Many will not be; a general question to either agent is a work
|
||||||
|
item with no record. A work item can also gain a record partway through, for
|
||||||
|
example a question that turns into a change to make.
|
||||||
|
- **Where the spec requires a record, it has one.** Dev plan gates are externalized
|
||||||
|
to Gitea (Part H), and ticket-driven ops work has its ticket (C4). When a record is
|
||||||
|
linked, external truth wins on divergence (A2), and gate state can be re-derived
|
||||||
|
from it after state loss. Whether *every* gated work item must have a record is
|
||||||
|
open (Part H).
|
||||||
|
- **Work items are not nested.** A milestone is a phase *within* a dev work item, and
|
||||||
|
a delegation (A13) is an episode within one. Neither is a work item of its own.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
|
||||||
|
- **A scope holds context shared by the work items that operate on it:** for a repo,
|
||||||
|
the clone and the project's conventions; for infrastructure, the A17 world model
|
||||||
|
of that site or service.
|
||||||
|
- **Many work items may share one scope.** A feature waiting on review and a bug fix
|
||||||
|
in the same repo are two work items on one scope. So are two failures found in the
|
||||||
|
same log run.
|
||||||
|
- **A work item has zero or more scopes.** A dev work item has exactly one repo for
|
||||||
|
now. A general question may have none.
|
||||||
|
- **Scope identifiers follow existing naming.** Repos use `owner/repo`. Sites use the
|
||||||
|
site identifier (Part 0), combined with the service.
|
||||||
|
|
||||||
|
### Park-and-switch: one active work item per agent
|
||||||
|
|
||||||
|
- **Each agent has at most one active work item.** Others are *parked*, with their
|
||||||
|
full position held in the state store (A2).
|
||||||
|
- **Switching happens only at safe points,** where the loop is waiting on a human
|
||||||
|
(an open gate, a pending question, a PR in review) or is idle. It never happens
|
||||||
|
mid-turn. Because turns run to completion, every point at which a command can
|
||||||
|
arrive is a safe point, with one exception: switching is refused while intents
|
||||||
|
are unresolved (A2). That keeps every pending intent owned by the active work
|
||||||
|
item, so boot reconciliation (A3) stays single-item.
|
||||||
|
- **The operator can always switch; the agent may switch on its own in two cases,**
|
||||||
|
when other work items are ready:
|
||||||
|
- *The active item is waiting on the operator* (an open gate, a pending question,
|
||||||
|
or a halt in `AWAIT_INPUT`). An alert must not sit for hours behind an
|
||||||
|
unanswered prompt. The item it left stays waiting; switching answers nothing
|
||||||
|
on its behalf.
|
||||||
|
- *The active item has closed* (completed or abandoned). Ready work should not
|
||||||
|
sit behind an idle agent until the operator notices.
|
||||||
|
|
||||||
|
Either way, it posts a notice naming both items and why it chose that one. The
|
||||||
|
agent never switches away from an item that is working.
|
||||||
|
- **A grace period comes before switching away from a waiting item.** After the
|
||||||
|
active item posts a plan or a question, the agent waits a configurable period
|
||||||
|
(default 10 minutes) before switching. An operator who is watching answers in
|
||||||
|
that time and the item carries on without a switch. Otherwise their approval
|
||||||
|
could sit behind a long milestone on the other item: once parked, an item only
|
||||||
|
becomes active again when the active one waits or closes. Closing an item needs
|
||||||
|
no grace period.
|
||||||
|
- **A parked item is ready when the agent can act on it without the operator:** it
|
||||||
|
has not started yet (a duty finding, a ticket), or intake arrived while it was
|
||||||
|
parked that answers what it was waiting for (an approval, an answer, a review
|
||||||
|
comment). An item still waiting on the operator, with nothing new, is not ready.
|
||||||
|
- **The model chooses which ready item to switch to.** This is one of the few
|
||||||
|
judgments it is trusted with outright, because choosing grants nothing: every
|
||||||
|
item still meets its own gates, and a poor choice costs time, not safety.
|
||||||
|
- *It is an agent-level call,* belonging to no work item (A10), made by the
|
||||||
|
`executor` role at the current config (A11 pins roles per item, and this call
|
||||||
|
has none).
|
||||||
|
- *It sees summaries rendered by code from the state store,* one per ready item:
|
||||||
|
source, age, severity, current state, title. It never sees another item's
|
||||||
|
transcript, so work items stay isolated (A10).
|
||||||
|
- *It falls back to the oldest ready item* if the call fails or its answer does
|
||||||
|
not name one of them. Switching never waits on a model.
|
||||||
|
- *The switch is one state change* (A9 `work_item.switch`), naming both items,
|
||||||
|
the reason, and whether the model, the fallback, or the operator chose.
|
||||||
|
- **Parking gives up authority.** Parking a work item unmounts any elevated MCP
|
||||||
|
instances (F3) its approved plan mounted. They are remounted when it becomes
|
||||||
|
active again, if its approval has not expired (C2). An approved plan's
|
||||||
|
credentials never serve another item.
|
||||||
|
- **Nothing is lost while parked.** Intake for a parked work item, such as comments
|
||||||
|
on its Gitea issue or PR, is read from a durable per-thread cursor when it becomes
|
||||||
|
active again. It is not dropped, and it is not replayed from the beginning.
|
||||||
|
- **The state schema must not assume one item.** Work items live in their own table,
|
||||||
|
and "active" is a pointer to one of them. Allowing more than one active item later
|
||||||
|
(Part H) is then a control-loop change, not a migration of every stored item.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## PART A — THE AGENT LIBRARY (`nisse-core`)
|
## PART A — THE AGENT LIBRARY (`nisse-core`)
|
||||||
|
|
||||||
The shared spine. Every component here is consumed by both agents. This is the part
|
The shared spine. Every component here is consumed by both agents. This is the part
|
||||||
@@ -112,8 +242,9 @@ canonical log (A10) is the sole source of truth.
|
|||||||
process restart. This is what makes the whole system safe to redeploy and reboot; it
|
process restart. This is what makes the whole system safe to redeploy and reboot; it
|
||||||
blocks everything else.
|
blocks everything else.
|
||||||
|
|
||||||
Must persist: active project/task, per-project state-machine position, approvals
|
Must persist: the active work item and each work item's state-machine position
|
||||||
granted, pending questions, session continuity, (dev) git clone locations.
|
(0.2), approvals granted, pending questions, session continuity, and scope state such
|
||||||
|
as (dev) git clone locations.
|
||||||
|
|
||||||
Disposable (never on the state volume): agent code, build artifacts/test containers,
|
Disposable (never on the state volume): agent code, build artifacts/test containers,
|
||||||
shipped-out logs.
|
shipped-out logs.
|
||||||
@@ -152,10 +283,18 @@ Every startup (clean or post-crash), before accepting operator commands:
|
|||||||
(tree diff for edits, Gitea query for pushes), or on any branch/log mismatch. The
|
(tree diff for edits, Gitea query for pushes), or on any branch/log mismatch. The
|
||||||
judgment is an observable check, never model self-assessment — the same reasoning
|
judgment is an observable check, never model self-assessment — the same reasoning
|
||||||
as A12.
|
as A12.
|
||||||
6. Any other interruption → move to **`AWAIT_INPUT`** and post to transport
|
6. **Interrupted mid-sequence (C2) — resume only on evidence.** An approved step
|
||||||
|
sequence may interrupt itself: rebooting the host the agent runs on is a
|
||||||
|
legitimate step. On restart, **continue** the sequence only when the intent log
|
||||||
|
names exactly which step was in flight and that step's expected outcome checks
|
||||||
|
out against the live system, and the approval has not expired. Anything else
|
||||||
|
(no recorded outcome check, a failed check, or more than one step
|
||||||
|
possibly in flight) gates to `AWAIT_INPUT`. The step counts as completed only
|
||||||
|
by that check, never by assuming it ran.
|
||||||
|
7. Any other interruption → move to **`AWAIT_INPUT`** and post to transport
|
||||||
(e.g. "restarted mid-milestone-2, branch at commit abc123, continue or re-plan?").
|
(e.g. "restarted mid-milestone-2, branch at commit abc123, continue or re-plan?").
|
||||||
Never blind-resume.
|
Never blind-resume.
|
||||||
7. Otherwise resume the last committed state.
|
8. Otherwise resume the last committed state.
|
||||||
|
|
||||||
### A4. Control Loop, State Machine, and Gating Primitives
|
### A4. Control Loop, State Machine, and Gating Primitives
|
||||||
|
|
||||||
@@ -194,9 +333,23 @@ Requirements:
|
|||||||
for copy-paste. Any other reply/comment is `reject <feedback>`. Reactions/emoji are
|
for copy-paste. Any other reply/comment is `reject <feedback>`. Reactions/emoji are
|
||||||
never an approval path. Approvals are consumed at first sight; later edits to an
|
never an approval path. Approvals are consumed at first sight; later edits to an
|
||||||
approval comment are ignored and logged.
|
approval comment are ignored and logged.
|
||||||
|
- **Approvals bind to a work item, too (0.2).** A bare `approve` binds to the
|
||||||
|
*active* work item's latest revision, under the unambiguity rule above. An
|
||||||
|
approval arriving on a *parked* item's external record (a Gitea or Vikunja
|
||||||
|
comment) is delivered when that item next becomes active. So is a Matrix reply to
|
||||||
|
a parked item's plan or prompt: the reply relation binds it to that item. Neither
|
||||||
|
is ever applied to the active item.
|
||||||
- **Autonomy inside an approved unit of work:** all tool/command use auto-approved
|
- **Autonomy inside an approved unit of work:** all tool/command use auto-approved
|
||||||
(Claude Agent SDK `bypassPermissions` or an allow-all `canUseTool`). Escalation to
|
(Claude Agent SDK `bypassPermissions` or an allow-all `canUseTool`). Escalation to
|
||||||
the human happens *only* via `ask_human`.
|
the human happens *only* via `ask_human`. For the Sysadmin Agent, "approved"
|
||||||
|
means the step list of C2. Only calls that match an approved step are
|
||||||
|
auto-approved.
|
||||||
|
- **Kill switch.** The operator command `stop` works in every state, for every
|
||||||
|
agent. It ends the current turn or delegation at the next tool boundary, unmounts
|
||||||
|
elevated MCP instances (F3), and moves to `AWAIT_INPUT`. The unfinished delegation
|
||||||
|
or step sequence is then handled like any other halt: continue, re-plan, or
|
||||||
|
abandon, on the operator's word. Unattended work needs a way to end it that does
|
||||||
|
not depend on the model cooperating.
|
||||||
- The loop must survive restart mid-work (relies on A2/A3).
|
- The loop must survive restart mid-work (relies on A2/A3).
|
||||||
|
|
||||||
### A5. LLM Access *(tiered)*
|
### A5. LLM Access *(tiered)*
|
||||||
@@ -236,8 +389,10 @@ Requirements:
|
|||||||
- **No vendor credentials on the agent VM.** LiteLLM holds every key, sourced from
|
- **No vendor credentials on the agent VM.** LiteLLM holds every key, sourced from
|
||||||
Bitwarden machine accounts (A7); the agent holds one LiteLLM token. Adding a vendor
|
Bitwarden machine accounts (A7); the agent holds one LiteLLM token. Adding a vendor
|
||||||
does not touch the agent VM, and a compromised agent leaks no provider keys.
|
does not touch the agent VM, and a compromised agent leaks no provider keys.
|
||||||
- Config from env/`.env`, **nothing hardcoded**: base URL, auth token, and the role
|
- Config from the agent's config file, **nothing hardcoded**: base URL, auth token,
|
||||||
mapping (A11). Changing any of it is an env change + restart, nothing more.
|
and the role mapping (A11). Secrets in it are references to environment
|
||||||
|
variables, never values. Changing any of it is a config edit + restart,
|
||||||
|
nothing more.
|
||||||
- **Budget caps and spend alerts live on the LiteLLM virtual key**, not in agent code.
|
- **Budget caps and spend alerts live on the LiteLLM virtual key**, not in agent code.
|
||||||
|
|
||||||
### A6. Browser Tool
|
### A6. Browser Tool
|
||||||
@@ -261,9 +416,13 @@ Requirements:
|
|||||||
Requirements:
|
Requirements:
|
||||||
- Client for **Bitwarden/Vaultwarden** via **machine account / Secrets Manager** (or
|
- Client for **Bitwarden/Vaultwarden** via **machine account / Secrets Manager** (or
|
||||||
`bw` CLI + API key). Not the personal vault.
|
`bw` CLI + API key). Not the personal vault.
|
||||||
- Reads only from a **dedicated, minimally-scoped machine account** per agent.
|
- Reads only from a **dedicated, minimally-scoped machine account** per agent, **plus
|
||||||
- A single **bootstrap token** (the machine-account credential) is the one secret
|
one per MCP server instance** (F3). Each (service, site, privilege) instance holds
|
||||||
that lives on the box: tight file perms, never in image or repo.
|
its own upstream credential, so it gets its own machine account, and the agent's
|
||||||
|
account cannot read an instance's credential.
|
||||||
|
- Each machine account has a single **bootstrap token**. These are the only secrets
|
||||||
|
that live on the box: tight file perms, never in image or repo. Each token is
|
||||||
|
available only to its own container.
|
||||||
- (Deferred, not first cut: dynamic/short-lived secrets via Vault/OpenBao. Leave the
|
- (Deferred, not first cut: dynamic/short-lived secrets via Vault/OpenBao. Leave the
|
||||||
interface able to grow toward this; don't build it yet.)
|
interface able to grow toward this; don't build it yet.)
|
||||||
|
|
||||||
@@ -298,9 +457,12 @@ by replaying the operational trail:
|
|||||||
- `ts` — UTC ISO-8601 timestamp
|
- `ts` — UTC ISO-8601 timestamp
|
||||||
- `agent` — `nisse-dev` | `nisse-ops`
|
- `agent` — `nisse-dev` | `nisse-ops`
|
||||||
- `event` — dotted type from the taxonomy below
|
- `event` — dotted type from the taxonomy below
|
||||||
- `task_id` — correlation ID for the enclosing unit of work, minted when the
|
- `task_id` — the enclosing work item's ID (0.2), minted when the work item
|
||||||
unit starts (`use <project>`, milestone start, ticket pickup) and threaded
|
opens (`use <project>`, ticket pickup, an operator question, a duty's finding)
|
||||||
through every event it causes; `null` only outside any unit (e.g. boot).
|
and threaded through every event it causes; `null` only outside any work item
|
||||||
|
(e.g. boot, a duty run before it yields a finding, or choosing which item to
|
||||||
|
switch to). A milestone is a phase
|
||||||
|
within a work item, so starting one does not mint a new ID.
|
||||||
- `cause` — what triggered the mutation: operator command, approval
|
- `cause` — what triggered the mutation: operator command, approval
|
||||||
(`rev-N` + channel), escalation trigger, reconciliation verdict, or crash
|
(`rev-N` + channel), escalation trigger, reconciliation verdict, or crash
|
||||||
recovery.
|
recovery.
|
||||||
@@ -311,6 +473,7 @@ by replaying the operational trail:
|
|||||||
|---|---|
|
|---|---|
|
||||||
| `state.transition` | `from`, `to`, `project` |
|
| `state.transition` | `from`, `to`, `project` |
|
||||||
| `intent.begin` / `intent.commit` / `intent.abort` | `intent_id`, `kind` |
|
| `intent.begin` / `intent.commit` / `intent.abort` | `intent_id`, `kind` |
|
||||||
|
| `work_item.switch` | `from` (parked or closed item, or `null`), `to`, `reason`, `chosen_by` (`model` / `fallback` / `operator`) |
|
||||||
| `gate.open` / `gate.approve` / `gate.reject` | `gate`, `rev`, `channel` |
|
| `gate.open` / `gate.approve` / `gate.reject` | `gate`, `rev`, `channel` |
|
||||||
| `boot.reconcile` | `action`, per-intent verdicts |
|
| `boot.reconcile` | `action`, per-intent verdicts |
|
||||||
| `schema.migrate` | `from_version`, `to_version` |
|
| `schema.migrate` | `from_version`, `to_version` |
|
||||||
@@ -338,6 +501,15 @@ The durable record is a **canonical event log** in SQLite, not a provider-shaped
|
|||||||
| `knowledge_retrieved` | document text as read, document ID, revision, retrieved-at (A17) |
|
| `knowledge_retrieved` | document text as read, document ID, revision, retrieved-at (A17) |
|
||||||
|
|
||||||
Rules:
|
Rules:
|
||||||
|
- **The log is scoped to a work item (0.2).** Every event carries its work item's ID,
|
||||||
|
and a model call for one work item renders only that item's events. Evaluation
|
||||||
|
capture (A16) may read across items.
|
||||||
|
- **Some calls belong to no work item,** and their events go to an **agent-level
|
||||||
|
scope**, with no work item ID: a duty run until it yields a finding, and the
|
||||||
|
choice of which item to switch to (0.2). Each such call renders only its own
|
||||||
|
events plus what code gives it. Agent-level events are never rendered into a
|
||||||
|
work item's call, and a work item's events reach an agent-level call only as a
|
||||||
|
code-rendered summary.
|
||||||
- **Canonical tool-call IDs are minted locally.** Provider IDs are mapped at the
|
- **Canonical tool-call IDs are minted locally.** Provider IDs are mapped at the
|
||||||
boundary and never used as a join key. Providers differ in both ID format and in
|
boundary and never used as a join key. Providers differ in both ID format and in
|
||||||
where tool calls sit structurally (content blocks vs. a separate array on the
|
where tool calls sit structurally (content blocks vs. a separate array on the
|
||||||
@@ -496,7 +668,13 @@ subsystem.
|
|||||||
|
|
||||||
### A15. Local Model Accommodations
|
### A15. Local Model Accommodations
|
||||||
|
|
||||||
- **Constrained decoding for tool-call JSON.**
|
- **Constrained decoding for tool-call JSON.** Requested with `tool_choice`
|
||||||
|
(`required`), and only of models whose LiteLLM entry declares
|
||||||
|
`supports_tool_choice`: enforcement is the model server's, and asking one that
|
||||||
|
can't honour it can be worse than not asking. Where it isn't available, the
|
||||||
|
loop enforces the contract itself: an explicit `finish` tool ends the work, a
|
||||||
|
prose reply is a violation, and a call to a tool not offered counts as a
|
||||||
|
failure (A12).
|
||||||
- **Reduced tool schema for the executor role:** fewer tools, flatter arguments, no
|
- **Reduced tool schema for the executor role:** fewer tools, flatter arguments, no
|
||||||
deeply nested optionals. Maintain **two tool manifests keyed on role**, rendered at
|
deeply nested optionals. Maintain **two tool manifests keyed on role**, rendered at
|
||||||
request time like everything else.
|
request time like everything else.
|
||||||
@@ -536,33 +714,24 @@ Part D describes the service; this defines the contract.
|
|||||||
desynchronise from the project.
|
desynchronise from the project.
|
||||||
|
|
||||||
**Agent-owned knowledge is always a cache, never authority.** An agent may rely on
|
**Agent-owned knowledge is always a cache, never authority.** An agent may rely on
|
||||||
it to know *what exists and where to look*; it must confirm a specific fact
|
it to know *what exists and where to look*. It must not rely on it for the facts an
|
||||||
against the live system *before acting on it*. Hard requirement for anything in
|
action depends on. A prompt cannot enforce that: the model cannot be prevented from
|
||||||
C2's confirm-first tier; advisory for read-only and reversible actions.
|
*believing* a fact it has read. The enforcement is elsewhere, and it is structural:
|
||||||
|
- **Step preconditions (C2) carry the weight.** Every elevated write is an approved
|
||||||
|
step, and a step that depends on a fact states it as a precondition, checked
|
||||||
|
against the live system immediately before the step runs. A stale world model
|
||||||
|
can produce a wrong plan, but it cannot turn that plan into an action on a
|
||||||
|
system that no longer matches it.
|
||||||
|
- **Provenance at the human gate.** When a plan is drafted from recalled facts, the
|
||||||
|
approval message says so: "target recalled from the world model, observed
|
||||||
|
2026-08-02" versus "confirmed by `zfs list` two minutes ago". The operator is
|
||||||
|
the authority there; this makes the evidence quality visible.
|
||||||
|
- **Audit for the rest.** Retrievals are logged (A10), so a wrong fact can be traced
|
||||||
|
to the actions that followed it.
|
||||||
|
|
||||||
**Enforcement is structural, because a prompt is not enforcement.** "Treat this as
|
**Dropped:** withholding facts past a staleness bound at retrieval, and refusing
|
||||||
a cache" is exactly the advisory guarantee A4 and A12 reject, and the model cannot
|
confirm-first actions without a verifying observation in the log. Both were
|
||||||
be prevented from *believing* a fact it has read. The enforceable goal is
|
approximations of what step preconditions (C2) now check directly.
|
||||||
narrower: **an unverified belief must not become an irreversible action.**
|
|
||||||
1. **The store decides what it serves.** Facts carry provenance and an observed-at
|
|
||||||
timestamp; retrieval marks or withholds anything past a staleness bound for
|
|
||||||
high-consequence classes.
|
|
||||||
2. **Verification as a precondition, checked against the log.** For confirm-first
|
|
||||||
actions, refuse unless a verifying observation exists *in this task's canonical
|
|
||||||
log*, recorded after the task began, naming the action's target. Inspectable
|
|
||||||
without asking the model anything — the same move as A12's observable triggers.
|
|
||||||
Approximate by nature, so build it for the confirm-first tier only.
|
|
||||||
3. **Provenance at the human gate** — the primary reliance, because C2 already
|
|
||||||
stops these actions for confirmation. The prompt states evidence quality:
|
|
||||||
"target recalled from the world model, observed 2026-08-02, not verified this
|
|
||||||
session" versus "confirmed by `zfs list` two minutes ago". The operator is
|
|
||||||
already the authority there; this makes the risk legible rather than invisible.
|
|
||||||
4. **Audit, not prevention, for the cheap tiers.** Log which facts were retrieved
|
|
||||||
and which actions followed, so a wrong fact is traceable afterwards.
|
|
||||||
|
|
||||||
Residual risk, stated plainly: a model can retype a fact from context with no
|
|
||||||
retrieval event, and no mechanism here catches that. That is why the world model
|
|
||||||
stays out of the authority path entirely.
|
|
||||||
|
|
||||||
**Never an instruction channel, and never an approval channel.**
|
**Never an instruction channel, and never an approval channel.**
|
||||||
- **Task instructions never come from the knowledge store.** Work is driven by
|
- **Task instructions never come from the knowledge store.** Work is driven by
|
||||||
@@ -593,15 +762,14 @@ a model rewriting a precise fact loses precision invisibly.
|
|||||||
log** — the text, plus document id and revision for provenance — never referenced
|
log** — the text, plus document id and revision for provenance — never referenced
|
||||||
by handle alone: A10 forbids a provider handle being the only reference to content,
|
by handle alone: A10 forbids a provider handle being the only reference to content,
|
||||||
and these documents are mutable. Snapshot once per task at first use, not per turn.
|
and these documents are mutable. Snapshot once per task at first use, not per turn.
|
||||||
**A3 consequence:** a task resumed across a restart carries the old snapshot, so
|
A task resumed across a restart carries the old snapshot; step preconditions, not
|
||||||
boot reconciliation re-fetches snapshotted knowledge and flags divergence rather
|
a re-fetch, are what stop it acting on a stale copy.
|
||||||
than silently continuing on a stale copy.
|
|
||||||
|
|
||||||
**Retrieval is structural first, discretionary second** — the A12/A14 pattern.
|
**Retrieval is structural first, discretionary second** — the A12/A14 pattern.
|
||||||
- Procedures are **bound to C2 action classes**: the policy taxonomy and the
|
- Procedures are **bound to entries in the C2 policy file** (a tool, or a protected
|
||||||
procedure index are the same list. Binding is deterministic and auditable
|
target), and fetched when a plan that uses that entry is drafted. Binding is
|
||||||
("which procedure governed this action?" has an exact answer), and costs nothing
|
deterministic and auditable ("which procedure governed this action?" has an
|
||||||
extra because the taxonomy must exist anyway.
|
exact answer), and costs little because the policy file must exist anyway.
|
||||||
- A model-invoked **search** exists *in addition* for discovery, never as a
|
- A model-invoked **search** exists *in addition* for discovery, never as a
|
||||||
replacement for the bound retrieval — a procedure the model may skip does not
|
replacement for the bound retrieval — a procedure the model may skip does not
|
||||||
constrain it.
|
constrain it.
|
||||||
@@ -609,10 +777,10 @@ than silently continuing on a stale copy.
|
|||||||
**Outage posture is asymmetric.** Unlike B2's poller, "never fails a task" does not
|
**Outage posture is asymmetric.** Unlike B2's poller, "never fails a task" does not
|
||||||
generalise here.
|
generalise here.
|
||||||
- **Write failure never blocks:** retry, or record locally and reconcile.
|
- **Write failure never blocks:** retry, or record locally and reconcile.
|
||||||
- **Read failure for a procedure bound to a confirm-first action gates.**
|
- **Read failure for a bound procedure gates the plan.** Planning without it is not
|
||||||
Proceeding is not a delayed reaction; it is acting unconstrained on precisely the
|
a delayed reaction; it is acting unconstrained on precisely the operation someone
|
||||||
operation someone wrote a procedure for.
|
wrote a procedure for.
|
||||||
- Read failure for read-only or reversible actions proceeds, logging the gap.
|
- Read failure for anything else proceeds, logging the gap.
|
||||||
|
|
||||||
**Accepted single point of failure.** Once the agent is the sole author and Outline
|
**Accepted single point of failure.** Once the agent is the sole author and Outline
|
||||||
the only home, losing Outline loses the world model. No local mirror: infrastructure
|
the only home, losing Outline loses the world model. No local mirror: infrastructure
|
||||||
@@ -620,7 +788,8 @@ facts are re-derivable by observation, which is the same premise the cache rule
|
|||||||
rests on.
|
rests on.
|
||||||
|
|
||||||
**Deferred:** the operator-writable procedures collection. The interface is defined
|
**Deferred:** the operator-writable procedures collection. The interface is defined
|
||||||
here; the content and the C2 binding list arrive with the policy taxonomy (Part H).
|
here; the content arrives when there are procedures worth writing, and binds to the
|
||||||
|
C2 policy file.
|
||||||
|
|
||||||
**Concrete mapping onto Outline.** Observed in the running instance: a **collection**
|
**Concrete mapping onto Outline.** Observed in the running instance: a **collection**
|
||||||
carries per-user permissions with three tiers — visible-to (read), read-write, and
|
carries per-user permissions with three tiers — visible-to (read), read-write, and
|
||||||
@@ -658,9 +827,9 @@ manager. Permissions are collection-scoped, not per-document.
|
|||||||
|
|
||||||
Outline can expose search, read, create and edit over MCP (remote, Streamable HTTP,
|
Outline can expose search, read, create and edit over MCP (remote, Streamable HTTP,
|
||||||
enabled workspace-wide). **Not adopted for the retrieval path**, because A17's
|
enabled workspace-wide). **Not adopted for the retrieval path**, because A17's
|
||||||
enforcement depends on this library sitting *between* the model and the store: staleness
|
contract depends on this library sitting *between* the model and the store:
|
||||||
marking, snapshotting into the canonical log, binding procedures to action classes, and
|
snapshotting into the canonical log, binding procedures to C2 policy entries, and
|
||||||
gating a failed confirm-first read all happen to a retrieval before the model sees it.
|
gating a failed bound read all happen to a retrieval before the model sees it.
|
||||||
MCP's purpose is the opposite — direct model access — and a procedure the model may
|
MCP's purpose is the opposite — direct model access — and a procedure the model may
|
||||||
decline to fetch does not constrain it (A12).
|
decline to fetch does not constrain it (A12).
|
||||||
|
|
||||||
@@ -672,12 +841,56 @@ membership, and that the workspace-level toggle does not force broader exposure
|
|||||||
intended.
|
intended.
|
||||||
|
|
||||||
|
|
||||||
|
### A18. Prompt Injection
|
||||||
|
|
||||||
|
**Assume any model that reads untrusted content may be compromised.** There is no
|
||||||
|
robust way to tell a model "this is data, not instruction". Instructions and data
|
||||||
|
reach it as one token stream, and every marker meant to separate them is text that
|
||||||
|
the data can imitate or argue against. Role separation, delimiters and detection
|
||||||
|
classifiers lower the success rate of injection. None of them bring it to zero, and
|
||||||
|
an attacker gets to try repeatedly. So the design does not depend on the model
|
||||||
|
telling them apart. It limits what a compromised model can do.
|
||||||
|
|
||||||
|
Untrusted content, for these agents: alert payloads, log lines, ticket and comment
|
||||||
|
text, web pages the browser reads, repository contents, and downloaded packages.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
- **Gates hold against a compromised model.** Nothing a model says can approve a
|
||||||
|
plan (A4), widen what a plan authorizes (C2), or reach a credential that was not
|
||||||
|
mounted (F3). Every one of those rules is enforced in code, outside the model.
|
||||||
|
- **Approval messages are rendered by code from what will actually run** (C2),
|
||||||
|
never from a model's description of it. Otherwise a compromised model can write
|
||||||
|
"restart Gitea" above a step that destroys a dataset.
|
||||||
|
- **Instruction channels are operator-scoped.** Only allowlisted identities can
|
||||||
|
drive work: operator commands (A1), PR review comments (B2), action-triggering
|
||||||
|
tickets (C4). Content from anyone else is data.
|
||||||
|
- **Parse intake into structured fields in code** wherever the source allows it,
|
||||||
|
e.g. alert name, severity, host, from Alertmanager's JSON. Less free text then
|
||||||
|
reaches the model at all, and what reaches it is labelled by where it came from.
|
||||||
|
- **Some injection is accepted because it can only reorder work.** An alert title
|
||||||
|
can tell the switch decision (0.2) to pick it first. The worst case is ordering,
|
||||||
|
and the switch notice makes it visible.
|
||||||
|
- **Check each agent against the lethal trifecta:** access to private data, exposure
|
||||||
|
to untrusted content, and a channel to send data out. Damage needs all three.
|
||||||
|
- `nisse-dev` reads untrusted content and holds little private data: its Gitea
|
||||||
|
token and the repos it works on. Its only ways out are Gitea and package
|
||||||
|
registries (B3). The registries are an accepted, narrow residual channel.
|
||||||
|
Routing them through a local mirror would close it, if that ever becomes worth
|
||||||
|
doing.
|
||||||
|
- `nisse-ops` has all three: it reads alerts and logs, holds infrastructure
|
||||||
|
credentials, and has broad egress. It relies on the gates above, not on
|
||||||
|
removing a leg.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PART B — THE DEV AGENT / `nisse-dev` (build first)
|
## PART B — THE DEV AGENT / `nisse-dev` (build first)
|
||||||
|
|
||||||
A thin app over `nisse-core`. Plans and implements software projects; produces PRs.
|
A thin app over `nisse-core`. Plans and implements software projects; produces PRs.
|
||||||
|
|
||||||
|
In 0.2's terms, a **project** below is a dev work item on a repo scope. `use
|
||||||
|
<project>` opens or resumes a work item on a repo, and `project complete` closes the
|
||||||
|
work item. The repo, its clone, and any other work items on it continue.
|
||||||
|
|
||||||
### B1. Workflow (state machine)
|
### B1. Workflow (state machine)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -700,10 +913,12 @@ PR_OPEN
|
|||||||
|
|
||||||
### B2. Requirements
|
### B2. Requirements
|
||||||
|
|
||||||
- **One active project at a time.** Operator selects it.
|
- **One active project at a time** (park-and-switch, 0.2). The operator selects it,
|
||||||
|
or the agent switches on its own under 0.2's rules; other projects stay parked at
|
||||||
|
their last position and resume where they left off.
|
||||||
- **Operator commands** (over transport): `use <project>`, `approve`,
|
- **Operator commands** (over transport): `use <project>`, `approve`,
|
||||||
`reject <feedback>`, `start milestone <n>`, `status`, plus free text during
|
`reject <feedback>`, `start milestone <n>`, `status`, `stop` (A4), plus free
|
||||||
planning (forwarded to the agent as answers).
|
text during planning (forwarded to the agent as answers).
|
||||||
- **Plan gating** per A4: architecture plan and each milestone plan require sign-off
|
- **Plan gating** per A4: architecture plan and each milestone plan require sign-off
|
||||||
before the next phase. Enforced structurally. Plans and approvals are externalized
|
before the next phase. Enforced structurally. Plans and approvals are externalized
|
||||||
to Gitea issue comments (Part H, decided); Matrix and Gitea are both approval
|
to Gitea issue comments (Part H, decided); Matrix and Gitea are both approval
|
||||||
@@ -746,13 +961,16 @@ PR_OPEN
|
|||||||
|
|
||||||
### B3. Deployment & Isolation
|
### B3. Deployment & Isolation
|
||||||
|
|
||||||
- **Dedicated VM** (Proxmox), not a bare container — the agent builds and runs Docker
|
- **Dedicated VM** (Proxmox), as for every agent (F1). The Dev Agent has an
|
||||||
containers, so it needs its own real Docker daemon. (Docker-in-a-container via
|
additional reason: it builds and runs Docker containers, so it needs its own real
|
||||||
socket-mount ≈ host root; via dind = nested-storage pain. The VM boundary is the
|
Docker daemon. (Docker-in-a-container via socket-mount ≈ host root; via dind =
|
||||||
correct trust boundary for unattended arbitrary command execution.)
|
nested-storage pain. The VM boundary is the correct trust boundary for unattended
|
||||||
- **Sealed to start:** egress allow-list (LiteLLM, Gitea, package registries), **no
|
arbitrary command execution.)
|
||||||
ingress**. Egress denial must include **RFC1918 except the specific LiteLLM/Gitea
|
- **Sealed to start:** `nisse-dev`'s egress allowlist (F2) permits LiteLLM, Gitea,
|
||||||
IPs** — sealed from the internet must also mean no reach to NAS/Proxmox/UDM.
|
and package registries, and there is **no ingress**. Egress denial must include
|
||||||
|
**RFC1918 except the specific LiteLLM/Gitea IPs**, and also the tailnet's
|
||||||
|
**`100.64.0.0/10`**, which is not RFC1918. Sealed from the internet must also mean
|
||||||
|
no reach to NAS/Proxmox/UDM.
|
||||||
- **The LAN-resident inference host is not a second exception.** It sits behind
|
- **The LAN-resident inference host is not a second exception.** It sits behind
|
||||||
LiteLLM (A5), so the allowlist keeps exactly one LLM destination regardless of how
|
LiteLLM (A5), so the allowlist keeps exactly one LLM destination regardless of how
|
||||||
many models or vendors are in play.
|
many models or vendors are in play.
|
||||||
@@ -777,7 +995,7 @@ tickets; gated on consequences. Built after the Dev Agent works.
|
|||||||
| Axis | Dev Agent | Sysadmin Agent |
|
| Axis | Dev Agent | Sysadmin Agent |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| Rollback | revert snapshot / discard branch | none for `zfs destroy` or a firewall lockout |
|
| Rollback | revert snapshot / discard branch | none for `zfs destroy` or a firewall lockout |
|
||||||
| Trust model | allow-all in sandbox | tiered **allow / deny / confirm** policy |
|
| Trust model | allow-all in sandbox | **approved step sequences**, checked on every call |
|
||||||
| Work shape | long milestones → a PR | short reactive tasks + scheduled checks → live changes |
|
| Work shape | long milestones → a PR | short reactive tasks + scheduled checks → live changes |
|
||||||
| Driver | Gitea projects | **Vikunja tickets** |
|
| Driver | Gitea projects | **Vikunja tickets** |
|
||||||
| Placement | sealed, own VLAN, no Tailscale | Tailscale, credentials, broad reach |
|
| Placement | sealed, own VLAN, no Tailscale | Tailscale, credentials, broad reach |
|
||||||
@@ -787,27 +1005,100 @@ deployments over the same library.
|
|||||||
|
|
||||||
### C2. Permission Policy Engine *(the defining addition)*
|
### C2. Permission Policy Engine *(the defining addition)*
|
||||||
|
|
||||||
Tiered policy, evaluated on every action:
|
**Every write requires an approved plan first.** Write credentials exist only in the
|
||||||
- **Read-only telemetry** (metrics, logs, status) — free.
|
elevated MCP server instances (F3), and those are mounted only after plan approval.
|
||||||
- **Reversible service actions** (restart a container) — allowed.
|
Read-only telemetry (metrics, logs, status) goes through the read-only instances,
|
||||||
- **High-consequence actions** (storage, networking, auth) — **confirm-first** over
|
which are mounted by default, so it needs no plan.
|
||||||
transport before executing.
|
|
||||||
|
|
||||||
This is the sysadmin analog of the dev agent's plan gate: the human boundary sits at
|
This is the sysadmin analog of the dev agent's plan gate. The dev agent has a second
|
||||||
irreversible / high-blast-radius actions.
|
gate after the work (the PR); the sysadmin agent does not, because a live change has
|
||||||
|
no review step before it takes effect. So its plan gate has to be exact.
|
||||||
|
|
||||||
Special cases fold into the tiers:
|
**An ops plan is a list of literal steps, and approval covers exactly those steps.**
|
||||||
- **Browser actions against admin UIs default to confirm-first.** Changes to
|
A write plan is submitted through `submit_plan` (A4) as structured data, not prose.
|
||||||
**network gear (UniFi) are the most conservatively gated action in the system** — a
|
Each step names:
|
||||||
bad remote UI click can sever the path used to fix it.
|
- the **tool and target**, e.g. the elevated Proxmox instance for `garrett-hall`,
|
||||||
|
node and VM ID;
|
||||||
|
- the **arguments**, in full. A config change carries the exact diff;
|
||||||
|
- an optional **precondition**, checked against the live system just before the
|
||||||
|
step runs;
|
||||||
|
- an optional **expected outcome**, checked just after, e.g. host reachable again,
|
||||||
|
config hash matches;
|
||||||
|
- a **retry allowance**, default one.
|
||||||
|
|
||||||
|
One approval can cover a sequence with several high-consequence steps: "reboot the
|
||||||
|
host, change the config, reboot again" is one plan and one `approve`. The plan may
|
||||||
|
also carry prose explaining it, which is context for the operator, never scope.
|
||||||
|
|
||||||
|
**The approval message is rendered by code, from the steps.** What the operator reads
|
||||||
|
is what will run; a model's description of a step is never the thing approved (see
|
||||||
|
A18). Steps that are irreversible, that touch a protected target, or that will take
|
||||||
|
down the agent's own host are flagged at the top of the message.
|
||||||
|
|
||||||
|
**Execution is checked against the approved steps.** Every tool call passes through a
|
||||||
|
matcher before it reaches an MCP instance:
|
||||||
|
- **A read-only call is always allowed.** The agent can look around between steps.
|
||||||
|
- **An elevated call runs only if it exactly matches the next unfinished approved
|
||||||
|
step.** Arguments are normalized against the tool's JSON Schema before comparison
|
||||||
|
(types, key order), so a model's formatting cannot cause a false mismatch.
|
||||||
|
- **Anything else halts:** a call not in the plan, different arguments, steps out of
|
||||||
|
order, or a step run again after it completed. The agent moves to `AWAIT_INPUT`,
|
||||||
|
and the operator decides whether to continue, re-plan, or abandon.
|
||||||
|
- **A failed step may be retried** up to its allowance. A failed precondition or
|
||||||
|
expected-outcome check halts, without retry: the world does not match the plan,
|
||||||
|
and improvising around that is exactly what the gate exists to prevent.
|
||||||
|
- **A step that may have interrupted the agent** (a reboot of its own host) resumes
|
||||||
|
under A3's sequence rule.
|
||||||
|
|
||||||
|
**Approvals expire.** An approved plan that has not started within the expiry window
|
||||||
|
must be approved again. An approval is a judgment about the world as it was when the
|
||||||
|
operator read the plan. The window is policy data (Part H).
|
||||||
|
|
||||||
|
**Classification is small, conservative, and data.** The policy is a declared,
|
||||||
|
versioned file, like the egress allowlist (F2). It holds:
|
||||||
|
- **Instance privilege.** Read-only or elevated, per MCP instance (F3). This alone
|
||||||
|
decides whether a call needs an approved step.
|
||||||
|
- **Protected targets.** The NAS, the gateways, the hosts the agents run on, and
|
||||||
|
anything else whose loss is expensive. Protection changes how a step is
|
||||||
|
presented, not whether it is allowed.
|
||||||
|
- **Each elevated tool's target argument** (`vmid`, `node`, a device MAC), so the
|
||||||
|
matcher and the renderer can tell which target a call touches. An elevated tool
|
||||||
|
with no mapping is treated as touching a protected target.
|
||||||
|
|
||||||
|
Defaults fail safe. Anything unclassified is treated as elevated. MCP tool annotations
|
||||||
|
(`readOnlyHint`, `destructiveHint`) are declared by the server itself, so they may
|
||||||
|
make a tool stricter, never looser. A model may propose entries for a new server's
|
||||||
|
tools offline, as a PR the operator reviews. Nothing classifies at run time.
|
||||||
|
|
||||||
|
**Deferred, with the reason:**
|
||||||
|
- **Bounded matching** for reversible tools: allowing any call within an approved
|
||||||
|
target and action, not only the exact arguments. Added one tool at a time, where
|
||||||
|
exact matching causes real friction. It needs a per-tool reversibility
|
||||||
|
classification, which is the up-front cost exact matching avoids.
|
||||||
|
- **Standing approvals:** a plan template with a trigger, a rate limit and an
|
||||||
|
expiry, e.g. "restart Gitea on sustained 502, at most twice an hour, notify
|
||||||
|
after". The same matcher, run without a fresh `approve`. Added when a routine fix
|
||||||
|
has been approved by hand often enough to show its shape.
|
||||||
|
|
||||||
|
Special cases:
|
||||||
|
- **Browser actions against admin UIs cannot be matched as steps,** since a click is
|
||||||
|
not a stable literal. Each one is confirm-first over transport, even within an
|
||||||
|
approved plan. Changes to **network gear (UniFi) are the most conservatively
|
||||||
|
gated action in the system**: a bad remote UI click can sever the path used to fix
|
||||||
|
it.
|
||||||
- **Action-triggering tickets are operator-scoped** (C4). Auto-generated tickets
|
- **Action-triggering tickets are operator-scoped** (C4). Auto-generated tickets
|
||||||
(e.g. from a monitoring alert) route through confirm-first, never drive unattended.
|
(e.g. from a monitoring alert) can only lead to a plan, which the operator
|
||||||
|
approves like any other. They never drive unattended.
|
||||||
|
|
||||||
### C3. Interfaces
|
### C3. Interfaces
|
||||||
|
|
||||||
|
- **MCP servers (F3):** Proxmox and UniFi, at both sites, through per-site,
|
||||||
|
per-privilege server instances on the agent's own Docker host. This is the primary
|
||||||
|
path to that infrastructure.
|
||||||
- **Browser (A6):** into admin UIs. **Prefer real APIs where they exist** (Proxmox,
|
- **Browser (A6):** into admin UIs. **Prefer real APIs where they exist** (Proxmox,
|
||||||
TrueNAS good; UniFi partial); use the browser only for genuinely UI-only gaps —
|
TrueNAS good; UniFi now reachable over MCP); use the browser only for genuinely
|
||||||
it's the least stable interface.
|
UI-only gaps. It's the least stable interface. For network gear, it is a fallback
|
||||||
|
for API gaps, not the primary path.
|
||||||
- **SSO (A8):** logs into admin UIs as `svc-nisse-ops`. This account is a high-value
|
- **SSO (A8):** logs into admin UIs as `svc-nisse-ops`. This account is a high-value
|
||||||
credential; its blast radius equals its Authelia scope, so scope it minimally.
|
credential; its blast radius equals its Authelia scope, so scope it minimally.
|
||||||
- **Secrets (A7):** UI/SSO logins and service API tokens from Bitwarden.
|
- **Secrets (A7):** UI/SSO logins and service API tokens from Bitwarden.
|
||||||
@@ -818,8 +1109,12 @@ Special cases fold into the tiers:
|
|||||||
### C4. Ticket-Driven Control (Vikunja)
|
### C4. Ticket-Driven Control (Vikunja)
|
||||||
|
|
||||||
The sysadmin analog of "dev agent driven by Gitea."
|
The sysadmin analog of "dev agent driven by Gitea."
|
||||||
- **Vikunja ticket = task of record** (durable, has status/queue/history). **Matrix =
|
- **Vikunja ticket = task of record** (durable, has status/queue/history): the
|
||||||
live conversation about it** (questions, confirm-first prompts, progress).
|
external record of a work item (0.2). **Matrix = live conversation about it**
|
||||||
|
(questions, halts, progress).
|
||||||
|
- **Scheduled checks and alert intake are duties (0.2).** Tickets they generate are
|
||||||
|
duty findings, deduplicated against open tickets, and like any auto-generated
|
||||||
|
ticket they can only lead to a plan the operator approves (C2).
|
||||||
- **Flow:** ticket filed → agent picks up → discuss/confirm over Matrix → update
|
- **Flow:** ticket filed → agent picks up → discuss/confirm over Matrix → update
|
||||||
status as it progresses → close with result.
|
status as it progresses → close with result.
|
||||||
- **Mechanics:** Vikunja REST API + webhooks (same two-direction, JSON-only pattern
|
- **Mechanics:** Vikunja REST API + webhooks (same two-direction, JSON-only pattern
|
||||||
@@ -827,16 +1122,19 @@ The sysadmin analog of "dev agent driven by Gitea."
|
|||||||
installed version's webhooks fire on comment creation; if not, poll comments as
|
installed version's webhooks fire on comment creation; if not, poll comments as
|
||||||
B2 polls Gitea.
|
B2 polls Gitea.
|
||||||
- **Approvals:** Vikunja is an `ApprovalSource` (A4) for plan gates on its own
|
- **Approvals:** Vikunja is an `ApprovalSource` (A4) for plan gates on its own
|
||||||
tickets, via `approve rev-N` comments. Live **confirm-first** prompts (C2) stay
|
tickets, via `approve rev-N` comments. Live prompts (C2's halts and browser
|
||||||
Matrix-primary — polling latency makes a ticket comment a poor interactive
|
confirmations) stay Matrix-primary — polling latency makes a ticket comment a poor
|
||||||
channel — with the outcome written back to the ticket as the record.
|
interactive channel — with the outcome written back to the ticket as the record.
|
||||||
- **Trust:** a driving ticket is an instruction channel into an infrastructure-
|
- **Trust:** a driving ticket is an instruction channel into an infrastructure-
|
||||||
changing agent → action-triggering ticket creation is operator-scoped (C2).
|
changing agent → action-triggering ticket creation is operator-scoped (C2).
|
||||||
|
|
||||||
### C5. Deployment & Isolation
|
### C5. Deployment & Isolation
|
||||||
|
|
||||||
- Longer-lived, **stateful VM** (not disposable).
|
- Longer-lived, **stateful VM** (not disposable), dedicated to this agent (F1).
|
||||||
- Has **Tailscale** + scoped credentials to reach infrastructure across both sites.
|
- Has **Tailscale** + scoped credentials to reach infrastructure across both sites.
|
||||||
|
- **Its MCP servers (F3) run on its own Docker host.** Its egress allowlist (F2)
|
||||||
|
therefore includes the Proxmox and UniFi endpoints at both sites, even though the
|
||||||
|
agent process never opens those connections itself.
|
||||||
- Updated **in place** via the container pipeline, **restart gated on an idle flag**.
|
- Updated **in place** via the container pipeline, **restart gated on an idle flag**.
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -899,7 +1197,89 @@ each agent is built — not standing up the service itself.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## PART F — DEPLOYMENT MODEL (both agents)
|
## PART F — DEPLOYMENT MODEL (all agents)
|
||||||
|
|
||||||
|
These rules apply to every agent, current and future. Parts B3 and C5 record how each
|
||||||
|
existing agent instantiates them.
|
||||||
|
|
||||||
|
### F1. Agent hosts: every agent gets its own VM
|
||||||
|
|
||||||
|
- **Every agent gets its own VM** (Proxmox). No agent runs as a container on a shared
|
||||||
|
host, and no two agents share a VM.
|
||||||
|
- **Why, in general:**
|
||||||
|
- *Isolation boundary between agents.* The VM is the trust boundary (Part H). A
|
||||||
|
compromised or misbehaving agent must not reach another agent's state,
|
||||||
|
credentials, or processes.
|
||||||
|
- *Per-agent credential attribution.* Everything an agent holds lives on its own
|
||||||
|
host and is issued to its own identity, so every upstream action traces back to
|
||||||
|
exactly one agent.
|
||||||
|
- **Agent-specific reasons add to this; they are not its foundation.** The Dev Agent
|
||||||
|
also needs a real Docker daemon, because it builds and runs containers (B3). An
|
||||||
|
agent that builds no code still gets a VM, for the two reasons above.
|
||||||
|
|
||||||
|
### F2. Egress policy: per agent instance
|
||||||
|
|
||||||
|
- **Every agent instance has its own default-deny allowlist.** Requirements differ by
|
||||||
|
agent type. No agent inherits another agent's policy.
|
||||||
|
- **The allowlist is a declared artifact,** versioned alongside the agent's
|
||||||
|
definition. Each entry is justified by the component that needs it.
|
||||||
|
- **The declaration point is fixed. The enforcement point is not.** Policy may be
|
||||||
|
enforced at the VM, at the Docker network, at the UniFi firewall, or at several of
|
||||||
|
these at once. Layering is deliberate: enforcement at the network layer survives
|
||||||
|
compromise of the agent's own host.
|
||||||
|
- **The allowlist is the union** of what the agent reaches directly and what its MCP
|
||||||
|
servers (F3) reach on its behalf. `nisse-ops` must permit Proxmox and UniFi
|
||||||
|
endpoints at both sites, even though the agent never opens those connections
|
||||||
|
itself.
|
||||||
|
- **The tailnet is `100.64.0.0/10`** (CGNAT shared address space), not RFC1918. A
|
||||||
|
rule written around RFC1918 denial does not cover it. Deny it explicitly.
|
||||||
|
|
||||||
|
### F3. MCP servers
|
||||||
|
|
||||||
|
**One instance per agent VM.** MCP servers run as containers on the owning agent's
|
||||||
|
Docker host. Images are shared through the OCI registry. Processes are not shared
|
||||||
|
between agents.
|
||||||
|
|
||||||
|
- *A shared process means a shared credential.* The upstream API's own audit log
|
||||||
|
then cannot attribute a call to a specific agent. Separate instances with separate
|
||||||
|
tokens give attribution at the API layer, instead of depending on our own
|
||||||
|
transcripts. This matters most for a read-only auditing agent, such as a future
|
||||||
|
`nisse-security`.
|
||||||
|
- *An agent should not host the tooling used to inspect it.*
|
||||||
|
- *Duplication is nearly free.* These servers wrap remote HTTP APIs and hold no
|
||||||
|
state. (Stateful servers are the exception that could justify relocation. That
|
||||||
|
item is parked in Part H.)
|
||||||
|
|
||||||
|
**One instance per (service, site, privilege level).**
|
||||||
|
|
||||||
|
- **Site belongs in the server's identity,** using the site identifier (Part 0), not
|
||||||
|
in a `site` tool argument. A wrong-house action should require a tool that isn't
|
||||||
|
mounted, not just a wrong parameter value.
|
||||||
|
- **Read-only and elevated access are separate instances** with separate upstream
|
||||||
|
credentials, each from its own machine account (A7). The read-only instance is
|
||||||
|
mounted by default. The elevated instance is mounted only after plan approval (C2).
|
||||||
|
This puts the capability gate at the credential layer, instead of a flag inside a
|
||||||
|
third-party server.
|
||||||
|
- **Per-task tool profiles** mount only the servers that a given task's scope
|
||||||
|
requires.
|
||||||
|
|
||||||
|
#### Rejected alternative: routed meta-tools / lazy tool registration
|
||||||
|
|
||||||
|
Several community Proxmox and UniFi MCP servers use search-then-dispatch over a large
|
||||||
|
generated tool catalog. This is **rejected in favor of per-task tool profiles**:
|
||||||
|
|
||||||
|
- *Two extra round-trips per call,* which is worse with a local model driving the
|
||||||
|
control loop.
|
||||||
|
- *The model can no longer discover capabilities it wasn't already looking for.*
|
||||||
|
That is backwards for exploratory diagnostic work.
|
||||||
|
- *Arguments arrive as an opaque blob,* instead of being constrained by per-tool JSON
|
||||||
|
Schema.
|
||||||
|
- *A dynamic tool list defeats prompt prefix caching,* and caching is much of what
|
||||||
|
makes a large static list affordable.
|
||||||
|
|
||||||
|
Acceptable only when consuming a third-party server whose surface we can't trim.
|
||||||
|
|
||||||
|
### F4. Containers, images, and restarts
|
||||||
|
|
||||||
- **Containers + persistent volumes.** Stateless image; named volumes carry state
|
- **Containers + persistent volumes.** Stateless image; named volumes carry state
|
||||||
across redeploys (and allow optional persistence between deployments).
|
across redeploys (and allow optional persistence between deployments).
|
||||||
@@ -907,6 +1287,9 @@ each agent is built — not standing up the service itself.
|
|||||||
- **Gate the restart, not the pull:** an idle/"safe-to-restart" flag; Watchtower must
|
- **Gate the restart, not the pull:** an idle/"safe-to-restart" flag; Watchtower must
|
||||||
never restart mid-work. *Unplanned* interruptions (reboot/crash) are handled by the
|
never restart mid-work. *Unplanned* interruptions (reboot/crash) are handled by the
|
||||||
durability layers (A2/A3), not by restart timing.
|
durability layers (A2/A3), not by restart timing.
|
||||||
|
- **Carve-out: third-party MCP server images** (F3) are excluded from Watchtower.
|
||||||
|
They hold root-equivalent credentials to Proxmox and UniFi, so they are
|
||||||
|
digest-pinned and updated deliberately after review.
|
||||||
- **Survive reboots:** VM + Docker service set to start on boot; `restart:
|
- **Survive reboots:** VM + Docker service set to start on boot; `restart:
|
||||||
unless-stopped`; ZFS snapshots of the state dataset (scheduled + on each approval).
|
unless-stopped`; ZFS snapshots of the state dataset (scheduled + on each approval).
|
||||||
- **Terraform + cloud-init:** deferred. When adopted, `bpg/proxmox` + cloud-init,
|
- **Terraform + cloud-init:** deferred. When adopted, `bpg/proxmox` + cloud-init,
|
||||||
@@ -936,7 +1319,7 @@ each agent is built — not standing up the service itself.
|
|||||||
10. **Extract `nisse-core`**; build the **Sysadmin Agent** (`nisse-ops`): policy
|
10. **Extract `nisse-core`**; build the **Sysadmin Agent** (`nisse-ops`): policy
|
||||||
engine (C2), SSO + scoped browser (A8/A6), secrets (A7), Vikunja control (C4),
|
engine (C2), SSO + scoped browser (A8/A6), secrets (A7), Vikunja control (C4),
|
||||||
knowledge store (A17/D) — world model first, operator procedures when the
|
knowledge store (A17/D) — world model first, operator procedures when the
|
||||||
C2 taxonomy they bind to exists — scheduled checks, alert intake.
|
C2 policy file they bind to exists — scheduled checks, alert intake.
|
||||||
- With the monorepo layout (Part H, decided), extraction is a **package split**
|
- With the monorepo layout (Part H, decided), extraction is a **package split**
|
||||||
within one tree: move code behind a package boundary, no release machinery
|
within one tree: move code behind a package boundary, no release machinery
|
||||||
required.
|
required.
|
||||||
@@ -976,10 +1359,24 @@ The rest resolve when reached, not now.
|
|||||||
are still moving. One repo can still build both agent images via path-filtered
|
are still moving. One repo can still build both agent images via path-filtered
|
||||||
CI. Splitting later stays easy if a real need appears; merging later would not
|
CI. Splitting later stays easy if a real need appears; merging later would not
|
||||||
have been.
|
have been.
|
||||||
- **Sysadmin browser access to network gear:** standing vs. per-task hand-off. The
|
- **Sysadmin fallback browser access to network gear:** standing vs. per-task
|
||||||
lock-yourself-out risk argues for per-task.
|
hand-off. With UniFi reachable over MCP (F3), the browser is no longer the primary
|
||||||
- **Policy action taxonomy (C2):** the concrete list of which operations are
|
path to network gear. It is a fallback for operations the UniFi API does not
|
||||||
allow vs. confirm. Best decided with the agent half-built in front of you.
|
expose. The open question is whether that *fallback* access is standing or handed
|
||||||
|
off per task. The lock-yourself-out risk still argues for per-task.
|
||||||
|
- **Ops gating model (C2) — decided: approved step sequences, exact matching.** A
|
||||||
|
per-tool allow/confirm taxonomy was the original plan. It would have to be
|
||||||
|
written in advance for a tool catalog that will grow large, and every mistake in
|
||||||
|
it is either friction or a hole. Approving literal step sequences makes most of
|
||||||
|
it unnecessary: every elevated write has to be an approved step anyway, and
|
||||||
|
classification is left to decide only how an approval is *presented* (protected
|
||||||
|
targets, irreversible steps). What remains open is data, best filled in with the
|
||||||
|
agent half-built in front of you: the protected-target list, each elevated
|
||||||
|
tool's target argument, and the approval expiry window.
|
||||||
|
- **Parked: bounded matching and standing approvals (C2).** Both loosen exact
|
||||||
|
matching and reuse the same matcher. Add bounded matching one tool at a time,
|
||||||
|
where exact matching causes real friction. Add standing approvals when a routine
|
||||||
|
fix has been approved by hand often enough to show its shape.
|
||||||
- **Approved-plan externalization — decided: Gitea-backed from the start.** Plans are
|
- **Approved-plan externalization — decided: Gitea-backed from the start.** Plans are
|
||||||
posted as issue comments on the project repo; the approval comment (author,
|
posted as issue comments on the project repo; the approval comment (author,
|
||||||
timestamp, revision tag) is the durable off-box record, authenticated by Gitea's
|
timestamp, revision tag) is the durable off-box record, authenticated by Gitea's
|
||||||
@@ -987,6 +1384,36 @@ The rest resolve when reached, not now.
|
|||||||
so boot-time reconciliation (A3) can re-derive gate state from Gitea even after
|
so boot-time reconciliation (A3) can re-derive gate state from Gitea even after
|
||||||
state-volume loss — external truth wins (A2). Approval mechanics and revision
|
state-volume loss — external truth wins (A2). Approval mechanics and revision
|
||||||
binding are specified in A4.
|
binding are specified in A4.
|
||||||
|
- **Parked: relocating stateful MCP servers off the owning agent's VM.** The trigger
|
||||||
|
is **statefulness** (a browser pool, a vector index, a warm cache), not sharing:
|
||||||
|
duplicating a stateful server duplicates its state. Relocating has a cost. A shared
|
||||||
|
server can't hold per-agent tokens, so per-agent identity has to move into the
|
||||||
|
request, through caller auth plus per-caller upstream credentials. That loses the
|
||||||
|
attribution property that makes instance-per-agent-VM (F3) clean.
|
||||||
|
Instance-per-agent-VM is the starting position. Migrate one server at a time, when
|
||||||
|
a specific server justifies it.
|
||||||
|
- **Parked: more than one active work item (0.2).** Park-and-switch is the starting
|
||||||
|
position. The pressure to relax it will come first from the Sysadmin Agent: an
|
||||||
|
alert arriving mid-diagnosis, or a duty's scheduled run coming due while a work
|
||||||
|
item is active, has to wait for a safe point. The agent's own switch (0.2) takes
|
||||||
|
the edge off: an alert waits for an item that is working, not for an operator
|
||||||
|
who is away. Allowing concurrent items would cost:
|
||||||
|
- *Serialization in the state store and control loop.* Transitions for different
|
||||||
|
items interleave, and the single orchestrator lock becomes per-item.
|
||||||
|
- *A scheduler.* Something has to decide which item's turn runs, especially with
|
||||||
|
one local inference host serving all of them.
|
||||||
|
- *Approval binding.* Two plans or halts open at once make a bare `approve`
|
||||||
|
ambiguous everywhere, so per-item rooms or strict reply threading become
|
||||||
|
mandatory rather than convenient.
|
||||||
|
- *Shared-scope conflicts.* Two active work items on one repo need separate
|
||||||
|
working trees. Two on one infrastructure scope could make changes that
|
||||||
|
interfere with each other.
|
||||||
|
- *Intent ownership.* Boot reconciliation has to sort pending intents by item, and
|
||||||
|
refusing to switch no longer keeps them single-item.
|
||||||
|
- **Open: when a work item must have an external record (0.2).** Plan-gated dev work
|
||||||
|
and ticket-driven ops work already require one. Whether every gated work item
|
||||||
|
needs one (for instance, an ops question that turns into a change to make)
|
||||||
|
is best settled once C2's step sequences are in use.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1011,9 +1438,10 @@ nisse-dev/ # app: sealed VM (Part B)
|
|||||||
browser → internal test network only
|
browser → internal test network only
|
||||||
|
|
||||||
nisse-ops/ # app: separate host, Tailscale (Part C)
|
nisse-ops/ # app: separate host, Tailscale (Part C)
|
||||||
policy engine, tiered confirmation, scheduled checks
|
policy engine (approved step sequences, C2), scheduled checks
|
||||||
Vikunja ticket intake, Outline runbooks/records
|
Vikunja ticket intake, Outline runbooks/records
|
||||||
browser → admin UIs (API-first, UI fallback), confirm-first gating
|
MCP → Proxmox/UniFi per site and privilege (F3); runs on its own VM
|
||||||
|
browser → admin UIs (API-first, UI fallback), confirm-first per action
|
||||||
|
|
||||||
infra/ # (future) Terraform + cloud-init (Part F)
|
infra/ # (future) Terraform + cloud-init (Part F)
|
||||||
nisse-dev.tf | nisse-ops.tf | templates/
|
nisse-dev.tf | nisse-ops.tf | templates/
|
||||||
|
|||||||
@@ -0,0 +1,515 @@
|
|||||||
|
# Tuning Guide
|
||||||
|
|
||||||
|
How to fit a local model onto the inference host, and how to set the agent's
|
||||||
|
tuning knobs. Part 1 covers the ideas you need to reason about the hardware.
|
||||||
|
Part 2 covers each knob in `nisse-dev.yaml`. Part 3 gives an order to tune
|
||||||
|
in.
|
||||||
|
|
||||||
|
The knobs live under `tuning:` in the config file (see
|
||||||
|
`nisse-dev.example.yaml`). They are read at startup, so a change takes effect
|
||||||
|
on the next restart. Unknown or misspelt keys are startup errors.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 1 — How a local model spends VRAM
|
||||||
|
|
||||||
|
A model server holding a model in GPU memory spends it on three things:
|
||||||
|
|
||||||
|
1. **Weights**, which are fixed for a given model and quantization.
|
||||||
|
2. **The KV cache**, which grows with context length.
|
||||||
|
3. **Working memory** for activations and runtime buffers. Budget 1–2 GB of
|
||||||
|
headroom.
|
||||||
|
|
||||||
|
On a 24 GB card, the whole game is trading (1) against (2).
|
||||||
|
|
||||||
|
### Weights and quantization
|
||||||
|
|
||||||
|
A model's weights take roughly *parameters × bits per weight ÷ 8* bytes.
|
||||||
|
|
||||||
|
| | 16-bit (full) | 8-bit | ~4.5-bit (Q4_K_M, AWQ) |
|
||||||
|
|---|---|---|---|
|
||||||
|
| 24B | 48 GB | 24 GB | ~14 GB |
|
||||||
|
| 32B | 64 GB | 32 GB | ~19 GB |
|
||||||
|
|
||||||
|
Quantization stores each weight in fewer bits. Common 4-bit formats come out
|
||||||
|
nearer 4.5–5 bits per weight once scaling factors are counted, so check the
|
||||||
|
actual file size. The quality cost is small at 5–6 bits, usually acceptable at
|
||||||
|
4, and it climbs quickly below that. For agent work the cost shows up first as
|
||||||
|
**worse tool-call discipline**, not as worse prose. The capability matrix
|
||||||
|
(Part 3) measures exactly that.
|
||||||
|
|
||||||
|
**Mixture-of-experts** models such as Qwen3-30B-A3B are different. Memory is
|
||||||
|
set by *total* parameters (30B), but speed is set by the parameters *active*
|
||||||
|
for each token (3B). An MoE model of a given size therefore generates much
|
||||||
|
faster than a dense model of the same size. It's often weaker than a dense
|
||||||
|
model of the same total size, but not always for tool use.
|
||||||
|
|
||||||
|
### The KV cache, and why context costs memory
|
||||||
|
|
||||||
|
As the model reads the prompt, every layer stores a key and a value vector for
|
||||||
|
every token, so that later tokens can attend to them without recomputing.
|
||||||
|
That store is the KV cache. Its size per token is:
|
||||||
|
|
||||||
|
2 (key and value) × layers × KV heads × head size × bytes per value
|
||||||
|
|
||||||
|
The numbers come from the model's `config.json` (`num_hidden_layers`,
|
||||||
|
`num_key_value_heads`, `head_dim`). Some worked examples at 16-bit:
|
||||||
|
|
||||||
|
| Model | Layers × KV heads × head size | Per token | 32k tokens |
|
||||||
|
|---|---|---|---|
|
||||||
|
| Qwen3-32B | 64 × 8 × 128 | 256 KB | ~8 GB |
|
||||||
|
| Mistral/Devstral Small 24B | 40 × 8 × 128 | 160 KB | ~5 GB |
|
||||||
|
| Qwen3-30B-A3B (MoE) | 48 × 4 × 128 | 96 KB | ~3 GB |
|
||||||
|
|
||||||
|
(Taken from the published configs; recheck against the model card before
|
||||||
|
relying on them.)
|
||||||
|
|
||||||
|
Two consequences:
|
||||||
|
|
||||||
|
- **Context length is a memory decision, not a free setting.** The spec's
|
||||||
|
estimate (Part H) was about 5 GB left for KV cache beside a 4-bit 32B model.
|
||||||
|
At 16-bit, that's only about 20k tokens of context for Qwen3-32B.
|
||||||
|
- **KV-cache quantization** stores keys and values in 8 bits (or 4). At 8
|
||||||
|
bits it halves the cache and is close to lossless. At 4 bits it is
|
||||||
|
noticeably worse. Quantizing the cache to 8-bit is usually the first lever
|
||||||
|
to pull, since it buys twice the context for almost nothing.
|
||||||
|
|
||||||
|
The server allocates the cache for the context length you configure. It
|
||||||
|
doesn't matter how much of it a request uses. So set the context length on the
|
||||||
|
server deliberately: it is the ceiling for *prompt plus response* on every
|
||||||
|
request.
|
||||||
|
|
||||||
|
### Prompt processing vs generation
|
||||||
|
|
||||||
|
A request has two phases, and they run at very different speeds:
|
||||||
|
|
||||||
|
- **Prefill** reads the prompt. It is parallel and fast: thousands of tokens
|
||||||
|
per second on a 4090.
|
||||||
|
- **Decode** writes the response one token at a time. Roughly 30–40 tokens per
|
||||||
|
second for a dense 32B at 4-bit, and several times that for a 3B-active MoE.
|
||||||
|
Treat these numbers as ballpark; measure your own.
|
||||||
|
|
||||||
|
An agent loop re-sends the whole transcript on every turn, which makes the
|
||||||
|
prompt long and the responses short (a tool call is often under 200 tokens).
|
||||||
|
So prefill matters more than the headline tokens-per-second suggests.
|
||||||
|
|
||||||
|
**Prefix caching** is the fix. If the start of a prompt matches the previous
|
||||||
|
request, the server reuses the KV cache for it and prefills only the new part.
|
||||||
|
llama.cpp does this per slot, and vLLM does it automatically. For this
|
||||||
|
workload it is often the single largest speed win. It works here because the
|
||||||
|
rendered transcript only grows at the end, so consecutive turns share
|
||||||
|
everything but their newest events.
|
||||||
|
|
||||||
|
### Tokens and characters
|
||||||
|
|
||||||
|
For English and code, a token is roughly **4 characters**. That's useful for
|
||||||
|
converting the character-based knobs below into context cost.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 2 — The knobs
|
||||||
|
|
||||||
|
For each knob: what it controls, what goes wrong at either extreme, and what
|
||||||
|
to look at when deciding.
|
||||||
|
|
||||||
|
### `llm.max_output_tokens` — default 16384
|
||||||
|
|
||||||
|
The ceiling on a single response, for every role. The ceiling actually sent
|
||||||
|
is the smaller of this and the model's own `max_output_tokens` in LiteLLM, so
|
||||||
|
a local model can be held to less without affecting the frontier roles.
|
||||||
|
|
||||||
|
- **Why it exists:** a response that hits the ceiling is truncated
|
||||||
|
mid-sentence, or worse, mid-tool-call, where it can still parse. The agent
|
||||||
|
detects truncation and treats it as an error, but a ceiling that's too
|
||||||
|
tight still costs a failed turn. Planning with a frontier model was observed
|
||||||
|
using over 4,000 output tokens.
|
||||||
|
- **Too high, on a local model:** the server reserves room for the whole
|
||||||
|
response inside the context window. With a 32k context and a 16k ceiling,
|
||||||
|
only 16k is left for the prompt. Some servers reject any request where
|
||||||
|
prompt plus ceiling exceeds the context.
|
||||||
|
- **Too low:** truncated plans and tool calls.
|
||||||
|
- **Deciding:** executor turns are short, so 2–4k is plenty for a local
|
||||||
|
executor. Frontier planning wants the large default. Leave this high, and
|
||||||
|
set a local model's smaller ceiling as its `max_output_tokens` in LiteLLM.
|
||||||
|
|
||||||
|
### `llm.request_timeout_s` — default 600
|
||||||
|
|
||||||
|
How long one model request may take before it is abandoned. It applies to
|
||||||
|
every request, on both routes.
|
||||||
|
|
||||||
|
- **Too low:** a slow model's long answer is cut off, and the executor
|
||||||
|
escalates work that was going well. At 120 s, the old fixed value, a dense
|
||||||
|
27B model on the RTX 4090 timed out writing one module in one turn.
|
||||||
|
- **Too high:** a hung connection holds the milestone for that long before
|
||||||
|
anything notices.
|
||||||
|
- **Deciding:** this is a safety net, not a budget. `max_output_tokens` and
|
||||||
|
`max_turns` bound the work; set this above the slowest single answer you
|
||||||
|
expect, which is the model's output ceiling divided by its generation
|
||||||
|
speed.
|
||||||
|
|
||||||
|
### `tuning.executor.max_turns` — default 40
|
||||||
|
|
||||||
|
How many model calls one milestone may make before the executor stops with
|
||||||
|
`capped` and asks you what to do.
|
||||||
|
|
||||||
|
- **Too low:** milestones that were making progress stop short. You'll see
|
||||||
|
`capped` outcomes on work that was going well.
|
||||||
|
- **Too high:** a confused model keeps going. Every turn also adds its tool
|
||||||
|
calls and results to the transcript, so a long run spends context as well
|
||||||
|
as time.
|
||||||
|
- **Deciding:** look at how many turns *successful* milestones take, and set
|
||||||
|
the cap comfortably above the high end, around 1.5–2× the turns of your
|
||||||
|
longer successful runs. A stronger model usually needs fewer turns, not
|
||||||
|
more.
|
||||||
|
- **Measured:** the default was 24 until the harder benchmark tasks arrived.
|
||||||
|
Haiku, used as the ceiling in calibration, hit 24 on both runs of
|
||||||
|
`refactor-rename` (editing ten files one at a time) and of
|
||||||
|
`long-milestone`, one of which had in fact finished the work. It is a guard
|
||||||
|
against a model trying forever, not a budget, so it was raised to 40.
|
||||||
|
|
||||||
|
### `tuning.executor.failure_limit` — default 2
|
||||||
|
|
||||||
|
How many consecutive failed `run_command` calls trigger escalation: the
|
||||||
|
sub-task is handed to a frontier model as a delegation (spec A12). A
|
||||||
|
successful command resets the count. Failed file reads don't count, since
|
||||||
|
reading is looking around, not doing the work.
|
||||||
|
|
||||||
|
- **Why it's a count and not a judgment:** small models are poorly calibrated
|
||||||
|
about their own competence. Asked "are you stuck?", they say no until far
|
||||||
|
too late, or yes constantly. A count of observable failures is a signal
|
||||||
|
that doesn't depend on the model's opinion of itself.
|
||||||
|
- **Too low:** normal iteration escalates. Test-first work *expects* a failing
|
||||||
|
test run before the fix, so a limit of 1 hands off routine work and spends
|
||||||
|
frontier tokens on it.
|
||||||
|
- **Too high:** a weak model circles, re-running the same failing command
|
||||||
|
with small variations, while the transcript fills up.
|
||||||
|
- **Deciding:** raise it as local models get stronger. Watch two things
|
||||||
|
together. First, how often delegations fix what the local model couldn't
|
||||||
|
(escalation is doing its job). Second, how often the local model recovers
|
||||||
|
on its own after its second failure (the limit may be too low).
|
||||||
|
|
||||||
|
### `tuning.expert.consult_budget` — default 3
|
||||||
|
|
||||||
|
How many `ask_expert` calls one milestone may make. `ask_expert` is a single
|
||||||
|
stateless question to a stronger model, with no access to the repository
|
||||||
|
(spec A14), for things like "is this API deprecated?".
|
||||||
|
|
||||||
|
- **Too low:** the executor can't get unstuck on a knowledge question it
|
||||||
|
can't answer from the code.
|
||||||
|
- **Too high:** a confused executor asks variations of the same question
|
||||||
|
instead of doing the work or failing. That's the circling A12 warns about.
|
||||||
|
- **Deciding:** count consults per milestone, and check whether the answers
|
||||||
|
changed what the executor did next. If it regularly exhausts the budget
|
||||||
|
without progress, the budget is fine and the model or the plan is the
|
||||||
|
problem.
|
||||||
|
|
||||||
|
### `tuning.delegation` — hard caps on one delegation
|
||||||
|
|
||||||
|
A delegation is a frontier model working a sub-task with full tool access
|
||||||
|
(spec A13). When a cap is hit, the delegation **fails**, and its branch and
|
||||||
|
work are discarded, because resuming on a half-edited tree isn't safe.
|
||||||
|
|
||||||
|
| Knob | Default | Caps |
|
||||||
|
|---|---|---|
|
||||||
|
| `max_turns` | 30 | model calls |
|
||||||
|
| `max_tokens` | 200000 | input + output tokens, as reported by the provider |
|
||||||
|
| `wall_clock_s` | 900 | elapsed time |
|
||||||
|
|
||||||
|
- **`max_tokens` is a scope limit, not the money limit.** Spending limits live
|
||||||
|
on the LiteLLM key (spec A5). This cap stops one delegation from growing
|
||||||
|
without bound. An unbounded one could still exhaust a daily budget and take
|
||||||
|
every frontier model away until it resets.
|
||||||
|
- **Too low:** delegations fail just short of finishing and their work is
|
||||||
|
thrown away. That is the most expensive way to be wrong, because you paid
|
||||||
|
for the work.
|
||||||
|
- **Too high:** a stuck delegation burns tokens and time before failing.
|
||||||
|
- **Deciding:** every delegation records the tokens it used and how long it
|
||||||
|
ran (the `delegation_completed` event, captured for evaluation per spec
|
||||||
|
A16). Set each cap above what successful delegations actually use, with
|
||||||
|
room to spare.
|
||||||
|
|
||||||
|
### `tuning.tools.command_timeout_s` — default 120
|
||||||
|
|
||||||
|
How long one `run_command` may run before its whole process group is killed.
|
||||||
|
|
||||||
|
- **Too low:** slow test suites, dependency installs and Docker builds get
|
||||||
|
killed and reported as failures. That also counts toward `failure_limit`.
|
||||||
|
- **Too high:** a command that never exits blocks the turn for the whole
|
||||||
|
timeout, such as a dev server started in the foreground or a test waiting on
|
||||||
|
input. `stop` waits for a running command to finish too, so this is also the
|
||||||
|
worst-case delay before a stop takes effect.
|
||||||
|
- **Deciding:** set it just above the slowest legitimate command in the
|
||||||
|
projects the agent works on.
|
||||||
|
|
||||||
|
### `tuning.tools.max_output_chars` — default 4000
|
||||||
|
|
||||||
|
How much of a tool's output (command output, file contents, directory
|
||||||
|
listings) the model sees. Longer output keeps its first quarter and last three
|
||||||
|
quarters, with a note in between saying how much was left out. The end gets
|
||||||
|
the larger share because test runners and compilers print their summary of
|
||||||
|
what failed last; the start keeps the exit code.
|
||||||
|
|
||||||
|
- **This is a context knob.** Every tool result stays in the transcript and is
|
||||||
|
re-sent every turn. 4,000 characters is about 1,000 tokens, so 20 turns of
|
||||||
|
maximal output is about 20k tokens, which is already most of a local
|
||||||
|
model's context.
|
||||||
|
- **Too low:** the model doesn't see the part of the output that matters.
|
||||||
|
- **Too high:** the transcript outgrows the context window sooner. The
|
||||||
|
context budget copes (see below), but by cutting older output harder.
|
||||||
|
|
||||||
|
### `tuning.context` — fitting each request into the window
|
||||||
|
|
||||||
|
Before any request is sent, the agent checks it against the model's context
|
||||||
|
window and makes it fit. A server doesn't always refuse a prompt that's too
|
||||||
|
long. Ollama silently drops its start, which is where the system prompt and
|
||||||
|
the plan are, so an unfitted request can fail in a way nobody sees.
|
||||||
|
|
||||||
|
The window is each model's `max_input_tokens` in LiteLLM (see *Declaring a
|
||||||
|
local model's limits* below). A model in use without one stops the agent at
|
||||||
|
startup. From the window come the output ceiling and a safety margin, and
|
||||||
|
the rest is for the prompt. When the prompt is too big, three steps apply in
|
||||||
|
order, stopping at the first that fits:
|
||||||
|
|
||||||
|
1. **Older tool results are cut** to about 600 characters, head and tail.
|
||||||
|
The most recent turns keep their output whole.
|
||||||
|
2. **The oldest turns are dropped,** tool calls together with their results.
|
||||||
|
A note at the start tells the model how many events were left out. The
|
||||||
|
log keeps everything; only the request is shortened.
|
||||||
|
3. **If the system prompt and the recent turns still don't fit,** the
|
||||||
|
request is not sent. The milestone stops and asks you.
|
||||||
|
|
||||||
|
Tokens are estimated from characters (pessimistically, at 3 per token) and
|
||||||
|
the estimate is corrected from the prompt sizes the server reports. The
|
||||||
|
correction only ever makes the estimate larger.
|
||||||
|
|
||||||
|
The executor also sends less in the first place: the approved milestone plan
|
||||||
|
goes in its system prompt, which is never cut, and it sees only what happened
|
||||||
|
since that plan. The planning conversation and earlier milestones stay in the
|
||||||
|
log.
|
||||||
|
|
||||||
|
| Knob | Default | |
|
||||||
|
|---|---|---|
|
||||||
|
| `keep_recent_turns` | 6 | model turns that are never cut or dropped |
|
||||||
|
| `safety_margin` | 0.1 | fraction of the window held back for estimation error |
|
||||||
|
|
||||||
|
- **`keep_recent_turns` too low:** the model loses sight of what it just did
|
||||||
|
and repeats itself. **Too high:** less room to cut before step 3 gives up;
|
||||||
|
on a small window, overflows come sooner.
|
||||||
|
- **`safety_margin` too low:** an underestimate reaches the server, and a
|
||||||
|
silently cutting server drops the start of the prompt. **Too high:** wasted
|
||||||
|
context. Lower it only once the correction from reported usage has settled,
|
||||||
|
which takes a few calls per model.
|
||||||
|
- **Deciding:** if milestones stop with "cannot fit", look first at
|
||||||
|
`tools.max_output_chars` and the model's window, not at these.
|
||||||
|
|
||||||
|
### Enforced tool calls — `supports_tool_choice`, declared in LiteLLM
|
||||||
|
|
||||||
|
The agent's loops depend on every turn being a well-formed call to a real
|
||||||
|
tool. A model only *writes text*: a "tool call" is text in a format the model
|
||||||
|
was trained to produce, which the server recognises and turns into a
|
||||||
|
structured call. By default (`tool_choice: auto`) the server just parses
|
||||||
|
whatever comes out, so prose, broken JSON and calls to invented tools are all
|
||||||
|
possible.
|
||||||
|
|
||||||
|
With `tool_choice: required`, a server that supports it **constrains
|
||||||
|
decoding**: at every step it rules out any token that couldn't lead to a
|
||||||
|
valid call to one of the offered tools, with arguments matching its schema.
|
||||||
|
The model still chooses the tool and the arguments; it can't produce anything
|
||||||
|
else. That makes it a property of the **model server** (and sometimes of the
|
||||||
|
model), not something a prompt can buy.
|
||||||
|
|
||||||
|
So the agent asks for it only where it is known to work: models whose LiteLLM
|
||||||
|
entry declares `supports_tool_choice: true`. Measured on the 4070:
|
||||||
|
|
||||||
|
| Alias | Server | `required` on a chat prompt | Declare |
|
||||||
|
|---|---|---|---|
|
||||||
|
| `local-fast` (Qwen3-8B-AWQ) | vLLM 0.29 | calls `finish` every time | `true` |
|
||||||
|
| `local-coder` (Qwen3-14B) | llama.cpp | can't stop; writes until the output ceiling (a known Qwen3-14B bug) | `false` |
|
||||||
|
| `claude-haiku-4-5` | Anthropic | calls `finish` every time | `true` (LiteLLM already says so) |
|
||||||
|
|
||||||
|
Asking a model that can't honour it can be worse than not asking: the
|
||||||
|
Qwen3-14B turn above costs a full output ceiling of generation, where a plain
|
||||||
|
prose reply would have cost a few hundred tokens.
|
||||||
|
|
||||||
|
Where it isn't enforced, the executor holds the contract itself:
|
||||||
|
|
||||||
|
- **`finish` is the only way to end a milestone.** It is refused once if
|
||||||
|
nothing has been changed yet.
|
||||||
|
- **A prose reply is a violation.** The model is told once; a second escalates.
|
||||||
|
- **A call to a tool that wasn't offered counts as a failure** toward
|
||||||
|
`failure_limit`, like a failed command, so an inventing model escalates in a
|
||||||
|
couple of turns instead of using up `max_turns`.
|
||||||
|
|
||||||
|
The capability matrix checks the declaration: a model declared `true` that
|
||||||
|
answers a required call in prose fails
|
||||||
|
`test_supports_tool_choice_is_declared_truthfully`.
|
||||||
|
|
||||||
|
### Declaring a local model's limits
|
||||||
|
|
||||||
|
LiteLLM knows commercial models' limits. For a local model, declare them in
|
||||||
|
LiteLLM's config as the model's `model_info`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
model_list:
|
||||||
|
- model_name: local-fast
|
||||||
|
litellm_params: {model: openai/qwen3-8b-awq, api_base: http://inference-host:8080/v1}
|
||||||
|
model_info:
|
||||||
|
max_input_tokens: 32768 # the server's per-request context
|
||||||
|
max_output_tokens: 4096 # the most one response may use
|
||||||
|
supports_function_calling: true
|
||||||
|
supports_tool_choice: true # only once measured; see above
|
||||||
|
```
|
||||||
|
|
||||||
|
`max_input_tokens` is the *whole* window, prompt plus response. The agent
|
||||||
|
takes the output ceiling out of it. That's how the commercial numbers work
|
||||||
|
too: Claude's prompt and response together must fit in its 200k.
|
||||||
|
|
||||||
|
The number to use is what the **server** allocates per request, not the
|
||||||
|
model's advertised maximum:
|
||||||
|
|
||||||
|
- **The model's ceiling:** `max_position_embeddings` in `config.json`, or
|
||||||
|
`<arch>.context_length` in a GGUF file. Some models reach further with RoPE
|
||||||
|
scaling; the model card says whether that needs switching on.
|
||||||
|
- **llama.cpp / llama-swap:** the `-c` / `--ctx-size` flag, shown as `n_ctx`
|
||||||
|
in the load log and in `GET /props`. With `--parallel N` each request gets
|
||||||
|
`n_ctx / N`, so run the agent's models with `--parallel 1`.
|
||||||
|
- **vLLM:** `--max-model-len`, reported by `GET /v1/models`.
|
||||||
|
- **Ollama:** 4096 unless `OLLAMA_CONTEXT_LENGTH` or a Modelfile's `num_ctx`
|
||||||
|
says otherwise. `GET /api/ps` shows the loaded model's `context_length`;
|
||||||
|
`ollama show` shows only the trained maximum.
|
||||||
|
|
||||||
|
Then check the declaration against the server:
|
||||||
|
|
||||||
|
uv run python scripts/probe_context.py local-fast
|
||||||
|
|
||||||
|
It sends one prompt half again as long as the declared window and reports
|
||||||
|
whether the server refused it, cut it (and where), or took it all. A cut
|
||||||
|
below the declaration is the case to fix: prompts between the two would be
|
||||||
|
silently cut. Measured on the Jetson's `llama3.2`: cut at 4096, matching its
|
||||||
|
declaration.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Benchmarking models
|
||||||
|
|
||||||
|
The capability matrix asks whether a model *can* hold the tool contract. The
|
||||||
|
benchmark asks whether it *finishes real work*: eight canned milestones, each an
|
||||||
|
approved plan over a small seed project, judged by a hidden check the model
|
||||||
|
never sees. Success means the check passes, not that the model said it was
|
||||||
|
done.
|
||||||
|
|
||||||
|
| Task | What it exercises |
|
||||||
|
|---|---|
|
||||||
|
| `write-function` | write a small function and its tests |
|
||||||
|
| `fix-bug` | find and fix the bug a failing test exposes, without touching the test |
|
||||||
|
| `multi-file` | thread a new option through several files |
|
||||||
|
| `long-output` | find one failure in test output far longer than `max_output_chars` |
|
||||||
|
| `trace-bug` | follow a wrong total to its cause in a shared helper, and fix it there rather than at the symptom |
|
||||||
|
| `refactor-rename` | rename a function across a ten-module package, sparing two similar names a blind replace would break |
|
||||||
|
| `spec-parser` | implement a parser from an eight-rule written spec when the plan gives only three examples, with tests |
|
||||||
|
| `long-milestone` | carry out eight dependent steps (model, storage, sorting, CLI) over 25+ turns |
|
||||||
|
|
||||||
|
The first four were the original set. By September 2026 all seven newer local
|
||||||
|
models passed all of them, so the last four were added to separate models that
|
||||||
|
can hold the tool contract from models that can do a milestone's real work.
|
||||||
|
Their plans stay explicit, as the planner is told to write them; what makes
|
||||||
|
them harder is the work itself: tracing a cause, searching a codebase,
|
||||||
|
reading a spec closely, and holding a plan over many turns.
|
||||||
|
|
||||||
|
It runs the executor exactly as the agent does (the same client wiring,
|
||||||
|
context budget and `tool_choice` handling, and your config's tuning knobs),
|
||||||
|
with **delegation and `ask_expert` turned off**. An escalation is a failure of
|
||||||
|
the model under test, not a frontier model's success.
|
||||||
|
|
||||||
|
**Evaluating a new model** is one command, once its LiteLLM entry declares its
|
||||||
|
limits:
|
||||||
|
|
||||||
|
scripts/evaluate_model.sh qwen3-32b-q4 "vLLM 0.29, AWQ, fp8 KV" 5
|
||||||
|
|
||||||
|
It runs the context probe, then the capability matrix, then five runs of
|
||||||
|
every task, and stops at the first step that fails. Or run the steps
|
||||||
|
yourself:
|
||||||
|
|
||||||
|
uv run python scripts/benchmark.py run --models A,B --runs 5 --note "how it's served"
|
||||||
|
uv run python scripts/benchmark.py report [--models A,B] [--tasks T]
|
||||||
|
|
||||||
|
**Results are kept.** Every run is one line in `bench/results/`, committed
|
||||||
|
with the repo, recording:
|
||||||
|
- the model's backing name and declared facts;
|
||||||
|
- your note on how it was served;
|
||||||
|
- the task's content hash;
|
||||||
|
- the agent's commit and tuning;
|
||||||
|
- the outcome, turns, failures, tokens, time and cost.
|
||||||
|
|
||||||
|
The report rebuilds the table from every stored run. Benchmarking a new model
|
||||||
|
means running only that model and reading it next to everything before it.
|
||||||
|
Runs against an older version of a task are marked `(old)`. Runs of one model
|
||||||
|
served differently (different note) are kept apart.
|
||||||
|
|
||||||
|
**Reading it:**
|
||||||
|
- **Compare the intervals, not the rates.** 3 of 5 (23–88%) and 5 of 5
|
||||||
|
(57–100%) overlap: five runs don't separate them. Add runs until the
|
||||||
|
intervals stop overlapping, or accept that the models are close.
|
||||||
|
- **Look at why runs failed**, not just how many (`outcome_note` and the
|
||||||
|
counts in each result line). An early shakedown on `local-fast` failed
|
||||||
|
mostly by escalation: the model was fixing its own mistakes and hit
|
||||||
|
`failure_limit: 2` mid-recovery. That says as much about the knob as about
|
||||||
|
the model. Tuning a knob and re-running is a fair comparison, because the
|
||||||
|
tuning is recorded with every run.
|
||||||
|
- **Cost** is per run, straight from LiteLLM, so a frontier model as executor
|
||||||
|
can be compared on success rate *and* dollars.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Part 3 — An order to tune in
|
||||||
|
|
||||||
|
Upstream choices change what's right downstream, so tune in this order and
|
||||||
|
change one thing at a time.
|
||||||
|
|
||||||
|
1. **Model and quantization.** Start with whether a candidate holds the
|
||||||
|
tool-call contract at all, and whether its server enforces `tool_choice`:
|
||||||
|
|
||||||
|
uv run pytest -m integration tests/integration/test_model_capability.py \
|
||||||
|
--capability-models=alias-a,alias-b
|
||||||
|
|
||||||
|
A model that falls back to prose instead of calling tools is disqualified,
|
||||||
|
however good its benchmarks look. Before comparing aliases, check what
|
||||||
|
each one actually points at in LiteLLM (`/model/info`, the
|
||||||
|
`litellm_params.model` field). Two aliases believed to be the same model
|
||||||
|
once weren't, and that cost a lot of debugging.
|
||||||
|
|
||||||
|
**Make sure LiteLLM has no fallback for a model you are measuring.** A
|
||||||
|
fallback answers a failed request with another model, under that model's
|
||||||
|
name, in a response that otherwise looks normal. One made a local model
|
||||||
|
that couldn't call tools at all look like it "held the tool contract 20 of
|
||||||
|
20": every answer was Claude's. The capability matrix and the probe now
|
||||||
|
check which model answered and fail on a mismatch, but the fallback also
|
||||||
|
quietly turns local work into frontier spending in production.
|
||||||
|
2. **Context length and KV-cache precision** on the server. Take the largest
|
||||||
|
context that fits beside the weights with 1–2 GB of headroom. Try an 8-bit
|
||||||
|
KV cache before giving up context.
|
||||||
|
3. **Declare the window** in LiteLLM and check it with the probe, then set
|
||||||
|
the model's `max_output_tokens` there so the prompt keeps most of the
|
||||||
|
window.
|
||||||
|
4. **The benchmark.** Run it and look at the evidence: success rate, turns,
|
||||||
|
escalations, and time taken.
|
||||||
|
5. **The loop knobs**: `failure_limit`, `max_turns`, `consult_budget`, the
|
||||||
|
delegation caps, then the tool limits, one at a time.
|
||||||
|
|
||||||
|
**Expect noise.** Local model behaviour varies a lot from run to run. One
|
||||||
|
early comparison measured 12 of 20 runs holding the tool contract against 20
|
||||||
|
of 20, a real difference (p ≈ 0.003). Three runs each could easily have
|
||||||
|
pointed the other way. Before changing a knob because of a result, repeat the
|
||||||
|
run enough times that the result would survive a coin flip.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Coming next
|
||||||
|
|
||||||
|
Later work that will add knobs, each with its own section here:
|
||||||
|
|
||||||
|
- **Compaction.** A model-written summary replacing old turns, instead of
|
||||||
|
dropping them. The log already has an event for it; it needs a summarizing
|
||||||
|
model and a policy for what is safe to summarize.
|
||||||
|
- **Benchmarking the other roles:** planner and delegate, with delegation
|
||||||
|
turned back on for comparisons across frontier tiers.
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# Example configuration for nisse-dev. Copy to nisse-dev.yaml (or point
|
||||||
|
# NISSE_CONFIG at your copy) and edit.
|
||||||
|
#
|
||||||
|
# - Structure lives here. Secrets do not: write them as ${VARIABLE} references,
|
||||||
|
# resolved from the environment (or .env, see .env.example) at startup.
|
||||||
|
# - Keep this file outside the workspace clone. Startup refuses otherwise,
|
||||||
|
# because the agent commits and pushes everything in the clone.
|
||||||
|
# - Unknown keys are errors, so a misspelt knob fails at startup instead of
|
||||||
|
# silently doing nothing.
|
||||||
|
|
||||||
|
state_db: ~/nisse-demo-workspace/state.db
|
||||||
|
workspace: ~/nisse-demo-workspace/project # the clone
|
||||||
|
project: demo
|
||||||
|
|
||||||
|
llm: # A5: LiteLLM is the only LLM destination
|
||||||
|
base_url: http://litellm.example:4000
|
||||||
|
token: ${NISSE_LLM_TOKEN}
|
||||||
|
# Ceiling on each response. The model's own max_output_tokens in LiteLLM
|
||||||
|
# caps it further, which is where to hold a local model to less.
|
||||||
|
max_output_tokens: 16384
|
||||||
|
# How long one request may take before it is abandoned. A safety net for a
|
||||||
|
# hung connection; slow local models can take minutes over a long answer.
|
||||||
|
request_timeout_s: 600
|
||||||
|
|
||||||
|
roles: # A11: role -> LiteLLM model alias
|
||||||
|
executor: local-fast # the local tier; the benchmark picks the model
|
||||||
|
planner: claude-opus-5-5 # high-end judgement: plans a weaker executor follows
|
||||||
|
delegate: claude-opus-5-5 # takes over what the executor got stuck on
|
||||||
|
consultant: claude-haiku-4-5 # one question at a time, no tools: cheap is enough
|
||||||
|
|
||||||
|
matrix: # A1
|
||||||
|
homeserver: https://matrix.example
|
||||||
|
user_id: "@nisse-dev:example"
|
||||||
|
token: ${NISSE_MATRIX_TOKEN}
|
||||||
|
room_id: "!roomid:example"
|
||||||
|
operators: ["@you:example"]
|
||||||
|
|
||||||
|
gitea: # B2
|
||||||
|
url: https://gitea.example
|
||||||
|
token: ${NISSE_GITEA_TOKEN}
|
||||||
|
repo: owner/demo-repo
|
||||||
|
operators: [your-gitea-login]
|
||||||
|
base_branch: main
|
||||||
|
|
||||||
|
# What each knob does, and how to choose a value: docs/Tuning-Guide.md.
|
||||||
|
# These are the defaults; the section can be left out entirely. The file is
|
||||||
|
# read at startup, so a change takes effect on the next restart.
|
||||||
|
tuning:
|
||||||
|
executor:
|
||||||
|
max_turns: 40 # model calls per milestone before it stops as capped
|
||||||
|
failure_limit: 2 # consecutive failed commands before escalating (A12)
|
||||||
|
expert:
|
||||||
|
consult_budget: 3 # ask_expert calls per milestone (A14)
|
||||||
|
delegation: # hard caps on one delegation (A13)
|
||||||
|
max_turns: 30
|
||||||
|
max_tokens: 200000
|
||||||
|
wall_clock_s: 900
|
||||||
|
tools:
|
||||||
|
command_timeout_s: 120 # one run_command before it is killed
|
||||||
|
max_output_chars: 4000 # tool output kept for the model, head and tail
|
||||||
|
context: # fitting each request into the model's window
|
||||||
|
keep_recent_turns: 6 # turns never cut or dropped
|
||||||
|
safety_margin: 0.1 # fraction of the window held back
|
||||||
@@ -3,7 +3,11 @@ name = "nisse-dev"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Dev agent: plans and implements software projects, produces Gitea PRs"
|
description = "Dev agent: plans and implements software projects, produces Gitea PRs"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
dependencies = ["nisse-core", "httpx>=0.27"]
|
dependencies = [
|
||||||
|
"nisse-core",
|
||||||
|
"httpx>=0.27",
|
||||||
|
"pyyaml>=6",
|
||||||
|
]
|
||||||
|
|
||||||
[project.scripts]
|
[project.scripts]
|
||||||
nisse-dev = "nisse_dev.main:main"
|
nisse-dev = "nisse_dev.main:main"
|
||||||
|
|||||||
@@ -0,0 +1,297 @@
|
|||||||
|
"""Composition root: the one place that knows how the whole agent fits together.
|
||||||
|
|
||||||
|
Every component below has been unit-tested in isolation for some time; nothing
|
||||||
|
had ever assembled them. That gap hid two live bugs — the delegation harness
|
||||||
|
and the Gitea comment poller were both fully built and reachable from nothing
|
||||||
|
but their own tests — which is the argument for having a single root rather
|
||||||
|
than constructing pieces ad hoc.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import threading
|
||||||
|
from collections.abc import Callable
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from nisse_dev.approvals import PlanBoard
|
||||||
|
from nisse_dev.cancel import Cancel
|
||||||
|
from nisse_dev.catalog import ModelCatalog, select_client
|
||||||
|
from nisse_dev.config import AgentConfig
|
||||||
|
from nisse_dev.context import BudgetedClient
|
||||||
|
from nisse_dev.control import Orchestrator
|
||||||
|
from nisse_dev.delegation import Caps, Delegation
|
||||||
|
from nisse_dev.executor import LLMExecutor
|
||||||
|
from nisse_dev.expert import ExpertDesk
|
||||||
|
from nisse_dev.git import GitRepo, authenticated_url
|
||||||
|
from nisse_dev.gitea import Comment, GiteaClient
|
||||||
|
from nisse_dev.llm import AnthropicPassthroughClient, LiteLLMClient
|
||||||
|
from nisse_dev.matrix import MatrixTransport
|
||||||
|
from nisse_dev.oplog import OpLog
|
||||||
|
from nisse_dev.planner import LLMPlanningAgent
|
||||||
|
from nisse_dev.poller import CommentPoller
|
||||||
|
from nisse_dev.publish import MilestonePublisher
|
||||||
|
from nisse_dev.state import StateStore
|
||||||
|
from nisse_dev.tools import Workspace
|
||||||
|
from nisse_dev.worker import SerialWorker
|
||||||
|
|
||||||
|
# How long shutdown waits for running work before leaving the store to the
|
||||||
|
# durability layer (A2/A3). A milestone can run far longer than a service
|
||||||
|
# manager allows for a stop, and a hard exit mid-work is already safe.
|
||||||
|
SHUTDOWN_WAIT_S = 10.0
|
||||||
|
|
||||||
|
TOOL_DRIVING_ROLES = ("planner", "executor", "delegate")
|
||||||
|
|
||||||
|
|
||||||
|
def discover_models(config: AgentConfig, oplog: OpLog) -> ModelCatalog:
|
||||||
|
"""Resolve every configured role against LiteLLM before anything runs.
|
||||||
|
|
||||||
|
Refusing to boot is the whole point: a role pointed at a model this key
|
||||||
|
cannot see is a misconfiguration, and the cheapest place to learn that is
|
||||||
|
here rather than ten minutes into a milestone. An unreachable proxy is
|
||||||
|
reported separately -- it is an outage, not a mistake, and telling them
|
||||||
|
apart is what lets an operator know whether to edit config or wait.
|
||||||
|
"""
|
||||||
|
catalog = ModelCatalog.discover(config.llm_base_url, config.llm_token)
|
||||||
|
catalog.validate(config.roles.pinned())
|
||||||
|
# Every role's context window must be known before a request is built
|
||||||
|
# against it (A10): a guess that is too large reaches a server that may
|
||||||
|
# silently cut the prompt.
|
||||||
|
catalog.require_limits(config.roles.pinned())
|
||||||
|
for role in TOOL_DRIVING_ROLES:
|
||||||
|
facts = catalog.facts(config.roles.resolve(role))
|
||||||
|
if facts.supports_tools is False:
|
||||||
|
# A warning, not a refusal: models LiteLLM declares tool-less are
|
||||||
|
# observed driving tool loops anyway, and A15 expects weak local
|
||||||
|
# models to be accommodated rather than excluded.
|
||||||
|
oplog.emit(
|
||||||
|
"model_tools_unsupported",
|
||||||
|
cause="startup",
|
||||||
|
role=role,
|
||||||
|
model=facts.alias,
|
||||||
|
backing=facts.backing,
|
||||||
|
)
|
||||||
|
return catalog
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Agent:
|
||||||
|
config: AgentConfig
|
||||||
|
store: StateStore
|
||||||
|
transport: MatrixTransport
|
||||||
|
orchestrator: Orchestrator
|
||||||
|
gitea: GiteaClient
|
||||||
|
account: str
|
||||||
|
worker: SerialWorker
|
||||||
|
_stopped: threading.Event
|
||||||
|
|
||||||
|
def start(self) -> None:
|
||||||
|
"""Boot in the order A3 requires: reconcile against external truth
|
||||||
|
before accepting a single operator command."""
|
||||||
|
self.worker.call(self.orchestrator.start)
|
||||||
|
self.transport.start()
|
||||||
|
state = self.worker.call(self.store.get_state)
|
||||||
|
where = state.state if state else "IDLE"
|
||||||
|
self.transport.send_message(
|
||||||
|
f"nisse-dev up as {self.account} — project {self.config.project}, "
|
||||||
|
f"state {where}, planner {self.config.roles.planner}, "
|
||||||
|
f"executor {self.config.roles.executor}"
|
||||||
|
)
|
||||||
|
|
||||||
|
def wait(self) -> None:
|
||||||
|
self._stopped.wait()
|
||||||
|
|
||||||
|
def stop(self) -> None:
|
||||||
|
"""Shut down outermost-first: stop accepting work, then stop talking,
|
||||||
|
then close the state store last so nothing writes to a closed file.
|
||||||
|
The store belongs to the worker, so the worker closes it."""
|
||||||
|
self.orchestrator.stop_watching()
|
||||||
|
self.transport.close()
|
||||||
|
self.gitea.close()
|
||||||
|
try:
|
||||||
|
self.worker.call(self.store.close, timeout=SHUTDOWN_WAIT_S)
|
||||||
|
except TimeoutError:
|
||||||
|
pass # still working; exiting mid-work is what A2/A3 are for
|
||||||
|
self.worker.stop(timeout=SHUTDOWN_WAIT_S)
|
||||||
|
self._stopped.set()
|
||||||
|
|
||||||
|
|
||||||
|
def make_llm_clients(
|
||||||
|
config: AgentConfig, catalog: ModelCatalog
|
||||||
|
) -> tuple[BudgetedClient, BudgetedClient]:
|
||||||
|
"""The two LLM routes, as every model call in the agent uses them.
|
||||||
|
|
||||||
|
Shared with the benchmark, so that what it measures is exactly what the
|
||||||
|
agent would do: the same context budget, the same declared limits, and
|
||||||
|
tool_choice asked only of models declared to enforce it.
|
||||||
|
"""
|
||||||
|
tuning = config.tuning
|
||||||
|
|
||||||
|
def limits_for(model: str) -> tuple[int, int | None]:
|
||||||
|
facts = catalog.facts(model)
|
||||||
|
assert facts.max_input_tokens is not None # require_limits checked
|
||||||
|
return facts.max_input_tokens, facts.max_output_tokens
|
||||||
|
|
||||||
|
def budgeted(client: object) -> BudgetedClient:
|
||||||
|
# Every request, on either route, is fitted to its model's window.
|
||||||
|
return BudgetedClient(
|
||||||
|
client,
|
||||||
|
limits_for,
|
||||||
|
max_output_tokens=config.max_output_tokens,
|
||||||
|
keep_recent_turns=tuning.context.keep_recent_turns,
|
||||||
|
safety_margin=tuning.context.safety_margin,
|
||||||
|
enforces_tool_choice=lambda m: (
|
||||||
|
catalog.facts(m).supports_tool_choice is True
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
normalized = budgeted(
|
||||||
|
LiteLLMClient(
|
||||||
|
config.llm_base_url,
|
||||||
|
config.llm_token,
|
||||||
|
max_tokens=config.max_output_tokens,
|
||||||
|
timeout_s=config.request_timeout_s,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
# Delegations prefer the vendor-native route; fidelity is cheap
|
||||||
|
# elsewhere (A5). Which route a given model gets is decided by
|
||||||
|
# select_client, not by where the caller happens to sit.
|
||||||
|
passthrough = budgeted(
|
||||||
|
AnthropicPassthroughClient(
|
||||||
|
config.llm_base_url,
|
||||||
|
config.llm_token,
|
||||||
|
max_tokens=config.max_output_tokens,
|
||||||
|
timeout_s=config.request_timeout_s,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return normalized, passthrough
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_workspace(config: AgentConfig, account: str) -> GitRepo:
|
||||||
|
"""Ensure the workspace is a clone of the project repo.
|
||||||
|
|
||||||
|
Cloning here rather than on `use <project>` is the single-project
|
||||||
|
shortcut: resolving a project name to a repo is issue #27's work.
|
||||||
|
"""
|
||||||
|
repo = GitRepo(config.workspace)
|
||||||
|
remote = authenticated_url(
|
||||||
|
config.gitea_url, config.gitea_repo, account, config.gitea_token
|
||||||
|
)
|
||||||
|
if not (config.workspace / ".git").exists():
|
||||||
|
config.workspace.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
GitRepo(config.workspace.parent).clone(remote, config.workspace)
|
||||||
|
repo.configure_identity(account, f"{account}@users.noreply.local")
|
||||||
|
return repo
|
||||||
|
|
||||||
|
|
||||||
|
def build_agent(
|
||||||
|
config: AgentConfig, *, notify: Callable[[str], None] | None = None
|
||||||
|
) -> Agent:
|
||||||
|
oplog = OpLog("nisse-dev")
|
||||||
|
worker = SerialWorker(
|
||||||
|
on_error=lambda exc: oplog.emit(
|
||||||
|
"worker.job_failed", cause="unhandled exception", error=repr(exc)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
worker.start()
|
||||||
|
# Opened on the worker, which is the only thread allowed to use it.
|
||||||
|
store = worker.call(lambda: StateStore(config.state_db, oplog=oplog))
|
||||||
|
cancel = Cancel()
|
||||||
|
# Before the clone, before the token checks: a typo'd role name should
|
||||||
|
# cost a second, not a repository.
|
||||||
|
catalog = discover_models(config, oplog)
|
||||||
|
|
||||||
|
gitea = GiteaClient(config.gitea_url, config.gitea_token)
|
||||||
|
# Whoami rather than another config variable: it names the identity every
|
||||||
|
# Gitea permission is evaluated against, and proves the token works before
|
||||||
|
# anything depends on it.
|
||||||
|
account = gitea.whoami()
|
||||||
|
|
||||||
|
git_repo = prepare_workspace(config, account)
|
||||||
|
tuning = config.tuning
|
||||||
|
workspace = Workspace(
|
||||||
|
config.workspace,
|
||||||
|
timeout_s=tuning.tools.command_timeout_s,
|
||||||
|
max_output_chars=tuning.tools.max_output_chars,
|
||||||
|
)
|
||||||
|
|
||||||
|
normalized, passthrough = make_llm_clients(config, catalog)
|
||||||
|
|
||||||
|
transport = MatrixTransport(config.matrix)
|
||||||
|
announce = notify or transport.send_message
|
||||||
|
|
||||||
|
expert = ExpertDesk(
|
||||||
|
normalized, store, config.roles, budget=tuning.expert.consult_budget
|
||||||
|
)
|
||||||
|
delegate_model = config.roles.resolve("delegate")
|
||||||
|
delegation = Delegation(
|
||||||
|
select_client(delegate_model, normalized, passthrough, catalog),
|
||||||
|
store,
|
||||||
|
workspace,
|
||||||
|
git_repo,
|
||||||
|
model=delegate_model,
|
||||||
|
backing_model=catalog.facts(delegate_model).backing,
|
||||||
|
caps=Caps(
|
||||||
|
turns=tuning.delegation.max_turns,
|
||||||
|
tokens=tuning.delegation.max_tokens,
|
||||||
|
wall_clock_s=tuning.delegation.wall_clock_s,
|
||||||
|
),
|
||||||
|
cancel=cancel,
|
||||||
|
)
|
||||||
|
executor = LLMExecutor(
|
||||||
|
normalized,
|
||||||
|
store,
|
||||||
|
config.roles,
|
||||||
|
workspace,
|
||||||
|
expert=expert,
|
||||||
|
delegate=delegation,
|
||||||
|
notify=announce,
|
||||||
|
cancel=cancel,
|
||||||
|
max_turns=tuning.executor.max_turns,
|
||||||
|
failure_limit=tuning.executor.failure_limit,
|
||||||
|
)
|
||||||
|
publisher = MilestonePublisher(
|
||||||
|
store,
|
||||||
|
gitea,
|
||||||
|
config.gitea_repo,
|
||||||
|
git_repo,
|
||||||
|
base_url=config.gitea_url,
|
||||||
|
account=account,
|
||||||
|
token=config.gitea_token,
|
||||||
|
base_branch=config.base_branch,
|
||||||
|
)
|
||||||
|
|
||||||
|
def poller_factory(on_comment: Callable[[Comment], None]) -> CommentPoller:
|
||||||
|
return CommentPoller(
|
||||||
|
gitea,
|
||||||
|
config.gitea_repo,
|
||||||
|
on_comment,
|
||||||
|
oplog=oplog,
|
||||||
|
# Its own comments are not new input; without this the agent
|
||||||
|
# answers itself.
|
||||||
|
ignore_authors={account},
|
||||||
|
)
|
||||||
|
|
||||||
|
orchestrator = Orchestrator(
|
||||||
|
store,
|
||||||
|
transport,
|
||||||
|
LLMPlanningAgent(normalized, store, config.roles, cancel=cancel),
|
||||||
|
operators=config.operators,
|
||||||
|
roles=config.roles,
|
||||||
|
executor=executor,
|
||||||
|
publisher=publisher,
|
||||||
|
gitea_operators=config.gitea_operators,
|
||||||
|
board=PlanBoard(gitea, config.gitea_repo),
|
||||||
|
delegate=delegation,
|
||||||
|
poller_factory=poller_factory,
|
||||||
|
catalog=catalog,
|
||||||
|
worker=worker,
|
||||||
|
cancel=cancel,
|
||||||
|
)
|
||||||
|
return Agent(
|
||||||
|
config=config,
|
||||||
|
store=store,
|
||||||
|
transport=transport,
|
||||||
|
orchestrator=orchestrator,
|
||||||
|
gitea=gitea,
|
||||||
|
account=account,
|
||||||
|
worker=worker,
|
||||||
|
_stopped=threading.Event(),
|
||||||
|
)
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
"""The executor benchmark: canned milestones, run repeatedly per model.
|
||||||
|
|
||||||
|
Choosing an executor model, and later tuning its knobs, should rest on
|
||||||
|
measured success rates, not on one run or on reputation. Each task is a fixed
|
||||||
|
approved plan over a seed project, with a hidden check the model never sees;
|
||||||
|
success means the check passes, not that the model said it finished. Runs use
|
||||||
|
the agent's own client wiring (context budget, declared limits, tool_choice
|
||||||
|
only where declared), so what is measured is what the agent would do.
|
||||||
|
|
||||||
|
Results are stored, one JSON line per run, with everything needed to compare
|
||||||
|
them later: the model's identity and declared facts, a note on how it was
|
||||||
|
served, the task's content hash, the agent's commit, and its tuning. A new
|
||||||
|
model then needs only its own runs to be compared with everything measured
|
||||||
|
before it.
|
||||||
|
|
||||||
|
See scripts/benchmark.py, and docs/Tuning-Guide.md for how to read results.
|
||||||
|
"""
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
"""Stored benchmark results, and the report that compares them.
|
||||||
|
|
||||||
|
Every run is one JSON line. The report is rebuilt from all of them each
|
||||||
|
time, so a model benchmarked today is read alongside every model measured
|
||||||
|
before it, as long as the task is still the same version.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import math
|
||||||
|
import statistics
|
||||||
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterable
|
||||||
|
from dataclasses import asdict, dataclass
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from nisse_dev.bench.runner import RunRecord
|
||||||
|
from nisse_dev.config import Tuning
|
||||||
|
|
||||||
|
|
||||||
|
def results_file(directory: Path, model: str, when: datetime | None = None) -> Path:
|
||||||
|
day = (when or datetime.now(UTC)).strftime("%Y-%m-%d")
|
||||||
|
return directory / f"{day}-{model}.jsonl"
|
||||||
|
|
||||||
|
|
||||||
|
def append(path: Path, record: RunRecord) -> None:
|
||||||
|
path.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
with path.open("a") as out:
|
||||||
|
out.write(json.dumps(record.to_json(), sort_keys=True) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def load(directory: Path) -> list[dict[str, Any]]:
|
||||||
|
records = []
|
||||||
|
for path in sorted(directory.glob("*.jsonl")):
|
||||||
|
for line in path.read_text().splitlines():
|
||||||
|
if line.strip():
|
||||||
|
records.append(json.loads(line))
|
||||||
|
return records
|
||||||
|
|
||||||
|
|
||||||
|
def wilson(successes: int, runs: int, z: float = 1.96) -> tuple[float, float]:
|
||||||
|
"""A 95% confidence interval for a success rate, sound at small counts.
|
||||||
|
|
||||||
|
Three successes in five runs and five in five overlap here, which is the
|
||||||
|
point: small samples of a noisy model do not separate much."""
|
||||||
|
if runs == 0:
|
||||||
|
return 0.0, 1.0
|
||||||
|
p = successes / runs
|
||||||
|
denominator = 1 + z * z / runs
|
||||||
|
centre = (p + z * z / (2 * runs)) / denominator
|
||||||
|
half = z * math.sqrt(p * (1 - p) / runs + z * z / (4 * runs * runs)) / denominator
|
||||||
|
return max(0.0, centre - half), min(1.0, centre + half)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Row:
|
||||||
|
model: str
|
||||||
|
note: str
|
||||||
|
task: str
|
||||||
|
runs: int
|
||||||
|
successes: int
|
||||||
|
median_turns: float
|
||||||
|
mean_wall_s: float # working time: rate-limit waits are left out
|
||||||
|
cost_per_run: float
|
||||||
|
stale: bool
|
||||||
|
rate_limited: int = 0 # runs that waited out the proxy's rate limit
|
||||||
|
tuning: str = "" # how the runs' tuning differs from today's defaults
|
||||||
|
|
||||||
|
@property
|
||||||
|
def interval(self) -> tuple[float, float]:
|
||||||
|
return wilson(self.successes, self.runs)
|
||||||
|
|
||||||
|
|
||||||
|
def tuning_label(tuning: dict[str, Any]) -> str:
|
||||||
|
"""The settings in a run's tuning that differ from today's defaults, as
|
||||||
|
"executor.max_turns=24"; empty when there are none. Relative to today's
|
||||||
|
defaults so that rows at the current settings stay unlabelled."""
|
||||||
|
defaults = asdict(Tuning())
|
||||||
|
return ", ".join(
|
||||||
|
f"{section}.{key}={value}"
|
||||||
|
for section, values in sorted(tuning.items())
|
||||||
|
for key, value in sorted((values or {}).items())
|
||||||
|
if defaults.get(section, {}).get(key) != value
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def summarise(
|
||||||
|
records: Iterable[dict[str, Any]], current_hashes: dict[str, str]
|
||||||
|
) -> list[Row]:
|
||||||
|
"""One row per (model, how it was served, task, task version, tuning).
|
||||||
|
|
||||||
|
Tuning is part of the key because a knob changes the result: runs capped
|
||||||
|
at 24 turns and runs allowed 40 are not the same measurement."""
|
||||||
|
groups: dict[tuple[str, str, str, str, str], list[dict[str, Any]]] = defaultdict(
|
||||||
|
list
|
||||||
|
)
|
||||||
|
for r in records:
|
||||||
|
key = (
|
||||||
|
r["backing"],
|
||||||
|
r.get("note", ""),
|
||||||
|
r["task"],
|
||||||
|
r["task_hash"],
|
||||||
|
tuning_label(r.get("tuning", {})),
|
||||||
|
)
|
||||||
|
groups[key].append(r)
|
||||||
|
rows = []
|
||||||
|
for (backing, note, task, task_hash, tuning), runs in sorted(groups.items()):
|
||||||
|
rows.append(
|
||||||
|
Row(
|
||||||
|
model=backing,
|
||||||
|
note=note,
|
||||||
|
task=task,
|
||||||
|
runs=len(runs),
|
||||||
|
successes=sum(1 for r in runs if r["success"]),
|
||||||
|
median_turns=statistics.median(r["turns"] for r in runs),
|
||||||
|
mean_wall_s=statistics.fmean(
|
||||||
|
r["wall_s"] - r.get("rate_limit_wait_s", 0.0) for r in runs
|
||||||
|
),
|
||||||
|
cost_per_run=statistics.fmean(r["cost"] for r in runs),
|
||||||
|
stale=current_hashes.get(task) != task_hash,
|
||||||
|
rate_limited=sum(1 for r in runs if r.get("rate_limit_wait_s")),
|
||||||
|
tuning=tuning,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return rows
|
||||||
|
|
||||||
|
|
||||||
|
def render(rows: list[Row]) -> str:
|
||||||
|
# A tuning column only when some rows need one, so a report at today's
|
||||||
|
# settings looks as it always has.
|
||||||
|
tuned = any(row.tuning for row in rows)
|
||||||
|
header = (
|
||||||
|
f"{'model':<34} {'note':<22} "
|
||||||
|
+ (f"{'tuning':<24} " if tuned else "")
|
||||||
|
+ f"{'task':<15} {'success':>8} {'95% CI':>11} "
|
||||||
|
f"{'turns':>6} {'time':>7} {'$/run':>8} {'429':>4}"
|
||||||
|
)
|
||||||
|
lines = [header, "-" * len(header)]
|
||||||
|
for row in rows:
|
||||||
|
low, high = row.interval
|
||||||
|
task = row.task + (" (old)" if row.stale else "")
|
||||||
|
tuning = f"{row.tuning[:24]:<24} " if tuned else ""
|
||||||
|
lines.append(
|
||||||
|
f"{row.model[:34]:<34} {row.note[:22]:<22} {tuning}{task[:15]:<15} "
|
||||||
|
f"{row.successes:>3}/{row.runs:<4} {low:>4.0%}-{high:<5.0%} "
|
||||||
|
f"{row.median_turns:>6.0f} {row.mean_wall_s:>6.0f}s {row.cost_per_run:>8.4f} "
|
||||||
|
f"{row.rate_limited or '':>4}"
|
||||||
|
)
|
||||||
|
if any(row.rate_limited for row in rows):
|
||||||
|
lines.append("")
|
||||||
|
lines.append(
|
||||||
|
"429 = runs that waited out the proxy's rate limit; time leaves the "
|
||||||
|
"waits out."
|
||||||
|
)
|
||||||
|
if tuned:
|
||||||
|
lines.append("")
|
||||||
|
lines.append("tuning = settings that differ from today's defaults.")
|
||||||
|
if any(row.stale for row in rows):
|
||||||
|
lines.append("")
|
||||||
|
lines.append(
|
||||||
|
"(old) = measured against an earlier version of the task; "
|
||||||
|
"not comparable with current runs."
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
@@ -0,0 +1,229 @@
|
|||||||
|
"""Running one task once with one executor model, and recording it."""
|
||||||
|
|
||||||
|
import io
|
||||||
|
import subprocess
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
from dataclasses import asdict, dataclass, field
|
||||||
|
from datetime import UTC, datetime
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from nisse_dev.bench.tasks import Task
|
||||||
|
from nisse_dev.canonical import (
|
||||||
|
CanonicalEvent,
|
||||||
|
PlanSubmitted,
|
||||||
|
StoredEvent,
|
||||||
|
ToolCall,
|
||||||
|
ToolResult,
|
||||||
|
UserMessage,
|
||||||
|
)
|
||||||
|
from nisse_dev.catalog import ModelFacts
|
||||||
|
from nisse_dev.config import Tuning
|
||||||
|
from nisse_dev.executor import LLMExecutor
|
||||||
|
from nisse_dev.llm import RoleMap
|
||||||
|
from nisse_dev.oplog import OpLog
|
||||||
|
from nisse_dev.state import StateStore
|
||||||
|
from nisse_dev.tools import Workspace
|
||||||
|
|
||||||
|
|
||||||
|
class MeteringClient:
|
||||||
|
"""Counts what a run spends, and notices a fallback: an answer from a
|
||||||
|
model other than the one asked for is not a measurement of it."""
|
||||||
|
|
||||||
|
def __init__(self, inner: Any, facts: ModelFacts) -> None:
|
||||||
|
self._inner = inner
|
||||||
|
self._facts = facts
|
||||||
|
self.calls = 0
|
||||||
|
self.prose_turns = 0
|
||||||
|
self.tokens_in = 0
|
||||||
|
self.tokens_out = 0
|
||||||
|
self.cost = 0.0
|
||||||
|
self.fallbacks: list[str] = []
|
||||||
|
self.rate_limit_wait_s = 0.0
|
||||||
|
|
||||||
|
def complete(
|
||||||
|
self,
|
||||||
|
events: list[StoredEvent],
|
||||||
|
tools: list[dict[str, Any]] | None,
|
||||||
|
model: str,
|
||||||
|
system: str | None = None,
|
||||||
|
) -> list[CanonicalEvent]:
|
||||||
|
self.calls += 1
|
||||||
|
try:
|
||||||
|
produced = self._inner.complete(events, tools, model, system=system)
|
||||||
|
finally:
|
||||||
|
# Counted even when the call fails: a rate limit that never
|
||||||
|
# cleared still cost the run its wait.
|
||||||
|
self.rate_limit_wait_s += float(
|
||||||
|
getattr(self._inner, "last_rate_limit_wait_s", 0.0) or 0.0
|
||||||
|
)
|
||||||
|
usage = getattr(self._inner, "last_usage", {}) or {}
|
||||||
|
self.tokens_in += int(usage.get("input", 0))
|
||||||
|
self.tokens_out += int(usage.get("output", 0))
|
||||||
|
self.cost += float(getattr(self._inner, "last_cost", 0.0) or 0.0)
|
||||||
|
answered = getattr(self._inner, "last_model", None)
|
||||||
|
if not self._facts.answered(answered):
|
||||||
|
self.fallbacks.append(str(answered))
|
||||||
|
if not any(isinstance(e, ToolCall) for e in produced):
|
||||||
|
self.prose_turns += 1
|
||||||
|
return produced
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RunRecord:
|
||||||
|
"""One run, with everything needed to compare it with runs made later
|
||||||
|
against other models, other servers, or other tuning."""
|
||||||
|
|
||||||
|
ts: str
|
||||||
|
model: str
|
||||||
|
backing: str
|
||||||
|
provider: str
|
||||||
|
model_info: dict[str, Any]
|
||||||
|
note: str
|
||||||
|
task: str
|
||||||
|
task_hash: str
|
||||||
|
commit: str
|
||||||
|
tuning: dict[str, Any]
|
||||||
|
success: bool
|
||||||
|
outcome: str
|
||||||
|
outcome_note: str
|
||||||
|
turns: int
|
||||||
|
prose_turns: int
|
||||||
|
failed_commands: int
|
||||||
|
invented_tools: int
|
||||||
|
trimmed_requests: int
|
||||||
|
tokens_in: int
|
||||||
|
tokens_out: int
|
||||||
|
cost: float
|
||||||
|
wall_s: float
|
||||||
|
fallbacks: list[str] = field(default_factory=list)
|
||||||
|
# Part of wall_s spent waiting out the proxy's rate limit, not working.
|
||||||
|
rate_limit_wait_s: float = 0.0
|
||||||
|
check_output: str = ""
|
||||||
|
|
||||||
|
def to_json(self) -> dict[str, Any]:
|
||||||
|
return asdict(self)
|
||||||
|
|
||||||
|
|
||||||
|
def agent_commit(repo: Path | None = None) -> str:
|
||||||
|
"""The agent's commit, marked when the tree had uncommitted changes."""
|
||||||
|
where = repo or Path(__file__).resolve().parent
|
||||||
|
try:
|
||||||
|
sha = subprocess.run(
|
||||||
|
["git", "rev-parse", "--short", "HEAD"],
|
||||||
|
cwd=where,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
).stdout.strip()
|
||||||
|
dirty = subprocess.run(
|
||||||
|
["git", "status", "--porcelain"],
|
||||||
|
cwd=where,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
).stdout.strip()
|
||||||
|
except (OSError, subprocess.CalledProcessError):
|
||||||
|
return "unknown"
|
||||||
|
return f"{sha}+dirty" if dirty else sha
|
||||||
|
|
||||||
|
|
||||||
|
def _count(events: list[StoredEvent]) -> tuple[int, int]:
|
||||||
|
tools = {
|
||||||
|
e.event.call_id: e.event.tool for e in events if isinstance(e.event, ToolCall)
|
||||||
|
}
|
||||||
|
failed = invented = 0
|
||||||
|
for stored in events:
|
||||||
|
event = stored.event
|
||||||
|
if isinstance(event, ToolResult) and not event.success:
|
||||||
|
if event.result.startswith("no such tool"):
|
||||||
|
invented += 1
|
||||||
|
elif tools.get(event.call_id) == "run_command":
|
||||||
|
failed += 1
|
||||||
|
return failed, invented
|
||||||
|
|
||||||
|
|
||||||
|
def run_task(
|
||||||
|
task: Task,
|
||||||
|
facts: ModelFacts,
|
||||||
|
client: Any,
|
||||||
|
tuning: Tuning,
|
||||||
|
*,
|
||||||
|
note: str = "",
|
||||||
|
commit: str = "",
|
||||||
|
workdir: Path | None = None,
|
||||||
|
) -> RunRecord:
|
||||||
|
"""Run one task once: seed a workspace, hand the executor the approved
|
||||||
|
plan, then judge the result with the hidden check.
|
||||||
|
|
||||||
|
Delegation and ask_expert are off: an escalation is a failure of the
|
||||||
|
model under test, not a frontier model's success."""
|
||||||
|
with tempfile.TemporaryDirectory(dir=workdir) as tmp:
|
||||||
|
root = Path(tmp)
|
||||||
|
workspace = Workspace(
|
||||||
|
root / "work",
|
||||||
|
timeout_s=tuning.tools.command_timeout_s,
|
||||||
|
max_output_chars=tuning.tools.max_output_chars,
|
||||||
|
)
|
||||||
|
task.seed_into(workspace.root)
|
||||||
|
store = StateStore(root / "state.db", oplog=OpLog("nisse-bench", io.StringIO()))
|
||||||
|
store.append_event(PlanSubmitted(plan=task.plan, model="benchmark"))
|
||||||
|
store.append_event(UserMessage(text="start milestone 1", source="benchmark"))
|
||||||
|
|
||||||
|
meter = MeteringClient(client, facts)
|
||||||
|
trims_before = getattr(client, "trimmed_requests", 0)
|
||||||
|
model = facts.alias
|
||||||
|
roles = RoleMap(executor=model, planner=model, delegate=model, consultant=model)
|
||||||
|
executor = LLMExecutor(
|
||||||
|
meter,
|
||||||
|
store,
|
||||||
|
roles,
|
||||||
|
workspace,
|
||||||
|
max_turns=tuning.executor.max_turns,
|
||||||
|
failure_limit=tuning.executor.failure_limit,
|
||||||
|
)
|
||||||
|
started = time.monotonic()
|
||||||
|
try:
|
||||||
|
outcome = executor.run(1)
|
||||||
|
status, detail = outcome.status, outcome.note
|
||||||
|
except Exception as exc: # noqa: BLE001 -- recorded, the benchmark goes on
|
||||||
|
status, detail = "error", repr(exc)
|
||||||
|
wall = time.monotonic() - started
|
||||||
|
|
||||||
|
success, check_output = task.run_check(workspace.root)
|
||||||
|
failed, invented = _count(store.read_events())
|
||||||
|
store.close()
|
||||||
|
|
||||||
|
return RunRecord(
|
||||||
|
ts=datetime.now(UTC).isoformat(timespec="seconds"),
|
||||||
|
model=model,
|
||||||
|
backing=facts.backing,
|
||||||
|
provider=facts.provider,
|
||||||
|
model_info={
|
||||||
|
"max_input_tokens": facts.max_input_tokens,
|
||||||
|
"max_output_tokens": facts.max_output_tokens,
|
||||||
|
"supports_tool_choice": facts.supports_tool_choice,
|
||||||
|
"supports_function_calling": facts.supports_tools,
|
||||||
|
},
|
||||||
|
note=note,
|
||||||
|
task=task.name,
|
||||||
|
task_hash=task.content_hash,
|
||||||
|
commit=commit,
|
||||||
|
tuning=asdict(tuning),
|
||||||
|
success=success and not meter.fallbacks,
|
||||||
|
outcome=status,
|
||||||
|
outcome_note=detail[:500],
|
||||||
|
turns=meter.calls,
|
||||||
|
prose_turns=meter.prose_turns,
|
||||||
|
failed_commands=failed,
|
||||||
|
invented_tools=invented,
|
||||||
|
trimmed_requests=getattr(client, "trimmed_requests", 0) - trims_before,
|
||||||
|
tokens_in=meter.tokens_in,
|
||||||
|
tokens_out=meter.tokens_out,
|
||||||
|
cost=round(meter.cost, 6),
|
||||||
|
wall_s=round(wall, 1),
|
||||||
|
fallbacks=meter.fallbacks,
|
||||||
|
rate_limit_wait_s=meter.rate_limit_wait_s,
|
||||||
|
check_output=check_output,
|
||||||
|
)
|
||||||
@@ -0,0 +1,102 @@
|
|||||||
|
"""Benchmark tasks: a seed project, an approved plan, and a hidden check.
|
||||||
|
|
||||||
|
Each task is a directory under ``tasks/``:
|
||||||
|
|
||||||
|
- ``task.yaml``: ``title``, ``plan`` (the approved milestone plan, written the
|
||||||
|
way the planner is asked to write them), and ``check`` (a shell command run
|
||||||
|
in the workspace afterwards).
|
||||||
|
- ``seed/``: the project the executor starts from.
|
||||||
|
- ``hidden/``: files the check needs, copied in only after the run, into
|
||||||
|
``.bench_check/``. The executor never sees them.
|
||||||
|
- ``solution/``: a reference solution, overlaid on the seed by the tests to
|
||||||
|
prove each task can be solved and that its check tells the difference.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import hashlib
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
TASKS_DIR = Path(__file__).parent / "tasks"
|
||||||
|
CHECK_DIR = ".bench_check"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Task:
|
||||||
|
name: str
|
||||||
|
title: str
|
||||||
|
plan: str
|
||||||
|
check: str
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
@property
|
||||||
|
def content_hash(self) -> str:
|
||||||
|
"""What the task is, so a changed task is never compared with runs of
|
||||||
|
its earlier version. Covers everything the model or the check sees;
|
||||||
|
not the reference solution."""
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
for part in ("task.yaml", "seed", "hidden"):
|
||||||
|
root = self.path / part
|
||||||
|
files = [root] if root.is_file() else sorted(root.rglob("*"))
|
||||||
|
for file in files:
|
||||||
|
if file.is_file() and "__pycache__" not in file.parts:
|
||||||
|
digest.update(str(file.relative_to(self.path)).encode())
|
||||||
|
digest.update(file.read_bytes())
|
||||||
|
return digest.hexdigest()[:16]
|
||||||
|
|
||||||
|
def seed_into(self, workspace: Path) -> None:
|
||||||
|
shutil.copytree(self.path / "seed", workspace, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
def apply_solution(self, workspace: Path) -> None:
|
||||||
|
shutil.copytree(self.path / "solution", workspace, dirs_exist_ok=True)
|
||||||
|
|
||||||
|
def run_check(self, workspace: Path, timeout_s: int = 300) -> tuple[bool, str]:
|
||||||
|
"""Copy the hidden files in and run the check. Returns whether it
|
||||||
|
passed, and its output for the record."""
|
||||||
|
target = workspace / CHECK_DIR
|
||||||
|
if target.exists():
|
||||||
|
shutil.rmtree(target) # never trust a directory the model made
|
||||||
|
shutil.copytree(self.path / "hidden", target)
|
||||||
|
try:
|
||||||
|
result = subprocess.run(
|
||||||
|
self.check,
|
||||||
|
shell=True,
|
||||||
|
cwd=workspace,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
timeout=timeout_s,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
return False, f"check timed out after {timeout_s}s"
|
||||||
|
output = (result.stdout + result.stderr)[-2000:]
|
||||||
|
return result.returncode == 0, output
|
||||||
|
|
||||||
|
|
||||||
|
def load_task(path: Path) -> Task:
|
||||||
|
spec = yaml.safe_load((path / "task.yaml").read_text())
|
||||||
|
return Task(
|
||||||
|
name=path.name,
|
||||||
|
title=str(spec["title"]),
|
||||||
|
plan=str(spec["plan"]).strip(),
|
||||||
|
check=str(spec["check"]),
|
||||||
|
path=path,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def load_tasks(names: list[str] | None = None, root: Path = TASKS_DIR) -> list[Task]:
|
||||||
|
available = {
|
||||||
|
p.name: p for p in sorted(root.iterdir()) if (p / "task.yaml").exists()
|
||||||
|
}
|
||||||
|
if names:
|
||||||
|
unknown = [n for n in names if n not in available]
|
||||||
|
if unknown:
|
||||||
|
raise ValueError(
|
||||||
|
f"unknown task(s): {', '.join(unknown)}; "
|
||||||
|
f"available: {', '.join(available)}"
|
||||||
|
)
|
||||||
|
return [load_task(available[n]) for n in names]
|
||||||
|
return [load_task(p) for p in available.values()]
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from inventory import Item, low_stock, total_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_total_value_counts_every_unit() -> None:
|
||||||
|
items = [Item("bolt", 0.25, 100), Item("nut", 0.10, 40)]
|
||||||
|
assert total_value(items) == 29.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_low_stock_is_alphabetical() -> None:
|
||||||
|
items = [Item("washer", 0.05, 2), Item("bolt", 0.25, 100), Item("anchor", 3.0, 1)]
|
||||||
|
assert low_stock(items) == ["anchor", "washer"]
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from inventory import Item, low_stock, total_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_visible_tests_were_not_changed() -> None:
|
||||||
|
original = (Path(__file__).parent / "original_test_inventory.py.txt").read_text()
|
||||||
|
assert Path("test_inventory.py").read_text() == original
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("items", "expected"),
|
||||||
|
[
|
||||||
|
([], 0.0),
|
||||||
|
([Item("bolt", 0.25, 100), Item("nut", 0.10, 40)], 29.0),
|
||||||
|
([Item("gear", 12.5, 3)], 37.5),
|
||||||
|
([Item("empty", 9.99, 0)], 0.0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_total_value(items: list[Item], expected: float) -> None:
|
||||||
|
assert total_value(items) == pytest.approx(expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_low_stock_still_works() -> None:
|
||||||
|
items = [Item("washer", 0.05, 2), Item("bolt", 0.25, 100)]
|
||||||
|
assert low_stock(items) == ["washer"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"""A tiny inventory."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Item:
|
||||||
|
name: str
|
||||||
|
unit_price: float
|
||||||
|
quantity: int
|
||||||
|
|
||||||
|
|
||||||
|
def total_value(items: list[Item]) -> float:
|
||||||
|
"""The value of all stock: each item's unit price times its quantity."""
|
||||||
|
total = 0.0
|
||||||
|
for item in items:
|
||||||
|
total += item.unit_price
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def low_stock(items: list[Item], threshold: int = 5) -> list[str]:
|
||||||
|
"""Names of items with fewer than `threshold` units, alphabetically."""
|
||||||
|
return sorted(item.name for item in items if item.quantity < threshold)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from inventory import Item, low_stock, total_value
|
||||||
|
|
||||||
|
|
||||||
|
def test_total_value_counts_every_unit() -> None:
|
||||||
|
items = [Item("bolt", 0.25, 100), Item("nut", 0.10, 40)]
|
||||||
|
assert total_value(items) == 29.0
|
||||||
|
|
||||||
|
|
||||||
|
def test_low_stock_is_alphabetical() -> None:
|
||||||
|
items = [Item("washer", 0.05, 2), Item("bolt", 0.25, 100), Item("anchor", 3.0, 1)]
|
||||||
|
assert low_stock(items) == ["anchor", "washer"]
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
"""A tiny inventory."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Item:
|
||||||
|
name: str
|
||||||
|
unit_price: float
|
||||||
|
quantity: int
|
||||||
|
|
||||||
|
|
||||||
|
def total_value(items: list[Item]) -> float:
|
||||||
|
"""The value of all stock: each item's unit price times its quantity."""
|
||||||
|
total = 0.0
|
||||||
|
for item in items:
|
||||||
|
total += item.unit_price * item.quantity
|
||||||
|
return total
|
||||||
|
|
||||||
|
|
||||||
|
def low_stock(items: list[Item], threshold: int = 5) -> list[str]:
|
||||||
|
"""Names of items with fewer than `threshold` units, alphabetically."""
|
||||||
|
return sorted(item.name for item in items if item.quantity < threshold)
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
title: Fix a bug a failing test exposes
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: fix the failing inventory test.
|
||||||
|
|
||||||
|
1. Run `python -m pytest -q` and read the failure.
|
||||||
|
2. The failing test is correct; the bug is in `inventory.py`. Do not change
|
||||||
|
any test file.
|
||||||
|
3. Fix `total_value` so that each item contributes its unit price times its
|
||||||
|
quantity.
|
||||||
|
4. Run `python -m pytest -q` until every test passes.
|
||||||
|
5. Call finish.
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
import json
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from tracker import storage
|
||||||
|
from tracker.core import add_task, complete, overdue, sort_tasks
|
||||||
|
from tracker.formatting import format_task
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
TODAY = date(2026, 10, 5)
|
||||||
|
|
||||||
|
|
||||||
|
# step 1
|
||||||
|
def test_task_has_an_optional_due_date() -> None:
|
||||||
|
assert Task(1, "a").due is None
|
||||||
|
assert Task(1, "a", False, date(2026, 1, 2)).due == date(2026, 1, 2)
|
||||||
|
|
||||||
|
|
||||||
|
# step 2
|
||||||
|
def test_save_writes_version_2_with_iso_dates(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
storage.save(path, [Task(1, "a", due=date(2026, 10, 1)), Task(2, "b")])
|
||||||
|
data = json.loads(path.read_text())
|
||||||
|
assert data["version"] == 2
|
||||||
|
assert [t["due"] for t in data["tasks"]] == ["2026-10-01", None]
|
||||||
|
|
||||||
|
|
||||||
|
def test_round_trip_keeps_dates(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
tasks = [Task(1, "a", due=date(2026, 10, 1)), Task(2, "b", done=True)]
|
||||||
|
storage.save(path, tasks)
|
||||||
|
assert storage.load(path) == tasks
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_1_files_still_load(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
path.write_text(
|
||||||
|
json.dumps({"version": 1, "tasks": [{"id": 4, "title": "old", "done": True}]})
|
||||||
|
)
|
||||||
|
assert storage.load(path) == [Task(4, "old", done=True, due=None)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("version", [0, 3, 99])
|
||||||
|
def test_other_versions_are_refused(tmp_path: Path, version: int) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
path.write_text(json.dumps({"version": version, "tasks": []}))
|
||||||
|
with pytest.raises(ValueError, match=f"unsupported tasks file version: {version}"):
|
||||||
|
storage.load(path)
|
||||||
|
|
||||||
|
|
||||||
|
# step 3
|
||||||
|
def test_add_task_takes_a_due_date() -> None:
|
||||||
|
tasks: list[Task] = []
|
||||||
|
assert add_task(tasks, "a", due=date(2026, 1, 1)).due == date(2026, 1, 1)
|
||||||
|
assert add_task(tasks, "b").due is None
|
||||||
|
|
||||||
|
|
||||||
|
# step 4
|
||||||
|
def test_sort_order() -> None:
|
||||||
|
tasks = [
|
||||||
|
Task(1, "undated"),
|
||||||
|
Task(2, "late", due=date(2026, 12, 1)),
|
||||||
|
Task(3, "early", due=date(2026, 9, 1)),
|
||||||
|
Task(4, "done dated", done=True, due=date(2026, 1, 1)),
|
||||||
|
Task(5, "done undated", done=True),
|
||||||
|
Task(6, "early twin", due=date(2026, 9, 1)),
|
||||||
|
Task(7, "undated twin"),
|
||||||
|
]
|
||||||
|
assert [t.id for t in sort_tasks(tasks)] == [3, 6, 2, 1, 7, 4, 5]
|
||||||
|
|
||||||
|
|
||||||
|
# step 5
|
||||||
|
def test_overdue() -> None:
|
||||||
|
tasks = [
|
||||||
|
Task(1, "due today", due=TODAY),
|
||||||
|
Task(2, "yesterday", due=date(2026, 10, 4)),
|
||||||
|
Task(3, "done late", done=True, due=date(2026, 1, 1)),
|
||||||
|
Task(4, "undated"),
|
||||||
|
Task(5, "long ago", due=date(2026, 1, 1)),
|
||||||
|
]
|
||||||
|
assert [t.id for t in overdue(tasks, TODAY)] == [5, 2]
|
||||||
|
|
||||||
|
|
||||||
|
# step 6
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("task", "line"),
|
||||||
|
[
|
||||||
|
(Task(3, "Buy milk"), "[ ] 3 Buy milk"),
|
||||||
|
(
|
||||||
|
Task(4, "Call Bob", done=True, due=date(2026, 10, 1)),
|
||||||
|
"[x] 4 Call Bob (due 2026-10-01)",
|
||||||
|
),
|
||||||
|
(Task(5, "Pay rent", due=date(2026, 9, 1)), "[!] 5 Pay rent (due 2026-09-01)"),
|
||||||
|
(Task(6, "Soon", due=date(2026, 10, 9)), "[ ] 6 Soon (due 2026-10-09)"),
|
||||||
|
(Task(7, "Today", due=TODAY), "[ ] 7 Today (due 2026-10-05)"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_format(task: Task, line: str) -> None:
|
||||||
|
assert format_task(task, TODAY) == line
|
||||||
|
|
||||||
|
|
||||||
|
# step 7, end to end through the real command line
|
||||||
|
def run(tmp_path: Path, *args: str) -> subprocess.CompletedProcess[str]:
|
||||||
|
env = {
|
||||||
|
**os.environ,
|
||||||
|
"TRACKER_FILE": str(tmp_path / "t.json"),
|
||||||
|
"TRACKER_TODAY": "2026-10-05",
|
||||||
|
}
|
||||||
|
return subprocess.run(
|
||||||
|
[sys.executable, "-m", "tracker", *args],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
env=env,
|
||||||
|
cwd=Path.cwd(),
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_command_line_flow(tmp_path: Path) -> None:
|
||||||
|
assert run(tmp_path, "add", "Buy milk").returncode == 0
|
||||||
|
assert run(tmp_path, "add", "Pay rent", "--due", "2026-10-01").returncode == 0
|
||||||
|
assert run(tmp_path, "add", "Dentist", "--due", "2026-11-20").returncode == 0
|
||||||
|
assert run(tmp_path, "done", "1").returncode == 0
|
||||||
|
listing = run(tmp_path, "list")
|
||||||
|
assert listing.stdout.splitlines() == [
|
||||||
|
"[!] 2 Pay rent (due 2026-10-01)",
|
||||||
|
"[ ] 3 Dentist (due 2026-11-20)",
|
||||||
|
"[x] 1 Buy milk",
|
||||||
|
], listing.stderr
|
||||||
|
assert run(tmp_path, "list", "--overdue").stdout.splitlines() == [
|
||||||
|
"[!] 2 Pay rent (due 2026-10-01)"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_bad_due_date_is_a_usage_error(tmp_path: Path) -> None:
|
||||||
|
result = run(tmp_path, "add", "x", "--due", "2026-13-40")
|
||||||
|
assert result.returncode == 2
|
||||||
|
assert "invalid --due date: 2026-13-40" in result.stderr
|
||||||
|
|
||||||
|
|
||||||
|
def test_today_defaults_to_the_real_date(tmp_path: Path) -> None:
|
||||||
|
env = {**os.environ, "TRACKER_FILE": str(tmp_path / "t.json")}
|
||||||
|
env.pop("TRACKER_TODAY", None)
|
||||||
|
subprocess.run(
|
||||||
|
[sys.executable, "-m", "tracker", "add", "Ancient", "--due", "2000-01-01"],
|
||||||
|
env=env,
|
||||||
|
cwd=Path.cwd(),
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
)
|
||||||
|
listing = subprocess.run(
|
||||||
|
[sys.executable, "-m", "tracker", "list", "--overdue"],
|
||||||
|
env=env,
|
||||||
|
cwd=Path.cwd(),
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
assert listing.stdout.splitlines() == ["[!] 1 Ancient (due 2000-01-01)"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_existing_behaviour_is_kept() -> None:
|
||||||
|
tasks: list[Task] = []
|
||||||
|
add_task(tasks, "a")
|
||||||
|
add_task(tasks, "b")
|
||||||
|
complete(tasks, 1)
|
||||||
|
assert [t.id for t in sort_tasks(tasks)] == [2, 1]
|
||||||
|
|
||||||
|
|
||||||
|
# step 8
|
||||||
|
def test_the_projects_own_tests_pass_and_cover_due_dates() -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-m", "pytest", "-q", "tests"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
cwd=Path.cwd(),
|
||||||
|
)
|
||||||
|
assert result.returncode == 0, result.stdout[-1500:]
|
||||||
|
written = "\n".join(p.read_text() for p in Path("tests").glob("test_*.py"))
|
||||||
|
for name in ("due", "overdue", "version"):
|
||||||
|
assert name in written, f"no test mentions {name!r}"
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import pytest
|
||||||
|
from tracker.core import add_task, complete, sort_tasks
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def test_ids_count_up() -> None:
|
||||||
|
tasks: list[Task] = []
|
||||||
|
assert [add_task(tasks, t).id for t in ("a", "b", "c")] == [1, 2, 3]
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_title_is_required() -> None:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
add_task([], " ")
|
||||||
|
|
||||||
|
|
||||||
|
def test_complete_and_sort() -> None:
|
||||||
|
tasks: list[Task] = []
|
||||||
|
for title in ("a", "b", "c"):
|
||||||
|
add_task(tasks, title)
|
||||||
|
complete(tasks, 1)
|
||||||
|
assert [t.id for t in sort_tasks(tasks)] == [2, 3, 1]
|
||||||
|
|
||||||
|
|
||||||
|
def test_completing_an_unknown_task() -> None:
|
||||||
|
with pytest.raises(KeyError):
|
||||||
|
complete([], 7)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tracker import storage
|
||||||
|
from tracker.cli import main
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def test_round_trip(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
storage.save(path, [Task(1, "a"), Task(2, "b", done=True)])
|
||||||
|
assert storage.load(path) == [Task(1, "a"), Task(2, "b", done=True)]
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_file_is_no_tasks(tmp_path: Path) -> None:
|
||||||
|
assert storage.load(tmp_path / "missing.json") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_add_done_list(tmp_path: Path, capsys) -> None:
|
||||||
|
path = str(tmp_path / "t.json")
|
||||||
|
main(["--file", path, "add", "Buy milk"])
|
||||||
|
main(["--file", path, "add", "Call Bob"])
|
||||||
|
main(["--file", path, "done", "1"])
|
||||||
|
capsys.readouterr()
|
||||||
|
main(["--file", path, "list"])
|
||||||
|
assert capsys.readouterr().out.splitlines() == ["[ ] 2 Call Bob", "[x] 1 Buy milk"]
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""A small command-line task tracker."""
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from tracker.cli import main
|
||||||
|
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
"""python -m tracker [--file PATH] {add,list,done} ...
|
||||||
|
|
||||||
|
The file defaults to $TRACKER_FILE, or tasks.json in the current directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tracker import storage
|
||||||
|
from tracker.core import add_task, complete, sort_tasks
|
||||||
|
from tracker.formatting import format_task
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="tracker")
|
||||||
|
parser.add_argument("--file", default=os.environ.get("TRACKER_FILE", "tasks.json"))
|
||||||
|
commands = parser.add_subparsers(dest="command", required=True)
|
||||||
|
add = commands.add_parser("add", help="add a task")
|
||||||
|
add.add_argument("title")
|
||||||
|
commands.add_parser("list", help="list tasks")
|
||||||
|
done = commands.add_parser("done", help="mark a task done")
|
||||||
|
done.add_argument("id", type=int)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
path = Path(args.file)
|
||||||
|
tasks = storage.load(path)
|
||||||
|
if args.command == "add":
|
||||||
|
task = add_task(tasks, args.title)
|
||||||
|
storage.save(path, tasks)
|
||||||
|
print(f"added {task.id}")
|
||||||
|
elif args.command == "done":
|
||||||
|
try:
|
||||||
|
complete(tasks, args.id)
|
||||||
|
except KeyError as exc:
|
||||||
|
parser.error(str(exc.args[0]))
|
||||||
|
storage.save(path, tasks)
|
||||||
|
else:
|
||||||
|
for task in sort_tasks(tasks):
|
||||||
|
print(format_task(task))
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""Operations on a list of tasks."""
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def add_task(tasks: list[Task], title: str) -> Task:
|
||||||
|
title = title.strip()
|
||||||
|
if not title:
|
||||||
|
raise ValueError("a task needs a title")
|
||||||
|
task = Task(id=max((t.id for t in tasks), default=0) + 1, title=title)
|
||||||
|
tasks.append(task)
|
||||||
|
return task
|
||||||
|
|
||||||
|
|
||||||
|
def complete(tasks: list[Task], task_id: int) -> Task:
|
||||||
|
for task in tasks:
|
||||||
|
if task.id == task_id:
|
||||||
|
task.done = True
|
||||||
|
return task
|
||||||
|
raise KeyError(f"no task {task_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def sort_tasks(tasks: list[Task]) -> list[Task]:
|
||||||
|
"""Tasks not done first, then by id."""
|
||||||
|
return sorted(tasks, key=lambda t: (t.done, t.id))
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
"""One line per task."""
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def format_task(task: Task) -> str:
|
||||||
|
mark = "x" if task.done else " "
|
||||||
|
return f"[{mark}] {task.id} {task.title}"
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""The task record."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Task:
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
done: bool = False
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
"""Tasks are kept in one JSON file."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
VERSION = 1
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path) -> list[Task]:
|
||||||
|
"""Every task in the file; an empty list when there is no file yet."""
|
||||||
|
if not path.exists():
|
||||||
|
return []
|
||||||
|
data = json.loads(path.read_text())
|
||||||
|
return [Task(id=t["id"], title=t["title"], done=t["done"]) for t in data["tasks"]]
|
||||||
|
|
||||||
|
|
||||||
|
def save(path: Path, tasks: list[Task]) -> None:
|
||||||
|
data = {
|
||||||
|
"version": VERSION,
|
||||||
|
"tasks": [{"id": t.id, "title": t.title, "done": t.done} for t in tasks],
|
||||||
|
}
|
||||||
|
path.write_text(json.dumps(data, indent=2))
|
||||||
+76
@@ -0,0 +1,76 @@
|
|||||||
|
import json
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from tracker import storage
|
||||||
|
from tracker.cli import main
|
||||||
|
from tracker.core import add_task, overdue, sort_tasks
|
||||||
|
from tracker.formatting import format_task
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def test_round_trip(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
tasks = [Task(1, "a"), Task(2, "b", done=True, due=date(2026, 10, 1))]
|
||||||
|
storage.save(path, tasks)
|
||||||
|
assert storage.load(path) == tasks
|
||||||
|
assert json.loads(path.read_text())["version"] == 2
|
||||||
|
|
||||||
|
|
||||||
|
def test_no_file_is_no_tasks(tmp_path: Path) -> None:
|
||||||
|
assert storage.load(tmp_path / "missing.json") == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_version_1_files_load(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
path.write_text(
|
||||||
|
json.dumps({"version": 1, "tasks": [{"id": 1, "title": "a", "done": False}]})
|
||||||
|
)
|
||||||
|
assert storage.load(path) == [Task(1, "a")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_unknown_versions_are_refused(tmp_path: Path) -> None:
|
||||||
|
path = tmp_path / "t.json"
|
||||||
|
path.write_text(json.dumps({"version": 9, "tasks": []}))
|
||||||
|
with pytest.raises(ValueError, match="unsupported tasks file version: 9"):
|
||||||
|
storage.load(path)
|
||||||
|
|
||||||
|
|
||||||
|
def test_due_dates_sort_and_overdue() -> None:
|
||||||
|
tasks: list[Task] = []
|
||||||
|
add_task(tasks, "undated")
|
||||||
|
add_task(tasks, "later", due=date(2026, 12, 1))
|
||||||
|
add_task(tasks, "sooner", due=date(2026, 9, 1))
|
||||||
|
assert [t.title for t in sort_tasks(tasks)] == ["sooner", "later", "undated"]
|
||||||
|
assert [t.title for t in overdue(tasks, date(2026, 10, 1))] == ["sooner"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_format_marks_overdue() -> None:
|
||||||
|
today = date(2026, 10, 5)
|
||||||
|
assert format_task(Task(5, "Pay rent", due=date(2026, 9, 1)), today) == (
|
||||||
|
"[!] 5 Pay rent (due 2026-09-01)"
|
||||||
|
)
|
||||||
|
assert format_task(Task(3, "Buy milk"), today) == "[ ] 3 Buy milk"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_add_done_list(tmp_path: Path, capsys, monkeypatch) -> None:
|
||||||
|
monkeypatch.setenv("TRACKER_TODAY", "2026-10-05")
|
||||||
|
path = str(tmp_path / "t.json")
|
||||||
|
main(["--file", path, "add", "Buy milk"])
|
||||||
|
main(["--file", path, "add", "Pay rent", "--due", "2026-10-01"])
|
||||||
|
main(["--file", path, "done", "1"])
|
||||||
|
capsys.readouterr()
|
||||||
|
main(["--file", path, "list"])
|
||||||
|
assert capsys.readouterr().out.splitlines() == [
|
||||||
|
"[!] 2 Pay rent (due 2026-10-01)",
|
||||||
|
"[x] 1 Buy milk",
|
||||||
|
]
|
||||||
|
main(["--file", path, "list", "--overdue"])
|
||||||
|
assert capsys.readouterr().out.splitlines() == ["[!] 2 Pay rent (due 2026-10-01)"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_rejects_a_bad_date(tmp_path: Path) -> None:
|
||||||
|
with pytest.raises(SystemExit) as caught:
|
||||||
|
main(["--file", str(tmp_path / "t.json"), "add", "x", "--due", "soon"])
|
||||||
|
assert caught.value.code == 2
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
"""python -m tracker [--file PATH] {add,list,done} ...
|
||||||
|
|
||||||
|
The file defaults to $TRACKER_FILE, or tasks.json in the current directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import os
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tracker import storage
|
||||||
|
from tracker.core import add_task, complete, overdue, sort_tasks
|
||||||
|
from tracker.formatting import format_task
|
||||||
|
|
||||||
|
|
||||||
|
def today() -> date:
|
||||||
|
value = os.environ.get("TRACKER_TODAY")
|
||||||
|
return date.fromisoformat(value) if value else date.today() # noqa: DTZ011 -- the plan asks for the local date
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="tracker")
|
||||||
|
parser.add_argument("--file", default=os.environ.get("TRACKER_FILE", "tasks.json"))
|
||||||
|
commands = parser.add_subparsers(dest="command", required=True)
|
||||||
|
add = commands.add_parser("add", help="add a task")
|
||||||
|
add.add_argument("title")
|
||||||
|
add.add_argument("--due", help="due date, YYYY-MM-DD")
|
||||||
|
listing = commands.add_parser("list", help="list tasks")
|
||||||
|
listing.add_argument("--overdue", action="store_true", help="only overdue tasks")
|
||||||
|
done = commands.add_parser("done", help="mark a task done")
|
||||||
|
done.add_argument("id", type=int)
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
path = Path(args.file)
|
||||||
|
tasks = storage.load(path)
|
||||||
|
now = today()
|
||||||
|
if args.command == "add":
|
||||||
|
due = None
|
||||||
|
if args.due is not None:
|
||||||
|
try:
|
||||||
|
due = date.fromisoformat(args.due)
|
||||||
|
except ValueError:
|
||||||
|
parser.error(f"invalid --due date: {args.due}")
|
||||||
|
task = add_task(tasks, args.title, due=due)
|
||||||
|
storage.save(path, tasks)
|
||||||
|
print(f"added {task.id}")
|
||||||
|
elif args.command == "done":
|
||||||
|
try:
|
||||||
|
complete(tasks, args.id)
|
||||||
|
except KeyError as exc:
|
||||||
|
parser.error(str(exc.args[0]))
|
||||||
|
storage.save(path, tasks)
|
||||||
|
else:
|
||||||
|
shown = overdue(tasks, now) if args.overdue else sort_tasks(tasks)
|
||||||
|
for task in shown:
|
||||||
|
print(format_task(task, now))
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
"""Operations on a list of tasks."""
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def add_task(tasks: list[Task], title: str, *, due: date | None = None) -> Task:
|
||||||
|
title = title.strip()
|
||||||
|
if not title:
|
||||||
|
raise ValueError("a task needs a title")
|
||||||
|
task = Task(id=max((t.id for t in tasks), default=0) + 1, title=title, due=due)
|
||||||
|
tasks.append(task)
|
||||||
|
return task
|
||||||
|
|
||||||
|
|
||||||
|
def complete(tasks: list[Task], task_id: int) -> Task:
|
||||||
|
for task in tasks:
|
||||||
|
if task.id == task_id:
|
||||||
|
task.done = True
|
||||||
|
return task
|
||||||
|
raise KeyError(f"no task {task_id}")
|
||||||
|
|
||||||
|
|
||||||
|
def sort_tasks(tasks: list[Task]) -> list[Task]:
|
||||||
|
"""Not done first; then dated tasks, earliest first; then undated; then id."""
|
||||||
|
return sorted(
|
||||||
|
tasks,
|
||||||
|
key=lambda t: (t.done, t.due is None, t.due or date.max, t.id),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def overdue(tasks: list[Task], today: date) -> list[Task]:
|
||||||
|
return [t for t in sort_tasks(tasks) if not t.done and t.due and t.due < today]
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
"""One line per task."""
|
||||||
|
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
|
||||||
|
def format_task(task: Task, today: date) -> str:
|
||||||
|
if task.done:
|
||||||
|
mark = "x"
|
||||||
|
elif task.due and task.due < today:
|
||||||
|
mark = "!"
|
||||||
|
else:
|
||||||
|
mark = " "
|
||||||
|
due = f" (due {task.due.isoformat()})" if task.due else ""
|
||||||
|
return f"[{mark}] {task.id} {task.title}{due}"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
"""The task record."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Task:
|
||||||
|
id: int
|
||||||
|
title: str
|
||||||
|
done: bool = False
|
||||||
|
due: date | None = None
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
"""Tasks are kept in one JSON file."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from datetime import date
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from tracker.models import Task
|
||||||
|
|
||||||
|
VERSION = 2
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path) -> list[Task]:
|
||||||
|
"""Every task in the file; an empty list when there is no file yet."""
|
||||||
|
if not path.exists():
|
||||||
|
return []
|
||||||
|
data = json.loads(path.read_text())
|
||||||
|
version = data.get("version")
|
||||||
|
if version not in (1, 2):
|
||||||
|
raise ValueError(f"unsupported tasks file version: {version}")
|
||||||
|
return [
|
||||||
|
Task(
|
||||||
|
id=t["id"],
|
||||||
|
title=t["title"],
|
||||||
|
done=t["done"],
|
||||||
|
due=date.fromisoformat(t["due"]) if t.get("due") else None,
|
||||||
|
)
|
||||||
|
for t in data["tasks"]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def save(path: Path, tasks: list[Task]) -> None:
|
||||||
|
data = {
|
||||||
|
"version": VERSION,
|
||||||
|
"tasks": [
|
||||||
|
{
|
||||||
|
"id": t.id,
|
||||||
|
"title": t.title,
|
||||||
|
"done": t.done,
|
||||||
|
"due": t.due.isoformat() if t.due else None,
|
||||||
|
}
|
||||||
|
for t in tasks
|
||||||
|
],
|
||||||
|
}
|
||||||
|
path.write_text(json.dumps(data, indent=2))
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
title: Carry out a long milestone whose steps build on each other
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: add due dates to the task tracker.
|
||||||
|
|
||||||
|
Run `python -m pytest -q` after each step; every test must still pass
|
||||||
|
before you start the next one. Use `datetime.date` for dates throughout.
|
||||||
|
|
||||||
|
1. `tracker/models.py`: add a field `due: date | None = None` to `Task`,
|
||||||
|
after `done`.
|
||||||
|
2. `tracker/storage.py`: `save` writes `"version": 2`, and each task's
|
||||||
|
`"due"` as an ISO string ("2026-10-01") or null. `load` reads version 1
|
||||||
|
files (their tasks have no "due"; use None) and version 2 files. Any
|
||||||
|
other version raises `ValueError("unsupported tasks file version: N")`,
|
||||||
|
with N the version found.
|
||||||
|
3. `tracker/core.py`: `add_task` takes a keyword argument
|
||||||
|
`due: date | None = None` and stores it on the new task.
|
||||||
|
4. `tracker/core.py`: change `sort_tasks` so that tasks not done come
|
||||||
|
before tasks done; within each of those two groups, tasks with a due
|
||||||
|
date come first, earliest due date first, then tasks without one; ties
|
||||||
|
are broken by id.
|
||||||
|
5. `tracker/core.py`: add `overdue(tasks: list[Task], today: date) -> list[Task]`:
|
||||||
|
tasks not done whose due date is before `today`, in `sort_tasks` order.
|
||||||
|
6. `tracker/formatting.py`: `format_task` takes a second argument
|
||||||
|
`today: date`. A task with a due date gets " (due 2026-10-01)" after its
|
||||||
|
title. An overdue task (as in step 5) shows `[!]` instead of `[ ]`.
|
||||||
|
Examples: "[ ] 3 Buy milk", "[x] 4 Call Bob (due 2026-10-01)",
|
||||||
|
"[!] 5 Pay rent (due 2026-09-01)".
|
||||||
|
7. `tracker/cli.py`: pass today's date to `format_task`. Today is the
|
||||||
|
`TRACKER_TODAY` environment variable (YYYY-MM-DD) when it is set, else
|
||||||
|
`date.today()`. `add` gains an option `--due YYYY-MM-DD`; an invalid date
|
||||||
|
must call `parser.error("invalid --due date: VALUE")`, which exits with
|
||||||
|
status 2. `list` gains a flag `--overdue` that lists only overdue tasks.
|
||||||
|
8. Add tests under `tests/` for each of steps 1-7, run `python -m pytest -q`
|
||||||
|
until everything passes, then check the whole flow by running:
|
||||||
|
`TRACKER_FILE=/tmp/tracker-check.json TRACKER_TODAY=2026-10-05 python -m tracker add "Pay rent" --due 2026-10-01`
|
||||||
|
then the same with `list --overdue`, which must print
|
||||||
|
`[!] 1 Pay rent (due 2026-10-01)`. Delete /tmp/tracker-check.json
|
||||||
|
before and after.
|
||||||
|
9. Call finish.
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import pytest
|
||||||
|
from rates import RATES
|
||||||
|
|
||||||
|
|
||||||
|
def test_every_region_is_present_once() -> None:
|
||||||
|
assert sorted(RATES) == [f"R{n:03d}" for n in range(300)]
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("n", range(300))
|
||||||
|
def test_rate(n: int) -> None:
|
||||||
|
assert RATES[f"R{n:03d}"] == pytest.approx(n % 25 / 100)
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
"""Tax rate per region code."""
|
||||||
|
|
||||||
|
RATES = {
|
||||||
|
"R000": 0.00,
|
||||||
|
"R001": 0.01,
|
||||||
|
"R002": 0.02,
|
||||||
|
"R003": 0.03,
|
||||||
|
"R004": 0.04,
|
||||||
|
"R005": 0.05,
|
||||||
|
"R006": 0.06,
|
||||||
|
"R007": 0.07,
|
||||||
|
"R008": 0.08,
|
||||||
|
"R009": 0.09,
|
||||||
|
"R010": 0.10,
|
||||||
|
"R011": 0.11,
|
||||||
|
"R012": 0.12,
|
||||||
|
"R013": 0.13,
|
||||||
|
"R014": 0.14,
|
||||||
|
"R015": 0.15,
|
||||||
|
"R016": 0.16,
|
||||||
|
"R017": 0.17,
|
||||||
|
"R018": 0.18,
|
||||||
|
"R019": 0.19,
|
||||||
|
"R020": 0.20,
|
||||||
|
"R021": 0.21,
|
||||||
|
"R022": 0.22,
|
||||||
|
"R023": 0.23,
|
||||||
|
"R024": 0.24,
|
||||||
|
"R025": 0.00,
|
||||||
|
"R026": 0.01,
|
||||||
|
"R027": 0.02,
|
||||||
|
"R028": 0.03,
|
||||||
|
"R029": 0.04,
|
||||||
|
"R030": 0.05,
|
||||||
|
"R031": 0.06,
|
||||||
|
"R032": 0.07,
|
||||||
|
"R033": 0.08,
|
||||||
|
"R034": 0.09,
|
||||||
|
"R035": 0.10,
|
||||||
|
"R036": 0.11,
|
||||||
|
"R037": 0.12,
|
||||||
|
"R038": 0.13,
|
||||||
|
"R039": 0.14,
|
||||||
|
"R040": 0.15,
|
||||||
|
"R041": 0.16,
|
||||||
|
"R042": 0.17,
|
||||||
|
"R043": 0.18,
|
||||||
|
"R044": 0.19,
|
||||||
|
"R045": 0.20,
|
||||||
|
"R046": 0.21,
|
||||||
|
"R047": 0.22,
|
||||||
|
"R048": 0.23,
|
||||||
|
"R049": 0.24,
|
||||||
|
"R050": 0.00,
|
||||||
|
"R051": 0.01,
|
||||||
|
"R052": 0.02,
|
||||||
|
"R053": 0.03,
|
||||||
|
"R054": 0.04,
|
||||||
|
"R055": 0.05,
|
||||||
|
"R056": 0.06,
|
||||||
|
"R057": 0.07,
|
||||||
|
"R058": 0.08,
|
||||||
|
"R059": 0.09,
|
||||||
|
"R060": 0.10,
|
||||||
|
"R061": 0.11,
|
||||||
|
"R062": 0.12,
|
||||||
|
"R063": 0.13,
|
||||||
|
"R064": 0.14,
|
||||||
|
"R065": 0.15,
|
||||||
|
"R066": 0.16,
|
||||||
|
"R067": 0.17,
|
||||||
|
"R068": 0.18,
|
||||||
|
"R069": 0.19,
|
||||||
|
"R070": 0.20,
|
||||||
|
"R071": 0.21,
|
||||||
|
"R072": 0.22,
|
||||||
|
"R073": 0.23,
|
||||||
|
"R074": 0.24,
|
||||||
|
"R075": 0.00,
|
||||||
|
"R076": 0.01,
|
||||||
|
"R077": 0.02,
|
||||||
|
"R078": 0.03,
|
||||||
|
"R079": 0.04,
|
||||||
|
"R080": 0.05,
|
||||||
|
"R081": 0.06,
|
||||||
|
"R082": 0.07,
|
||||||
|
"R083": 0.08,
|
||||||
|
"R084": 0.09,
|
||||||
|
"R085": 0.10,
|
||||||
|
"R086": 0.11,
|
||||||
|
"R087": 0.12,
|
||||||
|
"R088": 0.13,
|
||||||
|
"R089": 0.14,
|
||||||
|
"R090": 0.15,
|
||||||
|
"R091": 0.16,
|
||||||
|
"R092": 0.17,
|
||||||
|
"R093": 0.18,
|
||||||
|
"R094": 0.19,
|
||||||
|
"R095": 0.20,
|
||||||
|
"R096": 0.21,
|
||||||
|
"R097": 0.22,
|
||||||
|
"R098": 0.23,
|
||||||
|
"R099": 0.24,
|
||||||
|
"R100": 0.00,
|
||||||
|
"R101": 0.01,
|
||||||
|
"R102": 0.02,
|
||||||
|
"R103": 0.03,
|
||||||
|
"R104": 0.04,
|
||||||
|
"R105": 0.05,
|
||||||
|
"R106": 0.06,
|
||||||
|
"R107": 0.07,
|
||||||
|
"R108": 0.08,
|
||||||
|
"R109": 0.09,
|
||||||
|
"R110": 0.10,
|
||||||
|
"R111": 0.11,
|
||||||
|
"R112": 0.12,
|
||||||
|
"R113": 0.13,
|
||||||
|
"R114": 0.14,
|
||||||
|
"R115": 0.15,
|
||||||
|
"R116": 0.16,
|
||||||
|
"R117": 0.17,
|
||||||
|
"R118": 0.18,
|
||||||
|
"R119": 0.19,
|
||||||
|
"R120": 0.20,
|
||||||
|
"R121": 0.21,
|
||||||
|
"R122": 0.22,
|
||||||
|
"R123": 0.23,
|
||||||
|
"R124": 0.24,
|
||||||
|
"R125": 0.00,
|
||||||
|
"R126": 0.01,
|
||||||
|
"R127": 0.02,
|
||||||
|
"R128": 0.03,
|
||||||
|
"R129": 0.04,
|
||||||
|
"R130": 0.05,
|
||||||
|
"R131": 0.06,
|
||||||
|
"R132": 0.07,
|
||||||
|
"R133": 0.08,
|
||||||
|
"R134": 0.09,
|
||||||
|
"R135": 0.10,
|
||||||
|
"R136": 0.11,
|
||||||
|
"R137": 0.12,
|
||||||
|
"R138": 0.13,
|
||||||
|
"R139": 0.14,
|
||||||
|
"R140": 0.15,
|
||||||
|
"R141": 0.16,
|
||||||
|
"R142": 0.17,
|
||||||
|
"R143": 0.18,
|
||||||
|
"R144": 0.19,
|
||||||
|
"R145": 0.20,
|
||||||
|
"R146": 0.21,
|
||||||
|
"R147": 0.22,
|
||||||
|
"R148": 0.23,
|
||||||
|
"R149": 0.24,
|
||||||
|
"R150": 0.00,
|
||||||
|
"R151": 0.01,
|
||||||
|
"R152": 0.02,
|
||||||
|
"R153": 0.03,
|
||||||
|
"R154": 0.04,
|
||||||
|
"R155": 0.05,
|
||||||
|
"R156": 0.06,
|
||||||
|
"R157": 0.07,
|
||||||
|
"R158": 0.08,
|
||||||
|
"R159": 0.09,
|
||||||
|
"R160": 0.10,
|
||||||
|
"R161": 0.11,
|
||||||
|
"R162": 0.12,
|
||||||
|
"R163": 0.13,
|
||||||
|
"R164": 0.14,
|
||||||
|
"R165": 0.15,
|
||||||
|
"R166": 0.16,
|
||||||
|
"R167": 0.17,
|
||||||
|
"R168": 0.18,
|
||||||
|
"R169": 0.19,
|
||||||
|
"R170": 0.20,
|
||||||
|
"R171": 0.21,
|
||||||
|
"R172": 0.22,
|
||||||
|
"R173": 0.23,
|
||||||
|
"R174": 0.24,
|
||||||
|
"R175": 0.00,
|
||||||
|
"R176": 0.01,
|
||||||
|
"R177": 0.02,
|
||||||
|
"R178": 0.03,
|
||||||
|
"R179": 0.04,
|
||||||
|
"R180": 0.05,
|
||||||
|
"R181": 0.06,
|
||||||
|
"R182": 0.07,
|
||||||
|
"R183": 0.08,
|
||||||
|
"R184": 0.09,
|
||||||
|
"R185": 0.10,
|
||||||
|
"R186": 0.11,
|
||||||
|
"R187": 0.12,
|
||||||
|
"R188": 0.13,
|
||||||
|
"R189": 0.14,
|
||||||
|
"R190": 0.15,
|
||||||
|
"R191": 0.16,
|
||||||
|
"R192": 0.17,
|
||||||
|
"R193": 0.18,
|
||||||
|
"R194": 0.19,
|
||||||
|
"R195": 0.20,
|
||||||
|
"R196": 0.21,
|
||||||
|
"R197": 0.22,
|
||||||
|
"R198": 0.23,
|
||||||
|
"R199": 0.24,
|
||||||
|
"R200": 0.00,
|
||||||
|
"R201": 0.01,
|
||||||
|
"R202": 0.02,
|
||||||
|
"R203": 0.03,
|
||||||
|
"R204": 0.04,
|
||||||
|
"R205": 0.05,
|
||||||
|
"R206": 0.06,
|
||||||
|
"R207": 0.07,
|
||||||
|
"R208": 0.08,
|
||||||
|
"R209": 0.09,
|
||||||
|
"R210": 0.10,
|
||||||
|
"R211": 0.11,
|
||||||
|
"R212": 0.12,
|
||||||
|
"R213": 0.13,
|
||||||
|
"R214": 0.14,
|
||||||
|
"R215": 0.15,
|
||||||
|
"R216": 0.16,
|
||||||
|
"R217": 0.17,
|
||||||
|
"R218": 0.18,
|
||||||
|
"R219": 0.19,
|
||||||
|
"R220": 0.20,
|
||||||
|
"R221": 0.21,
|
||||||
|
"R222": 0.22,
|
||||||
|
"R223": 0.23,
|
||||||
|
"R224": 0.24,
|
||||||
|
"R225": 0.00,
|
||||||
|
"R226": 0.01,
|
||||||
|
"R227": 0.02,
|
||||||
|
"R228": 0.03,
|
||||||
|
"R229": 0.04,
|
||||||
|
"R230": 0.05,
|
||||||
|
"R231": 0.06,
|
||||||
|
"R232": 0.07,
|
||||||
|
"R233": 0.08,
|
||||||
|
"R234": 0.09,
|
||||||
|
"R235": 0.10,
|
||||||
|
"R236": 0.11,
|
||||||
|
"R237": 0.21,
|
||||||
|
"R238": 0.13,
|
||||||
|
"R239": 0.14,
|
||||||
|
"R240": 0.15,
|
||||||
|
"R241": 0.16,
|
||||||
|
"R242": 0.17,
|
||||||
|
"R243": 0.18,
|
||||||
|
"R244": 0.19,
|
||||||
|
"R245": 0.20,
|
||||||
|
"R246": 0.21,
|
||||||
|
"R247": 0.22,
|
||||||
|
"R248": 0.23,
|
||||||
|
"R249": 0.24,
|
||||||
|
"R250": 0.00,
|
||||||
|
"R251": 0.01,
|
||||||
|
"R252": 0.02,
|
||||||
|
"R253": 0.03,
|
||||||
|
"R254": 0.04,
|
||||||
|
"R255": 0.05,
|
||||||
|
"R256": 0.06,
|
||||||
|
"R257": 0.07,
|
||||||
|
"R258": 0.08,
|
||||||
|
"R259": 0.09,
|
||||||
|
"R260": 0.10,
|
||||||
|
"R261": 0.11,
|
||||||
|
"R262": 0.12,
|
||||||
|
"R263": 0.13,
|
||||||
|
"R264": 0.14,
|
||||||
|
"R265": 0.15,
|
||||||
|
"R266": 0.16,
|
||||||
|
"R267": 0.17,
|
||||||
|
"R268": 0.18,
|
||||||
|
"R269": 0.19,
|
||||||
|
"R270": 0.20,
|
||||||
|
"R271": 0.21,
|
||||||
|
"R272": 0.22,
|
||||||
|
"R273": 0.23,
|
||||||
|
"R274": 0.24,
|
||||||
|
"R275": 0.00,
|
||||||
|
"R276": 0.01,
|
||||||
|
"R277": 0.02,
|
||||||
|
"R278": 0.03,
|
||||||
|
"R279": 0.04,
|
||||||
|
"R280": 0.05,
|
||||||
|
"R281": 0.06,
|
||||||
|
"R282": 0.07,
|
||||||
|
"R283": 0.08,
|
||||||
|
"R284": 0.09,
|
||||||
|
"R285": 0.10,
|
||||||
|
"R286": 0.11,
|
||||||
|
"R287": 0.12,
|
||||||
|
"R288": 0.13,
|
||||||
|
"R289": 0.14,
|
||||||
|
"R290": 0.15,
|
||||||
|
"R291": 0.16,
|
||||||
|
"R292": 0.17,
|
||||||
|
"R293": 0.18,
|
||||||
|
"R294": 0.19,
|
||||||
|
"R295": 0.20,
|
||||||
|
"R296": 0.21,
|
||||||
|
"R297": 0.22,
|
||||||
|
"R298": 0.23,
|
||||||
|
"R299": 0.24,
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import pytest
|
||||||
|
from rates import RATES
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("code", [f"R{n:03d}" for n in range(300)])
|
||||||
|
def test_rate(code: str) -> None:
|
||||||
|
assert RATES[code] == pytest.approx(int(code[1:]) % 25 / 100)
|
||||||
@@ -0,0 +1,304 @@
|
|||||||
|
"""Tax rate per region code."""
|
||||||
|
|
||||||
|
RATES = {
|
||||||
|
"R000": 0.00,
|
||||||
|
"R001": 0.01,
|
||||||
|
"R002": 0.02,
|
||||||
|
"R003": 0.03,
|
||||||
|
"R004": 0.04,
|
||||||
|
"R005": 0.05,
|
||||||
|
"R006": 0.06,
|
||||||
|
"R007": 0.07,
|
||||||
|
"R008": 0.08,
|
||||||
|
"R009": 0.09,
|
||||||
|
"R010": 0.10,
|
||||||
|
"R011": 0.11,
|
||||||
|
"R012": 0.12,
|
||||||
|
"R013": 0.13,
|
||||||
|
"R014": 0.14,
|
||||||
|
"R015": 0.15,
|
||||||
|
"R016": 0.16,
|
||||||
|
"R017": 0.17,
|
||||||
|
"R018": 0.18,
|
||||||
|
"R019": 0.19,
|
||||||
|
"R020": 0.20,
|
||||||
|
"R021": 0.21,
|
||||||
|
"R022": 0.22,
|
||||||
|
"R023": 0.23,
|
||||||
|
"R024": 0.24,
|
||||||
|
"R025": 0.00,
|
||||||
|
"R026": 0.01,
|
||||||
|
"R027": 0.02,
|
||||||
|
"R028": 0.03,
|
||||||
|
"R029": 0.04,
|
||||||
|
"R030": 0.05,
|
||||||
|
"R031": 0.06,
|
||||||
|
"R032": 0.07,
|
||||||
|
"R033": 0.08,
|
||||||
|
"R034": 0.09,
|
||||||
|
"R035": 0.10,
|
||||||
|
"R036": 0.11,
|
||||||
|
"R037": 0.12,
|
||||||
|
"R038": 0.13,
|
||||||
|
"R039": 0.14,
|
||||||
|
"R040": 0.15,
|
||||||
|
"R041": 0.16,
|
||||||
|
"R042": 0.17,
|
||||||
|
"R043": 0.18,
|
||||||
|
"R044": 0.19,
|
||||||
|
"R045": 0.20,
|
||||||
|
"R046": 0.21,
|
||||||
|
"R047": 0.22,
|
||||||
|
"R048": 0.23,
|
||||||
|
"R049": 0.24,
|
||||||
|
"R050": 0.00,
|
||||||
|
"R051": 0.01,
|
||||||
|
"R052": 0.02,
|
||||||
|
"R053": 0.03,
|
||||||
|
"R054": 0.04,
|
||||||
|
"R055": 0.05,
|
||||||
|
"R056": 0.06,
|
||||||
|
"R057": 0.07,
|
||||||
|
"R058": 0.08,
|
||||||
|
"R059": 0.09,
|
||||||
|
"R060": 0.10,
|
||||||
|
"R061": 0.11,
|
||||||
|
"R062": 0.12,
|
||||||
|
"R063": 0.13,
|
||||||
|
"R064": 0.14,
|
||||||
|
"R065": 0.15,
|
||||||
|
"R066": 0.16,
|
||||||
|
"R067": 0.17,
|
||||||
|
"R068": 0.18,
|
||||||
|
"R069": 0.19,
|
||||||
|
"R070": 0.20,
|
||||||
|
"R071": 0.21,
|
||||||
|
"R072": 0.22,
|
||||||
|
"R073": 0.23,
|
||||||
|
"R074": 0.24,
|
||||||
|
"R075": 0.00,
|
||||||
|
"R076": 0.01,
|
||||||
|
"R077": 0.02,
|
||||||
|
"R078": 0.03,
|
||||||
|
"R079": 0.04,
|
||||||
|
"R080": 0.05,
|
||||||
|
"R081": 0.06,
|
||||||
|
"R082": 0.07,
|
||||||
|
"R083": 0.08,
|
||||||
|
"R084": 0.09,
|
||||||
|
"R085": 0.10,
|
||||||
|
"R086": 0.11,
|
||||||
|
"R087": 0.12,
|
||||||
|
"R088": 0.13,
|
||||||
|
"R089": 0.14,
|
||||||
|
"R090": 0.15,
|
||||||
|
"R091": 0.16,
|
||||||
|
"R092": 0.17,
|
||||||
|
"R093": 0.18,
|
||||||
|
"R094": 0.19,
|
||||||
|
"R095": 0.20,
|
||||||
|
"R096": 0.21,
|
||||||
|
"R097": 0.22,
|
||||||
|
"R098": 0.23,
|
||||||
|
"R099": 0.24,
|
||||||
|
"R100": 0.00,
|
||||||
|
"R101": 0.01,
|
||||||
|
"R102": 0.02,
|
||||||
|
"R103": 0.03,
|
||||||
|
"R104": 0.04,
|
||||||
|
"R105": 0.05,
|
||||||
|
"R106": 0.06,
|
||||||
|
"R107": 0.07,
|
||||||
|
"R108": 0.08,
|
||||||
|
"R109": 0.09,
|
||||||
|
"R110": 0.10,
|
||||||
|
"R111": 0.11,
|
||||||
|
"R112": 0.12,
|
||||||
|
"R113": 0.13,
|
||||||
|
"R114": 0.14,
|
||||||
|
"R115": 0.15,
|
||||||
|
"R116": 0.16,
|
||||||
|
"R117": 0.17,
|
||||||
|
"R118": 0.18,
|
||||||
|
"R119": 0.19,
|
||||||
|
"R120": 0.20,
|
||||||
|
"R121": 0.21,
|
||||||
|
"R122": 0.22,
|
||||||
|
"R123": 0.23,
|
||||||
|
"R124": 0.24,
|
||||||
|
"R125": 0.00,
|
||||||
|
"R126": 0.01,
|
||||||
|
"R127": 0.02,
|
||||||
|
"R128": 0.03,
|
||||||
|
"R129": 0.04,
|
||||||
|
"R130": 0.05,
|
||||||
|
"R131": 0.06,
|
||||||
|
"R132": 0.07,
|
||||||
|
"R133": 0.08,
|
||||||
|
"R134": 0.09,
|
||||||
|
"R135": 0.10,
|
||||||
|
"R136": 0.11,
|
||||||
|
"R137": 0.12,
|
||||||
|
"R138": 0.13,
|
||||||
|
"R139": 0.14,
|
||||||
|
"R140": 0.15,
|
||||||
|
"R141": 0.16,
|
||||||
|
"R142": 0.17,
|
||||||
|
"R143": 0.18,
|
||||||
|
"R144": 0.19,
|
||||||
|
"R145": 0.20,
|
||||||
|
"R146": 0.21,
|
||||||
|
"R147": 0.22,
|
||||||
|
"R148": 0.23,
|
||||||
|
"R149": 0.24,
|
||||||
|
"R150": 0.00,
|
||||||
|
"R151": 0.01,
|
||||||
|
"R152": 0.02,
|
||||||
|
"R153": 0.03,
|
||||||
|
"R154": 0.04,
|
||||||
|
"R155": 0.05,
|
||||||
|
"R156": 0.06,
|
||||||
|
"R157": 0.07,
|
||||||
|
"R158": 0.08,
|
||||||
|
"R159": 0.09,
|
||||||
|
"R160": 0.10,
|
||||||
|
"R161": 0.11,
|
||||||
|
"R162": 0.12,
|
||||||
|
"R163": 0.13,
|
||||||
|
"R164": 0.14,
|
||||||
|
"R165": 0.15,
|
||||||
|
"R166": 0.16,
|
||||||
|
"R167": 0.17,
|
||||||
|
"R168": 0.18,
|
||||||
|
"R169": 0.19,
|
||||||
|
"R170": 0.20,
|
||||||
|
"R171": 0.21,
|
||||||
|
"R172": 0.22,
|
||||||
|
"R173": 0.23,
|
||||||
|
"R174": 0.24,
|
||||||
|
"R175": 0.00,
|
||||||
|
"R176": 0.01,
|
||||||
|
"R177": 0.02,
|
||||||
|
"R178": 0.03,
|
||||||
|
"R179": 0.04,
|
||||||
|
"R180": 0.05,
|
||||||
|
"R181": 0.06,
|
||||||
|
"R182": 0.07,
|
||||||
|
"R183": 0.08,
|
||||||
|
"R184": 0.09,
|
||||||
|
"R185": 0.10,
|
||||||
|
"R186": 0.11,
|
||||||
|
"R187": 0.12,
|
||||||
|
"R188": 0.13,
|
||||||
|
"R189": 0.14,
|
||||||
|
"R190": 0.15,
|
||||||
|
"R191": 0.16,
|
||||||
|
"R192": 0.17,
|
||||||
|
"R193": 0.18,
|
||||||
|
"R194": 0.19,
|
||||||
|
"R195": 0.20,
|
||||||
|
"R196": 0.21,
|
||||||
|
"R197": 0.22,
|
||||||
|
"R198": 0.23,
|
||||||
|
"R199": 0.24,
|
||||||
|
"R200": 0.00,
|
||||||
|
"R201": 0.01,
|
||||||
|
"R202": 0.02,
|
||||||
|
"R203": 0.03,
|
||||||
|
"R204": 0.04,
|
||||||
|
"R205": 0.05,
|
||||||
|
"R206": 0.06,
|
||||||
|
"R207": 0.07,
|
||||||
|
"R208": 0.08,
|
||||||
|
"R209": 0.09,
|
||||||
|
"R210": 0.10,
|
||||||
|
"R211": 0.11,
|
||||||
|
"R212": 0.12,
|
||||||
|
"R213": 0.13,
|
||||||
|
"R214": 0.14,
|
||||||
|
"R215": 0.15,
|
||||||
|
"R216": 0.16,
|
||||||
|
"R217": 0.17,
|
||||||
|
"R218": 0.18,
|
||||||
|
"R219": 0.19,
|
||||||
|
"R220": 0.20,
|
||||||
|
"R221": 0.21,
|
||||||
|
"R222": 0.22,
|
||||||
|
"R223": 0.23,
|
||||||
|
"R224": 0.24,
|
||||||
|
"R225": 0.00,
|
||||||
|
"R226": 0.01,
|
||||||
|
"R227": 0.02,
|
||||||
|
"R228": 0.03,
|
||||||
|
"R229": 0.04,
|
||||||
|
"R230": 0.05,
|
||||||
|
"R231": 0.06,
|
||||||
|
"R232": 0.07,
|
||||||
|
"R233": 0.08,
|
||||||
|
"R234": 0.09,
|
||||||
|
"R235": 0.10,
|
||||||
|
"R236": 0.11,
|
||||||
|
"R237": 0.12,
|
||||||
|
"R238": 0.13,
|
||||||
|
"R239": 0.14,
|
||||||
|
"R240": 0.15,
|
||||||
|
"R241": 0.16,
|
||||||
|
"R242": 0.17,
|
||||||
|
"R243": 0.18,
|
||||||
|
"R244": 0.19,
|
||||||
|
"R245": 0.20,
|
||||||
|
"R246": 0.21,
|
||||||
|
"R247": 0.22,
|
||||||
|
"R248": 0.23,
|
||||||
|
"R249": 0.24,
|
||||||
|
"R250": 0.00,
|
||||||
|
"R251": 0.01,
|
||||||
|
"R252": 0.02,
|
||||||
|
"R253": 0.03,
|
||||||
|
"R254": 0.04,
|
||||||
|
"R255": 0.05,
|
||||||
|
"R256": 0.06,
|
||||||
|
"R257": 0.07,
|
||||||
|
"R258": 0.08,
|
||||||
|
"R259": 0.09,
|
||||||
|
"R260": 0.10,
|
||||||
|
"R261": 0.11,
|
||||||
|
"R262": 0.12,
|
||||||
|
"R263": 0.13,
|
||||||
|
"R264": 0.14,
|
||||||
|
"R265": 0.15,
|
||||||
|
"R266": 0.16,
|
||||||
|
"R267": 0.17,
|
||||||
|
"R268": 0.18,
|
||||||
|
"R269": 0.19,
|
||||||
|
"R270": 0.20,
|
||||||
|
"R271": 0.21,
|
||||||
|
"R272": 0.22,
|
||||||
|
"R273": 0.23,
|
||||||
|
"R274": 0.24,
|
||||||
|
"R275": 0.00,
|
||||||
|
"R276": 0.01,
|
||||||
|
"R277": 0.02,
|
||||||
|
"R278": 0.03,
|
||||||
|
"R279": 0.04,
|
||||||
|
"R280": 0.05,
|
||||||
|
"R281": 0.06,
|
||||||
|
"R282": 0.07,
|
||||||
|
"R283": 0.08,
|
||||||
|
"R284": 0.09,
|
||||||
|
"R285": 0.10,
|
||||||
|
"R286": 0.11,
|
||||||
|
"R287": 0.12,
|
||||||
|
"R288": 0.13,
|
||||||
|
"R289": 0.14,
|
||||||
|
"R290": 0.15,
|
||||||
|
"R291": 0.16,
|
||||||
|
"R292": 0.17,
|
||||||
|
"R293": 0.18,
|
||||||
|
"R294": 0.19,
|
||||||
|
"R295": 0.20,
|
||||||
|
"R296": 0.21,
|
||||||
|
"R297": 0.22,
|
||||||
|
"R298": 0.23,
|
||||||
|
"R299": 0.24,
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
title: Find one failure in long test output
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: fix the one wrong entry in the rate table.
|
||||||
|
|
||||||
|
1. Run `python -m pytest -v`. Exactly one test fails, and its output names a
|
||||||
|
region code.
|
||||||
|
2. The tests are correct. In `rates.py`, the rate for region code RNNN must be
|
||||||
|
(NNN mod 25) / 100. Correct only the failing region's entry; leave every
|
||||||
|
other line as it is.
|
||||||
|
3. Run `python -m pytest -q` until every test passes.
|
||||||
|
4. Call finish.
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
from greeter.cli import main
|
||||||
|
from greeter.core import greet
|
||||||
|
|
||||||
|
|
||||||
|
def test_default_is_unchanged() -> None:
|
||||||
|
assert greet("Bob") == "Hello, Bob!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_shout() -> None:
|
||||||
|
assert greet("Bob", shout=True) == "HELLO, BOB!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_default(capsys) -> None:
|
||||||
|
assert main(["Ada"]) == 0
|
||||||
|
assert capsys.readouterr().out == "Hello, Ada!\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_shout(capsys) -> None:
|
||||||
|
assert main(["--shout", "Ada"]) == 0
|
||||||
|
assert capsys.readouterr().out == "HELLO, ADA!\n"
|
||||||
|
|
||||||
|
|
||||||
|
def test_module_entry_point() -> None:
|
||||||
|
result = subprocess.run(
|
||||||
|
[sys.executable, "-m", "greeter", "--shout", "Bob"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
assert result.stdout == "HELLO, BOB!\n"
|
||||||
Whitespace-only changes.
@@ -0,0 +1,3 @@
|
|||||||
|
from greeter.cli import main
|
||||||
|
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
from greeter.core import greet
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="greeter")
|
||||||
|
parser.add_argument("name")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
print(greet(args.name))
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
def greet(name: str) -> str:
|
||||||
|
return f"Hello, {name}!"
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from greeter.cli import main
|
||||||
|
from greeter.core import greet
|
||||||
|
|
||||||
|
|
||||||
|
def test_greet() -> None:
|
||||||
|
assert greet("Bob") == "Hello, Bob!"
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_prints_the_greeting(capsys) -> None:
|
||||||
|
assert main(["Bob"]) == 0
|
||||||
|
assert capsys.readouterr().out == "Hello, Bob!\n"
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import argparse
|
||||||
|
|
||||||
|
from greeter.core import greet
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="greeter")
|
||||||
|
parser.add_argument("name")
|
||||||
|
parser.add_argument("--shout", action="store_true")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
print(greet(args.name, shout=args.shout))
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
def greet(name: str, shout: bool = False) -> str:
|
||||||
|
greeting = f"Hello, {name}!"
|
||||||
|
return greeting.upper() if shout else greeting
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
title: Thread a new option through several files
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: add a --shout option to the greeter.
|
||||||
|
|
||||||
|
1. In `greeter/core.py`, give `greet` a keyword argument `shout: bool = False`.
|
||||||
|
When it is true, return the whole greeting in upper case:
|
||||||
|
greet("Bob", shout=True) == "HELLO, BOB!". The default behaviour is unchanged.
|
||||||
|
2. In `greeter/cli.py`, add a `--shout` flag (action="store_true") and pass it
|
||||||
|
to `greet`.
|
||||||
|
3. Add two tests to `tests/test_greeter.py`: one for greet with shout=True, and
|
||||||
|
one that calls `main(["--shout", "Bob"])` and checks the printed output
|
||||||
|
using pytest's capsys fixture.
|
||||||
|
4. Run `python -m pytest -q` until every test passes, and check that
|
||||||
|
`python -m greeter --shout Bob` prints `HELLO, BOB!`.
|
||||||
|
5. Call finish.
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
import re
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
ROOT = Path.cwd()
|
||||||
|
OLD = re.compile(r"\bload_config\b")
|
||||||
|
|
||||||
|
|
||||||
|
def project_files() -> list[Path]:
|
||||||
|
return [
|
||||||
|
p
|
||||||
|
for p in [*ROOT.rglob("*.py"), *ROOT.rglob("*.md")]
|
||||||
|
if ".bench_check" not in p.parts and "__pycache__" not in p.parts
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_old_name_is_gone_everywhere() -> None:
|
||||||
|
left = [
|
||||||
|
f"{p.relative_to(ROOT)}:{n}: {line.strip()}"
|
||||||
|
for p in project_files()
|
||||||
|
for n, line in enumerate(p.read_text().splitlines(), 1)
|
||||||
|
if OLD.search(line)
|
||||||
|
]
|
||||||
|
assert not left, "load_config still appears:\n" + "\n".join(left)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_new_name_is_exported() -> None:
|
||||||
|
import shopkeeper
|
||||||
|
from shopkeeper.config import read_settings
|
||||||
|
|
||||||
|
assert shopkeeper.read_settings is read_settings
|
||||||
|
assert "read_settings" in shopkeeper.__all__
|
||||||
|
assert not hasattr(shopkeeper, "load_config")
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_similar_names_were_left_alone() -> None:
|
||||||
|
from shopkeeper import config
|
||||||
|
|
||||||
|
assert callable(config.load_config_defaults)
|
||||||
|
assert callable(config.reload_config)
|
||||||
|
assert not hasattr(config, "read_settings_defaults")
|
||||||
|
assert not hasattr(config, "reread_settings")
|
||||||
|
readme = (ROOT / "README.md").read_text()
|
||||||
|
assert "load_config_defaults()" in readme
|
||||||
|
assert "reload_config(settings)" in readme
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_new_name_appears_in_every_file_that_used_the_old_one() -> None:
|
||||||
|
for name in [
|
||||||
|
"catalog",
|
||||||
|
"pricing",
|
||||||
|
"inventory",
|
||||||
|
"notifications",
|
||||||
|
"orders",
|
||||||
|
"reports",
|
||||||
|
"storage",
|
||||||
|
"cli",
|
||||||
|
]:
|
||||||
|
source = (ROOT / "shopkeeper" / f"{name}.py").read_text()
|
||||||
|
assert "read_settings" in source, f"shopkeeper/{name}.py"
|
||||||
|
assert "read_settings" in (ROOT / "README.md").read_text()
|
||||||
|
|
||||||
|
|
||||||
|
def test_behaviour_is_unchanged(
|
||||||
|
tmp_path: Path, monkeypatch: pytest.MonkeyPatch
|
||||||
|
) -> None:
|
||||||
|
from shopkeeper.config import read_settings, reload_config
|
||||||
|
|
||||||
|
(tmp_path / "catalog.csv").write_text("sku,name,unit_price\nSKU-1,bolt,0.25\n")
|
||||||
|
ini = tmp_path / "shop.ini"
|
||||||
|
ini.write_text(
|
||||||
|
f"[shop]\ncurrency = EUR\ntax_percent = 20\ncatalog_file = {tmp_path / 'catalog.csv'}\n"
|
||||||
|
)
|
||||||
|
monkeypatch.delenv("SHOPKEEPER_CONFIG", raising=False)
|
||||||
|
settings = read_settings(ini)
|
||||||
|
assert (settings.currency, settings.tax_percent, settings.low_stock_threshold) == (
|
||||||
|
"EUR",
|
||||||
|
20.0,
|
||||||
|
5,
|
||||||
|
)
|
||||||
|
ini.write_text(ini.read_text().replace("EUR", "GBP"))
|
||||||
|
assert reload_config(settings).currency == "GBP"
|
||||||
|
assert read_settings().currency == "USD"
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_projects_own_tests_pass() -> None:
|
||||||
|
run = subprocess.run(
|
||||||
|
[sys.executable, "-m", "pytest", "-q", "tests"],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
assert run.returncode == 0, run.stdout[-2000:]
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_command_line_still_works(tmp_path: Path) -> None:
|
||||||
|
(tmp_path / "catalog.csv").write_text("sku,name,unit_price\nSKU-2,hammer,12.50\n")
|
||||||
|
ini = tmp_path / "shop.ini"
|
||||||
|
ini.write_text(
|
||||||
|
f"[shop]\ncurrency = EUR\ntax_percent = 20\ncatalog_file = {tmp_path / 'catalog.csv'}\n"
|
||||||
|
)
|
||||||
|
run = subprocess.run(
|
||||||
|
[
|
||||||
|
sys.executable,
|
||||||
|
"-m",
|
||||||
|
"shopkeeper",
|
||||||
|
"--config",
|
||||||
|
str(ini),
|
||||||
|
"price",
|
||||||
|
"SKU-2",
|
||||||
|
"2",
|
||||||
|
],
|
||||||
|
cwd=ROOT,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
assert run.stdout.strip() == "30.00 EUR", run.stderr[-1000:]
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# shopkeeper
|
||||||
|
|
||||||
|
A small shop back office: catalogue, pricing, stock, orders and reports.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Settings come from an INI file. Load them once and pass them around:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from shopkeeper import load_config
|
||||||
|
|
||||||
|
settings = load_config("shop.ini")
|
||||||
|
```
|
||||||
|
|
||||||
|
`load_config()` with no path uses `SHOPKEEPER_CONFIG`, or the built-in
|
||||||
|
defaults from `load_config_defaults()` when that is unset. After editing the
|
||||||
|
file, `reload_config(settings)` reads it again.
|
||||||
|
|
||||||
|
## Command line
|
||||||
|
|
||||||
|
python -m shopkeeper --config shop.ini price SKU-1 3
|
||||||
|
python -m shopkeeper --config shop.ini stock
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
"""A small shop back office."""
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config, load_config_defaults, reload_config
|
||||||
|
|
||||||
|
__all__ = ["Settings", "load_config", "load_config_defaults", "reload_config"]
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
from shopkeeper.cli import main
|
||||||
|
|
||||||
|
raise SystemExit(main())
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""The product catalogue: SKU, name and unit price, from a CSV file."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Product:
|
||||||
|
sku: str
|
||||||
|
name: str
|
||||||
|
unit_price: float
|
||||||
|
|
||||||
|
|
||||||
|
def read_catalog(settings: Settings | None = None) -> dict[str, Product]:
|
||||||
|
"""Every product, keyed by SKU. Uses load_config() when no settings are given."""
|
||||||
|
settings = settings or load_config()
|
||||||
|
path = Path(settings.catalog_file)
|
||||||
|
with path.open(newline="") as handle:
|
||||||
|
return {
|
||||||
|
row["sku"]: Product(row["sku"], row["name"], float(row["unit_price"]))
|
||||||
|
for row in csv.DictReader(handle)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Command line: python -m shopkeeper --config FILE COMMAND ..."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper import storage
|
||||||
|
from shopkeeper.catalog import read_catalog
|
||||||
|
from shopkeeper.config import load_config
|
||||||
|
from shopkeeper.pricing import format_price, with_tax
|
||||||
|
from shopkeeper.reports import stock_report
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="shopkeeper")
|
||||||
|
parser.add_argument("--config", help="settings file (see load_config)")
|
||||||
|
parser.add_argument("--stock-file", default="stock.json")
|
||||||
|
commands = parser.add_subparsers(dest="command", required=True)
|
||||||
|
price = commands.add_parser(
|
||||||
|
"price", help="price of a quantity of one SKU, with tax"
|
||||||
|
)
|
||||||
|
price.add_argument("sku")
|
||||||
|
price.add_argument("quantity", type=int)
|
||||||
|
commands.add_parser("stock", help="stock report")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
settings = load_config(args.config)
|
||||||
|
catalog = read_catalog(settings)
|
||||||
|
if args.command == "price":
|
||||||
|
amount = with_tax(catalog[args.sku].unit_price * args.quantity, settings)
|
||||||
|
print(format_price(amount, settings))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
stock_report(
|
||||||
|
catalog, storage.load(Path(args.stock_file), settings), settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Settings, read from an INI file."""
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
ENV_VAR = "SHOPKEEPER_CONFIG"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Settings:
|
||||||
|
currency: str
|
||||||
|
tax_percent: float
|
||||||
|
low_stock_threshold: int
|
||||||
|
catalog_file: str
|
||||||
|
notify_email: str
|
||||||
|
source: str | None = None # the file these came from, if any
|
||||||
|
|
||||||
|
|
||||||
|
def load_config_defaults() -> dict[str, Any]:
|
||||||
|
"""The built-in settings, used for anything the file leaves out."""
|
||||||
|
return {
|
||||||
|
"currency": "USD",
|
||||||
|
"tax_percent": 0.0,
|
||||||
|
"low_stock_threshold": 5,
|
||||||
|
"catalog_file": "catalog.csv",
|
||||||
|
"notify_email": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def load_config(path: str | Path | None = None) -> Settings:
|
||||||
|
"""Settings from `path`, or from $SHOPKEEPER_CONFIG, or the defaults.
|
||||||
|
|
||||||
|
Values missing from the file fall back to load_config_defaults().
|
||||||
|
"""
|
||||||
|
values = load_config_defaults()
|
||||||
|
chosen = path or os.environ.get(ENV_VAR)
|
||||||
|
if chosen:
|
||||||
|
parser = configparser.ConfigParser()
|
||||||
|
if not parser.read(chosen):
|
||||||
|
raise FileNotFoundError(f"no settings file at {chosen}")
|
||||||
|
section = parser["shop"] if parser.has_section("shop") else {}
|
||||||
|
for key, default in values.items():
|
||||||
|
if key in section:
|
||||||
|
values[key] = type(default)(section[key])
|
||||||
|
return Settings(**values, source=str(chosen) if chosen else None)
|
||||||
|
|
||||||
|
|
||||||
|
def reload_config(settings: Settings) -> Settings:
|
||||||
|
"""Read the settings again from the file they came from."""
|
||||||
|
return load_config(settings.source)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Stock levels."""
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
|
||||||
|
|
||||||
|
class Inventory:
|
||||||
|
def __init__(self, settings: Settings | None = None) -> None:
|
||||||
|
self.settings = settings or load_config()
|
||||||
|
self._stock: dict[str, int] = {}
|
||||||
|
|
||||||
|
def add(self, sku: str, quantity: int) -> None:
|
||||||
|
if quantity <= 0:
|
||||||
|
raise ValueError("quantity must be positive")
|
||||||
|
self._stock[sku] = self._stock.get(sku, 0) + quantity
|
||||||
|
|
||||||
|
def take(self, sku: str, quantity: int) -> None:
|
||||||
|
if self._stock.get(sku, 0) < quantity:
|
||||||
|
raise ValueError(f"not enough {sku} in stock")
|
||||||
|
self._stock[sku] -= quantity
|
||||||
|
|
||||||
|
def level(self, sku: str) -> int:
|
||||||
|
return self._stock.get(sku, 0)
|
||||||
|
|
||||||
|
def low_stock(self) -> list[str]:
|
||||||
|
"""SKUs below the threshold from load_config(), alphabetically."""
|
||||||
|
limit = self.settings.low_stock_threshold
|
||||||
|
return sorted(sku for sku, count in self._stock.items() if count < limit)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""Messages to the shop owner. Collected in memory; sending is elsewhere."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier:
|
||||||
|
def __init__(self, settings: Settings | None = None) -> None:
|
||||||
|
self.settings = settings or load_config()
|
||||||
|
self.outbox: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def notify(self, message: str) -> bool:
|
||||||
|
if not self.settings.notify_email:
|
||||||
|
log.info("no notify_email in load_config settings; dropped: %s", message)
|
||||||
|
return False
|
||||||
|
self.outbox.append((self.settings.notify_email, message))
|
||||||
|
return True
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""Orders: take stock, price the lines, warn when stock runs low."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from shopkeeper.catalog import Product
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.notifications import Notifier
|
||||||
|
from shopkeeper.pricing import with_tax
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Order:
|
||||||
|
lines: list[tuple[Product, int]] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add(self, product: Product, quantity: int) -> None:
|
||||||
|
self.lines.append((product, quantity))
|
||||||
|
|
||||||
|
def net(self) -> float:
|
||||||
|
return round(sum(p.unit_price * q for p, q in self.lines), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def place(
|
||||||
|
order: Order,
|
||||||
|
inventory: Inventory,
|
||||||
|
notifier: Notifier,
|
||||||
|
settings: Settings | None = None,
|
||||||
|
) -> float:
|
||||||
|
"""Take the order's stock and return its price with tax."""
|
||||||
|
settings = settings or load_config()
|
||||||
|
for product, quantity in order.lines:
|
||||||
|
inventory.take(product.sku, quantity)
|
||||||
|
for sku in inventory.low_stock():
|
||||||
|
notifier.notify(f"low stock: {sku}")
|
||||||
|
return with_tax(order.net(), settings)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"""Prices with tax, in the shop's currency."""
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
|
||||||
|
|
||||||
|
def with_tax(amount: float, settings: Settings | None = None) -> float:
|
||||||
|
settings = settings or load_config()
|
||||||
|
return round(amount * (1 + settings.tax_percent / 100), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def format_price(amount: float, settings: Settings | None = None) -> str:
|
||||||
|
# The currency comes from load_config, never from the catalogue.
|
||||||
|
settings = settings or load_config()
|
||||||
|
return f"{amount:.2f} {settings.currency}"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""Plain-text reports."""
|
||||||
|
|
||||||
|
from shopkeeper.catalog import Product
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.pricing import format_price
|
||||||
|
|
||||||
|
|
||||||
|
def stock_report(
|
||||||
|
catalog: dict[str, Product], inventory: Inventory, settings: Settings | None = None
|
||||||
|
) -> str:
|
||||||
|
settings = settings or load_config()
|
||||||
|
lines = []
|
||||||
|
for sku in sorted(catalog):
|
||||||
|
product = catalog[sku]
|
||||||
|
value = product.unit_price * inventory.level(sku)
|
||||||
|
lines.append(
|
||||||
|
f"{sku:<8} {product.name:<16} {inventory.level(sku):>5} "
|
||||||
|
f"{format_price(value, settings):>14}"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""Saving and loading stock levels as JSON."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, load_config
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
|
||||||
|
|
||||||
|
def save(inventory: Inventory, path: Path) -> None:
|
||||||
|
levels = {sku: inventory.level(sku) for sku in inventory._stock}
|
||||||
|
path.write_text(json.dumps(levels, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path, settings: Settings | None = None) -> Inventory:
|
||||||
|
inventory = Inventory(settings or load_config())
|
||||||
|
for sku, count in json.loads(path.read_text()).items():
|
||||||
|
if count:
|
||||||
|
inventory.add(sku, count)
|
||||||
|
return inventory
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def shop(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> Path:
|
||||||
|
"""A shop directory with a settings file and a catalogue."""
|
||||||
|
(tmp_path / "catalog.csv").write_text(
|
||||||
|
"sku,name,unit_price\nSKU-1,bolt,0.25\nSKU-2,hammer,12.50\n"
|
||||||
|
)
|
||||||
|
ini = tmp_path / "shop.ini"
|
||||||
|
ini.write_text(
|
||||||
|
"[shop]\ncurrency = EUR\ntax_percent = 20\nlow_stock_threshold = 3\n"
|
||||||
|
f"catalog_file = {tmp_path / 'catalog.csv'}\nnotify_email = owner@example.com\n"
|
||||||
|
)
|
||||||
|
monkeypatch.delenv("SHOPKEEPER_CONFIG", raising=False)
|
||||||
|
monkeypatch.chdir(tmp_path)
|
||||||
|
return ini
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
from shopkeeper import load_config, load_config_defaults, reload_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_without_a_file() -> None:
|
||||||
|
settings = load_config()
|
||||||
|
assert settings.currency == load_config_defaults()["currency"]
|
||||||
|
assert settings.source is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_values_from_the_file(shop) -> None:
|
||||||
|
settings = load_config(shop)
|
||||||
|
assert (settings.currency, settings.tax_percent, settings.low_stock_threshold) == (
|
||||||
|
"EUR",
|
||||||
|
20.0,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_variable(shop, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setenv("SHOPKEEPER_CONFIG", str(shop))
|
||||||
|
assert load_config().currency == "EUR"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_reads_the_file_again(shop) -> None:
|
||||||
|
settings = load_config(shop)
|
||||||
|
shop.write_text(shop.read_text().replace("EUR", "GBP"))
|
||||||
|
assert reload_config(settings).currency == "GBP"
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_missing_file_is_an_error(tmp_path) -> None:
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
load_config(tmp_path / "nope.ini")
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from shopkeeper.catalog import read_catalog
|
||||||
|
from shopkeeper.cli import main
|
||||||
|
from shopkeeper.config import load_config
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.notifications import Notifier
|
||||||
|
from shopkeeper.orders import Order, place
|
||||||
|
from shopkeeper.pricing import format_price, with_tax
|
||||||
|
|
||||||
|
|
||||||
|
def test_tax_and_format(shop) -> None:
|
||||||
|
settings = load_config(shop)
|
||||||
|
assert with_tax(10.0, settings) == 12.0
|
||||||
|
assert format_price(12.0, settings) == "12.00 EUR"
|
||||||
|
|
||||||
|
|
||||||
|
def test_placing_an_order_takes_stock_and_warns(shop) -> None:
|
||||||
|
settings = load_config(shop)
|
||||||
|
catalog = read_catalog(settings)
|
||||||
|
inventory = Inventory(settings)
|
||||||
|
inventory.add("SKU-1", 10)
|
||||||
|
notifier = Notifier(settings)
|
||||||
|
order = Order()
|
||||||
|
order.add(catalog["SKU-1"], 8)
|
||||||
|
assert place(order, inventory, notifier, settings) == 2.4
|
||||||
|
assert inventory.level("SKU-1") == 2
|
||||||
|
assert notifier.outbox == [("owner@example.com", "low stock: SKU-1")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_price(shop, capsys) -> None:
|
||||||
|
assert main(["--config", str(shop), "price", "SKU-2", "2"]) == 0
|
||||||
|
assert capsys.readouterr().out.strip() == "30.00 EUR"
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
# shopkeeper
|
||||||
|
|
||||||
|
A small shop back office: catalogue, pricing, stock, orders and reports.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
Settings come from an INI file. Load them once and pass them around:
|
||||||
|
|
||||||
|
```python
|
||||||
|
from shopkeeper import read_settings
|
||||||
|
|
||||||
|
settings = read_settings("shop.ini")
|
||||||
|
```
|
||||||
|
|
||||||
|
`read_settings()` with no path uses `SHOPKEEPER_CONFIG`, or the built-in
|
||||||
|
defaults from `load_config_defaults()` when that is unset. After editing the
|
||||||
|
file, `reload_config(settings)` reads it again.
|
||||||
|
|
||||||
|
## Command line
|
||||||
|
|
||||||
|
python -m shopkeeper --config shop.ini price SKU-1 3
|
||||||
|
python -m shopkeeper --config shop.ini stock
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
"""A small shop back office."""
|
||||||
|
|
||||||
|
from shopkeeper.config import (
|
||||||
|
Settings,
|
||||||
|
load_config_defaults,
|
||||||
|
read_settings,
|
||||||
|
reload_config,
|
||||||
|
)
|
||||||
|
|
||||||
|
__all__ = ["Settings", "load_config_defaults", "read_settings", "reload_config"]
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
"""The product catalogue: SKU, name and unit price, from a CSV file."""
|
||||||
|
|
||||||
|
import csv
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Product:
|
||||||
|
sku: str
|
||||||
|
name: str
|
||||||
|
unit_price: float
|
||||||
|
|
||||||
|
|
||||||
|
def read_catalog(settings: Settings | None = None) -> dict[str, Product]:
|
||||||
|
"""Every product, keyed by SKU. Uses read_settings() when no settings are given."""
|
||||||
|
settings = settings or read_settings()
|
||||||
|
path = Path(settings.catalog_file)
|
||||||
|
with path.open(newline="") as handle:
|
||||||
|
return {
|
||||||
|
row["sku"]: Product(row["sku"], row["name"], float(row["unit_price"]))
|
||||||
|
for row in csv.DictReader(handle)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
"""Command line: python -m shopkeeper --config FILE COMMAND ..."""
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper import storage
|
||||||
|
from shopkeeper.catalog import read_catalog
|
||||||
|
from shopkeeper.config import read_settings
|
||||||
|
from shopkeeper.pricing import format_price, with_tax
|
||||||
|
from shopkeeper.reports import stock_report
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv: list[str] | None = None) -> int:
|
||||||
|
parser = argparse.ArgumentParser(prog="shopkeeper")
|
||||||
|
parser.add_argument("--config", help="settings file (see read_settings)")
|
||||||
|
parser.add_argument("--stock-file", default="stock.json")
|
||||||
|
commands = parser.add_subparsers(dest="command", required=True)
|
||||||
|
price = commands.add_parser(
|
||||||
|
"price", help="price of a quantity of one SKU, with tax"
|
||||||
|
)
|
||||||
|
price.add_argument("sku")
|
||||||
|
price.add_argument("quantity", type=int)
|
||||||
|
commands.add_parser("stock", help="stock report")
|
||||||
|
args = parser.parse_args(argv)
|
||||||
|
|
||||||
|
settings = read_settings(args.config)
|
||||||
|
catalog = read_catalog(settings)
|
||||||
|
if args.command == "price":
|
||||||
|
amount = with_tax(catalog[args.sku].unit_price * args.quantity, settings)
|
||||||
|
print(format_price(amount, settings))
|
||||||
|
else:
|
||||||
|
print(
|
||||||
|
stock_report(
|
||||||
|
catalog, storage.load(Path(args.stock_file), settings), settings
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return 0
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
"""Settings, read from an INI file."""
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import os
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
ENV_VAR = "SHOPKEEPER_CONFIG"
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class Settings:
|
||||||
|
currency: str
|
||||||
|
tax_percent: float
|
||||||
|
low_stock_threshold: int
|
||||||
|
catalog_file: str
|
||||||
|
notify_email: str
|
||||||
|
source: str | None = None # the file these came from, if any
|
||||||
|
|
||||||
|
|
||||||
|
def load_config_defaults() -> dict[str, Any]:
|
||||||
|
"""The built-in settings, used for anything the file leaves out."""
|
||||||
|
return {
|
||||||
|
"currency": "USD",
|
||||||
|
"tax_percent": 0.0,
|
||||||
|
"low_stock_threshold": 5,
|
||||||
|
"catalog_file": "catalog.csv",
|
||||||
|
"notify_email": "",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def read_settings(path: str | Path | None = None) -> Settings:
|
||||||
|
"""Settings from `path`, or from $SHOPKEEPER_CONFIG, or the defaults.
|
||||||
|
|
||||||
|
Values missing from the file fall back to load_config_defaults().
|
||||||
|
"""
|
||||||
|
values = load_config_defaults()
|
||||||
|
chosen = path or os.environ.get(ENV_VAR)
|
||||||
|
if chosen:
|
||||||
|
parser = configparser.ConfigParser()
|
||||||
|
if not parser.read(chosen):
|
||||||
|
raise FileNotFoundError(f"no settings file at {chosen}")
|
||||||
|
section = parser["shop"] if parser.has_section("shop") else {}
|
||||||
|
for key, default in values.items():
|
||||||
|
if key in section:
|
||||||
|
values[key] = type(default)(section[key])
|
||||||
|
return Settings(**values, source=str(chosen) if chosen else None)
|
||||||
|
|
||||||
|
|
||||||
|
def reload_config(settings: Settings) -> Settings:
|
||||||
|
"""Read the settings again from the file they came from."""
|
||||||
|
return read_settings(settings.source)
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Stock levels."""
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
|
||||||
|
|
||||||
|
class Inventory:
|
||||||
|
def __init__(self, settings: Settings | None = None) -> None:
|
||||||
|
self.settings = settings or read_settings()
|
||||||
|
self._stock: dict[str, int] = {}
|
||||||
|
|
||||||
|
def add(self, sku: str, quantity: int) -> None:
|
||||||
|
if quantity <= 0:
|
||||||
|
raise ValueError("quantity must be positive")
|
||||||
|
self._stock[sku] = self._stock.get(sku, 0) + quantity
|
||||||
|
|
||||||
|
def take(self, sku: str, quantity: int) -> None:
|
||||||
|
if self._stock.get(sku, 0) < quantity:
|
||||||
|
raise ValueError(f"not enough {sku} in stock")
|
||||||
|
self._stock[sku] -= quantity
|
||||||
|
|
||||||
|
def level(self, sku: str) -> int:
|
||||||
|
return self._stock.get(sku, 0)
|
||||||
|
|
||||||
|
def low_stock(self) -> list[str]:
|
||||||
|
"""SKUs below the threshold from read_settings(), alphabetically."""
|
||||||
|
limit = self.settings.low_stock_threshold
|
||||||
|
return sorted(sku for sku, count in self._stock.items() if count < limit)
|
||||||
+20
@@ -0,0 +1,20 @@
|
|||||||
|
"""Messages to the shop owner. Collected in memory; sending is elsewhere."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
|
||||||
|
log = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class Notifier:
|
||||||
|
def __init__(self, settings: Settings | None = None) -> None:
|
||||||
|
self.settings = settings or read_settings()
|
||||||
|
self.outbox: list[tuple[str, str]] = []
|
||||||
|
|
||||||
|
def notify(self, message: str) -> bool:
|
||||||
|
if not self.settings.notify_email:
|
||||||
|
log.info("no notify_email in read_settings settings; dropped: %s", message)
|
||||||
|
return False
|
||||||
|
self.outbox.append((self.settings.notify_email, message))
|
||||||
|
return True
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
"""Orders: take stock, price the lines, warn when stock runs low."""
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from shopkeeper.catalog import Product
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.notifications import Notifier
|
||||||
|
from shopkeeper.pricing import with_tax
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Order:
|
||||||
|
lines: list[tuple[Product, int]] = field(default_factory=list)
|
||||||
|
|
||||||
|
def add(self, product: Product, quantity: int) -> None:
|
||||||
|
self.lines.append((product, quantity))
|
||||||
|
|
||||||
|
def net(self) -> float:
|
||||||
|
return round(sum(p.unit_price * q for p, q in self.lines), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def place(
|
||||||
|
order: Order,
|
||||||
|
inventory: Inventory,
|
||||||
|
notifier: Notifier,
|
||||||
|
settings: Settings | None = None,
|
||||||
|
) -> float:
|
||||||
|
"""Take the order's stock and return its price with tax."""
|
||||||
|
settings = settings or read_settings()
|
||||||
|
for product, quantity in order.lines:
|
||||||
|
inventory.take(product.sku, quantity)
|
||||||
|
for sku in inventory.low_stock():
|
||||||
|
notifier.notify(f"low stock: {sku}")
|
||||||
|
return with_tax(order.net(), settings)
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
"""Prices with tax, in the shop's currency."""
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
|
||||||
|
|
||||||
|
def with_tax(amount: float, settings: Settings | None = None) -> float:
|
||||||
|
settings = settings or read_settings()
|
||||||
|
return round(amount * (1 + settings.tax_percent / 100), 2)
|
||||||
|
|
||||||
|
|
||||||
|
def format_price(amount: float, settings: Settings | None = None) -> str:
|
||||||
|
# The currency comes from read_settings, never from the catalogue.
|
||||||
|
settings = settings or read_settings()
|
||||||
|
return f"{amount:.2f} {settings.currency}"
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
"""Plain-text reports."""
|
||||||
|
|
||||||
|
from shopkeeper.catalog import Product
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.pricing import format_price
|
||||||
|
|
||||||
|
|
||||||
|
def stock_report(
|
||||||
|
catalog: dict[str, Product], inventory: Inventory, settings: Settings | None = None
|
||||||
|
) -> str:
|
||||||
|
settings = settings or read_settings()
|
||||||
|
lines = []
|
||||||
|
for sku in sorted(catalog):
|
||||||
|
product = catalog[sku]
|
||||||
|
value = product.unit_price * inventory.level(sku)
|
||||||
|
lines.append(
|
||||||
|
f"{sku:<8} {product.name:<16} {inventory.level(sku):>5} "
|
||||||
|
f"{format_price(value, settings):>14}"
|
||||||
|
)
|
||||||
|
return "\n".join(lines)
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
"""Saving and loading stock levels as JSON."""
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from shopkeeper.config import Settings, read_settings
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
|
||||||
|
|
||||||
|
def save(inventory: Inventory, path: Path) -> None:
|
||||||
|
levels = {sku: inventory.level(sku) for sku in inventory._stock}
|
||||||
|
path.write_text(json.dumps(levels, indent=2, sort_keys=True))
|
||||||
|
|
||||||
|
|
||||||
|
def load(path: Path, settings: Settings | None = None) -> Inventory:
|
||||||
|
inventory = Inventory(settings or read_settings())
|
||||||
|
for sku, count in json.loads(path.read_text()).items():
|
||||||
|
if count:
|
||||||
|
inventory.add(sku, count)
|
||||||
|
return inventory
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
import pytest
|
||||||
|
from shopkeeper import load_config_defaults, read_settings, reload_config
|
||||||
|
|
||||||
|
|
||||||
|
def test_defaults_without_a_file() -> None:
|
||||||
|
settings = read_settings()
|
||||||
|
assert settings.currency == load_config_defaults()["currency"]
|
||||||
|
assert settings.source is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_values_from_the_file(shop) -> None:
|
||||||
|
settings = read_settings(shop)
|
||||||
|
assert (settings.currency, settings.tax_percent, settings.low_stock_threshold) == (
|
||||||
|
"EUR",
|
||||||
|
20.0,
|
||||||
|
3,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_environment_variable(shop, monkeypatch: pytest.MonkeyPatch) -> None:
|
||||||
|
monkeypatch.setenv("SHOPKEEPER_CONFIG", str(shop))
|
||||||
|
assert read_settings().currency == "EUR"
|
||||||
|
|
||||||
|
|
||||||
|
def test_reload_reads_the_file_again(shop) -> None:
|
||||||
|
settings = read_settings(shop)
|
||||||
|
shop.write_text(shop.read_text().replace("EUR", "GBP"))
|
||||||
|
assert reload_config(settings).currency == "GBP"
|
||||||
|
|
||||||
|
|
||||||
|
def test_a_missing_file_is_an_error(tmp_path) -> None:
|
||||||
|
with pytest.raises(FileNotFoundError):
|
||||||
|
read_settings(tmp_path / "nope.ini")
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
from shopkeeper.catalog import read_catalog
|
||||||
|
from shopkeeper.cli import main
|
||||||
|
from shopkeeper.config import read_settings
|
||||||
|
from shopkeeper.inventory import Inventory
|
||||||
|
from shopkeeper.notifications import Notifier
|
||||||
|
from shopkeeper.orders import Order, place
|
||||||
|
from shopkeeper.pricing import format_price, with_tax
|
||||||
|
|
||||||
|
|
||||||
|
def test_tax_and_format(shop) -> None:
|
||||||
|
settings = read_settings(shop)
|
||||||
|
assert with_tax(10.0, settings) == 12.0
|
||||||
|
assert format_price(12.0, settings) == "12.00 EUR"
|
||||||
|
|
||||||
|
|
||||||
|
def test_placing_an_order_takes_stock_and_warns(shop) -> None:
|
||||||
|
settings = read_settings(shop)
|
||||||
|
catalog = read_catalog(settings)
|
||||||
|
inventory = Inventory(settings)
|
||||||
|
inventory.add("SKU-1", 10)
|
||||||
|
notifier = Notifier(settings)
|
||||||
|
order = Order()
|
||||||
|
order.add(catalog["SKU-1"], 8)
|
||||||
|
assert place(order, inventory, notifier, settings) == 2.4
|
||||||
|
assert inventory.level("SKU-1") == 2
|
||||||
|
assert notifier.outbox == [("owner@example.com", "low stock: SKU-1")]
|
||||||
|
|
||||||
|
|
||||||
|
def test_cli_price(shop, capsys) -> None:
|
||||||
|
assert main(["--config", str(shop), "price", "SKU-2", "2"]) == 0
|
||||||
|
assert capsys.readouterr().out.strip() == "30.00 EUR"
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
title: Rename a function used across a package
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: rename `load_config` to `read_settings` across the project.
|
||||||
|
|
||||||
|
1. Find every use of `load_config`: run
|
||||||
|
`grep -rnw load_config --include=*.py --include=*.md .`
|
||||||
|
It is defined in `shopkeeper/config.py` and used in many other modules,
|
||||||
|
in tests, in docstrings, in comments, in a log message and in
|
||||||
|
`README.md`.
|
||||||
|
2. Rename the function to `read_settings` everywhere: its definition,
|
||||||
|
imports, calls, `__all__`, docstrings, comments, strings and
|
||||||
|
`README.md`. Afterwards the grep in step 1 must print nothing.
|
||||||
|
3. Rename only that exact name. `load_config_defaults` and `reload_config`
|
||||||
|
are different functions and keep their names. Change no behaviour.
|
||||||
|
4. Update the tests under `tests/` to use the new name, and run
|
||||||
|
`python -m pytest -q` until every test passes.
|
||||||
|
5. Call finish.
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import pytest
|
||||||
|
from durations import parse_duration
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("text", "seconds"),
|
||||||
|
[
|
||||||
|
("2h", 7200), # rule 1
|
||||||
|
("1h30m", 5400),
|
||||||
|
("1d2h3m4s", 93784),
|
||||||
|
("45s", 45),
|
||||||
|
("1d", 86400),
|
||||||
|
("1H30M", 5400), # rule 2
|
||||||
|
("2D", 172800),
|
||||||
|
(" 1h 30m ", 5400), # rule 4
|
||||||
|
("1d 2h 3m 4s", 93784),
|
||||||
|
("90", 90), # rule 5
|
||||||
|
(" 7 ", 7),
|
||||||
|
("1.5h", 5400), # rule 6
|
||||||
|
("1.5m", 90),
|
||||||
|
("0.25h", 900),
|
||||||
|
("1:30", 90), # rule 7
|
||||||
|
("1:02:03", 3723),
|
||||||
|
("90:00", 5400),
|
||||||
|
("0:05", 5),
|
||||||
|
(" 1:30 ", 90),
|
||||||
|
("0", 0), # rule 8
|
||||||
|
("0s", 0),
|
||||||
|
("0:00", 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_valid(text: str, seconds: int) -> None:
|
||||||
|
assert parse_duration(text) == seconds
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text",
|
||||||
|
[
|
||||||
|
"1x", # rule 1: not a unit
|
||||||
|
"h",
|
||||||
|
".5h", # not a number
|
||||||
|
"1.h",
|
||||||
|
"+2h",
|
||||||
|
"30m1h", # rule 3
|
||||||
|
"1h2h",
|
||||||
|
"1s1m",
|
||||||
|
"1 h", # rule 4
|
||||||
|
"1h 30", # rule 5
|
||||||
|
"30 1h",
|
||||||
|
"1h1.5m", # rule 6
|
||||||
|
"0.5s",
|
||||||
|
"0.01m",
|
||||||
|
"1.5",
|
||||||
|
"1:60", # rule 7
|
||||||
|
"1:2",
|
||||||
|
"1:02:60",
|
||||||
|
"1:02:03:04",
|
||||||
|
"1.5:30",
|
||||||
|
"1: 30",
|
||||||
|
"", # rule 8
|
||||||
|
" ",
|
||||||
|
"-5",
|
||||||
|
"-1h",
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_invalid(text: str) -> None:
|
||||||
|
with pytest.raises(ValueError) as caught:
|
||||||
|
parse_duration(text)
|
||||||
|
assert repr(text) in str(caught.value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_result_is_an_int() -> None:
|
||||||
|
assert type(parse_duration("1.5h")) is int
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_plan_asked_for_tests_and_they_pass() -> None:
|
||||||
|
# At least one valid and one invalid input for each of the eight rules.
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
run = subprocess.run(
|
||||||
|
[sys.executable, "-m", "pytest", "-q", "test_durations.py"],
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
check=False,
|
||||||
|
)
|
||||||
|
assert run.returncode == 0, run.stdout[-1500:]
|
||||||
|
summary = run.stdout.strip().splitlines()[-1]
|
||||||
|
assert int(summary.split()[0]) >= 16, summary
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
# parse_duration
|
||||||
|
|
||||||
|
`parse_duration(text: str) -> int` turns a human-written duration into a whole
|
||||||
|
number of seconds.
|
||||||
|
|
||||||
|
A *number* below is one or more digits, optionally followed by a dot and one
|
||||||
|
or more digits: `2`, `1.5`, `0.25`. Nothing else is a number: not `.5`, `1.`,
|
||||||
|
`+2` or `-2`.
|
||||||
|
|
||||||
|
## Rules
|
||||||
|
|
||||||
|
1. **Unit form.** One or more components, each a number immediately followed
|
||||||
|
by a unit: `d` (86400 seconds), `h` (3600), `m` (60), `s` (1).
|
||||||
|
`"2h"` is 7200, `"1h30m"` is 5400, `"1d2h3m4s"` is 93784.
|
||||||
|
2. **Case.** Units are case-insensitive: `"1H30M"` is 5400.
|
||||||
|
3. **Order.** Components appear in the order d, h, m, s, and each unit at
|
||||||
|
most once. `"30m1h"` and `"1h2h"` are invalid.
|
||||||
|
4. **Spaces.** Spaces are allowed between components and around the whole
|
||||||
|
text: `" 1h 30m "` is 5400. A space between a number and its unit is not
|
||||||
|
allowed: `"1 h"` is invalid.
|
||||||
|
5. **Bare number.** A number with no unit, and nothing else, is seconds:
|
||||||
|
`"90"` is 90. A bare number mixed with components is invalid: `"1h 30"`.
|
||||||
|
6. **Decimals.** Only the first component may have a decimal part:
|
||||||
|
`"1.5h"` is 5400, but `"1h1.5m"` is invalid. The result must be a whole
|
||||||
|
number of seconds: `"1.5m"` is 90, but `"0.5s"` and `"0.01m"` are invalid.
|
||||||
|
7. **Clock form.** `M:SS` or `H:MM:SS`: whole numbers separated by colons.
|
||||||
|
`"1:30"` is 90, `"1:02:03"` is 3723, `"90:00"` is 5400. Every field after
|
||||||
|
the first is exactly two digits and below 60: `"1:60"`, `"1:2"` and
|
||||||
|
`"1:02:60"` are invalid. More than three fields, decimals, and spaces inside
|
||||||
|
the clock (`"1: 30"`) are invalid; spaces around it are allowed.
|
||||||
|
8. **Zero and errors.** Zero is allowed: `"0"`, `"0s"` and `"0:00"` are all 0.
|
||||||
|
An empty string, only spaces, or a negative duration is invalid.
|
||||||
|
|
||||||
|
Every invalid input raises `ValueError`, and its message includes the input
|
||||||
|
exactly as given, in `repr` form: `invalid duration: '1h2h'`.
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
"""Human-written durations. See SPEC.md."""
|
||||||
|
|
||||||
|
|
||||||
|
def parse_duration(text: str) -> int:
|
||||||
|
"""A duration in whole seconds, as SPEC.md specifies."""
|
||||||
|
raise NotImplementedError
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
"""Human-written durations. See SPEC.md."""
|
||||||
|
|
||||||
|
import re
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
_NUMBER = r"\d+(?:\.\d+)?"
|
||||||
|
_UNIT_FORM = re.compile(
|
||||||
|
rf"(?:(?P<d>{_NUMBER})d)?\s*(?:(?P<h>{_NUMBER})h)?\s*"
|
||||||
|
rf"(?:(?P<m>{_NUMBER})m)?\s*(?:(?P<s>{_NUMBER})s)?",
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
_CLOCK = re.compile(r"\d+(?::[0-5]\d){1,2}")
|
||||||
|
_SECONDS = {"d": 86400, "h": 3600, "m": 60, "s": 1}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_duration(text: str) -> int:
|
||||||
|
"""A duration in whole seconds, as SPEC.md specifies."""
|
||||||
|
|
||||||
|
def invalid() -> ValueError:
|
||||||
|
return ValueError(f"invalid duration: {text!r}")
|
||||||
|
|
||||||
|
stripped = text.strip()
|
||||||
|
if not stripped:
|
||||||
|
raise invalid()
|
||||||
|
if _CLOCK.fullmatch(stripped):
|
||||||
|
total = 0
|
||||||
|
for field in stripped.split(":"):
|
||||||
|
total = total * 60 + int(field)
|
||||||
|
return total
|
||||||
|
if re.fullmatch(_NUMBER, stripped):
|
||||||
|
seconds = Decimal(stripped)
|
||||||
|
else:
|
||||||
|
match = _UNIT_FORM.fullmatch(stripped)
|
||||||
|
if not match or not any(match.groupdict().values()):
|
||||||
|
raise invalid()
|
||||||
|
present = [(unit, value) for unit, value in match.groupdict().items() if value]
|
||||||
|
if any("." in value for _unit, value in present[1:]):
|
||||||
|
raise invalid()
|
||||||
|
seconds = sum(Decimal(value) * _SECONDS[unit] for unit, value in present)
|
||||||
|
if seconds != seconds.to_integral_value():
|
||||||
|
raise invalid()
|
||||||
|
return int(seconds)
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import pytest
|
||||||
|
from durations import parse_duration
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("text", "seconds"),
|
||||||
|
[
|
||||||
|
("1h30m", 5400),
|
||||||
|
("1H30M", 5400),
|
||||||
|
(" 1h 30m ", 5400),
|
||||||
|
("90", 90),
|
||||||
|
("1.5h", 5400),
|
||||||
|
("1:02:03", 3723),
|
||||||
|
("0s", 0),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_valid(text: str, seconds: int) -> None:
|
||||||
|
assert parse_duration(text) == seconds
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"text", ["1x", "30m1h", "1 h", "1h 30", "1h1.5m", "0.5s", "1:60", "", "-5"]
|
||||||
|
)
|
||||||
|
def test_invalid(text: str) -> None:
|
||||||
|
with pytest.raises(ValueError, match="invalid duration"):
|
||||||
|
parse_duration(text)
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
title: Implement a parser from a written specification
|
||||||
|
check: python -m pytest -q .bench_check
|
||||||
|
plan: |
|
||||||
|
Milestone 1: implement `parse_duration` as `SPEC.md` specifies.
|
||||||
|
|
||||||
|
1. Read all of `SPEC.md`. It has eight numbered rules; every one applies.
|
||||||
|
2. In `durations.py`, replace the body of `parse_duration(text: str) -> int`
|
||||||
|
so that it follows every rule in `SPEC.md`, including the inputs each
|
||||||
|
rule says are invalid and the error message for them. Examples:
|
||||||
|
"1h30m" -> 5400; "90" -> 90; "1:02:03" -> 3723.
|
||||||
|
3. Create `test_durations.py` with pytest tests: at least one valid and one
|
||||||
|
invalid input for each of the eight rules.
|
||||||
|
4. Run `python -m pytest -q test_durations.py`, and fix `durations.py`
|
||||||
|
until every test passes.
|
||||||
|
5. Call finish.
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from invoicing.invoice import Invoice
|
||||||
|
from invoicing.lines import LineItem
|
||||||
|
from invoicing.report import render
|
||||||
|
|
||||||
|
|
||||||
|
def d(value: str) -> Decimal:
|
||||||
|
return Decimal(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_single_currency_total() -> None:
|
||||||
|
invoice = Invoice(
|
||||||
|
"USD", [LineItem("widget", d("19.99"), 3), LineItem("gadget", d("5.00"), 2)]
|
||||||
|
)
|
||||||
|
assert invoice.total() == d("69.97")
|
||||||
|
|
||||||
|
|
||||||
|
def test_twenty_percent_tax() -> None:
|
||||||
|
invoice = Invoice("USD", [LineItem("service", d("100.00"), 1)], tax_percent=d("20"))
|
||||||
|
assert invoice.total() == d("120.00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_currency_total() -> None:
|
||||||
|
# 100 USD is 91.95 EUR at the reference rates (1 EUR = 1.0875 USD).
|
||||||
|
invoice = Invoice(
|
||||||
|
"EUR",
|
||||||
|
[
|
||||||
|
LineItem("hosting", d("100.00"), 1, "USD"),
|
||||||
|
LineItem("support", d("50.00"), 1, "EUR"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert invoice.total() == d("141.95")
|
||||||
|
|
||||||
|
|
||||||
|
def test_report_shows_the_total() -> None:
|
||||||
|
invoice = Invoice("USD", [LineItem("widget", d("10.00"), 2)])
|
||||||
|
assert render(invoice).splitlines()[-1].split() == ["Total", "20.00", "USD"]
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
from decimal import Decimal
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from invoicing.currency import convert
|
||||||
|
from invoicing.discounts import Discount
|
||||||
|
from invoicing.invoice import Invoice
|
||||||
|
from invoicing.lines import LineItem
|
||||||
|
|
||||||
|
|
||||||
|
def d(value: str) -> Decimal:
|
||||||
|
return Decimal(value)
|
||||||
|
|
||||||
|
|
||||||
|
def test_the_visible_tests_were_not_changed() -> None:
|
||||||
|
original = (Path(__file__).parent / "original_test_invoice.py.txt").read_text()
|
||||||
|
assert Path("tests/test_invoice.py").read_text() == original
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("amount", "source", "target", "expected"),
|
||||||
|
[
|
||||||
|
("100.00", "USD", "EUR", "91.95"),
|
||||||
|
("100.00", "EUR", "USD", "108.75"),
|
||||||
|
("250.00", "GBP", "EUR", "290.44"),
|
||||||
|
("10000", "JPY", "USD", "67.12"),
|
||||||
|
("42.00", "USD", "USD", "42.00"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_conversion(amount: str, source: str, target: str, expected: str) -> None:
|
||||||
|
assert convert(d(amount), source, target) == d(expected)
|
||||||
|
|
||||||
|
|
||||||
|
def test_multi_currency_total() -> None:
|
||||||
|
invoice = Invoice(
|
||||||
|
"EUR",
|
||||||
|
[
|
||||||
|
LineItem("hosting", d("100.00"), 1, "USD"),
|
||||||
|
LineItem("support", d("50.00"), 1, "EUR"),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
assert invoice.total() == d("141.95")
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_discount() -> None:
|
||||||
|
# Same shared helper, no currency involved: 12.5% of 1000.00 is 125.00.
|
||||||
|
invoice = Invoice(
|
||||||
|
"USD", [LineItem("licence", d("1000.00"), 1)], discount=Discount(d("12.5"))
|
||||||
|
)
|
||||||
|
assert invoice.discount_total() == d("125.00")
|
||||||
|
assert invoice.total() == d("875.00")
|
||||||
|
|
||||||
|
|
||||||
|
def test_fractional_tax() -> None:
|
||||||
|
# 8.25% of 1000.00 is 82.50.
|
||||||
|
invoice = Invoice(
|
||||||
|
"USD", [LineItem("licence", d("1000.00"), 1)], tax_percent=d("8.25")
|
||||||
|
)
|
||||||
|
assert invoice.tax() == d("82.50")
|
||||||
|
|
||||||
|
|
||||||
|
def test_everything_together() -> None:
|
||||||
|
invoice = Invoice(
|
||||||
|
"GBP",
|
||||||
|
[
|
||||||
|
LineItem("seats", d("40.00"), 10, "EUR"),
|
||||||
|
LineItem("setup", d("99.00"), 1, "GBP"),
|
||||||
|
],
|
||||||
|
discount=Discount(d("7.5")),
|
||||||
|
tax_percent=d("17.5"),
|
||||||
|
)
|
||||||
|
# 400 EUR = 344.31 GBP; subtotal 443.31; discount 33.25; tax 71.76.
|
||||||
|
assert invoice.subtotal() == d("443.31")
|
||||||
|
assert invoice.discount_total() == d("33.25")
|
||||||
|
assert invoice.tax() == d("71.76")
|
||||||
|
assert invoice.total() == d("481.82")
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
"""Invoices with lines in several currencies, discounts and tax."""
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
"""Currency conversion at fixed reference rates."""
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from invoicing.money import apply_rate, money, to_cents
|
||||||
|
|
||||||
|
# Value of one unit of each currency in US dollars.
|
||||||
|
RATES_TO_USD: dict[str, Decimal] = {
|
||||||
|
"USD": money("1"),
|
||||||
|
"EUR": money("1.0875"),
|
||||||
|
"GBP": money("1.2634"),
|
||||||
|
"JPY": money("0.006712"),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def rate(from_currency: str, to_currency: str) -> Decimal:
|
||||||
|
"""How many units of `to_currency` one unit of `from_currency` buys."""
|
||||||
|
try:
|
||||||
|
return RATES_TO_USD[from_currency] / RATES_TO_USD[to_currency]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise ValueError(f"unknown currency: {exc.args[0]}") from None
|
||||||
|
|
||||||
|
|
||||||
|
def convert(amount: Decimal, from_currency: str, to_currency: str) -> Decimal:
|
||||||
|
if from_currency == to_currency:
|
||||||
|
return to_cents(amount)
|
||||||
|
return apply_rate(amount, rate(from_currency, to_currency))
|
||||||
Loaded 100 of 171 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user