TL;DR
- Claude Code subagent'ları, bir dosya aksini söylemedikçe Haiku 5.5'te çalışmaz. Yerleşik Explore agent'ı ve her özel subagent ana modeli devralır; subagent model değişkeni tek başına Explore'a dokunmaz.
- Sorunu iki dosya çözer: yalnızca kâşif için bir proje
Explore.mdagent dosyası (Haiku 5.5, effort low) ve her subagent'ı Haiku'ya zorlamak içinCLAUDE_CODE_SUBAGENT_MODEL_FORCEiçeren birsettings.jsonenv bloğu. - Bizim matrisimiz, tek bir gerçek repoda 182 çalıştırma: keşif, testler, lint, commit, refactor, inceleme ve özet; hepsi Haiku 5.5'te, hem low hem medium effort'ta gizli bir test ya da sabit bir kuralla kabul edildi (her birinde 70'in 70'i).
- Yedisini de effort low'da Haiku 5.5'e yönlendir. Karmaşık agentic kodlama için Sonnet 5.5 ya da Opus 5.5'i sakla; bu Anthropic'in kendi tavsiyesi.
- Gerçek plan sayacı neredeyse hiç oynamadı: 70 Haiku low çalıştırmasında beş saatlik pencere %21'den %21'e, 182 çalıştırmanın tamamında haftalık pencere %67.
/usagedökümü modelleri faturalama ağırlıklarıyla değil, yerel ağırlıklarla sıralar. - Tam model id'sini sabitle.
haikutakma adı Bedrock, Vertex ve Foundry'de Haiku 4.5'tir.
Kaynaklar ne diyor
Haiku 5.5'in fiyatı, 100k token'a kadar olan prompt'lar için milyon girdi token'ı başına $0,10, bunun üstünde $0,50; çıktıda $0,50 ve $2,50. Haiku 4.5 $1,00 ve $5,00, Sonnet 5.5 $2,00 ve $10,00 s1. Fiyat sayfası Opus 5.5'i milyon token başına $4 ve $20 olarak listeliyor ve Haiku 5.5'in, 1M bağlamlı diğer modellerin aksine prompt uzunluğuna göre fiyatlandığını not ediyor s20. Manşetteki %90, bir fiyat indirimi değil; önceki Haiku'nun prompt'u 100k token'ın altında kalan isteklerinin payı s1.
Anthropic, Haiku 5.5'i Opus 5.5 ve Sonnet 5.5'in yanında bir subagent olarak konumlandırıyor ve karmaşık agentic kodlama için Sonnet 5.5 ile Opus 5.5'in daha iyi seçim olmaya devam ettiğini, Haiku 5.5'in ise dar kapsamlı işler için olduğunu söylüyor s1. Terminal-Bench 4.0'da lansman yazısı Haiku 5.5 için %39,2, Sonnet 5.5 için %70,6 veriyor s1.
Varsayılan olarak hiçbir şey Haiku'ya gitmiyor. v2.1.198'den beri yerleşik Explore agent'ı Haiku'yu varsayılan almayı bırakıp ana modeli devralıyor s24. Bir subagent'ın modeli için belgelenmiş çözümleme sırası şöyle: çağrı başına parametre, tanımın frontmatter'ı, CLAUDE_CODE_SUBAGENT_MODEL, sonra ana konuşmanın modeli s2. CLAUDE_CODE_SUBAGENT_MODEL tek başına yerleşik Explore ve Plan subagent'larını değiştirmiyor s2. Explore adlı bir kullanıcı ya da proje subagent'ı yerleşik olanı geçersiz kılıyor ve dokümanlar Haiku modelli bir tane tanımlamayı öneriyor s2. CLAUDE_CODE_SUBAGENT_MODEL_FORCE=1, v2.1.257'den itibaren tek bir modeli her subagent'a uyguluyor s2 s24.
Maliyet dokümanı basit subagent görevleri için model: haiku öneriyor ve subagent isteklerinin yine aynı kullanımdan düştüğünü belirtiyor s3. Haftalık sayaçlar tüm modeller için bir tane, Fable dahilse onun için bir tane daha s22, ve diğer modeller aynı limitlerden çekiyor s23.
Claude Code'da Haiku 5.5, Opus 5.5, Sonnet 5.5 ve Fable için thinking kapatılamıyor: alwaysThinkingEnabled false ve MAX_THINKING_TOKENS=0 etkisiz. Effort low'dan max'a kadar gidiyor ve Haiku 5.5'in varsayılanı medium s21. haiku takma adı Anthropic API'sinde Haiku 5.5'e, Platform on AWS, Bedrock, Vertex ve Foundry'de Haiku 4.5'e çözülüyor s21.
Bir uygulayıcının Pro planı kurulumu (medium'da ana Opus 5.5, bir Sonnet 5.5 implementer, salt okunur bir Haiku 5.5 scoper, low'da bir Haiku 5.5 shipper) on oturumda 13 PR bildiriyor; her biri beş saatlik pencerenin yaklaşık %6'sı, tek PR'lık bir oturum için haftalık sayacın yaklaşık %1'i s4. Kendi çekincesi: tasarruf daha ucuz modelden değil, ana bağlamın küçük kalmasından geliyor, çünkü subagent token'larını Opus fiyatından hesaplamak yalnızca %5 ekliyor s4.
İki dosya
İki dosya var, birini ya da ikisini al. Proje agent dosyası .claude/agents/Explore.md Explore adını taşıdığı için yerleşik kâşifin yerini alır. Tam model id'si, effort low, yalnızca okuma ve arama araçları.
Explore.md
---
name: Explore
description: Read-only codebase explorer. Use it to find where something is defined or called and to summarize what it found, with file paths and function names.
model: claude-haiku-5-5
effort: low
tools: Read, Glob, Grep, Bash
---
You explore a codebase and report back. Search with Glob and Grep, read only the files you need, and never edit anything. Answer with file paths and function names, then stop.
.claude/settings.json settings dosyası env bloğunu taşır: subagent modeli ve onu her subagent'a uygulatan force bayrağı.
settings.json
{
"env": {
"CLAUDE_CODE_SUBAGENT_MODEL": "claude-haiku-5-5",
"CLAUDE_CODE_SUBAGENT_MODEL_FORCE": "1"
}
}
Hangisi kime:
- Her subagent ucuz olsun istiyorsan env bloğunu kullan. FORCE her agent'ı Haiku'ya çeker, bilerek Opus'a sabitlediklerini de.
- Yalnızca kâşif hareket etsin istiyorsan Explore dosyasını kullan. Diğer subagent'lar kendi modellerinde kalır.
CLAUDE_CODE_SUBAGENT_MODELdeğişkenini FORCE olmadan ayarlamak Explore için hiçbir şey yapmaz. Bizim iki headless çalıştırmamız, ana oturum Fable'da, "Explore agent'ını kullanarak sorgu önbelleğinin nerede temizlendiğini bul": hiçbir ayar yokken araç sonucu Opus 5.5'i, değişken yalnızca Haiku 5.5'e ayarlıyken yine Opus 5.5'i gösterdi. Explore dosyasıyla ya da iki env değişkeniyle Haiku 5.5 okundu.
Kontrol: /tasks her subagent'ın çalıştığı modeli ve effort'u gösterir (v2.1.243 ve sonrası s24), headless çalıştırmanın Agent araç sonucu da resolvedModel taşır. O satıra güven, dosyaya asla.
Takma ad tuzağı: haiku değil, claude-haiku-5-5 yaz. Takma ad yalnızca Anthropic API'sinde Haiku 5.5'tir; Bedrock, Vertex ve Foundry'de hâlâ Haiku 4.5 demek s21.
Daha eski bir açık rapor, bir subagent'ın frontmatter modelinin sessizce yok sayıldığını söylüyor s25; daha yeni bir rapor ise 2.1.277'den 2.1.290'a kadar 1.371 subagent'ın 1.149'unun ebeveynin Opus modelinde çalıştığını sayıyor s10. Claude Code 2.1.293'te 182 çalıştırmamızın hepsi yapılandırılan modeli taşıdı (sonuç tablosunda model_ok True). Yine de kontrol et.
Yönlendirme tablosu
18d73a1 sürümündeki tinydb üzerinde yedi gündelik görev, 226 test, headless claude -p, görev başına bir deterministik kabul ya da ret kontrolü. Her hücre: kabul edilen çalıştırmalar, çalıştırma başına ortalama liste fiyatı, ortalama duvar saati saniyesi. Haiku hücreleri görev başına 10 çalıştırma, Sonnet ve Opus hücreleri görev başına 3 çalıştırma.
| Görev | Haiku 5.5 medium | Haiku 5.5 low | Sonnet 5.5 | Opus 5.5 | Yönlendirme |
|---|---|---|---|---|---|
| explore | 10/10, $0,0047, 13,4 sn | 10/10, $0,0035, 10,7 sn | 3/3, $0,0679, 30,0 sn | 3/3, $0,1454, 18,0 sn | Haiku 5.5, effort low |
| tests | 10/10, $0,0033, 9,8 sn | 10/10, $0,0031, 9,0 sn | 3/3, $0,0531, 20,0 sn | 3/3, $0,0892, 14,3 sn | Haiku 5.5, effort low |
| lint | 10/10, $0,0048, 16,7 sn | 10/10, $0,0041, 14,0 sn | 3/3, $0,0693, 23,3 sn | 3/3, $0,1459, 27,7 sn | Haiku 5.5, effort low |
| commit | 10/10, $0,0017, 5,1 sn | 10/10, $0,0018, 4,7 sn | 3/3, $0,0313, 7,0 sn | 3/3, $0,0593, 5,7 sn | Haiku 5.5, effort low |
| refactor | 10/10, $0,0078, 26,3 sn | 10/10, $0,0052, 17,1 sn | 3/3, $0,1050, 30,3 sn | 3/3, $0,1675, 30,3 sn | Haiku 5.5, effort low |
| review | 10/10, $0,0040, 16,0 sn | 10/10, $0,0030, 10,6 sn | 3/3, $0,0500, 20,3 sn | 3/3, $0,0951, 17,3 sn | Haiku 5.5, effort low |
| summary | 10/10, $0,0037, 12,4 sn | 10/10, $0,0032, 8,4 sn | 3/3, $0,0347, 8,7 sn | 3/3, $0,0701, 7,7 sn | Haiku 5.5, effort low |
| Yedisi birden | 70/70, toplam $0,3006 | 70/70, toplam $0,2382 | 21/21, toplam $1,2339 | 21/21, toplam $2,3175 |
"refactor" görevi küçük özellik: tinydb.operations içine push ve pull güncelleme işlemlerini eklemek, gizli bir test dosyasıyla notlandı. Haiku low'da $0,0052, Opus'ta $0,1675 tuttu; süre 17,1 sn'ye karşı 30,3 sn.
Sonnet ya da Opus'ta kalan şey: Anthropic'in kendi sözleriyle karmaşık agentic kodlama s1. Bu yedi görev, küçük bir repoda her biri bir dakikadan kısa süren dar kapsamlı görevler.
Harness
Tabloyu üreten benchmark. Plan sayacı paylaşımlı olduğu için, hesabı başka hiçbir şey kullanmazken sade bir terminalden çalıştır. tinydb checkout'u, gizli testler ve referans yamalar pakette yok: harness'ı kendi reponun üzerine yönelt ve kendi kontrollerini yaz.
Çalıştırıcı ve sayaç:
README.md: harness'ın ne olduğu ve neyin dışarıda bırakıldığı.README.md
# routing bench
The benchmark harness behind the routing table: run.sh runs one cell headless and reads the real meters from the rate_limit_event, matrix.sh runs cells sequentially, check.py decides accept or reject per task, analyze.py writes the results table. tasks/ holds the seven prompts, inject/ the planted bugs. The tinydb checkout (commit 18d73a1), the hidden tests and the reference patches are not included: bring your own repo and checks.
- `run.sh`: tek bir hücre; sabitlenmiş modelle headless `claude -p`, taze bir çalışma kopyası, bir sayaç sonrası ölçümü, sonra kontrol. run.sh
#!/usr/bin/env bash
# One run: fresh tinydb work copy + task injection, headless claude -p, extract tokens/meter, check.
# Usage: run.sh <config> <task> <rep>
# config: a key of configs.json (haiku-med | haiku-low | sonnet | opus)
# task: a key of tasks.json (explore | tests | lint | commit | refactor | review | summary)
set -uo pipefail
CFG="$1"; TASK="$2"; REP="${3:-1}"
HERE="$(cd "$(dirname "$0")" && pwd)"
. "$HERE/env.sh"
PY="$HERE/.venv/bin/python"
MAX_TURNS="${BENCH_MAX_TURNS:-40}"
NAME="${TASK}-${CFG}-r${REP}"
OUT="$HERE/runs/$NAME"; W="$HERE/work/$NAME"
rm -rf "$OUT"; mkdir -p "$OUT"
"$HERE/prep.sh" "$TASK" "$W" > "$OUT/prep.txt" 2>&1 || { echo "$NAME: prep failed"; cat "$OUT/prep.txt"; exit 2; }
( cd "$W" && git diff HEAD > "$OUT/pre.diff" )
# Config args (model pinned by full id, optional --effort)
CARGS=()
while IFS= read -r a; do CARGS+=("$a"); done < <("$PY" -c "
import json; [print(a) for a in json.load(open('$HERE/configs.json'))['$CFG']['args']]")
[ ${#CARGS[@]} -gt 0 ] || { echo "bad config $CFG" >&2; exit 2; }
# Fixed allow-list for every cell, including Bash(ruff:*) for the lint task.
TOOLS="Read,Edit,Write,MultiEdit,Glob,Grep,Skill,TodoWrite,Bash(uv:*),Bash(python:*),Bash(python3:*),Bash(pytest:*),Bash(ruff:*),Bash(git status:*),Bash(git diff:*),Bash(git log:*),Bash(ls:*),Bash(cat:*),Bash(mkdir:*),Bash(mktemp:*),Bash(head:*),Bash(tail:*),Bash(grep:*),Bash(wc:*)"
cd "$W"
START=$(date +%s)
claude -p "$(cat "$HERE/tasks/$TASK.md")" "${CARGS[@]}" \
--output-format stream-json --verbose --max-turns "$MAX_TURNS" \
--permission-mode acceptEdits --allowedTools "$TOOLS" \
--setting-sources project --strict-mcp-config \
> "$OUT/stream.jsonl" 2> "$OUT/stderr.txt"
echo "$?" > "$OUT/exit_code.txt"
END=$(date +%s)
echo "$((END-START))" > "$OUT/wall_s.txt"
# After-meter: one cheap Haiku probe (its own stream is kept for audit).
"$HERE/probe.sh" "$OUT/probe.jsonl" > "$OUT/after.json" 2>/dev/null
# Diff of the agent's work (tracked + untracked) vs the post-injection state
# (index left untouched: the commit task's staged changes stay staged for the check)
git diff HEAD -- . ':(exclude).coverage' > "$OUT/post.diff"
git diff HEAD --numstat -- . ':(exclude).coverage' > "$OUT/numstat.txt"
git ls-files --others --exclude-standard | while IFS= read -r f; do
git diff --no-index -- /dev/null "$f" >> "$OUT/post.diff"
echo "$(wc -l < "$f" | tr -d ' ') 0 $f" >> "$OUT/numstat.txt"
done
"$PY" "$HERE/extract.py" "$OUT" "$CFG" "$TASK" "$REP" > "$OUT/result.json"
"$PY" "$HERE/check.py" "$TASK" "$W" "$OUT/result.json" > "$OUT/check.txt"; echo "$?" >> "$OUT/check.txt"
"$PY" "$HERE/extract.py" "$OUT" "$CFG" "$TASK" "$REP" --score
- `matrix.sh`: sıralı hücreleri birbiri ardına çalıştırır ve bir loga ekler. matrix.sh
#!/usr/bin/env bash
# Run an ordered list of "<config> <task> <rep>" lines SEQUENTIALLY (the plan meter is shared:
# never run two cells at once). Blank lines and #comments are skipped. Appends to matrix.log.
# Usage: matrix.sh [matrix-file] (default: matrix.txt)
# SKIP_DONE=1 matrix.sh ... skip cells whose runs/<task>-<config>-r<rep>/score.json exists (resume)
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"; cd "$HERE"
LIST="${1:-matrix.txt}"
# one matrix at a time: the meter is shared
mkdir "$HERE/.matrix.lock" 2>/dev/null || { echo "another matrix.sh is running (remove .matrix.lock if stale)" >&2; exit 1; }
trap 'rmdir "$HERE/.matrix.lock"' EXIT
echo "=== $(date '+%F %T') start $LIST" >> matrix.log
grep -vE '^\s*(#|$)' "$LIST" | while read -r CFG TASK REP; do
if [ "${SKIP_DONE:-0}" = 1 ] && [ -f "runs/${TASK}-${CFG}-r${REP}/score.json" ]; then
echo "$(date '+%F %T') skip ${TASK}-${CFG}-r${REP}" >> matrix.log; continue
fi
line=$(./run.sh "$CFG" "$TASK" "$REP" < /dev/null 2>&1 | tail -1)
echo "$(date '+%F %T') $line" | tee -a matrix.log
done
echo "=== $(date '+%F %T') done $LIST" >> matrix.log
- `gen_matrix.py`: sıralı matrisi yazar; her yapılandırma bitişik bir blok, ucuz yapılandırmalar önce, ikinci tur ters sırada. gen_matrix.py
#!/usr/bin/env python3
"""Write the ordered matrix file.
gen_matrix.py [--reps 3] [--passes 2] [--configs haiku-low,haiku-med,sonnet,opus] [--out matrix.txt]
Order: configs run as contiguous BLOCKS (all tasks of one rep set), cheap configs first; tasks are
rotated inside each block so no task always runs first. With --passes 2 the second pass runs the
configs in reverse order (ABBA), which interleaves configs over time without breaking the
contiguous segments that analyze.py needs to beat the 1% meter resolution.
"""
import argparse, json, os
HERE = os.path.dirname(os.path.abspath(__file__))
ap = argparse.ArgumentParser()
ap.add_argument("--reps", type=int, default=3, help="reps per config x task per pass")
ap.add_argument("--passes", type=int, default=2)
ap.add_argument("--configs", default="haiku-low,haiku-med,sonnet,opus")
ap.add_argument("--start-rep", type=int, default=1, help="first rep number (use 2 to keep the r1 smoke runs)")
ap.add_argument("--out", default=os.path.join(HERE, "matrix.txt"))
a = ap.parse_args()
tasks = list(json.load(open(os.path.join(HERE, "tasks.json"))))
cfgs = a.configs.split(",")
lines, rep = [], a.start_rep
for p in range(a.passes):
order = cfgs if p % 2 == 0 else list(reversed(cfgs))
lines.append(f"# pass {p + 1}: {' -> '.join(order)}")
for c in order:
lines.append(f"# block {c}")
for r in range(a.reps):
k = rep + r
rot = (k - 1) % len(tasks)
for t in tasks[rot:] + tasks[:rot]:
lines.append(f"{c} {t} {k}")
rep += a.reps
open(a.out, "w").write("\n".join(lines) + "\n")
n = sum(1 for l in lines if not l.startswith("#"))
print(f"{a.out}: {n} runs ({len(cfgs)} configs x {len(tasks)} tasks x {a.reps} reps x {a.passes} passes)")
- `prep.sh`: taze bir çalışma kopyası kurar ve görevin yerleştirilmiş değişikliğini uygular. prep.sh
#!/usr/bin/env bash
# Build a fresh work copy for one task: upstream tinydb, baseline commit, task injection.
# Usage: prep.sh <task> <workdir>
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
TASK="$1"; W="$2"
read -r INJ MODE < <("$HERE/.venv/bin/python" -c "
import json,sys; t=json.load(open('$HERE/tasks.json'))['$TASK']; print(t['inject'] or '-', t['mode'] or '-')")
rm -rf "$W"; mkdir -p "$(dirname "$W")"
cp -R "$HERE/upstream" "$W"
cd "$W"
/usr/bin/find . -name __pycache__ -type d -prune -exec rm -rf {} +
rm -f .coverage
git update-index -q --refresh || true
G() { git -c user.email=dev@example.com -c user.name=dev "$@"; }
# Injections that belong to the baseline are folded into the top upstream commit
# (amend, same message/author/date), so `git log` shows no extra "bug" commit.
if [ "$MODE" = "commit" ]; then
git apply "$HERE/$INJ"
git add -A
G commit -q --amend --no-edit --no-verify
fi
case "$MODE" in
stage) git apply --index "$HERE/$INJ" ;;
worktree) git apply "$HERE/$INJ" ;;
esac
- `env.sh`: her çağrının paylaştığı kimlik doğrulama ve ortam. env.sh
# Sourced by run.sh and probe.sh.
# Auth: an isolated CLAUDE_CONFIG_DIR is not logged in
# (`claude auth status` -> loggedIn false), so runs use the user's default config dir:
# CLAUDE_CONFIG_DIR is deliberately left unset.
unset CLAUDE_CONFIG_DIR
# Fresh work copies at the same path must not inherit memory written by an earlier rep.
export CLAUDE_CODE_DISABLE_AUTO_MEMORY=1
# The bench venv (pytest, ruff, mypy) comes first, so `python -m pytest` and `ruff` just work.
export PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/.venv/bin:$PATH"
- `probe.sh`: güncel plan sayacını JSON olarak yazdıran tek bir Haiku turu. probe.sh
#!/usr/bin/env bash
# Cheapest possible call: one Haiku turn answering "ok". Prints the current plan
# meter read from its rate_limit_event as one JSON line. The probe itself costs a
# few hundred tokens of Haiku (well under the 1% meter resolution).
# Usage: probe.sh [raw-stream-out-file]
set -uo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
[ -f "$HERE/env.sh" ] && . "$HERE/env.sh"
RAW="${1:-$(mktemp -t hrb-probe)}"; case "$RAW" in /*) ;; *) RAW="$PWD/$RAW";; esac
cd "$(mktemp -d -t hrb-probe-cwd)"
claude --model claude-haiku-5-5 -p "ok" --max-turns 1 --output-format stream-json --verbose \
--setting-sources project --strict-mcp-config > "$RAW" 2>/dev/null
"$HERE/.venv/bin/python" "$HERE/meter.py" "$RAW"
- `meter.py`: bir akışın `rate_limit_event` satırlarından beş saatlik ve yedi günlük pencereleri okur. meter.py
#!/usr/bin/env python3
"""Read a stream-json file and print the plan meter carried by its rate_limit_event lines.
Output (one JSON line): {"ts", "five_hour", "five_hour_resets_at", "seven_day",
"seven_day_resets_at", "n_events", "raw_first", "raw_last"}: utilizations are the
values of the FIRST event (the meter as the run began). `--last` uses the last event.
"""
import json, sys, time
def events(path):
out = []
for line in open(path, errors="replace"):
line = line.strip()
if not line.startswith("{"):
continue
try:
o = json.loads(line)
except Exception:
continue
if o.get("type") == "rate_limit_event":
out.append(o)
return out
def windows(ev):
info = ev.get("rate_limit_info") or {}
uw = info.get("unifiedWindows") or {}
def w(name):
x = uw.get(name) or {}
return x.get("utilization"), x.get("resetsAt")
fh, fhr = w("five_hour")
sd, sdr = w("seven_day")
return {"five_hour": fh, "five_hour_resets_at": fhr, "seven_day": sd, "seven_day_resets_at": sdr}
def meter(path, which="first"):
evs = events(path)
if not evs:
return {"ts": int(time.time()), "n_events": 0, "five_hour": None, "seven_day": None}
ev = evs[0] if which == "first" else evs[-1]
d = {"ts": int(time.time()), "n_events": len(evs)}
d.update(windows(ev))
d["raw_first"] = evs[0].get("rate_limit_info")
d["raw_last"] = evs[-1].get("rate_limit_info")
return d
if __name__ == "__main__":
which = "last" if "--last" in sys.argv else "first"
path = [a for a in sys.argv[1:] if not a.startswith("--")][0]
print(json.dumps(meter(path, which)))
- `extract.py`: bir çalıştırmanın akışından token'ları, maliyeti, turları ve sayacı bir skor kaydına çıkarır. extract.py
#!/usr/bin/env python3
"""Extract one run's numbers from runs/<name>/stream.jsonl.
extract.py <outdir> <config> <task> <rep> -> prints the stream's `result` line (result.json)
extract.py <outdir> <config> <task> <rep> --score -> writes score.json, prints a one-line summary
"""
import json, os, sys
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, HERE)
from meter import meter # noqa: E402
out, cfg, task, rep = sys.argv[1:5]
score_mode = "--score" in sys.argv
stream = os.path.join(out, "stream.jsonl")
lines = []
for raw in open(stream, errors="replace") if os.path.exists(stream) else []:
raw = raw.strip()
if raw.startswith("{"):
try:
lines.append(json.loads(raw))
except Exception:
pass
result = next((o for o in reversed(lines) if o.get("type") == "result"), None)
init = next((o for o in lines if o.get("type") == "system" and o.get("subtype") == "init"), {})
if not score_mode:
print(json.dumps(result or {"type": "result", "result": "", "missing": True}))
sys.exit(0)
def rd(name, default=None):
p = os.path.join(out, name)
try:
return open(p).read().strip()
except Exception:
return default
def jl(name):
try:
return json.loads(rd(name) or "null")
except Exception:
return None
cfgs = json.load(open(os.path.join(HERE, "configs.json")))
before = meter(stream, "first") if os.path.exists(stream) else None
after = jl("after.json")
r = result or {}
usage = r.get("usage") or {}
tok = {k: usage.get(k) or 0 for k in ("input_tokens", "cache_read_input_tokens", "cache_creation_input_tokens", "output_tokens")}
mu = r.get("modelUsage") or {}
assistant_models = sorted({(o.get("message") or {}).get("model") for o in lines
if o.get("type") == "assistant" and (o.get("message") or {}).get("model") not in (None, "<synthetic>")})
check_txt = (rd("check.txt", "") or "").split("\n")
verdict_line = check_txt[0] if check_txt else ""
numstat = [l.split("\t") for l in (rd("numstat.txt", "") or "").split("\n") if l.strip()]
added = sum(int(a) for a, d, f in numstat if a.isdigit())
deleted = sum(int(d) for a, d, f in numstat if d.isdigit())
changed_by_agent = (rd("pre.diff", "") or "") != (rd("post.diff", "") or "")
def strip_raw(m):
if not m:
return m
return {k: v for k, v in m.items() if not k.startswith("raw_")}
s = {
"run": os.path.basename(out), "config": cfg, "task": task, "rep": int(rep),
"expected_model": cfgs[cfg]["model"], "config_args": cfgs[cfg]["args"],
"init_model": init.get("model"), "assistant_models": assistant_models,
"model_usage_keys": sorted(mu.keys()),
"model_ok": init.get("model") == cfgs[cfg]["model"] and set(mu.keys()) <= {cfgs[cfg]["model"]} and bool(mu),
"claude_code_version": init.get("claude_code_version"),
"meter_before": strip_raw(before), "meter_after": strip_raw(after),
"n_rate_limit_events": (before or {}).get("n_events", 0),
"has_result_line": result is not None,
"subtype": r.get("subtype"), "is_error": r.get("is_error"), "stop_reason": r.get("stop_reason"),
"num_turns": r.get("num_turns"), "duration_ms": r.get("duration_ms"), "duration_api_ms": r.get("duration_api_ms"),
"total_cost_usd": r.get("total_cost_usd"), "usage": usage, "tokens": tok, "modelUsage": mu,
"wall_s": int(rd("wall_s.txt", "0") or 0), "exit_code": rd("exit_code.txt"),
"final_text": (r.get("result") or "")[-2000:],
"diff": {"files": len(numstat), "added": added, "deleted": deleted, "changed_by_agent": changed_by_agent},
"check": {"verdict": verdict_line.split(" ")[0] if verdict_line else None, "reason": verdict_line[5:] if verdict_line else None},
}
s["accepted"] = s["check"]["verdict"] == "PASS"
json.dump(s, open(os.path.join(out, "score.json"), "w"), indent=2)
mb, ma = s["meter_before"] or {}, s["meter_after"] or {}
print(f"{s['run']}: {s['check']['verdict']} turns={s['num_turns']} cost=${(s['total_cost_usd'] or 0):.3f} "
f"in={tok['input_tokens']} cr={tok['cache_read_input_tokens']} cc={tok['cache_creation_input_tokens']} out={tok['output_tokens']} "
f"wall={s['wall_s']}s 5h {mb.get('five_hour')}->{ma.get('five_hour')} 7d {mb.get('seven_day')}->{ma.get('seven_day')} "
f"model_ok={s['model_ok']} | {s['check']['reason']}")
- `check.py`: görev başına kabul ya da ret kararı. check.py
#!/usr/bin/env python3
"""Deterministic accept/reject check for one run.
Usage: check.py <task> <workdir> <result.json>
result.json = the `result` line of the run's stream (only its "result" text is used).
Prints `PASS <reason>` or `FAIL <reason>`; exit 0 on PASS, 1 on FAIL.
"""
import json, os, re, subprocess, sys
HERE = os.path.dirname(os.path.abspath(__file__))
PY = os.path.join(HERE, ".venv", "bin", "python")
RUFF = os.path.join(HERE, ".venv", "bin", "ruff")
def final_text(path):
try:
r = json.load(open(path))
except Exception:
return ""
return r.get("result") or "" if isinstance(r, dict) else ""
def sh(args, cwd, env=None):
p = subprocess.run(args, cwd=cwd, capture_output=True, text=True, env=env)
return p.returncode, p.stdout + p.stderr
def suite(wd):
rc, out = sh([PY, "-m", "pytest", "-q", "-p", "no:cacheprovider", "-o", "addopts=", "tests"], wd)
m = re.search(r"(\d+) passed", out)
f = re.search(r"(\d+) failed", out)
e = re.search(r"(\d+) error", out)
return rc == 0, f"suite {m.group(1) if m else 0} passed, {f.group(1) if f else 0} failed, {e.group(1) if e else 0} errors"
def git_diff(wd, *paths):
# tracked changes vs the baseline commit + untracked files under paths
_, d = sh(["git", "diff", "HEAD", "--", *paths], wd)
_, u = sh(["git", "ls-files", "--others", "--exclude-standard", "--", *paths], wd)
return d.strip(), u.strip()
def words(s):
return re.findall(r"[A-Za-z0-9_']+", s)
def strip_fences(s):
lines = s.strip().split("\n")
if lines and lines[0].strip().startswith("```"):
lines = lines[1:]
if lines and lines[-1].strip().startswith("```"):
lines = lines[:-1]
return "\n".join(lines).strip()
def c_explore(wd, txt):
t = txt.lower()
miss = []
if "table.py" not in t:
miss.append("table.py")
if "clear_cache" not in t and "_query_cache" not in t:
miss.append("clear_cache/_query_cache")
names = ["insert_multiple", "update_multiple", "insert", "update", "upsert", "remove", "truncate"]
found = {n for n in names if re.search(r"(?<![a-z_])" + n + r"(?![a-z_])", t)}
if len(found) < 3:
miss.append(f"only {len(found)} mutating methods {sorted(found)}")
return (not miss), ("missing " + ", ".join(miss)) if miss else f"table.py + cache method + {len(found)} methods {sorted(found)}"
def c_tests(wd, txt):
ok, s = suite(wd)
d, u = git_diff(wd, "tests")
if d or u:
return False, f"tests/ modified; {s}"
return ok, s + "; tests/ untouched"
def c_lint(wd, txt):
rc, out = sh([RUFF, "check", "tinydb", "--output-format", "concise", "--no-cache"], wd)
cfg_d, _ = git_diff(wd, "ruff.toml", "pyproject.toml")
d, _ = git_diff(wd, "tinydb")
noqa = [l for l in d.split("\n") if l.startswith("+") and "noqa" in l.lower()]
ok, s = suite(wd)
why = []
if rc != 0:
found = [l for l in out.split("\n") if l.startswith("Found ")]
why.append("ruff: " + (found[0] if found else out.strip().split("\n")[-1]))
if cfg_d:
why.append("lint config changed")
if noqa:
why.append(f"{len(noqa)} noqa added")
if not ok:
why.append(s)
return (not why), ("; ".join(why) if why else f"ruff clean; {s}")
DIFF_LINE = re.compile(r"^(diff --git |index [0-9a-f]{6,}\.\.|@@ |\+\+\+ |--- a/|--- /dev/null|[+-] {3,}\S|[+-](def|class|import|from|return) )")
def c_commit(wd, txt):
msg = strip_fences(txt)
if not msg:
return False, "empty answer"
first = msg.split("\n")[0].strip()
why = []
if len(first) > 72:
why.append(f"first line {len(first)} chars")
if "push" not in first.lower():
why.append("no 'push' in first line")
if "pull" not in first.lower():
why.append("no 'pull' in first line")
dl = [l for l in msg.split("\n") if DIFF_LINE.match(l)]
if dl:
why.append(f"{len(dl)} diff lines")
return (not why), ("; ".join(why) if why else f"first line {len(first)} chars: {first!r}")
def c_refactor(wd, txt):
env = dict(os.environ, TINYDB_TREE=wd)
rc, out = sh([PY, "-m", "pytest", "-q", "-c", os.path.join(HERE, "hidden", "pytest.ini"),
"--rootdir", os.path.join(HERE, "hidden"), os.path.join(HERE, "hidden", "test_T1.py")], wd, env)
m = re.search(r"(\d+) passed", out)
hp = int(m.group(1)) if m else 0
ok, s = suite(wd)
return (hp == 5 and ok), f"hidden {hp}/5; {s}"
REVIEW_PATTERNS = [
r"limit\s*=?=\s*0\b",
r"limit\s+(of\s+)?(0|zero)\b",
r"\b(0|zero)\s+limit",
r"zero[- ]limit",
r"limit.{0,150}\b(falsy|falsey|truthy|truthiness)",
r"\b(falsy|falsey|truthy|truthiness)\b.{0,150}limit",
r"if limit:.{0,150}\b(0|zero)\b",
r"\b(0|zero)\b.{0,150}if limit:",
]
def c_review(wd, txt):
t = txt.lower().replace("`", "")
for p in REVIEW_PATTERNS:
m = re.search(p, t, re.S)
if m:
return True, f"matched {p!r}: {m.group(0)[:80]!r}"
return False, "planted limit=0 defect not mentioned"
def c_summary(wd, txt):
n = len(words(txt))
t = txt.lower()
mods = [m for m in ["database", "table", "queries", "storages", "middlewares", "operations"] if m in t]
why = []
if n > 170:
why.append(f"{n} words > 170")
if len(mods) < 4:
why.append(f"only {len(mods)} modules {mods}")
return (not why), ("; ".join(why) if why else f"{n} words, modules {mods}")
CHECKS = {"explore": c_explore, "tests": c_tests, "lint": c_lint, "commit": c_commit,
"refactor": c_refactor, "review": c_review, "summary": c_summary}
if __name__ == "__main__":
task, wd, res = sys.argv[1], os.path.abspath(sys.argv[2]), sys.argv[3]
ok, why = CHECKS[task](wd, final_text(res))
print(("PASS " if ok else "FAIL ") + why)
sys.exit(0 if ok else 1)
- `analyze.py`: çalıştırmaları görev başına ve yapılandırma başına tablolara toplar. analyze.py
#!/usr/bin/env python3
"""Aggregate runs/*/score.json into results.json and results.md.
Per config x task: accept rate, mean tokens, mean cost, mean turns, mean wall.
Per config: measured plan-meter deltas (five_hour, seven_day, in percentage points), computed two ways:
- per_run_sum: sum over the config's runs of (after - before). Quantized at 1% per run, so
runs far below 1% mostly read 0: a lower-bound-ish, very noisy number.
- segments: runs sorted by start time are cut into maximal contiguous segments of the same
config; each segment contributes (after of its last run - before of its first run). This
telescopes the quantization: one +-1 pt error per segment instead of per run. PREFERRED.
Segments/runs whose window resetsAt changed between before and after are excluded for that window.
Plus the /usage-style local heuristic: weighted = cached*1 + uncached*10 + cache_create*12.5 + output*50,
times the model tier (haiku 1 / sonnet 3 / opus 5). The probe's own Haiku tokens after each run are
counted as overhead in the heuristic (they also move the meter).
Usage: analyze.py [--runs-dir runs] [--out-prefix results]
"""
import glob, json, os, statistics, sys
HERE = os.path.dirname(os.path.abspath(__file__))
RUNS = os.path.join(HERE, sys.argv[sys.argv.index("--runs-dir") + 1]) if "--runs-dir" in sys.argv else os.path.join(HERE, "runs")
OUTP = sys.argv[sys.argv.index("--out-prefix") + 1] if "--out-prefix" in sys.argv else "results"
CFGS = json.load(open(os.path.join(HERE, "configs.json")))
TASKS = list(json.load(open(os.path.join(HERE, "tasks.json"))).keys())
W = {"cache_read_input_tokens": 1, "input_tokens": 10, "cache_creation_input_tokens": 12.5, "output_tokens": 50}
TIER = {c: v["tier"] for c, v in CFGS.items()}
def weighted(tok, tier):
raw = sum((tok.get(k) or 0) * w for k, w in W.items())
return raw, raw * tier
MU = {"cacheReadInputTokens": "cache_read_input_tokens", "inputTokens": "input_tokens",
"cacheCreationInputTokens": "cache_creation_input_tokens", "outputTokens": "output_tokens"}
def model_tier(name, fallback):
n = (name or "").lower()
return 5 if "opus" in n else 3 if "sonnet" in n else 1 if "haiku" in n else fallback
def heuristic_run(s):
"""Tiered heuristic per model actually used (modelUsage), so helper calls on another
model are weighted with their own tier; falls back to `usage` x config tier."""
mu = s.get("modelUsage") or {}
if not mu:
return weighted(s["tokens"], TIER[s["config"]])[1]
return sum(weighted({MU[k]: v.get(k) or 0 for k in MU}, model_tier(m, TIER[s["config"]]))[1] for m, v in mu.items())
def probe_tokens(run_dir):
p = os.path.join(run_dir, "probe.jsonl")
if not os.path.exists(p):
return {}
for line in reversed(open(p, errors="replace").read().strip().split("\n")):
try:
o = json.loads(line)
except Exception:
continue
if o.get("type") == "result":
u = o.get("usage") or {}
return {k: u.get(k) or 0 for k in W}
return {}
def mean(xs):
xs = [x for x in xs if x is not None]
return statistics.mean(xs) if xs else None
def window_delta(b, a, w):
if not b or not a or b.get(w) is None or a.get(w) is None:
return None, "missing"
if b.get(w + "_resets_at") != a.get(w + "_resets_at"):
return None, "reset"
return round((a[w] - b[w]) * 100, 2), "ok"
scores = []
for p in sorted(glob.glob(os.path.join(RUNS, "*", "score.json"))):
s = json.load(open(p))
if s.get("config") not in CFGS:
continue
s["_dir"] = os.path.dirname(p)
s["_start"] = (s.get("meter_before") or {}).get("ts") or os.path.getmtime(os.path.join(s["_dir"], "stream.jsonl"))
s["_probe_tok"] = probe_tokens(s["_dir"])
scores.append(s)
# meter_before.ts is written at extraction time; order by stream file creation instead
for s in scores:
st = os.stat(os.path.join(s["_dir"], "stream.jsonl"))
s["_start"] = getattr(st, "st_birthtime", st.st_mtime)
scores.sort(key=lambda s: s["_start"])
cells, per_cfg = {}, {}
for s in scores:
cells.setdefault((s["config"], s["task"]), []).append(s)
per_cfg.setdefault(s["config"], []).append(s)
cell_rows = []
for (c, t), ss in sorted(cells.items(), key=lambda kv: (list(CFGS).index(kv[0][0]), TASKS.index(kv[0][1]) if kv[0][1] in TASKS else 99)):
row = {"config": c, "task": t, "n": len(ss),
"accept_rate": sum(1 for s in ss if s.get("accepted")) / len(ss),
"mean_turns": mean([s.get("num_turns") for s in ss]),
"mean_cost_usd": mean([s.get("total_cost_usd") for s in ss]),
"mean_wall_s": mean([s.get("wall_s") for s in ss]),
"mean_tokens": {k: mean([s["tokens"].get(k) for s in ss]) for k in W},
"mean_weighted_heuristic": mean([heuristic_run(s) for s in ss]),
"model_ok_all": all(s.get("model_ok") for s in ss)}
cell_rows.append(row)
# contiguous segments in global start order
segments, cur = [], None
for s in scores:
if cur and cur["config"] == s["config"]:
cur["runs"].append(s)
else:
cur = {"config": s["config"], "runs": [s]}
segments.append(cur)
cfg_rows = []
for c, ss in per_cfg.items():
tok_sum = {k: sum(s["tokens"].get(k) or 0 for s in ss) for k in W}
probe_sum = {k: sum(s["_probe_tok"].get(k, 0) for s in ss) for k in W}
raw_w, _ = weighted(tok_sum, TIER[c])
heur = sum(heuristic_run(s) for s in ss)
probe_raw, probe_heur = weighted(probe_sum, 1)
row = {"config": c, "runs": len(ss), "accept_rate": sum(1 for s in ss if s.get("accepted")) / len(ss),
"tokens_sum": tok_sum, "probe_tokens_sum": probe_sum,
"cost_sum_usd": round(sum(s.get("total_cost_usd") or 0 for s in ss), 4),
"weighted_raw_M": raw_w / 1e6, "heuristic_M": (heur + probe_heur) / 1e6}
for w in ("five_hour", "seven_day"):
per_run, excl = 0.0, 0
for s in ss:
d, why = window_delta(s.get("meter_before"), s.get("meter_after"), w)
if d is None:
excl += 1
else:
per_run += d
seg_sum, seg_n, seg_excl = 0.0, 0, 0
for g in segments:
if g["config"] != c:
continue
d, why = window_delta(g["runs"][0].get("meter_before"), g["runs"][-1].get("meter_after"), w)
if d is None:
seg_excl += 1
else:
seg_sum += d; seg_n += 1
row[w] = {"per_run_sum_pts": round(per_run, 2), "per_run_excluded": excl,
"segments_sum_pts": round(seg_sum, 2), "segments": seg_n, "segments_excluded": seg_excl,
"quantization_error_pts": f"+-{seg_n}",
"pts_per_M_heuristic": round(seg_sum / row["heuristic_M"], 4) if row["heuristic_M"] else None,
"pts_per_M_weighted_raw": round(seg_sum / row["weighted_raw_M"], 4) if row["weighted_raw_M"] else None}
cfg_rows.append(row)
cfg_rows.sort(key=lambda r: list(CFGS).index(r["config"]))
out = {"generated_from": len(scores), "cells": cell_rows, "configs": cfg_rows,
"segments": [{"config": g["config"], "runs": [s["run"] for s in g["runs"]],
"before": g["runs"][0].get("meter_before"), "after": g["runs"][-1].get("meter_after")} for g in segments],
"heuristic_weights": {"tokens": W, "tier": TIER}}
json.dump(out, open(os.path.join(HERE, OUTP + ".json"), "w"), indent=2, default=str)
f = lambda x, d=0: "-" if x is None else (f"{x:,.{d}f}")
md = ["# Results", "", f"{len(scores)} runs. Meter deltas in percentage points of the plan window (1-pt resolution).", "",
"## Per config x task", "",
"| config | task | n | accept | turns | input | cache read | cache create | output | cost $ | wall s | heuristic (M) | model ok |",
"|---|---|---|---|---|---|---|---|---|---|---|---|---|"]
for r in cell_rows:
t = r["mean_tokens"]
md.append(f"| {r['config']} | {r['task']} | {r['n']} | {r['accept_rate']:.0%} | {f(r['mean_turns'],1)} | {f(t['input_tokens'])} | "
f"{f(t['cache_read_input_tokens'])} | {f(t['cache_creation_input_tokens'])} | {f(t['output_tokens'])} | "
f"{f(r['mean_cost_usd'],4)} | {f(r['mean_wall_s'],1)} | {f(r['mean_weighted_heuristic']/1e6,2)} | {r['model_ok_all']} |")
md += ["", "## Per config: measured meter vs local heuristic", "",
"| config | runs | accept | cost $ | heuristic (M units) | 5h pts (segments) | 5h pts (per-run sum) | 7d pts (segments) | segments | 5h pts / M heuristic | 7d pts / M heuristic |",
"|---|---|---|---|---|---|---|---|---|---|---|"]
for r in cfg_rows:
fh, sd = r["five_hour"], r["seven_day"]
md.append(f"| {r['config']} | {r['runs']} | {r['accept_rate']:.0%} | {r['cost_sum_usd']} | {r['heuristic_M']:.2f} | "
f"{fh['segments_sum_pts']} (excl {fh['segments_excluded']}) | {fh['per_run_sum_pts']} | {sd['segments_sum_pts']} (excl {sd['segments_excluded']}) | "
f"{fh['segments']} | {fh['pts_per_M_heuristic']} | {sd['pts_per_M_heuristic']} |")
md += ["", "If the /usage heuristic matched the real meter, `pts / M heuristic` would be the same for every config.",
"Each segment carries up to +-1 pt of quantization error; trust a config's number only when its segment sum is several points.", ""]
open(os.path.join(HERE, OUTP + ".md"), "w").write("\n".join(md))
print("\n".join(md))
Yapılandırma:
configs.json: tam model id'li dört işçi, low bloğu için--effort low.configs.json
{
"haiku-med": {"args": ["--model", "claude-haiku-5-5"], "model": "claude-haiku-5-5", "tier": 1, "note": "default effort"},
"haiku-low": {"args": ["--model", "claude-haiku-5-5", "--effort", "low"], "model": "claude-haiku-5-5", "tier": 1},
"sonnet": {"args": ["--model", "claude-sonnet-5-5"], "model": "claude-sonnet-5-5", "tier": 3, "note": "default effort"},
"opus": {"args": ["--model", "claude-opus-5-5"], "model": "claude-opus-5-5", "tier": 5, "note": "default effort"}
}
- `tasks.json`: görev başına, yerleştirilen değişiklik ve nasıl uygulandığı. tasks.json
{
"explore": {"inject": null, "mode": null, "kind": "answer"},
"tests": {"inject": "inject/tests.patch", "mode": "commit", "kind": "code"},
"lint": {"inject": "inject/lint.patch", "mode": "commit", "kind": "code"},
"commit": {"inject": "reference/T1.patch", "mode": "stage", "kind": "answer"},
"refactor": {"inject": null, "mode": null, "kind": "code"},
"review": {"inject": "inject/review.patch", "mode": "worktree", "kind": "answer"},
"summary": {"inject": null, "mode": null, "kind": "answer"}
}
- `ruff.toml`: lint görevi için sabitlenmiş lint yapılandırması. ruff.toml
# Pinned lint config for the `lint` task: a small explicit rule set.
# Upstream tinydb @ 18d73a1 is clean under it (the two per-file ignores cover
# pre-existing F401s: the __version__ re-export and middlewares' Optional).
[lint]
select = ["F401", "F541", "F841", "E722"]
[lint.per-file-ignores]
"tinydb/__init__.py" = ["F401"]
"tinydb/middlewares.py" = ["F401"]
Yerleştirilen değişiklikler:
inject/tests.patch:Query.__ge__içindeki bir karşılaştırmayı çevirir, böylece bir test başarısız olur.inject/tests.patch
diff --git a/tinydb/queries.py b/tinydb/queries.py
index cdab9fb..db00a2a 100644
--- a/tinydb/queries.py
+++ b/tinydb/queries.py
@@ -322,7 +322,7 @@ class Query(QueryInstance):
:param rhs: The value to compare against
"""
return self._generate_test(
- lambda value: value >= rhs,
+ lambda value: value > rhs,
('>=', self._path, rhs)
)
- `inject/lint.patch`: sabitlenmiş lint yapılandırmasını ve beş bulguyu ekler. inject/lint.patch
diff --git a/ruff.toml b/ruff.toml
new file mode 100644
--- /dev/null
+++ b/ruff.toml
@@ -0,0 +1,6 @@
+[lint]
+select = ["F401", "F541", "F841", "E722"]
+
+[lint.per-file-ignores]
+"tinydb/__init__.py" = ["F401"]
+"tinydb/middlewares.py" = ["F401"]
diff --git a/tinydb/operations.py b/tinydb/operations.py
index 7101a23..f8541fe 100644
--- a/tinydb/operations.py
+++ b/tinydb/operations.py
@@ -10,6 +10,7 @@ This would delete the ``foo`` field from all documents where ``foo`` equals 2.
from collections.abc import Callable, MutableMapping
+import sys
from typing import Any, Union
@@ -18,7 +19,7 @@ def delete(field: str) -> Callable[[MutableMapping], None]:
Delete a given field from the document.
"""
def transform(doc: MutableMapping):
- del doc[field]
+ removed = doc.pop(field)
return transform
diff --git a/tinydb/queries.py b/tinydb/queries.py
index cdab9fb..489a2be 100644
--- a/tinydb/queries.py
+++ b/tinydb/queries.py
@@ -227,7 +227,7 @@ class Query(QueryInstance):
:return: A :class:`~tinydb.queries.QueryInstance` object
"""
if not self._path and not allow_empty_path:
- raise ValueError('Query has no path')
+ raise ValueError(f'Query has no path')
def runner(value):
try:
diff --git a/tinydb/table.py b/tinydb/table.py
index f4027bf..eb8058c 100644
--- a/tinydb/table.py
+++ b/tinydb/table.py
@@ -818,7 +818,7 @@ class Table:
try:
raw_table = tables[self.name]
- except KeyError:
+ except:
# The table does not exist yet, so it is empty
raw_table = {}
diff --git a/tinydb/utils.py b/tinydb/utils.py
index 43cad4e..07114d5 100644
--- a/tinydb/utils.py
+++ b/tinydb/utils.py
@@ -2,6 +2,7 @@
Utility functions.
"""
+import os
from collections import OrderedDict, abc
from collections.abc import Iterator
from typing import TypeVar, Generic, Union, Optional, TYPE_CHECKING, cast
- `inject/review.patch`: incelenecek bir `if limit:` kenar durumu içeren bir özellik diff'i. inject/review.patch
diff --git a/tinydb/table.py b/tinydb/table.py
index f4027bf..4e06ba1 100644
--- a/tinydb/table.py
+++ b/tinydb/table.py
@@ -244,10 +244,21 @@ class Table:
return list(iter(self))
- def search(self, cond: QueryLike) -> list[Document]:
+ def search(
+ self,
+ cond: QueryLike,
+ *,
+ sort_by: Optional[str] = None,
+ reverse: bool = False,
+ limit: Optional[int] = None,
+ ) -> list[Document]:
"""
Search for all documents matching a 'where' cond.
+ Results can optionally be sorted by a field (``sort_by``, descending
+ with ``reverse=True``; documents without the field always come last)
+ and truncated to the first ``limit`` results after sorting.
+
Note: a repeated search with the same ``cond`` is served from an
internal cache. The returned list is a fresh copy, but its
documents are the same objects held by the cache -- treat them as
@@ -255,8 +266,34 @@ class Table:
what a later identical search returns.
:param cond: the condition to check against
+ :param sort_by: optional field name to sort the results by
+ :param reverse: sort in descending order
+ :param limit: optional maximum number of results
:returns: list of matching documents
"""
+ if limit is not None and limit < 0:
+ raise ValueError('limit must not be negative')
+
+ # The cache always holds the plain, unsorted, untruncated result;
+ # _search returns a fresh list we are free to reorder/slice
+ docs = self._search(cond)
+
+ if sort_by is not None:
+ field = sort_by
+ present = [doc for doc in docs if field in doc]
+ missing = [doc for doc in docs if field not in doc]
+ present.sort(key=lambda doc: doc[field], reverse=reverse)
+ docs = present + missing
+
+ if limit:
+ docs = docs[:limit]
+
+ return docs
+
+ def _search(self, cond: QueryLike) -> list[Document]:
+ """
+ Plain search backed by the query cache. Returns a fresh list.
+ """
# First, we check the query cache to see if it has results for this
# query
Prompt'lar ve gerçek referanslar:
tasks/explore.md: sorgu önbelleğinin nerede geçersiz kılındığı ve bunu hangi public metotların tetiklediği.tasks/explore.md
Where does TinyDB invalidate its query cache, and which public methods trigger it? Give file paths and function names.
- `tasks/tests.md`: pytest başarısız oluyor, kütüphane hatasını düzelt, testlere dokunma. tasks/tests.md
pytest fails on this repo. Find the bug in the library code and fix it. Do not change the tests.
- `tasks/lint.md`: davranışı değiştirmeden her ruff bulgusunu düzelt. tasks/lint.md
ruff reports issues in tinydb/ (run `ruff check tinydb`). Fix them all without changing behaviour.
- `tasks/commit.md`: staged değişiklikler için commit mesajını yaz. tasks/commit.md
Write the commit message for the staged changes. Output only the message.
- `tasks/refactor.md`: push ve pull işlemleri özelliği. tasks/refactor.md
I keep writing little lambdas to append things to list fields, so can you add two update operations to `tinydb.operations`, next to `increment`, `delete` and friends? `push(field, value)` should append `value` to the list stored in `field`, keeping the existing order, and `pull(field, value)` should remove every occurrence of `value` from that list, not just the first one. If a document doesn't have the field yet, `push` should create it as a one-element list, while `pull` should just leave that document alone without raising. If the field exists but isn't a list, `push` has to raise a `TypeError` instead of doing something weird with it. They should be used exactly like the existing operations, e.g. `db.update(push('tags', 'python'), User.name == 'john')`.
- `tasks/review.md`: commit edilmemiş değişiklikleri hatalar ve kenar durumlar için incele. tasks/review.md
Review the uncommitted changes for bugs and edge cases. List findings only.
- `tasks/summary.md`: reponun organizasyonunu en fazla 150 kelimeyle özetle. tasks/summary.md
Summarize how TinyDB is organised for a new contributor in at most 150 words: the main modules and what each owns.
- `tasks/explore.truth.md`: explore kontrolü için gerçek referans ve eşleştirme kuralı. tasks/explore.truth.md
# explore: ground truth (tinydb @ 18d73a1)
- The query cache is `Table._query_cache`, an `LRUCache` (tinydb/utils.py) created in
`Table.__init__` (tinydb/table.py, `query_cache_class = LRUCache`, capacity
`default_query_cache_capacity = 10`). It is filled in `Table.search`.
- Invalidation happens in one place: `Table._update_table()` (tinydb/table.py) ends with
`self.clear_cache()`, and `Table.clear_cache()` does `self._query_cache.clear()`.
- Every public write goes through `_update_table`, so these trigger it:
`insert`, `insert_multiple`, `update` (all three branches: callable/fields, by doc_ids, by cond),
`update_multiple`, `upsert` (via `update` or `insert`), `remove` (by cond or doc_ids), `truncate`.
`Table.__init__(persist_empty=True)` also calls it. `clear_cache()` itself is public.
`TinyDB` (tinydb/database.py) forwards these via `__getattr__` to the default table;
`drop_table`/`drop_tables` do not clear a cache, they forget the `Table` instances (`self._tables`).
## Check rule (check.py explore)
PASS iff the final answer
1. contains `table.py`, AND
2. contains `clear_cache` or `_query_cache`, AND
3. names at least 3 distinct methods among
insert, insert_multiple, update, update_multiple, upsert, remove, truncate (word match, case-insensitive).
tasks/explore.truth.md on GitHub
- `tasks/review.truth.md`: review kontrolü için gerçek referans ve eşleştirme kuralı.tasks/review.truth.md
# review: planted defect and matching rule
The uncommitted change is the reference T4 patch (`Table.search(cond, *, sort_by, reverse, limit)`)
with one defect: the truncation guard is `if limit:` instead of `if limit is not None:`, so
`search(cond, limit=0)` returns ALL matching documents instead of `[]` (0 is falsy).
Everything else in the change is correct (missing-field docs last in both directions,
negative limit -> ValueError, cache keeps the plain result).
## Check rule (check.py review)
PASS iff the final answer (case-insensitive) matches at least one of:
- `limit\s*=?=\s*0` (e.g. "limit=0", "limit == 0")
- `limit\s+(of\s+)?(0|zero)\b` (e.g. "limit of 0", "limit zero")
- `\b(0|zero)\s+limit` / `zero[- ]limit`
- `limit` within 150 characters of `falsy|falsey|truthy|truthiness` (either order)
- `if limit:` together with `0` or `zero` within 150 characters
Decoy answers (generic review, or only the sort order) must FAIL.
tasks/review.truth.md on GitHub
Pazartesi bunu yap
- Force bayrağı var olsun diye Claude Code 2.1.257 ya da üstünü,
/tasksher subagent'ın modelini göstersin diye 2.1.243 ya da üstünü çalıştır. - Hangi dosyayı istediğine karar ver: yalnızca kâşif için Explore dosyası, her subagent için env bloğu.
-
Explore.mddosyasını.claude/agents/Explore.mdolarak kaydet ya da env bloğunu.claude/settings.jsoniçine birleştir. - Özellikle Bedrock, Vertex ya da Foundry'de
haikutakma adını değil, tam id olanclaude-haiku-5-5kullan. - Bir oturum başlat, Explore agent'ı üzerinden kod tabanı araması iste, sonra
/tasksaç ve çalıştığı modeli ve effort'u oku. - Karmaşık agentic kodlama yapan her subagent'ı Sonnet 5.5 ya da Opus 5.5'te tut ve böyle bir agent'ı bilerek sabitlediysen FORCE ayarlama.
- Tablomuza kendi reponda güvenmeden önce kendi yedi görevini harness üzerinden yeniden çalıştır.
-
/usagedökümünü bir kota tahmini olarak değil, "hangi skill ya da agent yerel geçmişimi yiyor" diye oku.
Ölçümler
Her headless çalıştırmanın rate_limit_event olayından okunan plan sayacı, bir puanlık çözünürlük. Her blok bitişik, hesaba başka hiçbir şey dokunmadan. Haftalık pencere 182 çalıştırmanın her birinde %67 okundu.
| Blok | Çalıştırma | Beş saatlik, önce | Beş saatlik, sonra | Haftalık, önce | Haftalık, sonra | Liste fiyatı |
|---|---|---|---|---|---|---|
| Haiku 5.5 low | 70 | %21 | %21 | %67 | %67 | $0,2382 |
| Haiku 5.5 medium | 70 | %21 | %21 | %67 | %67 | $0,3006 |
| Sonnet 5.5 | 21 | %21 | %22 | %67 | %67 | $1,2339 |
| Opus 5.5 | 21 | %22 | %23 | %67 | %67 | $2,3175 |
Matris özeti: 182 çalıştırma, tüm matris liste fiyatıyla yaklaşık $4,09, beş saatlik pencerenin iki puanı ve haftanın sıfır puanı. Her blok bir puana kadar yuvarlama payı taşıdığı için Sonnet'in bir puanı ile Opus'un bir puanı ayırt edilemez. Dar kapsamlı görev, modellerin herhangi birinde bu sayaçta görünmez. Üç modelin tur başına göreli maliyetini anlatan destek sayfası bir çarpan vermiyor s9.
Yerel dökümle karşılaştırma, yapılandırma başına:
| Yapılandırma | Çalıştırma | Kabul | Liste fiyatı | /usage buluşsal değeri (M birim) |
Beş saatlik puan |
|---|---|---|---|---|---|
| haiku-med | 70 | %100 | $0,3006 | 29,48 | 0,0 |
| haiku-low | 70 | %100 | $0,2382 | 23,91 | 0,0 |
| sonnet | 21 | %100 | $1,2339 | 16,15 | 1,0 |
| opus | 21 | %100 | $2,3175 | 27,30 | 1,0 |
/usage dökümü makinendeki yerel geçmişten hesaplanır s3. İstemci binary'sinden okunan formülü, model katmanı çarpı kayıt ağırlığıdır. Model katmanı: Fable 10, Opus 5, Haiku 1, diğer her şey 3. Kayıt ağırlığı: önbellekteki token'lar 1, önbelleksiz girdi 10, önbellek oluşturma 12,5, çıktı 50. Bunlar istemcinin yerel atıf ağırlıkları; faturalama değil ve sunucunun kotanı nasıl saydığının kanıtı da değil. Aradaki fark ortada: Haiku medium bloğu 29,48, Opus bloğu 27,30 alıyor; yani panel Haiku bloğunu Opus bloğunun üstüne sıralıyor, gerçek sayaç ise sıfıra karşı bir puan okudu.
Bir hesabın bir aylık loglarının token'ları nereye koyduğu: 2.631 subagent çalışması, 42'si Haiku 4.5'te, subagent'lar tüm token'ların %48,1'inde, Haiku 4.5 ağırlıklı maliyetin %0,27'sinde ve model ne olursa olsun medyan subagent başlatma prompt'u 47.117 token.
Effort, aynı kabul edilen özellikte (refactor) iştahı değiştirir: Haiku medium 13,7 turda 232.985 önbellek okuma token'ı okudu, Haiku low 7,6 turda 139.801, Opus 8,3 turda 87.612.
Daha ileri
- 100k fiyat uçurumu: 100.000 prompt token'ının üstünde yüksek katman, aşan kısma değil tüm isteğe uygulanır s8. Önbellek token'larının 100k'ya sayılıp sayılmadığı o yazarın yorumu, alıntılanmış bir kural değil. API anahtarı ekonomisi için önemli, abonelik sayacı için değil. Fiyat sayfası: s20.
- Tokenizer: aynı metin Haiku 5.5'te Haiku 4.5'e göre yaklaşık %30 fazla token sayılıyor ve API'de adaptive thinking varsayılan olarak açık, kaldıraç effort s12.
- Görev başına effort ve token: bağımsız bir çalıştırma, max effort'ta Intelligence Index görevi başına yaklaşık 162k çıktı token'ı, 38 skor için high effort'ta yaklaşık 55k ölçtü s7.
- Rakamları ve tasarrufun küçük bir ana bağlamdan geldiğine dair kendi çekincesiyle tam bir uygulayıcı kurulumu: s4.
- Açık issue #100082, bazı sürümlerde subagent frontmatter modeli yok sayılıyor: s10.
- Açık issue #100154, subagent transkriptleri
output_tokensdeğerini eksik bildiriyor, aynı brief için 15.575'e karşı 461, sunucu tarafı faturalama etkilenmiyor s11./usageyerel geçmişten çıktıyı 50 ile çarptığı için bu eksik sayım panele ulaşıyor. - Burada test edilmedi: ana model olarak Haiku (kaynaklar Haiku 5.5'i bir subagent olarak çerçeveliyor), bir gateway üzerinden sağlayıcılar arası model karışımları (Claude Code ayarı değil), 1M bağlamlı prompt'lar ve Max planlarla gelen API kredileri (Claude Code kotası değil, Claude Platform).
SSS
CLAUDE_CODE_SUBAGENT_MODEL ayarlamak Explore'u Haiku'ya alır mı?
Hayır. Tek başına yerleşik Explore ve Plan subagent'larını değiştirmez s2. Explore agent dosyasını ya da force bayrağını ekle.
FORCE Opus subagent'larımı bozar mı?
Tek bir modeli her subagent'a uygular, yani evet: bilerek Opus'a sabitlediğin bir agent da Haiku'da çalışır. Yalnızca kâşif ucuz olsun istiyorsan Explore dosyasını kullan.
Haiku 5.5 subagent işi için yeterince iyi mi?
Tek küçük repodaki bu yedi dar kapsamlı görev için low effort'ta 70 kez 70'inde, medium'da 70 kez 70'inde kabul edildi. Anthropic'in kendisi karmaşık agentic kodlama için Sonnet 5.5 ve Opus 5.5'i saklıyor s1 ve Terminal-Bench 4.0 farkı %39,2'ye karşı %70,6.
Geçiş plan kotasından ne kadar geri verir?
Dar kapsamlı bir görev için, hangi modelde olursa olsun sayacın bir puanından az: 182 çalıştırmalık matrisin tamamı beş saatlik pencereyi iki puan, haftalık pencereyi sıfır puan oynattı. Gerçek bir haftanın gittiği yer ana bağlam ve her subagent'ın taşıdığı başlatma prompt'u.
Daha çok tasarruf için thinking'i kapatabilir miyim?
Claude Code'da bu modellerde hayır s21. Geriye kalan kadran effort.
AIDive