Skip to content

feat: add frozen recording agent evaluation - #3378

Open
TomCC7 wants to merge 10 commits into
mainfrom
cc/feat/frozen-qa-eval
Open

feat: add frozen recording agent evaluation#3378
TomCC7 wants to merge 10 commits into
mainfrom
cc/feat/frozen-qa-eval

Conversation

@TomCC7

@TomCC7 TomCC7 commented Aug 5, 2026

Copy link
Copy Markdown
Member

Contribution path

Problem

DimOS lacked a direct way to evaluate an agent against replay-derived Memory2 state and stable case/result contracts that can later support other sources, tasks, and validators. This PR delivers the first vertical slice: one integer question over a frozen recording.

Solution

DIM-1390: eval execution framework

The final replay-first path is a synchronous CLI rather than a DimOS module:

case.json
  -> resolve recording and cutoff
  -> prepare/cache derived global map
  -> expose cutoff-limited read-only memory through python_exec
  -> run pinned stock Pi
  -> privately validate ANSWER: <integer>
  -> atomically publish result and diagnostics

dimos eval run executes exactly one case without starting a robot, simulation, replay blueprint, or module graph. It prepares a frozen source/derived Memory2 overlay, hosts an in-process MCP session controller with one persistent Jupyter kernel, and runs stock Pi 0.80.10 in a Node subprocess with only python_exec enabled.

The runner keeps the oracle outside the public case, distinguishes semantic failure from infrastructure failure, streams map/Pi/tool progress live, bounds execution time, cleans up Pi/MCP/Jupyter resources, and atomically publishes result.json plus optional Pi transcript and stderr diagnostics.

DIM-1392: eval data structures

The final primitive is a strict, immutable, versioned Pydantic envelope. These are the implemented field definitions; validation methods are omitted:

class BaseEvalModel(BaseModel):
    model_config = ConfigDict(extra="forbid", frozen=True, strict=True)
    schema_version: Literal["1.0"] = "1.0"


class FrozenRecordingSource(BaseEvalModel):
    kind: Literal["frozen_memory"] = "frozen_memory"
    recording: NonEmpty
    progress: float = Field(ge=0, le=1, allow_inf_nan=False)


class IntegerQuestionTask(BaseEvalModel):
    kind: Literal["integer_question"] = "integer_question"
    prompt: NonEmpty
    answer_marker: Literal["ANSWER:"] = "ANSWER:"


class ExactIntegerValidatorRef(BaseEvalModel):
    kind: Literal["exact_integer"] = "exact_integer"
    revision: NonEmpty
    private_path: NonEmpty


SourceSpec = Annotated[FrozenRecordingSource, Field(discriminator="kind")]
TaskSpec = Annotated[IntegerQuestionTask, Field(discriminator="kind")]
ValidatorRef = Annotated[ExactIntegerValidatorRef, Field(discriminator="kind")]


class EvalCase(BaseEvalModel):
    case_id: NonEmpty
    source: SourceSpec
    task: TaskSpec
    validator: ValidatorRef


class CompactEvalResult(BaseEvalModel):
    case_id: str
    recording: str
    progress: float
    model: str
    thinking_level: str
    final_response: str = ""
    prediction_status: Literal["parsed", "invalid", "not_evaluated"]
    integer_answer: int | None = None
    passed: bool | None = None
    validator_revision: str
    tool_call_count: int = Field(ge=0)
    duration_seconds: float = Field(ge=0)
    infra_error: str | None = None

Compared with the ticket's proposed general-purpose dataclass, source, task/query, and validator/scorer remain explicit extension points. setup, actions, and per-case timeout stay out until an execution mode uses them. Prepared replay data has a separate versioned FrozenMemoryManifest, and runtime-only agent settings live in EvalRunConfig.

