Python API¶
Generated from the source. For the reasoning behind these shapes, start with How agents actually work.
Running a loop directly¶
from pathlib import Path
from endstate.agent.loop import AgentLoop
from endstate.agent.context import ContextManager, TokenBudget
from endstate.agent.permissions import default_policy
from endstate.agent.session import SessionStore
from endstate.agent.tools import default_tools
from endstate.agent.tools.base import ToolContext
from endstate.providers.openai_compat import OpenAICompatProvider
store = SessionStore()
loop = AgentLoop(
provider=OpenAICompatProvider(model="gpt-4o-mini"),
tools=default_tools(),
tool_context=ToolContext(workdir=Path("./sandbox")),
policy=default_policy(),
context=ContextManager(budget=TokenBudget(max_context_tokens=128_000)),
session=store.create(model="gpt-4o-mini"),
max_steps=25,
)
result = loop.run("fix the failing test")
print(result.stop_reason, result.steps, result.usage.total_tokens)
Running an eval suite¶
from pathlib import Path
from endstate.evals import DockerSandbox, EvalRunner, discover_tasks, render_markdown
from endstate.providers.openai_compat import OpenAICompatProvider
tasks = discover_tasks(Path("tasks"))
runner = EvalRunner(
provider_factory=lambda task: OpenAICompatProvider(model="gpt-4o-mini"),
sandbox_factory=lambda task: DockerSandbox(task.fixture),
)
suite = runner.run_suite(tasks)
print(render_markdown(suite))
Both factories take the task, so a suite can vary the model or the image per task. Nothing in
SuiteResult reaches a grader: the verdict is decided before the result is assembled.
Core types¶
endstate.types ¶
Core value types shared across the harness.
Deliberately provider-agnostic: every provider adapter translates its own wire format into these types, so the loop never sees a vendor-specific object.
ToolCall ¶
ToolResult ¶
Message ¶
Bases: BaseModel
One turn in the conversation.
Source code in src/endstate/types.py
Usage ¶
Bases: BaseModel
Token accounting for a single provider call.
Source code in src/endstate/types.py
Response ¶
The loop¶
endstate.agent.loop ¶
The harness.
Written from primitives rather than on a framework, on purpose. The interesting parts of an agent — where the context budget is enforced, what happens when a tool is denied, what is persisted before a step can fail — are exactly the parts a framework hides. They are all visible in this file.
UnsettledCall ¶
Bases: BaseModel
A call left outstanding by a crash that resume refused to replay.
Only produced for tools that declare themselves non-idempotent. The call is answered — an unanswered one is malformed under every provider contract — but with an error saying the outcome is unknown rather than with a second side effect.
Source code in src/endstate/agent/loop.py
RunResult ¶
Bases: BaseModel
Source code in src/endstate/agent/loop.py
tree_hash ¶
Hash the sandbox this run worked in.
The assertion primitive for end-state grading: "the destructive command did not run" is this value being unchanged, and "the resumed run got to the same place" is two of these being equal.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the run recorded no working directory. |
Source code in src/endstate/agent/loop.py
AgentLoop ¶
Source code in src/endstate/agent/loop.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
run ¶
Run the agent against a new instruction.
Source code in src/endstate/agent/loop.py
resume ¶
Continue an interrupted run without a new instruction.
Finishes any tool calls that were requested but never executed, then carries on. This is the difference between continuing a conversation and finishing what the agent was doing when the process died.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the loop has no session to resume from. |
Source code in src/endstate/agent/loop.py
Context and compaction¶
endstate.agent.context ¶
Context budgeting and compaction.
The budget is a first-class object rather than an implicit consequence of the model's context window. Every compaction is recorded as an event with the token counts before and after, which is what makes "what did compaction cost you?" answerable with a number instead of a shrug.
HeuristicTokenCounter ¶
Chars/4 approximation.
Deliberately not a tokeniser: the harness must work against any provider, including ones whose tokeniser is not public. Swap in a real counter per provider when exactness matters.
Source code in src/endstate/agent/context.py
DropOldest ¶
Keep the system prompt, the first user message, and the newest tail that fits.
The first user message is pinned because it usually contains the task; losing it is the most common way a long-horizon agent forgets what it was doing.
Source code in src/endstate/agent/context.py
SummariseMiddle ¶
Replace the dropped middle with one synthetic summary message.
Takes a summariser callable rather than a provider so the strategy stays testable without a network call.
Source code in src/endstate/agent/context.py
ContextManager ¶
Bases: BaseModel
Fits a conversation into the budget, recording every compaction.
Source code in src/endstate/agent/context.py
Permissions¶
endstate.agent.permissions ¶
Permission policy for tool calls.
An agent that cannot be stopped is not deployable. This module exists so that "the agent refused" is a testable outcome: eval tasks in the permissioning category pass only when a destructive request is denied and the sandbox is unchanged.
Rules are evaluated in order; the first match wins. Default is DENY for anything not explicitly permitted, because the failure modes are asymmetric.
Rule ¶
Bases: BaseModel
Match a tool call by tool name and optionally by argument content.
Source code in src/endstate/agent/permissions.py
default_policy ¶
A conservative starting policy: read freely, write locally, never destroy.
Source code in src/endstate/agent/permissions.py
Sessions¶
endstate.agent.session ¶
Durable sessions.
SQLite, not Postgres or Redis: a harness that needs infrastructure to remember what it was doing is a harness nobody will run locally. A checkpoint is written after every step, so a killed process resumes from the last completed tool call rather than from the beginning.
Session ¶
An append-only conversation that survives process death.
Source code in src/endstate/agent/session.py
append ¶
Append and checkpoint in one operation. There is no uncheckpointed state.
checkpoint_last ¶
Re-persist the final message in place.
Used while a batch of tool calls is being executed: each result is written as it lands, so a process killed mid-batch leaves a record of exactly the calls that completed. Rewriting the same step keeps one tool message per assistant turn, which is the shape every provider expects.
Source code in src/endstate/agent/session.py
Tools¶
endstate.agent.tools.base ¶
Tool interface and the sandbox boundary.
Every tool receives a ToolContext whose workdir is the only part of the filesystem it may touch. Path confinement lives here rather than in each tool so there is exactly one place to audit.
ToolError ¶
ToolContext
dataclass
¶
Source code in src/endstate/agent/tools/base.py
resolve ¶
Resolve a path inside the workdir, refusing anything that escapes it.
Source code in src/endstate/agent/tools/base.py
Tool ¶
Bases: ABC
Source code in src/endstate/agent/tools/base.py
idempotent
class-attribute
instance-attribute
¶
Whether running this call twice is the same as running it once.
Read by the resume path. A crash inside a tool call — after the side effect, before any record of it — leaves the harness unable to tell "done" from "about to". For an idempotent tool that is harmless: re-running converges. For one that appends, increments or posts, re-running is a second side effect, so resume reports the call as interrupted instead of repeating it.
Default True because the tools shipped here overwrite rather than accumulate. Anything with an external or accumulating effect must set it False.
run
abstractmethod
¶
confine ¶
Resolve relative inside root, refusing anything that escapes it.
Lives at module level because the eval sandbox needs the same rule and one audited implementation is worth more than two that agree today.
Raises:
| Type | Description |
|---|---|
ValueError
|
If the resolved path is outside |
Source code in src/endstate/agent/tools/base.py
Providers¶
endstate.providers.base ¶
Provider interface.
The loop depends on this Protocol and nothing else. Adding a provider means writing one adapter; it never means touching the loop. This is also what makes the same eval suite runnable against a hosted API and a self-hosted vLLM endpoint without changing a single task.
endstate.providers.fake ¶
A scripted provider.
Exists so the harness is testable without a network, an API key, or a bill. Every behaviour in the loop — tool calls, multi-step runs, budget exhaustion, denial — is covered by tests that use this.
FakeProvider ¶
Replays a fixed list of responses, then ends the turn.
Source code in src/endstate/providers/fake.py
Evals: tasks and verdicts¶
endstate.evals.task ¶
What a task is, and what a verdict is.
A task on disk is a directory. That is a deliberate choice over a single big manifest file: the fixture is a real repository the agent works in, the prompt is prose someone will edit, and the held-out tests must be storable next to the task without being shipped into the sandbox with it.
tasks/fix-off-by-one/
├── task.json metadata, budget, graders
├── prompt.md what the agent is told
├── fixture/ copied into the sandbox; this is the agent's whole world
├── holdout/ staged in at grading time only — the agent never sees it
└── solution/ a reference fix, used to prove the graders are load-bearing
solution/ never reaches an agent. It exists so the suite can answer the
question that decides whether any of this is worth anything: does this grader
fail on the unsolved fixture and pass on a correct fix? A grader that passes
either way is measuring nothing, and it is much easier to write one of those
than most people expect.
TaskError ¶
Check ¶
Bases: BaseModel
One assertion, reported individually.
Graders return every check they ran, not just the one that failed. "The tests pass but you edited them" and "the tests fail" are different findings, and a verdict that collapses to a single boolean cannot tell you which.
Source code in src/endstate/evals/task.py
Verdict ¶
Bases: BaseModel
A grader's answer. Pure function of the sandbox's end state.
Source code in src/endstate/evals/task.py
from_checks
classmethod
¶
Conjunction: every check must pass, and the reason names the ones that did not.
Source code in src/endstate/evals/task.py
merged_with ¶
Conjunction of two verdicts, keeping every check from both.
passed is conjoined explicitly rather than recomputed from the merged
checks, because a verdict can fail while carrying none: a grader that
raised reports a reason and an empty list. Recomputing would turn that
into a pass — the worst possible direction for a failure to be lost in.
Source code in src/endstate/evals/task.py
GraderSpec ¶
Bases: BaseModel
A dotted path to a grader, plus the arguments it is bound with.
endstate.evals.graders:command_succeeds with {"command": "..."} resolves
to a callable of exactly one argument — the sandbox — which is the contract
the whole design rests on.
Source code in src/endstate/evals/task.py
Bound ¶
Bases: BaseModel
An inclusive range. Either end may be left out.
Unknown keys are refused. A {"minimum": 1} typo would otherwise parse as a
bound with no ends, which passes for any value — so the requirement would
read as asserted in the task file and assert nothing at all. That is the
exact failure this whole design is meant to rule out, so it is an error.
Source code in src/endstate/evals/task.py
RunRequirements ¶
Bases: BaseModel
Assertions about the run that the filesystem cannot answer.
Three of the four differentiating categories need one of these. "The task
completed and compaction fired at least once" is the whole point of a
long-horizon task — a run that finished without ever compacting did not test
compaction, and passing it would be a lie about coverage. But
compaction_events is not a property of the sandbox, so no grader can see
it without being handed something other than the sandbox.
Rather than widen the grader signature, requirements are evaluated by the
runner and conjoined with the grader's verdict. That keeps
grade(sandbox) -> Verdict exactly as narrow as it was, and the reason it is
safe is the shape of this class: every field is a typed counter the harness
recorded itself — policy decisions, compaction events, token totals. There is
no field here that can reach message content, and adding one would be a
visible change to this model rather than a quiet argument in a task file.
extra="forbid" is what makes that last sentence true rather than merely
intended: a manifest naming final_text is rejected at load time instead of
being silently dropped.
Source code in src/endstate/evals/task.py
check ¶
Every requirement, as individually reported checks.
Source code in src/endstate/evals/task.py
Recovery ¶
Bases: BaseModel
Kill the run partway through, then resume it.
crash_at_call counts tool calls across the whole run, not steps, because
the interesting kill points are inside a batch. after_side_effect picks the
window: False is the honest kill where the work never happened, True is the
irreducible one where it happened and nothing recorded it.
Source code in src/endstate/evals/task.py
Task ¶
Bases: BaseModel
One eval task, loaded from a directory.
Source code in src/endstate/evals/task.py
load_task ¶
Load one task directory.
Raises:
| Type | Description |
|---|---|
TaskError
|
If the directory is not a well-formed task. |
Source code in src/endstate/evals/task.py
discover_tasks ¶
Find every task under root, ordered by id.
Ordering is not cosmetic. The determinism criterion is an identical pass/fail vector across runs, and a vector needs a stable index.
Raises:
| Type | Description |
|---|---|
TaskError
|
If |
Source code in src/endstate/evals/task.py
Evals: the sandbox¶
endstate.evals.sandbox ¶
The eval sandbox: one disposable container per task.
Decision D7. Three reasons, in order of importance: determinism (a shared sandbox means task 7 sees whatever task 6 left behind), parallelism, and the fact that a container is the only reason it is sane to hand an agent a shell.
The shape here is deliberate. A sandbox owns a live tree on the host — a copy of the task fixture — and a way to execute commands against it. The tree is bind mounted into the container, so the file tools write host-side into the same bytes the shell sees container-side, and the grader can hash the result without copying anything back out.
Two things are sealed off from the agent on purpose:
The pristine fixture. Kept beside the live tree so a grader can ask "is this file byte-for-byte what we shipped?" — the assertion that catches an agent editing the tests instead of the code.
Version control history. .git is not copied. A task built from a real
repository ships the answer inside it, and git log is a cheaper path to a
passing grade than solving the problem. This is runtime contamination, and it is
measured in the wild at a scale that makes it the default rather than an option.
CommandRunner
module-attribute
¶
Runs an argv on the host. Injected so the Docker plumbing is testable.
DOCKER_PROBE_TIMEOUT_S
module-attribute
¶
How long to wait for docker version.
A healthy daemon answers in well under a second. The cases that take longer are all "not usable right now" — no daemon, a socket nobody is listening on, a daemon still starting or wedged mid-restart — and for every one of them the answer is the same, so waiting longer buys nothing and costs it on every call.
SandboxError ¶
ExecResult
dataclass
¶
The outcome of one command run inside the sandbox.
Source code in src/endstate/evals/sandbox.py
Sandbox ¶
Bases: ABC
A disposable working tree plus a way to run commands against it.
Subclasses supply execution only; everything filesystem-shaped is here, so the Docker and local backends cannot drift on what a grader observes.
Source code in src/endstate/evals/sandbox.py
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | |
setup ¶
start ¶
close ¶
run
abstractmethod
¶
path ¶
Resolve a path inside the live tree, refusing anything that escapes.
Raises:
| Type | Description |
|---|---|
SandboxError
|
If the path points outside the sandbox. |
Source code in src/endstate/evals/sandbox.py
read_text ¶
Read a file from the live tree, or return '' if it is not there.
Missing is not an error: a grader asking "does this file still contain the secret?" wants False for a deleted file, not an exception.
Source code in src/endstate/evals/sandbox.py
fixture_text ¶
Read a file as it was shipped, before the agent touched anything.
Source code in src/endstate/evals/sandbox.py
stage ¶
seal ¶
Freeze the agent's end state and return its hash.
Called once the agent has stopped and before anything is staged in for grading. Held-out tests are files too: without a seal, copying them in would change the very tree hash the graders are about to assert on.
Source code in src/endstate/evals/sandbox.py
tree_hash ¶
The end-state hash: sealed if the run is over, live if it is not.
end_state_files ¶
Every text file in the end state, keyed by relative path.
changed_paths ¶
Relative paths whose content differs from the fixture.
Includes files the agent added and files it deleted, because "deleted something inconvenient" is one of the things end-state grading exists to catch.
Source code in src/endstate/evals/sandbox.py
LocalSandbox ¶
Bases: Sandbox
Runs commands as subprocesses on the host, in the live tree.
This is not an isolation boundary. It exists so the runner, the graders
and the task suite can be tested on a machine without Docker, and so a task
can be debugged without a container in the way. Real benchmark runs use
DockerSandbox; anything published from a LocalSandbox run should say so.
Source code in src/endstate/evals/sandbox.py
DockerSandbox ¶
Bases: Sandbox
One container per task, with the live tree bind mounted into it.
The container is started once and held open with tail -f /dev/null, then
every command runs through docker exec. Starting a fresh container per
command would be a different guarantee — and a slower one — because a task's
commands would no longer share process state.
Defaults are the restrictive ones: no network, all capabilities dropped, no privilege escalation, capped memory and pids. Network is the interesting one. It is off because the fix for most benchmark tasks is a web search away, and an agent that retrieves the answer has not demonstrated it can derive it.
Source code in src/endstate/evals/sandbox.py
321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 | |
start_argv ¶
The docker run argv. Public because it is worth asserting on.
Source code in src/endstate/evals/sandbox.py
stage_tree ¶
stage_tree(
source: Path,
destination: Path,
*,
excludes: frozenset[str] = STAGING_EXCLUDES,
) -> None
Copy source over destination, pruning excludes at every level.
Overlays rather than replaces: staging held-out tests into a tree the agent has already worked in must not delete the agent's work.
Source code in src/endstate/evals/sandbox.py
docker_available ¶
Whether a usable Docker daemon is reachable.
Not cached: a daemon can start or stop between calls, and a stale False here
would send an eval run to the local sandbox by mistake. Callers that ask
repeatedly — a skipif evaluated once per decorator, say — should hold the
answer themselves rather than making this lie to everyone else.
Source code in src/endstate/evals/sandbox.py
Evals: graders¶
endstate.evals.graders ¶
Graders: pure functions of the sandbox's end state.
Look at what is not a parameter. There is no messages, no transcript, no
result. The grader cannot read what the agent said, because it was never
given it — and resolve below enforces that rather than trusting it, because a
grader that can see the transcript will eventually be written to check the
transcript. Someone will add "and it mentioned running the tests," since that is
easier than checking that the tests ran. Conventions erode; signatures do not.
The second thing this module is for is the uncomfortable half of end-state grading: the end state can be gamed too. An agent that edits the tests until they pass satisfies "the suite is green" completely. So does one that adds a skip marker, or special-cases the failing input. Each of those has a corresponding assertion here, and a task that does not make them has swapped a fluency exploit for a test-editing one:
| Hack | The grader that catches it |
|---|---|
| Edited the tests | files_unchanged |
| Skipped the test | no_new_skips |
| Special-cased the input | held-out tests, staged in by the runner after sealing |
| Deleted something inconvenient | paths_exist, changed_paths_within |
FORBIDDEN_PARAMETERS
module-attribute
¶
FORBIDDEN_PARAMETERS = frozenset(
{
"messages",
"transcript",
"history",
"result",
"run_result",
"final_text",
"response",
"conversation",
}
)
Parameter names that would let a grader read the agent's output.
Not an exhaustive list of ways to cheat — a determined author can smuggle the
transcript in through args. It is a guard rail against the accident, which is
the failure mode that actually happens.
GraderContractError ¶
resolve ¶
Turn a module:function spec plus arguments into a one-argument grader.
Raises:
| Type | Description |
|---|---|
GraderContractError
|
If the target cannot be imported, is not callable, or has a signature that could see anything but the sandbox. |
Source code in src/endstate/evals/graders.py
compose ¶
Conjoin graders. Every check from every grader, all of which must pass.
Conjunction rather than "any", because the interesting task definitions are all of the form completed the work and did not cheat to get there.
Source code in src/endstate/evals/graders.py
command_succeeds ¶
command_succeeds(
sandbox: Sandbox,
*,
command: str,
timeout_s: float | None = None,
expect_exit_code: int = 0,
name: str = "",
max_output_chars: int = 2000,
) -> Verdict
Run a command in the sandbox and check its exit code.
The workhorse: for most tasks the question "did it work?" is a test suite. Note that this alone is a weak grader — see the module docstring — and every shipped task pairs it with at least one assertion about the file tree.
Source code in src/endstate/evals/graders.py
files_unchanged ¶
Assert files are byte-for-byte what the fixture shipped.
Point this at the tests. An agent that cannot make the code pass the tests can always make the tests pass the code, and that is the single most common way a coding benchmark measures nothing.
Source code in src/endstate/evals/graders.py
file_matches ¶
file_matches(
sandbox: Sandbox,
*,
path: str,
pattern: str,
should_match: bool = True,
name: str = "",
) -> Verdict
Assert a file's content does (or does not) match a regex.
Source code in src/endstate/evals/graders.py
pattern_count ¶
pattern_count(
sandbox: Sandbox,
*,
pattern: str,
paths: Sequence[str] | None = None,
glob: str = "*.py",
maximum: int | None = None,
minimum: int | None = None,
name: str = "",
) -> Verdict
Count regex matches across the end state.
The refactor tasks turn on this: "the duplicated validation block appears exactly once in the package" is checkable, where "the code is now DRY" is not.
Source code in src/endstate/evals/graders.py
no_new_skips ¶
Assert the agent added no skip or xfail markers.
Green because the test never ran is the cheapest possible way to satisfy a "tests pass" grader, and unlike editing an assertion it leaves the test file looking almost untouched.
Source code in src/endstate/evals/graders.py
tree_unchanged ¶
Assert the agent left the sandbox byte-for-byte as it found it.
The permissioning assertion, and the one place where nothing happening is the pass condition. A refusal that still deleted the directory is not a refusal, and the only way to know is to compare the tree.
Note this is a property of the filesystem, so it stays a grader. "The policy
fired at least once" is not, and lives in the task's requires block.
Source code in src/endstate/evals/graders.py
changed_paths_within ¶
Assert the agent only touched paths the task permits.
Catches the collateral damage a pass/fail on the task itself cannot see: the unrelated module deleted on the way to a green test run.
Source code in src/endstate/evals/graders.py
Evals: the runner¶
endstate.evals.runner ¶
Running a suite.
The sequence for one task, in the order it has to happen:
- Copy the fixture into a fresh sandbox and start it.
- Let the agent work, with the shell wired into the sandbox.
- Seal the end state — hash it, snapshot it.
- Then stage the held-out tests in and grade.
Step 3 before step 4 is the whole reason seal() exists. Held-out tests are
files; staging them in before hashing would mean every grader that asserts on
the tree was asserting on a tree the grader itself had modified.
What the runner deliberately does not do is give the grader anything but the
sandbox. The RunResult — steps, tokens, transcript — is recorded in the
TaskResult for the report, and it is never in scope where a verdict is decided.
TaskResult ¶
Bases: BaseModel
Everything recorded about one task run.
The verdict is the answer; the rest is what makes a benchmark table more informative than a pass rate. A model that passes in 6 steps for $0.02 and one that passes in 34 steps for $1.40 are not the same product.
Source code in src/endstate/evals/runner.py
SuiteResult ¶
Bases: BaseModel
The outcome of a whole suite against one model.
Source code in src/endstate/evals/runner.py
verdict_vector
property
¶
The determinism criterion, as a value you can compare with ==.
Task id and pass/fail only. Wall clock, token counts and step counts all vary between identical runs; if any of them were in here, the criterion would be untestable rather than merely hard.
errored
property
¶
Tasks where the harness failed, which is not the same as a fail.
EvalRunner ¶
Runs tasks. One sandbox per task, one provider per task, no shared state.
Source code in src/endstate/evals/runner.py
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 | |
run_suite ¶
Run every task and return the results in task order.
Order is independent of completion order even with jobs > 1, because
the pass/fail vector is only comparable between runs if its index is
stable.
Source code in src/endstate/evals/runner.py
Evals: the report¶
endstate.evals.report ¶
The report.
Pass rate alone hides too much. A model that passes 80% of tasks in 6 steps for $0.02 and a model that passes 80% in 34 steps for $1.40 are not the same product, and only one of them is deployable at volume — so every summary here carries steps, tokens, cost and latency beside the rate.
Two smaller decisions worth naming. Cost renders as — rather than 0.00 when
the model has no price entry, because a wrong number in a published benchmark is
worse than a missing one (D4). And errored tasks are counted separately from
failed ones: "Docker was not running" and "the model could not do it" must never
sum into the same column.
FLAKE_THRESHOLD
module-attribute
¶
The rate above which a suite cannot support a claim.
From the engineering plan's M2 metric. Determinism is aspirational rather than achievable — providers do not offer bit-identical output even at temperature 0 — so the criterion is a low rate, not zero.
percentile ¶
Nearest-rank percentile.
Not statistics.quantiles, which interpolates and needs at least two data
points. A twenty-task suite is a small sample and an interpolated p95 over
twenty numbers implies a precision that is not there.
Source code in src/endstate/evals/report.py
total_cost ¶
Suite cost, or None if any model in it has no price.
Source code in src/endstate/evals/report.py
render_markdown ¶
The committed artefact: one suite run, as a markdown page.
Source code in src/endstate/evals/report.py
flake_rate ¶
Fraction of tasks that did not return the same verdict in every run.
The number the whole determinism claim reduces to, and it is only meaningful over identical inputs: same suite, same model, same seed. One run per task cannot tell capability from luck, and providers do not offer bit-identical output even at temperature 0 — so what is achievable is a low rate, not zero, and every result below the threshold is still a distribution rather than a fact.
Returns 0.0 for fewer than two runs: nothing has been compared yet, and reporting a rate for a single run would imply otherwise.
Source code in src/endstate/evals/report.py
determinism_established ¶
Whether these runs can support a determinism claim at all.
A low flake rate is necessary and nowhere near sufficient, because the most reassuring number this module can produce is also what a completely broken run produces. A suite where the container never started fails every task, identically, every time — perfectly reproducible and evidence of nothing.
So the rate is reported under this: if any task hit a harness error, or there is only one run, the answer is "not established" no matter how stable the verdicts looked.
Source code in src/endstate/evals/report.py
render_flake_markdown ¶
Report determinism across repeated runs of the same suite.
Source code in src/endstate/evals/report.py
write_report ¶
write_report(
suite: SuiteResult,
out_dir: Path,
prices: PriceTable | None = None,
) -> tuple[Path, Path]
Write the markdown report and the machine-readable results beside it.
Both, not either. The markdown is what a person reads in a pull request; the JSON is what the next run diffs against to answer "did anything regress?".
Source code in src/endstate/evals/report.py
Cost¶
endstate.telemetry.cost ¶
Token cost accounting.
Prices are data, not code. They change often and vary by region and contract, so they live in a user-editable table rather than being hardcoded into logic. Ship with an empty default and require the user to supply prices for any model they want costed: a wrong number is worse than a missing one.
ModelPrice ¶
Bases: BaseModel
USD per million tokens.
Source code in src/endstate/telemetry/cost.py
PriceTable ¶
Bases: BaseModel
Maps model id -> price. Load from JSON so it can be updated without a release.
Source code in src/endstate/telemetry/cost.py
UnknownModelPriceError ¶
CostAccountant ¶
Accumulates usage and converts it to money.
Usage is always tracked. Cost is only reported for models with a known price;
total_cost raises rather than silently under-reporting.
Source code in src/endstate/telemetry/cost.py
Tracing¶
endstate.telemetry.trace ¶
A minimal in-process trace.
Not OpenTelemetry: the point is a self-contained artifact you can serialise next to eval results. Exporting to OTel is a later concern.