feat(evals): TypeSafe (Jev) navigation policy agent - #4206
Draft
ruthwikdasyam wants to merge 9 commits into
Draft
ruthwikdasyam wants to merge 9 commits into
ruthwikdasyam wants to merge 9 commits into
Conversation
Barebones scaffold for evaluating TypeSafe's System One model as a closed-loop navigation policy in DimSim. Skeleton for review; the model call path is untested against the live API. The agent calls no skill. Each tick it assembles a world state, asks Jev for one world-frame unit step plus a termination flag, rotates the pick into the body frame and publishes a single Twist. Two contracts are isolated for iteration: WorldState (input) and STEP_CRITERIA/build_questions (output). Obstacles, room bounds and the goal come from a static scene JSON; the robot pose comes live off /odom. Notes: - Publishes /nav_cmd_vel, not /cmd_vel: MovementManager owns cmd_vel in the go2 stack, so this drops into the existing arbitration slot. - Blueprint carries mcp-server but no skill container, so MCP comes up with zero tools exposed. Sim.start() requires MCP, so it cannot be dropped entirely. - observe() is fully wired; no stubs remain. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
❌ 4 Tests Failed:
View the top 3 failed test(s) by shortest run time
To view more test analytics, go to the Test Analytics Dashboard |
… API Add a `typesafe` extra (typesafe-sdk>=0.6.0) with an exclude-newer override, TYPESAFE_API_KEY in default.env and the conftest passthrough. Fix four bugs found by inspecting the installed SDK rather than the docs: - config.model was never passed to the client; it now sets the client default and the recorded model_name comes from response.model - default model is jev-latest, not jev-1.13 - Usage fields are input_tokens/output_tokens, so token metrics were silently always zero - request timeout is now explicit (SDK default is 10s) Add offline tests for the body-frame rotation, confidence gate, scene loading and preflight rejection. 12 pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the invented apartment_bed.json with apartment_couch.json built from misc/DimSim/scenes/apartment/objects/manifest.json: real object centres for 30 floor-standing obstacles, converted Three.js Y-up -> ROS world frame. Footprints are a uniform 0.5 m half-extent (the manifest has no extents; they live in the GLBs) — approximate, tunable per scene. Target the couch instead of the bed so the arrival band (2.0 m) matches the DimSim-native go-to-couch rubric. The docs' BED constant did not match this scene's manifest anyway. A test pins the committed JSON to the generator output so it cannot drift from the manifest silently. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sim.start() only waits for MCP, but the headless browser boots long after that on a cold start (Deno + Chromium download, Vite build): the first live run handed TypeSafePolicy a robot that did not exist yet and it gave up on /odom after 10s, 0.7s before the engine came up. DimSimEnvironment now overrides prepare_recording() to block until a fresh odom row lands in the recording, mirroring HabitatEnvironment. Freshness uses the Observation's recorder timestamp, which is set even if the sim leaves PoseStamped.ts at zero. The agent's own pose wait is lifted from a hard-coded 10s to a 60s config backstop. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
First live run: Jev chose the correct direction 60/60 ticks, every Twist reached the sim bridge, and the robot moved 0.00 m. DimSim's "holonomic" ground model only integrates linear.x and angular.z (physics.ts:74); linear.y is stored and ignored, and the agent commanded pure strafe. Jev's compass contract is unchanged. twist() now treats the chosen world direction as a target heading: rotate in place until within align_deg, then drive forward with a proportional yaw correction. The controller is re-evaluated against the live pose every control_dt (100 ms) for the whole tick, which also beats the bridge's 500 ms cmd_vel deadman that would otherwise zero a once-per-second command half the time. Speed drops to 0.2 m/s (0.6 m/s after the sim's 3x scale) so one tick is ~0.6 m, not 1.2 m, against 0.5 m obstacle boxes. Grader: a robot that never moved scored 0.3 because ~1e-9 m of odom jitter satisfied travelled > 0 and earned full directness. Directness now needs at least 0.1 m of travel; regression test added. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Replace the manifest-derived scene (real centres, invented 1 m squares) with DimSim's own annotation snapshot from PR #4208: 107 objects and walls with real extents, already in the ROS world frame odometry uses. Copied verbatim (misc/DimSim/scenes/apartment/object_detections.json at 7fd0e2f); the live SceneClient.get_object_detections() emits the same schema, so going live later is a one-line change in run(). load_scene() now reads that schema and filters geometrically instead of by word list: boxes whose bottom clears the robot's 0.74 m capsule top are overhead (door headers, wall cabinets, the TV); footprints contained in another kept footprint are clutter (books on shelves, plates on carts, bedding, a chair under its table). 107 -> 15 walls + 33 objects. Room bounds come from the outer walls. The second live run ended pressed against the real couch, 0.69 m before my approximate box even began, and scored 0.54 because arrival measured to the couch's centre, which is inside the couch. The grader now measures to the goal box edge; that run re-scores 0.85 (test added). The state gains goal_box so "reached" has something concrete to mean. Step criteria drop compass words: the scene's wall labels put "wall-east" at +y, which contradicts "+y = north" in the same prompt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…truction
Add typesafe_nav_bathtub. The bathtub is in the bathroom: from spawn the
robot must leave through the main doorway at (0, 0) and enter through the
bathroom door near (-2.5, 1), moving away from the goal twice. Greedy
bearing cannot do this, so the suite can now separate a policy that
reads the walls from one that does not.
The goal was an agent config (goal_label="sectional"), so two cases with
different goals could not share a run. It is now a case property: the
agent derives it from the instruction as the longest word that occurs in
a scene label ("sectional", "bathtub"), with goal_label kept as an
override. Code does the lookup, not Jev; letting the model pick the goal
would be a separate Choice question and a separate eval. A test checks
that every case's instruction resolves to the same box its grader uses.
The couch instruction becomes "navigate to the sectional couch" so it
names the object by a label word. Each case now builds its own
DimSimEnvironment; cases are tagged "couch" / "bathtub" for --tags.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The five options become body-frame (linear.x, angular.z) pairs: forward, backward, turn left, turn right, stop. twist() scales the pick by speed and turn_rate and that is the command; no atan2, no alignment band, no yaw correction, no pose needed for control. "Turn right" and "back" are now literal choices on Jev's menu rather than something code derives. The trade is explicit: Jev must now reason about its own heading (robot_yaw_deg is in the state; the instruction says 0 = +x, 90 = +y), which is the kind of numeric judgment it is weakest at. The bathtub run showed it cannot read wall geometry either way, so simpler wins until that is solved. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On the body-frame contract Jev's confidence sits around 0.31, so the 0.35 gate zeroed 49 of 60 decisions in the last run and became the de-facto controller: we were measuring the threshold, not the model. The SDK's choice is the argmax of probabilities (verified 60/60 on that run), and it is now applied as is. Only a "0,0" pick, reached > 0.8, max_ticks or the timeout stops the robot. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Contribution path
Standalone draft off
main. Scaffold for review — not ready to merge.Problem
We want to evaluate TypeSafe's System One model (Jev) as a navigation policy, not as a grader: feed it a god-view world state, have it emit a direction, and drive the robot in sim until it reports it has arrived. Then grade the episode offline from the recording.
Solution
TypeSafePolicyis anAgent, so it plugs into the existing runner with no framework change. Each tick it assembles aWorldState, asks Jev for one world-frame unit step plus a termination flag, rotates the pick into the body frame, and publishes a singleTwist. Grading is entirely post-hoc off the--recordrecording.Two contracts are deliberately isolated for iteration:
WorldState: robot pose, goal, room bounds, and labeled axis-aligned obstacle boxes, all in one global ROS world frame. Obstacles/room/goal come from a scene JSON; pose comes live off/odom.STEP_CRITERIA/build_questions(): aChoiceover five coordinate-keyed unit steps(0,0) (0,1) (1,0) (0,-1) (-1,0), plus areachedNoul.Design notes worth reviewing:
/nav_cmd_vel, not/cmd_vel.MovementManagerownscmd_vel: Out[Twist]in the go2 stack, so publishing there would collide. This drops into the existing arbitration slot, keeping teleop override andstop_movementworking.["unitree-go2", "mcp-server"]with no skill container, so MCP comes up with zero tools exposed.mcp-servercannot be dropped entirely —Sim.start()hard-blocks onMcpAdapter.wait_for_ready().NNN-request.json/NNN-response.jsonnaming matches whatEvalRunnerglobs forrequest_attempts.Scene
apartment_couch.jsonis generated from the real DimSim manifest (misc/DimSim/scenes/apartment/objects/manifest.json) bydimos.evals.suites.lib.dimsim_scene: 30 floor-standing obstacles with real centres, converted from Three.js Y-up to the ROS world frame using the same axis swapDimSimClientdoes. Wall-mounted items and tabletop clutter are filtered out (Jev's accuracy falls with irrelevant state).Footprints are a uniform 0.5 m half-extent — the manifest carries positions but not extents (those live in the GLBs). Real centres, approximate sizes;
--half-extenttunes it. A test pins the committed JSON to the generator output so it can't drift from the manifest silently.The goal is the couch (
sectional) with a 2.0 m arrival band, matching the DimSim-nativego-to-couchrubric so both harnesses agree on success.Dependency
New
typesafeextra (typesafe-sdk>=0.6.0). The package shipped after the repo's 7-dayexclude-newerwindow, so it gets anexclude-newer-packageoverride — same pattern as the nine packages already listed there.TYPESAFE_API_KEYis added todefault.envand the conftest passthrough.How to Test
dimos evals run dimos.evals.suites.typesafe_nav \ --agent dimos.evals.agents.typesafe_policy \ --set scene_json=dimos/evals/suites/scenes/apartment_couch.jsonOffline (no key, no sim):
Validation so far
ruff check/ruff format/ pre-commit clean.observe()merge, preflight rejection, manifest frame swap, clutter filter, room bounds, and committed-JSON-matches-generator.jev-1.13.0answered aChoice+Noulfan-out and every field the agent reads (model,choice,confidence,probabilities,noul,usage.input_tokens/output_tokens) came back as coded.modelwas never passed to the client, default model isjev-latestnotjev-1.13,Usagefields areinput_tokens/output_tokensso token metrics were silently zero, and the request timeout was implicit.Known gaps
reached = 0.27, so thereachedinstruction and a code-computed goal distance will need to agree on what "arrived" means.AI assistance
Claude (Claude Code) wrote the scaffold, generator, tests and PR body under user-directed design review; contracts and architecture decided by the user.
Checklist