Supporting architecture

  • Frozen data: replay preparation resolves normalized progress to an exact timestamp, runs the production voxel mapper, stores selected global_map data in a derived sidecar, and records source identity, mapper settings, and per-stream boundaries in a manifest. Bundles are cached and revalidated before use.
  • Read-only memory: SQLite stores now support URI mode=ro plus PRAGMA query_only=ON; mutation paths reject writes. FrozenMemoryStore overlays source and derived streams and applies one inclusive cutoff to all of them.
  • CodePolicy: an in-process session controller lazily manages a persistent Jupyter kernel, preserves state across calls, filters credential-like environment variables, bounds output, and interrupts or restarts timed-out execution. The official MCP server exposes only loopback python_exec.
  • Pi: a small TypeScript extension connects stock Pi to that MCP tool. Built-in tools and extension discovery are disabled. The API key exists only in the Pi child environment; JSON stdout/stderr are consumed live, redacted, bounded, and used for progress and cleanup.
  • CLI and artifacts: semantic failure exits 0, caught infrastructure failure exits 1, and preflight failure exits 2. Output is built in a temporary sibling and renamed into place, never merged into a nonempty directory.
  • Coverage: the PR includes the direct Hong Kong smoke case, docs, Python tests across contracts/storage/preparation/kernel/MCP/Pi/CLI, a mocked TypeScript adapter test, and the real 4,235-frame map-preparation gate. The smoke oracle is sentinel 0, not an authoritative room count.

Future live and simulation evals

The case/result envelope, Pi runner, MCP adapter, progress events, validator seam, and artifact publisher do not depend on frozen replay. A live source can replace bundle preparation with attachment to a running DimOS instance; LiveDimosEnvironment already exposes read-only memory and a connected app handle through the same persistent python_exec boundary.

A DimSim source/runner can own scene setup, seed, blueprint lifecycle, reset, actions, and telemetry, then add tagged task and validator variants for long-horizon scoring. Live and simulation evals therefore extend source-specific lifecycle runners and model unions without replacing the agent or result pipeline introduced here.

How to Test

Install the agent dependencies and build the local Pi adapter once:

uv sync --extra agents
npm ci --prefix packages/pi-code-policy-extension
npm run build --prefix packages/pi-code-policy-extension

Run the direct testcase:

OPENAI_API_KEY=... uv run dimos eval run \
  dimos/benchmark/short_horizon_qa/cases/demo_go2_hongkong_office-room-count-smoke/case.json \
  --output=/tmp/dimos-eval-smoke

Run the focused automated tests:

uv run pytest \
  dimos/agents/test_code_policy_core.py \
  dimos/agents/test_code_policy_server.py \
  dimos/benchmark/agent_eval \
  dimos/benchmark/short_horizon_qa \
  dimos/cli/test_eval.py \
  dimos/memory2/store/test_frozen.py
npm test --prefix packages/pi-code-policy-extension

CodePolicy runs trusted, unsandboxed Python. Run only trusted evaluation agents, or place the command in an OS sandbox or container.

AI assistance

Codex with GPT-5 assisted throughout design exploration, implementation, test generation, debugging, and PR drafting. The author reviewed the architecture and implementation decisions interactively.

Checklist

  • I have read and approved the CLA.

@mintlify

mintlify Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
dimensional 🟢 Ready View Preview Aug 5, 2026, 8:13 PM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@codecov

codecov Bot commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 87.44719% with 208 lines in your changes missing coverage. Please review.
✅ All tests successful. No failed tests found.

Files with missing lines Patch % Lines
dimos/agents/code_policy_core.py 78.35% 32 Missing and 10 partials ⚠️
dimos/benchmark/agent_eval/pi_process.py 77.50% 18 Missing and 18 partials ⚠️
dimos/cli/eval.py 67.74% 22 Missing and 8 partials ⚠️
dimos/benchmark/short_horizon_qa/prepare.py 82.87% 12 Missing and 13 partials ⚠️
dimos/benchmark/short_horizon_qa/service.py 60.41% 14 Missing and 5 partials ⚠️
dimos/benchmark/agent_eval/single_case.py 81.81% 12 Missing and 6 partials ⚠️
dimos/agents/code_policy_server.py 81.94% 7 Missing and 6 partials ⚠️
dimos/memory2/store/sqlite.py 76.92% 3 Missing and 3 partials ⚠️
dimos/benchmark/agent_eval/models.py 94.20% 2 Missing and 2 partials ⚠️
dimos/benchmark/short_horizon_qa/eval.py 86.66% 2 Missing and 2 partials ⚠️
... and 5 more
@@            Coverage Diff             @@
##             main    #3378      +/-   ##
==========================================
+ Coverage   76.09%   76.26%   +0.17%     
==========================================
  Files        1189     1211      +22     
  Lines      115284   116918    +1634     
  Branches    10366    10512     +146     
