This document describes the current architecture of snowl and the direction required to turn it into an industrial-grade evaluation platform.
Snowl is currently a local, single-machine evaluation platform with four user-facing surfaces:
- authoring surface
project.ymltask.pyagent.pyscorer.pytool.py
- execution surface
snowl eval path/to/project.ymlsnowl bench run <benchmark> --project path/to/project.yml
- artifact surface
.snowl/runs/<run_id>/...
- observability surface
- plain CLI operator logs
- Next.js Web monitor
It is not yet a distributed scheduler.
The real execution unit is:
Task x AgentVariant x Sample
Important nuance:
Taskdefines the workload and environment expectationsAgentVariantbinds one executable identity to one model/configurationSampleis the concrete row executed by the trial- one active
Scorerevaluates each trial today
Main code:
snowl/project_config.pysnowl/eval.pysnowl/agents/model_variants.pysnowl/core/
Responsibilities:
- load
project.yml - resolve project root and eval code paths
- resolve provider config
- expand
agent_matrix.modelsintoAgentVariants - keep
judge.modelseparate from tested models - validate
Task,Agent,Scorer, andToolSpec
Current product rule:
project.ymlis the formal source of trutheval.code.base_dirand module paths explicitly describe where the eval code lives- user-facing provider and benchmark config should not depend on env vars
Main code:
snowl/eval.py
Responsibilities:
- filter tasks / agents / variants
- expand the execution plan
- compute
run_idand defaultexperiment_id - create live artifact directories
- write early bootstrap artifacts
- emit bootstrap information to CLI and Web observers
Important current behavior:
manifest.json,plan.json, and liveprofiling.jsonskeletons are written at run startupevents.jsonlis live-appended during execution- CLI owns the lifecycle of its auto-started Web monitor sidecar
Main code:
snowl/runtime/engine.py
Responsibilities:
- construct
TrialRequest - prepare initial
AgentState - inject
task_id,sample_id,variant_id, andmodel - emit runtime events
- execute agent logic
- execute scorer logic
- normalize output into
TaskResult
Tool middleware is applied inside the agent's tool execution path. When ReActAgent is configured with middlewares, a MiddlewareChain wraps each tool call: intercept_call transforms arguments before execution, intercept_result transforms the result after execution. Calls flow forward through the chain; results flow in reverse. Middleware is only invoked for known, allowed tools.
The engine now exposes two runtime phases explicitly:
execute_agent_phase(...) -> PartialTrialResultscore_trial_phase(...) -> FinalTrialOutcome
This decoupling allows scoring to stop occupying the same slot as agent execution.
Main code:
snowl/runtime/resource_scheduler.pysnowl/eval.py
Current scheduler model is provider-aware and phase-aware.
It separately controls:
max_running_trialsmax_container_slotsmax_buildsmax_scoring_tasksprovider_budgets[provider_id]
Key semantics:
- agent execution uses
running_trial_slot() - scoring uses
scoring_slot() - remote model calls use
provider_slot(provider_id) - build/setup work uses
build_slot() - sandbox/container capacity uses container/sandbox slots
This is the current local control plane for concurrency.
Main code:
snowl/runtime/container_runtime.pysnowl/runtime/container_providers.py- benchmark-specific launchers under
snowl/benchmarks/*
Responsibilities:
- resolve the correct container provider for a benchmark
- translate trial context into benchmark-specific lifecycle steps
- isolate resources by
task_id + sample_id + variant_id - emit pretask and environment lifecycle events
Current rule:
- container trial resources must be variant-aware
variant_idis part of compose project names, log paths, and related resource names
This is what makes multi-model TerminalBench and OSWorld runs safe.
Main code:
snowl/eval.pysnowl/aggregator/
Durable run artifacts include at least:
manifest.jsonplan.jsonsummary.jsonaggregate.jsonprofiling.jsontrials.jsonlevents.jsonlmetrics_wide.csvrun.log
This run directory is the contract boundary between execution and observability.
Main code:
snowl/cli.pysnowl/ui/console.py
Current behavior:
- default mode is plain foreground operator logging
--cli-uienables the legacy live console UI- the auto-started Web monitor runs as a managed background sidecar
Main code:
webui/snowl/_webui/snowl/web/runtime.pywebui/src/server/monitor.ts
Current behavior:
- Next.js full-stack monitor
/is the run gallery / operator board/runs/[runId]is the single-run workspace/compareis a secondary history view- monitor indexes runs from
.snowl/runs/ - monitor consumes live
events.jsonland run artifacts
Repo fact:
webui/is the editable source of truthsnowl/_webui/is the packaged mirror
At a high level:
project.yml
-> project config + code path resolution
-> agent matrix expansion
-> eval plan
-> resource scheduler
-> execute phase / score phase
-> artifacts + live events
-> CLI + Web monitor
Concrete flow:
snowl eval path/to/project.ymlloads the project config- Snowl loads
task.py,agent.py,scorer.py, and optionaltool.pyfromeval.code - model entries expand into
AgentVariants PlanTrials are built and scheduled- execute and score phases consume different budgets
- artifacts and events are written continuously
- CLI and Web consume the same underlying run facts
In practice, remote provider limits are usually the real bottleneck for agent evaluation.
That is why Snowl now treats provider concurrency as a first-class resource:
- agent remote requests consume provider budget
- judge remote requests consume provider budget
- if agent and judge share one provider, they share the same budget
This is more correct than thinking only in terms of global worker count.
The platform already has several strong foundations:
- strict execution contract across QA, terminal, and GUI tasks
- YAML-first project authoring
- multi-model expansion through
AgentVariant - variant-aware container isolation
- live structured event stream
- usable run-first operator workflows
These are the foundations to preserve while scaling the runtime.
Snowl still needs more work before it feels fully industrial:
- better phase scheduling heuristics and queueing policy
- stronger warm-pool / spec-hash reuse
- richer backpressure and stall diagnosis
- more benchmark-internal config routed through YAML instead of legacy env fallbacks
- stronger quantitative benchmarking for container-heavy workloads
- more robust resume/retry semantics across phases
The current near-term architecture priorities are:
- finish the YAML-first config migration across all official workflows
- harden provider-aware, phase-aware scheduling
- quantify runtime throughput improvements with reproducible baselines
- improve container-heavy benchmark reliability and diagnostics
- keep CLI, Web, and artifact contracts aligned