==========================================
+ Hits        87720    89164    +1444     
- Misses      24553    24670     +117     
- Partials     3011     3084      +73     
Flag Coverage Δ
OS-ubuntu-24.04-arm 70.57% <86.72%> (+0.41%) ⬆️
OS-ubuntu-latest 72.37% <86.96%> (+0.22%) ⬆️
Py-3.10 72.37% <86.96%> (+0.22%) ⬆️
Py-3.11 72.37% <86.96%> (+0.22%) ⬆️
Py-3.12 72.36% <86.84%> (+0.22%) ⬆️
Py-3.13 72.37% <86.96%> (+0.21%) ⬆️
Py-3.14 72.36% <86.96%> (+0.21%) ⬆️
Py-3.14t 72.36% <86.96%> (+0.21%) ⬆️
SelfHosted-Large 29.64% <29.08%> (+<0.01%) ⬆️
SelfHosted-Linux 35.88% <36.39%> (+0.03%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dimos/agents/mcp/mcp_adapter.py 75.60% <100.00%> (+3.65%) ⬆️
dimos/agents/mcp/test_mcp_adapter.py 100.00% <100.00%> (ø)
dimos/agents/test_code_policy_core.py 100.00% <100.00%> (ø)
dimos/agents/test_code_policy_server.py 100.00% <100.00%> (ø)
dimos/benchmark/agent_eval/test_pi_process.py 100.00% <100.00%> (ø)
dimos/benchmark/agent_eval/test_single_case.py 100.00% <100.00%> (ø)
dimos/benchmark/short_horizon_qa/test_eval.py 100.00% <100.00%> (ø)
...s/benchmark/short_horizon_qa/test_hongkong_eval.py 100.00% <100.00%> (ø)
dimos/benchmark/short_horizon_qa/test_prepare.py 100.00% <100.00%> (ø)
dimos/cli/dimos.py 64.62% <100.00%> (+0.15%) ⬆️
... and 20 more

... and 6 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread packages/pi-code-policy-adapter/src/code-policy-main.ts Outdated
Comment thread dimos/benchmark/agent_eval/auth.py Outdated
Comment thread dimos/agents/code_policy_core.py
Comment thread dimos/benchmark/agent_eval/json.py Outdated
Comment thread dimos/benchmark/agent_eval/pi_adapter.py Outdated
Comment thread dimos/benchmark/agent_eval/base.py Outdated
Comment thread dimos/benchmark/agent_eval/store.py Outdated
Comment thread dimos/agents/code_policy_server.py
Comment thread dimos/memory2/observationstore/sqlite.py
@greptile-apps

greptile-apps Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR adds a synchronous frozen-recording evaluation pipeline with strict case/result contracts, replay-derived map caching, read-only Memory2 access, and a constrained Pi-to-Python execution bridge.

  • Adds frozen-memory preparation, validation, caching, and atomic result publication.
  • Adds a persistent Jupyter-backed MCP tool and pinned Pi adapter.
  • Extends SQLite stores with read-only connection and mutation behavior.
  • Adds CLI integration, documentation, fixtures, and focused Python/TypeScript tests.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
dimos/benchmark/agent_eval/single_case.py Orchestrates preflight, concurrent-safe bundle caching, agent execution, validation, cleanup, and atomic artifact publication.
dimos/benchmark/short_horizon_qa/prepare.py Builds and atomically publishes sealed derived-map bundles with cutoff metadata.
dimos/memory2/store/sqlite.py Adds read-only store handling and propagates it to reconstructed path-backed SQLite components.
dimos/memory2/store/frozen.py Adds a cutoff-bounded, read-only overlay across source and derived Memory2 streams.
dimos/agents/code_policy_core.py Adds persistent Jupyter execution with bounded output, timeout recovery, and filtered kernel environment.
dimos/benchmark/agent_eval/pi_process.py Runs the pinned Pi CLI, processes streamed events, bounds diagnostics, and handles timeout cleanup.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    C[Eval case] --> B[Resolve or prepare frozen bundle]
    B --> M[Read-only Memory2 overlay]
    M --> P[Persistent Python MCP session]
    P --> A[Pi agent]
    A --> V[Private integer validator]
    V --> R[Atomic result directory]
Loading

Reviews (2): Last reviewed commit: "fix: harden frozen eval CI and caching" | Re-trigger Greptile

Comment thread dimos/benchmark/agent_eval/single_case.py Outdated
@TomCC7

TomCC7 commented Aug 5, 2026

Copy link
Copy Markdown
Member Author

Addressed the remaining Greptile findings in 280530850:

  • Read-only propagation: path-backed SQLite blob/vector components reconstructed by a read-only parent now open with mode=ro/query_only, including a regression test that verifies reads still work, writes are blocked, and no external WAL is created.
  • Concurrent cache publication: a losing runner accepts only a complete manifest published by the winner; partial or unrelated failures remain errors.
  • CI help assertion: strips Rich ANSI styling before checking the fully qualified option name, preserving the colored-help coverage without depending on escape placement.

Local verification: 72 tests passed (1 deselected), plus Ruff, mypy on the changed sources, and targeted pre-commit hooks.

@TomCC7
TomCC7 force-pushed the cc/feat/frozen-qa-eval branch from 2805308 to 03d9ca7 Compare August 8, 2026 15:58
@github-actions github-actions Bot removed the ready-to-merge Required CI checks have passed on this PR label Aug 8, 2026
@github-actions github-actions Bot added the ready-to-merge Required CI checks have passed on this PR label Aug 8, 2026
@@ -0,0 +1,7 @@
# Pi CodePolicy extension

This package adds one `python_exec` tool to the stock Pi CLI. The tool connects

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to use Pi for agent execution? WHy not jsut use langchain client directly its already in the repo

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's hard for me to find a single strength for our inhouse agent other than 'its already in the repo' :(

I feel like it's too 'low-level' for the features we really care about. For example, I added gpt5.6 support from api handling level in a previous PR #2999 . I also checked if there's an easy way to support codex subscription for the inhouse agent, the answer is also no.

These features are so basic for any of the 'agent runtime' framework in the field. You should be able to just load an extension and that's it (https://awesome-pi.site/extensions/). The primary reason for pi here is it's the most light-weight one.

Nevertheless, the rest of the structure is decoupled from the actual agent being used right now, the exposed mcp can be used on any agent.

_TERMINAL_INTEGER = re.compile(r"(?:^|\n)ANSWER:\s*(-?\d+)\s*\Z")


class ExactIntegerOracle(BaseEvalModel):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These should be generic since evaluation functions will end up an infinite list if we need to define scoring for Every single type. This is for integers, what about how we eval Vector3 or Pose or RobotState?

@pytest.mark.self_hosted
def test_real_hongkong_recording_prepares_direct_demo_case(tmp_path: Path) -> None:
case_path = (
Path(__file__).parent / "cases" / "demo_go2_hongkong_office-room-count-smoke" / "case.json"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cant reference files like this. Will break if you pip install dimos as a library

if self._conn is None:
assert self._path is not None
disposable, self._conn = open_disposable_sqlite_connection(self._path)
disposable, self._conn = open_disposable_sqlite_connection(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dont touch mem2 in an evals PR


class PiAgentConfig(BaseEvalModel):
backend: Literal["pi"] = "pi"
model: Literal["gpt-5.6-luna"] = "gpt-5.6-luna"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why literal - if not configurable then we cant eval the underlying model

backend: Literal["pi"] = "pi"
model: Literal["gpt-5.6-luna"] = "gpt-5.6-luna"
thinking_level: Literal["medium"] = "medium"
api_key_env: str = Field(default="OPENAI_API_KEY", min_length=1)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

im pretty sure we dont handle env vars like this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-to-merge Required CI checks have passed on this PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants