devagent-physical-engine 0.11.2__tar.gz → 0.11.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {devagent_physical_engine-0.11.2/src/devagent_physical_engine.egg-info → devagent_physical_engine-0.11.4}/PKG-INFO +27 -5
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/README.md +27 -5
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/pyproject.toml +1 -1
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/__init__.py +1 -1
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/coordinator.py +40 -1
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ai_cli.py +88 -7
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/engineering_agent.py +44 -1
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/openai.py +82 -2
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_qualification.py +24 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4/src/devagent_physical_engine.egg-info}/PKG-INFO +27 -5
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine.egg-info/SOURCES.txt +1 -0
- devagent_physical_engine-0.11.4/tests/test_ai_cli.py +100 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_natural_language_agent_v07.py +21 -0
- devagent_physical_engine-0.11.4/tests/test_openai_strict_schema_v0113.py +68 -0
- devagent_physical_engine-0.11.2/tests/test_ai_cli.py +0 -54
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/NOTICE +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/setup.cfg +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/__init__.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/compiler.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/contracts.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/critic.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/evidence.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/interpreter.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/planner.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/recovery.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/routing.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/runtime.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/semantic.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/agent/structured.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/cli.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/continuous_clearance.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/doctor.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/engineering_request.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/execution.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/models.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/operating_envelope.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/__init__.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/benchmark.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/candidates.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/contracts.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/evaluator.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/evidence.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/experience.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/measured.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/optimizer.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/optimization/orchestrator.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/physical_campaign.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/physical_evidence.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/physical_motion.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/planning.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/preexecution_contract.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/__init__.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/anthropic.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/common.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/factory.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/provider_adapters/gemini.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/providers.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/qualification_cli.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/qualification_harness.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/robot_platform.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/robots.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/__init__.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/acceptance.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/commands.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/continuous_clearance_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/doctor.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/fk_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/frame_alignment.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/gazebo_world.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/joint_state_recorder.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/measured_motion.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/moveit_scene.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/preexecution.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/qualification_v10.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/qualification_v11.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/scene_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/state_validity_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/tf_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/tool_scene.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/tool_scene_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/trajectory.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/ur5e.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/ur5e_adapter.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/ur5e_v10_adapter.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/workpiece_scene.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/ros2/workpiece_scene_probe.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/setup_profile.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/sim_real_qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/simulation.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/simulation_platform.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/tool_collision.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/trajectory_qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/twin.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/twin_builder.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/twin_materialization.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/verification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine/workpiece_lifecycle.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine.egg-info/dependency_links.txt +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine.egg-info/entry_points.txt +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine.egg-info/requires.txt +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/src/devagent_physical_engine.egg-info/top_level.txt +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_agent_core.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_agent_semantic_hardening.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ai_cli_v07.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_continuous_clearance_v011.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_core.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_engineering_request_v07.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_interpreter_profiles_v08.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_live_provider_qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_measured_motion_v10.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_optimization_v05.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_physical_campaign_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_physical_evidence_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_physical_motion_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_preexecution_contract_v10.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_provider_and_qualification.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_qualification_cli_lifecycle_v06.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_qualification_harness_v06.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_real_provider_adapters.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_robot_platform_v08.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ros2_production_receipts_v011.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ros2_simulation.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ros_trajectory_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_setup_profile.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_sim_real_qualification_v011.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_simulation_platform_v08.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_tool_collision_v011.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_trajectory_qualification_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_twin_materialization_v10.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_twin_v08.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ur5e_adapter_scopes_v09.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_ur5e_v10_adapter_scopes.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_v052_stabilization.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_v08_catalog_mapping_regression.py +0 -0
- {devagent_physical_engine-0.11.2 → devagent_physical_engine-0.11.4}/tests/test_workpiece_lifecycle_v011.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: devagent-physical-engine
|
|
3
|
-
Version: 0.11.
|
|
3
|
+
Version: 0.11.4
|
|
4
4
|
Summary: Verification-first agentic planning runtime for robotic and industrial automation.
|
|
5
5
|
Author: Tom Ha
|
|
6
6
|
License: All Rights Reserved
|
|
@@ -49,10 +49,12 @@ DevAgent Smart Physical Engine adds AI-assisted engineering to existing automati
|
|
|
49
49
|
|
|
50
50
|
## Current version
|
|
51
51
|
|
|
52
|
-
**v0.11.
|
|
52
|
+
**v0.11.4 — Beta software / evidence-gated physical runtime**
|
|
53
53
|
|
|
54
54
|
The Python software stack is regression-tested on Python 3.11, 3.12, and 3.13 with separate static/coverage, package-install, and runtime dependency security gates. The UR5e Gazebo/MoveIt path now contains production-candidate continuous-clearance, tool-collision, and workpiece-lifecycle capabilities, but those physical-simulation scopes remain **EXPERIMENTAL until the executable target-workstation v0.11 campaign passes**.
|
|
55
55
|
|
|
56
|
+
v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 adds live terminal progress tracing for AI request, engineering, and provider-qualification workflows. Progress is written to `stderr`; final machine-readable JSON remains isolated on `stdout`.
|
|
57
|
+
|
|
56
58
|
Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
|
|
57
59
|
|
|
58
60
|
## v0.11 capability status
|
|
@@ -63,6 +65,7 @@ Real robot execution remains locked. Functional-safety certification and site qu
|
|
|
63
65
|
| OpenAI / Anthropic / Gemini structured adapters | Implemented |
|
|
64
66
|
| Planner / Critic / Recovery | Implemented |
|
|
65
67
|
| Natural-language engineering request front door | Implemented |
|
|
68
|
+
| Live terminal stage tracing (`stderr`) | Implemented; `--quiet` supported |
|
|
66
69
|
| Deterministic compiler / semantic / verification gates | Implemented |
|
|
67
70
|
| Multi-vendor robot profile registry | Implemented |
|
|
68
71
|
| UR5e / FANUC CRX / KUKA KR / ABB IRB abstraction | Implemented; physical qualification is vendor-specific |
|
|
@@ -195,19 +198,38 @@ export ANTHROPIC_API_KEY='...'
|
|
|
195
198
|
export GEMINI_API_KEY='...'
|
|
196
199
|
```
|
|
197
200
|
|
|
198
|
-
Examples
|
|
201
|
+
Examples use the currently supported deterministic goal action `load`:
|
|
199
202
|
|
|
200
203
|
```bash
|
|
201
204
|
devagent-physical-ai doctor --provider openai
|
|
202
205
|
devagent-physical-ai qualify --provider openai --model <model-id>
|
|
203
206
|
devagent-physical-ai request \
|
|
204
|
-
"Use a UR5e to
|
|
207
|
+
"Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
|
|
205
208
|
--provider openai --model <model-id>
|
|
206
209
|
devagent-physical-ai engineer \
|
|
207
|
-
"Use a UR5e to
|
|
210
|
+
"Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
|
|
208
211
|
--provider openai --model <model-id>
|
|
209
212
|
```
|
|
210
213
|
|
|
214
|
+
Live commands now show stage-by-stage progress while they are running, for example:
|
|
215
|
+
|
|
216
|
+
```text
|
|
217
|
+
[DevAgent + 0.00s] START provider=openai model=<model-id>
|
|
218
|
+
[DevAgent + 0.00s] INTERPRETER understanding engineering request
|
|
219
|
+
[DevAgent + 6.84s] INTERPRETER validated request_id=REQ-...
|
|
220
|
+
[DevAgent + 6.84s] REASONING risk_score=1 max_revisions=1 critic_threshold=0.70
|
|
221
|
+
[DevAgent + 6.84s] PLANNER revision=0 generating high-level task graph
|
|
222
|
+
[DevAgent + 12.21s] PLANNER revision=0 proposal compiled and deterministically verified
|
|
223
|
+
[DevAgent + 12.21s] CRITIC revision=0 reviewing verified plan
|
|
224
|
+
[DevAgent + 15.77s] CRITIC revision=0 decision=accept score=0.91
|
|
225
|
+
[DevAgent + 15.77s] PLANNING verified plan accepted after revision=0
|
|
226
|
+
[DevAgent + 15.77s] SIMULATION running deterministic model execution
|
|
227
|
+
[DevAgent + 15.78s] SIMULATION deterministic model execution completed
|
|
228
|
+
[DevAgent + 15.78s] COMPLETE ready_for_twin_simulation=true
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Progress logs are emitted to `stderr`, so piping `stdout` still produces clean JSON. For CI or scripts that do not want terminal progress, add `--quiet`.
|
|
232
|
+
|
|
211
233
|
Equivalent routing supports Anthropic and Gemini. Live AI qualification does not unlock real robot execution.
|
|
212
234
|
|
|
213
235
|
## Software production gates
|
|
@@ -8,10 +8,12 @@ DevAgent Smart Physical Engine adds AI-assisted engineering to existing automati
|
|
|
8
8
|
|
|
9
9
|
## Current version
|
|
10
10
|
|
|
11
|
-
**v0.11.
|
|
11
|
+
**v0.11.4 — Beta software / evidence-gated physical runtime**
|
|
12
12
|
|
|
13
13
|
The Python software stack is regression-tested on Python 3.11, 3.12, and 3.13 with separate static/coverage, package-install, and runtime dependency security gates. The UR5e Gazebo/MoveIt path now contains production-candidate continuous-clearance, tool-collision, and workpiece-lifecycle capabilities, but those physical-simulation scopes remain **EXPERIMENTAL until the executable target-workstation v0.11 campaign passes**.
|
|
14
14
|
|
|
15
|
+
v0.11.3 normalized provider-neutral agent schemas at the OpenAI transport boundary so strict Structured Outputs can represent DevAgent optional fields without changing Anthropic/Gemini or deterministic core semantics. v0.11.4 adds live terminal progress tracing for AI request, engineering, and provider-qualification workflows. Progress is written to `stderr`; final machine-readable JSON remains isolated on `stdout`.
|
|
16
|
+
|
|
15
17
|
Real robot execution remains locked. Functional-safety certification and site qualification are not claimed.
|
|
16
18
|
|
|
17
19
|
## v0.11 capability status
|
|
@@ -22,6 +24,7 @@ Real robot execution remains locked. Functional-safety certification and site qu
|
|
|
22
24
|
| OpenAI / Anthropic / Gemini structured adapters | Implemented |
|
|
23
25
|
| Planner / Critic / Recovery | Implemented |
|
|
24
26
|
| Natural-language engineering request front door | Implemented |
|
|
27
|
+
| Live terminal stage tracing (`stderr`) | Implemented; `--quiet` supported |
|
|
25
28
|
| Deterministic compiler / semantic / verification gates | Implemented |
|
|
26
29
|
| Multi-vendor robot profile registry | Implemented |
|
|
27
30
|
| UR5e / FANUC CRX / KUKA KR / ABB IRB abstraction | Implemented; physical qualification is vendor-specific |
|
|
@@ -154,19 +157,38 @@ export ANTHROPIC_API_KEY='...'
|
|
|
154
157
|
export GEMINI_API_KEY='...'
|
|
155
158
|
```
|
|
156
159
|
|
|
157
|
-
Examples
|
|
160
|
+
Examples use the currently supported deterministic goal action `load`:
|
|
158
161
|
|
|
159
162
|
```bash
|
|
160
163
|
devagent-physical-ai doctor --provider openai
|
|
161
164
|
devagent-physical-ai qualify --provider openai --model <model-id>
|
|
162
165
|
devagent-physical-ai request \
|
|
163
|
-
"Use a UR5e to
|
|
166
|
+
"Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
|
|
164
167
|
--provider openai --model <model-id>
|
|
165
168
|
devagent-physical-ai engineer \
|
|
166
|
-
"Use a UR5e to
|
|
169
|
+
"Use a UR5e to load BOX_101 from conveyor_a to cnc_04. Simulate and verify the plan." \
|
|
167
170
|
--provider openai --model <model-id>
|
|
168
171
|
```
|
|
169
172
|
|
|
173
|
+
Live commands now show stage-by-stage progress while they are running, for example:
|
|
174
|
+
|
|
175
|
+
```text
|
|
176
|
+
[DevAgent + 0.00s] START provider=openai model=<model-id>
|
|
177
|
+
[DevAgent + 0.00s] INTERPRETER understanding engineering request
|
|
178
|
+
[DevAgent + 6.84s] INTERPRETER validated request_id=REQ-...
|
|
179
|
+
[DevAgent + 6.84s] REASONING risk_score=1 max_revisions=1 critic_threshold=0.70
|
|
180
|
+
[DevAgent + 6.84s] PLANNER revision=0 generating high-level task graph
|
|
181
|
+
[DevAgent + 12.21s] PLANNER revision=0 proposal compiled and deterministically verified
|
|
182
|
+
[DevAgent + 12.21s] CRITIC revision=0 reviewing verified plan
|
|
183
|
+
[DevAgent + 15.77s] CRITIC revision=0 decision=accept score=0.91
|
|
184
|
+
[DevAgent + 15.77s] PLANNING verified plan accepted after revision=0
|
|
185
|
+
[DevAgent + 15.77s] SIMULATION running deterministic model execution
|
|
186
|
+
[DevAgent + 15.78s] SIMULATION deterministic model execution completed
|
|
187
|
+
[DevAgent + 15.78s] COMPLETE ready_for_twin_simulation=true
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Progress logs are emitted to `stderr`, so piping `stdout` still produces clean JSON. For CI or scripts that do not want terminal progress, add `--quiet`.
|
|
191
|
+
|
|
170
192
|
Equivalent routing supports Anthropic and Gemini. Live AI qualification does not unlock real robot execution.
|
|
171
193
|
|
|
172
194
|
## Software production gates
|
|
@@ -292,4 +314,4 @@ DevAgent Smart Physical Engine
|
|
|
292
314
|
Copyright © 2026 Tom Ha
|
|
293
315
|
Original creator: Tom Ha
|
|
294
316
|
Original project: https://github.com/tomha85/devagent-physical-engine
|
|
295
|
-
All rights reserved.
|
|
317
|
+
All rights reserved.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "devagent-physical-engine"
|
|
7
|
-
version = "0.11.
|
|
7
|
+
version = "0.11.4"
|
|
8
8
|
description = "Verification-first agentic planning runtime for robotic and industrial automation."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.11"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
-
from typing import Any, Mapping
|
|
4
|
+
from typing import Any, Callable, Mapping
|
|
5
5
|
|
|
6
6
|
from .contracts import AgentEvidence, CriticDecision, RoutingPolicy
|
|
7
7
|
from .critic import CriticAgent
|
|
@@ -9,6 +9,8 @@ from .evidence import task_graph_hash
|
|
|
9
9
|
from .planner import PlannerAgent
|
|
10
10
|
from ..models import Goal, Resource, TaskGraph, WorldState
|
|
11
11
|
|
|
12
|
+
ProgressCallback = Callable[[str, str], None]
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class PlanningCoordinationError(RuntimeError):
|
|
14
16
|
pass
|
|
@@ -52,12 +54,18 @@ class PlanningCoordinator:
|
|
|
52
54
|
critic_policy: RoutingPolicy,
|
|
53
55
|
*,
|
|
54
56
|
engineering_context: Mapping[str, Any] | None = None,
|
|
57
|
+
progress: ProgressCallback | None = None,
|
|
55
58
|
) -> VerifiedPlanArtifact:
|
|
56
59
|
feedback: tuple[str, ...] = ()
|
|
57
60
|
planner_evidence: list[AgentEvidence] = []
|
|
58
61
|
critic_evidence: list[AgentEvidence] = []
|
|
59
62
|
|
|
60
63
|
for revision in range(self.max_revision_rounds + 1):
|
|
64
|
+
if progress is not None:
|
|
65
|
+
progress(
|
|
66
|
+
"PLANNER",
|
|
67
|
+
f"revision={revision} generating high-level task graph",
|
|
68
|
+
)
|
|
61
69
|
planned = self.planner.plan(
|
|
62
70
|
goal,
|
|
63
71
|
resources,
|
|
@@ -67,6 +75,15 @@ class PlanningCoordinator:
|
|
|
67
75
|
engineering_context=engineering_context,
|
|
68
76
|
)
|
|
69
77
|
planner_evidence.append(planned.evidence)
|
|
78
|
+
if progress is not None:
|
|
79
|
+
progress(
|
|
80
|
+
"PLANNER",
|
|
81
|
+
f"revision={revision} proposal compiled and deterministically verified",
|
|
82
|
+
)
|
|
83
|
+
progress(
|
|
84
|
+
"CRITIC",
|
|
85
|
+
f"revision={revision} reviewing verified plan",
|
|
86
|
+
)
|
|
70
87
|
|
|
71
88
|
reviewed = self.critic.review(
|
|
72
89
|
goal,
|
|
@@ -77,11 +94,24 @@ class PlanningCoordinator:
|
|
|
77
94
|
engineering_context=engineering_context,
|
|
78
95
|
)
|
|
79
96
|
critic_evidence.append(reviewed.evidence)
|
|
97
|
+
if progress is not None:
|
|
98
|
+
progress(
|
|
99
|
+
"CRITIC",
|
|
100
|
+
(
|
|
101
|
+
f"revision={revision} decision={reviewed.critique.decision.value} "
|
|
102
|
+
f"score={reviewed.critique.score:.2f}"
|
|
103
|
+
),
|
|
104
|
+
)
|
|
80
105
|
|
|
81
106
|
if (
|
|
82
107
|
reviewed.critique.decision is CriticDecision.ACCEPT
|
|
83
108
|
and reviewed.critique.score >= self.min_accept_score
|
|
84
109
|
):
|
|
110
|
+
if progress is not None:
|
|
111
|
+
progress(
|
|
112
|
+
"PLANNING",
|
|
113
|
+
f"verified plan accepted after revision={revision}",
|
|
114
|
+
)
|
|
85
115
|
return VerifiedPlanArtifact(
|
|
86
116
|
graph=planned.graph,
|
|
87
117
|
graph_hash=task_graph_hash(planned.graph),
|
|
@@ -91,6 +121,8 @@ class PlanningCoordinator:
|
|
|
91
121
|
)
|
|
92
122
|
|
|
93
123
|
if reviewed.critique.decision is CriticDecision.REJECT:
|
|
124
|
+
if progress is not None:
|
|
125
|
+
progress("PLANNING", f"critic rejected revision={revision}")
|
|
94
126
|
raise PlanningCoordinationError(
|
|
95
127
|
"critic_rejected_plan:"
|
|
96
128
|
+ ";".join(reviewed.critique.issues)
|
|
@@ -104,5 +136,12 @@ class PlanningCoordinator:
|
|
|
104
136
|
feedback = reviewed.critique.issues or (
|
|
105
137
|
"critic_requested_revision",
|
|
106
138
|
)
|
|
139
|
+
if progress is not None:
|
|
140
|
+
progress(
|
|
141
|
+
"PLANNING",
|
|
142
|
+
f"revision={revision} requires another planning round",
|
|
143
|
+
)
|
|
107
144
|
|
|
145
|
+
if progress is not None:
|
|
146
|
+
progress("PLANNING", "revision budget exhausted")
|
|
108
147
|
raise PlanningCoordinationError("revision_budget_exhausted")
|
|
@@ -4,6 +4,9 @@ import argparse
|
|
|
4
4
|
import importlib.util
|
|
5
5
|
import json
|
|
6
6
|
import os
|
|
7
|
+
import sys
|
|
8
|
+
import time
|
|
9
|
+
from collections.abc import Callable
|
|
7
10
|
|
|
8
11
|
from .agent import (
|
|
9
12
|
AgentRuntime,
|
|
@@ -20,6 +23,8 @@ from .provider_adapters import PROVIDER_NAMES, create_provider
|
|
|
20
23
|
from .provider_qualification import qualify_live_provider
|
|
21
24
|
from .providers import ModelRegistry, ModelSpec, QualificationStatus
|
|
22
25
|
|
|
26
|
+
ProgressCallback = Callable[[str, str], None]
|
|
27
|
+
|
|
23
28
|
|
|
24
29
|
def _add_live_request_args(parser: argparse.ArgumentParser) -> None:
|
|
25
30
|
parser.add_argument("text", help="Natural-language engineering request")
|
|
@@ -33,6 +38,11 @@ def _add_live_request_args(parser: argparse.ArgumentParser) -> None:
|
|
|
33
38
|
)
|
|
34
39
|
parser.add_argument("--timeout", type=float, default=30.0)
|
|
35
40
|
parser.add_argument("--max-attempts", type=int, default=2)
|
|
41
|
+
parser.add_argument(
|
|
42
|
+
"--quiet",
|
|
43
|
+
action="store_true",
|
|
44
|
+
help="Suppress terminal progress logs; final JSON remains on stdout",
|
|
45
|
+
)
|
|
36
46
|
|
|
37
47
|
|
|
38
48
|
def _parser() -> argparse.ArgumentParser:
|
|
@@ -54,6 +64,11 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
54
64
|
qualify.add_argument("--timeout", type=float, default=30.0)
|
|
55
65
|
qualify.add_argument("--max-attempts", type=int, default=2)
|
|
56
66
|
qualify.add_argument("--max-revisions", type=int, default=2)
|
|
67
|
+
qualify.add_argument(
|
|
68
|
+
"--quiet",
|
|
69
|
+
action="store_true",
|
|
70
|
+
help="Suppress terminal progress logs; final JSON remains on stdout",
|
|
71
|
+
)
|
|
57
72
|
|
|
58
73
|
request = sub.add_parser(
|
|
59
74
|
"request",
|
|
@@ -93,6 +108,28 @@ def provider_doctor(provider: str) -> dict:
|
|
|
93
108
|
}
|
|
94
109
|
|
|
95
110
|
|
|
111
|
+
def _terminal_progress(enabled: bool) -> ProgressCallback | None:
|
|
112
|
+
if not enabled:
|
|
113
|
+
return None
|
|
114
|
+
started = time.monotonic()
|
|
115
|
+
|
|
116
|
+
def emit(stage: str, message: str) -> None:
|
|
117
|
+
elapsed = time.monotonic() - started
|
|
118
|
+
label = stage.strip().upper()[:12]
|
|
119
|
+
print(
|
|
120
|
+
f"[DevAgent +{elapsed:7.2f}s] {label:<12} {message}",
|
|
121
|
+
file=sys.stderr,
|
|
122
|
+
flush=True,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return emit
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def _emit(progress: ProgressCallback | None, stage: str, message: str) -> None:
|
|
129
|
+
if progress is not None:
|
|
130
|
+
progress(stage, message)
|
|
131
|
+
|
|
132
|
+
|
|
96
133
|
def _live_runtime(provider_name: str, model: str, *, timeout: float, max_attempts: int):
|
|
97
134
|
provider = create_provider(provider_name)
|
|
98
135
|
registry = ModelRegistry()
|
|
@@ -123,30 +160,51 @@ def _live_runtime(provider_name: str, model: str, *, timeout: float, max_attempt
|
|
|
123
160
|
return provider, runtime, policy
|
|
124
161
|
|
|
125
162
|
|
|
126
|
-
def _request(args: argparse.Namespace) -> int:
|
|
163
|
+
def _request(args: argparse.Namespace, progress: ProgressCallback | None = None) -> int:
|
|
164
|
+
_emit(progress, "START", f"provider={args.provider} model={args.model}")
|
|
127
165
|
_, runtime, policy = _live_runtime(
|
|
128
166
|
args.provider,
|
|
129
167
|
args.model,
|
|
130
168
|
timeout=args.timeout,
|
|
131
169
|
max_attempts=args.max_attempts,
|
|
132
170
|
)
|
|
171
|
+
_emit(progress, "INTERPRETER", "understanding engineering request")
|
|
133
172
|
interpreted = RequirementInterpreterAgent(runtime).interpret(
|
|
134
173
|
args.text,
|
|
135
174
|
policy,
|
|
136
175
|
follow_ups=args.follow_up,
|
|
137
176
|
)
|
|
177
|
+
if interpreted.validated is not None:
|
|
178
|
+
_emit(
|
|
179
|
+
progress,
|
|
180
|
+
"INTERPRETER",
|
|
181
|
+
f"validated request_id={interpreted.validated.request_id}",
|
|
182
|
+
)
|
|
183
|
+
elif interpreted.rejection_code:
|
|
184
|
+
_emit(
|
|
185
|
+
progress,
|
|
186
|
+
"INTERPRETER",
|
|
187
|
+
f"request rejected code={interpreted.rejection_code}",
|
|
188
|
+
)
|
|
189
|
+
else:
|
|
190
|
+
_emit(progress, "INTERPRETER", "more engineering information required")
|
|
191
|
+
|
|
138
192
|
payload = interpreted.to_dict()
|
|
139
193
|
payload["provider_mode"] = "simulation_only_ephemeral_route"
|
|
140
194
|
payload["real_execution_allowed"] = False
|
|
141
195
|
print(json.dumps(payload, indent=2))
|
|
142
196
|
if interpreted.validated is not None:
|
|
197
|
+
_emit(progress, "COMPLETE", "request validation passed")
|
|
143
198
|
return 0
|
|
144
199
|
if interpreted.rejection_code:
|
|
200
|
+
_emit(progress, "COMPLETE", "request validation rejected")
|
|
145
201
|
return 4
|
|
202
|
+
_emit(progress, "COMPLETE", "request requires follow-up information")
|
|
146
203
|
return 3
|
|
147
204
|
|
|
148
205
|
|
|
149
|
-
def _engineer(args: argparse.Namespace) -> int:
|
|
206
|
+
def _engineer(args: argparse.Namespace, progress: ProgressCallback | None = None) -> int:
|
|
207
|
+
_emit(progress, "START", f"provider={args.provider} model={args.model}")
|
|
150
208
|
_, runtime, policy = _live_runtime(
|
|
151
209
|
args.provider,
|
|
152
210
|
args.model,
|
|
@@ -164,6 +222,7 @@ def _engineer(args: argparse.Namespace) -> int:
|
|
|
164
222
|
planner_policy=policy,
|
|
165
223
|
critic_policy=policy,
|
|
166
224
|
follow_ups=args.follow_up,
|
|
225
|
+
progress=progress,
|
|
167
226
|
)
|
|
168
227
|
payload = result.to_dict()
|
|
169
228
|
payload["provider_mode"] = "simulation_only_ephemeral_route"
|
|
@@ -171,12 +230,25 @@ def _engineer(args: argparse.Namespace) -> int:
|
|
|
171
230
|
print(json.dumps(payload, indent=2))
|
|
172
231
|
if result.ready_for_twin_simulation:
|
|
173
232
|
return 0
|
|
233
|
+
_emit(
|
|
234
|
+
progress,
|
|
235
|
+
"COMPLETE",
|
|
236
|
+
(
|
|
237
|
+
"ready_for_twin_simulation=false "
|
|
238
|
+
f"failure_code={result.failure_code or 'more_information_required'}"
|
|
239
|
+
),
|
|
240
|
+
)
|
|
174
241
|
if result.interpretation.validated is None and not result.interpretation.rejection_code:
|
|
175
242
|
return 3
|
|
176
243
|
return 4
|
|
177
244
|
|
|
178
245
|
|
|
179
|
-
def _natural_language_failure(
|
|
246
|
+
def _natural_language_failure(
|
|
247
|
+
exc: BaseException,
|
|
248
|
+
*,
|
|
249
|
+
progress: ProgressCallback | None = None,
|
|
250
|
+
) -> int:
|
|
251
|
+
_emit(progress, "FAILED", f"pipeline stopped type={type(exc).__name__}")
|
|
180
252
|
print(
|
|
181
253
|
json.dumps(
|
|
182
254
|
{
|
|
@@ -198,18 +270,21 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
198
270
|
print(json.dumps(result, indent=2))
|
|
199
271
|
return 0 if result["ready_for_live_qualification"] else 1
|
|
200
272
|
|
|
273
|
+
progress = _terminal_progress(not getattr(args, "quiet", False))
|
|
274
|
+
|
|
201
275
|
if args.command == "request":
|
|
202
276
|
try:
|
|
203
|
-
return _request(args)
|
|
277
|
+
return _request(args, progress)
|
|
204
278
|
except Exception as exc:
|
|
205
|
-
return _natural_language_failure(exc)
|
|
279
|
+
return _natural_language_failure(exc, progress=progress)
|
|
206
280
|
|
|
207
281
|
if args.command == "engineer":
|
|
208
282
|
try:
|
|
209
|
-
return _engineer(args)
|
|
283
|
+
return _engineer(args, progress)
|
|
210
284
|
except Exception as exc:
|
|
211
|
-
return _natural_language_failure(exc)
|
|
285
|
+
return _natural_language_failure(exc, progress=progress)
|
|
212
286
|
|
|
287
|
+
_emit(progress, "START", f"provider={args.provider} model={args.model}")
|
|
213
288
|
provider = create_provider(args.provider)
|
|
214
289
|
report = qualify_live_provider(
|
|
215
290
|
provider,
|
|
@@ -217,6 +292,12 @@ def main(argv: list[str] | None = None) -> int:
|
|
|
217
292
|
timeout_s=args.timeout,
|
|
218
293
|
max_attempts=args.max_attempts,
|
|
219
294
|
max_revision_rounds=args.max_revisions,
|
|
295
|
+
progress=progress,
|
|
296
|
+
)
|
|
297
|
+
_emit(
|
|
298
|
+
progress,
|
|
299
|
+
"RESULT",
|
|
300
|
+
f"passed={str(report.passed).lower()} failure_code={report.failure_code or 'none'}",
|
|
220
301
|
)
|
|
221
302
|
print(json.dumps(report.to_dict(), indent=2))
|
|
222
303
|
return 0 if report.passed else 2
|
|
@@ -4,7 +4,12 @@ from dataclasses import asdict, dataclass
|
|
|
4
4
|
from typing import Any, Sequence
|
|
5
5
|
|
|
6
6
|
from .agent.contracts import AgentEvidence, RoutingPolicy
|
|
7
|
-
from .agent.coordinator import
|
|
7
|
+
from .agent.coordinator import (
|
|
8
|
+
PlanningCoordinationError,
|
|
9
|
+
PlanningCoordinator,
|
|
10
|
+
ProgressCallback,
|
|
11
|
+
VerifiedPlanArtifact,
|
|
12
|
+
)
|
|
8
13
|
from .agent.critic import CriticAgent
|
|
9
14
|
from .agent.evidence import task_graph_to_dict
|
|
10
15
|
from .agent.interpreter import InterpretedEngineeringRequest, RequirementInterpreterAgent
|
|
@@ -153,7 +158,10 @@ class SmartEngineeringAgent:
|
|
|
153
158
|
planner_policy: RoutingPolicy,
|
|
154
159
|
critic_policy: RoutingPolicy,
|
|
155
160
|
follow_ups: Sequence[str] = (),
|
|
161
|
+
progress: ProgressCallback | None = None,
|
|
156
162
|
) -> SmartEngineeringResult:
|
|
163
|
+
if progress is not None:
|
|
164
|
+
progress("INTERPRETER", "understanding engineering request")
|
|
157
165
|
interpreted = self.interpreter.interpret(
|
|
158
166
|
text,
|
|
159
167
|
interpreter_policy,
|
|
@@ -161,12 +169,32 @@ class SmartEngineeringAgent:
|
|
|
161
169
|
)
|
|
162
170
|
request = interpreted.validated
|
|
163
171
|
if request is None:
|
|
172
|
+
if progress is not None:
|
|
173
|
+
if interpreted.rejection_code:
|
|
174
|
+
progress(
|
|
175
|
+
"INTERPRETER",
|
|
176
|
+
f"request rejected code={interpreted.rejection_code}",
|
|
177
|
+
)
|
|
178
|
+
else:
|
|
179
|
+
progress("INTERPRETER", "more engineering information required")
|
|
164
180
|
return SmartEngineeringResult(
|
|
165
181
|
interpretation=interpreted,
|
|
166
182
|
failure_code=interpreted.rejection_code,
|
|
167
183
|
)
|
|
168
184
|
|
|
185
|
+
if progress is not None:
|
|
186
|
+
progress("INTERPRETER", f"validated request_id={request.request_id}")
|
|
187
|
+
|
|
169
188
|
strategy = self.reasoning_policy.select(request)
|
|
189
|
+
if progress is not None:
|
|
190
|
+
progress(
|
|
191
|
+
"REASONING",
|
|
192
|
+
(
|
|
193
|
+
f"risk_score={strategy.risk_score} "
|
|
194
|
+
f"max_revisions={strategy.max_revision_rounds} "
|
|
195
|
+
f"critic_threshold={strategy.min_critic_score:.2f}"
|
|
196
|
+
),
|
|
197
|
+
)
|
|
170
198
|
goal = request.goal()
|
|
171
199
|
resources = [request.resource()]
|
|
172
200
|
# Explicit nominal model state, never represented as observed site state.
|
|
@@ -191,14 +219,19 @@ class SmartEngineeringAgent:
|
|
|
191
219
|
planner_policy,
|
|
192
220
|
critic_policy,
|
|
193
221
|
engineering_context=request.to_dict(),
|
|
222
|
+
progress=progress,
|
|
194
223
|
)
|
|
195
224
|
except PlanningCoordinationError as exc:
|
|
225
|
+
if progress is not None:
|
|
226
|
+
progress("PLANNING", "planning coordination failed")
|
|
196
227
|
return SmartEngineeringResult(
|
|
197
228
|
interpretation=interpreted,
|
|
198
229
|
reasoning_strategy=strategy,
|
|
199
230
|
failure_code=f"planning_failed:{str(exc).split(':', 1)[0]}",
|
|
200
231
|
)
|
|
201
232
|
except Exception as exc:
|
|
233
|
+
if progress is not None:
|
|
234
|
+
progress("PLANNING", f"planning failed type={type(exc).__name__}")
|
|
202
235
|
# Never expose provider/internal exception text as customer evidence.
|
|
203
236
|
return SmartEngineeringResult(
|
|
204
237
|
interpretation=interpreted,
|
|
@@ -206,6 +239,8 @@ class SmartEngineeringAgent:
|
|
|
206
239
|
failure_code=f"planning_failed:{type(exc).__name__}",
|
|
207
240
|
)
|
|
208
241
|
|
|
242
|
+
if progress is not None:
|
|
243
|
+
progress("SIMULATION", "running deterministic model execution")
|
|
209
244
|
simulation_world = WorldState(facts=set(world.facts), values=dict(world.values))
|
|
210
245
|
simulation = ExecutionSupervisor().execute(
|
|
211
246
|
plan.graph,
|
|
@@ -216,6 +251,11 @@ class SmartEngineeringAgent:
|
|
|
216
251
|
)
|
|
217
252
|
if not simulation.completed:
|
|
218
253
|
issue_codes = sorted({issue.code for issue in simulation.issues})
|
|
254
|
+
if progress is not None:
|
|
255
|
+
progress(
|
|
256
|
+
"SIMULATION",
|
|
257
|
+
"blocked by deterministic constraints",
|
|
258
|
+
)
|
|
219
259
|
return SmartEngineeringResult(
|
|
220
260
|
interpretation=interpreted,
|
|
221
261
|
reasoning_strategy=strategy,
|
|
@@ -227,6 +267,9 @@ class SmartEngineeringAgent:
|
|
|
227
267
|
),
|
|
228
268
|
)
|
|
229
269
|
|
|
270
|
+
if progress is not None:
|
|
271
|
+
progress("SIMULATION", "deterministic model execution completed")
|
|
272
|
+
progress("COMPLETE", "ready_for_twin_simulation=true")
|
|
230
273
|
return SmartEngineeringResult(
|
|
231
274
|
interpretation=interpreted,
|
|
232
275
|
reasoning_strategy=strategy,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any, Callable
|
|
3
|
+
from typing import Any, Callable, Mapping
|
|
4
4
|
|
|
5
5
|
from ..agent.contracts import ModelRequest, ModelResponse
|
|
6
6
|
from ..agent.runtime import ProviderError, ProviderProtocolError, ProviderUnavailable
|
|
@@ -17,6 +17,86 @@ from .common import (
|
|
|
17
17
|
)
|
|
18
18
|
|
|
19
19
|
|
|
20
|
+
def _nullable_openai_schema(value: Mapping[str, Any]) -> dict[str, Any]:
|
|
21
|
+
"""Return an OpenAI-strict-compatible nullable schema for an optional field."""
|
|
22
|
+
|
|
23
|
+
result = dict(value)
|
|
24
|
+
schema_type = result.get("type")
|
|
25
|
+
if isinstance(schema_type, str):
|
|
26
|
+
if schema_type != "null":
|
|
27
|
+
result["type"] = [schema_type, "null"]
|
|
28
|
+
return result
|
|
29
|
+
if isinstance(schema_type, list):
|
|
30
|
+
types = list(schema_type)
|
|
31
|
+
if "null" not in types:
|
|
32
|
+
types.append("null")
|
|
33
|
+
result["type"] = types
|
|
34
|
+
return result
|
|
35
|
+
|
|
36
|
+
enum = result.get("enum")
|
|
37
|
+
if isinstance(enum, list):
|
|
38
|
+
values = list(enum)
|
|
39
|
+
if None not in values:
|
|
40
|
+
values.append(None)
|
|
41
|
+
result["enum"] = values
|
|
42
|
+
return result
|
|
43
|
+
|
|
44
|
+
any_of = result.get("anyOf")
|
|
45
|
+
if isinstance(any_of, list):
|
|
46
|
+
branches = list(any_of)
|
|
47
|
+
if not any(
|
|
48
|
+
isinstance(branch, Mapping) and branch.get("type") == "null"
|
|
49
|
+
for branch in branches
|
|
50
|
+
):
|
|
51
|
+
branches.append({"type": "null"})
|
|
52
|
+
result["anyOf"] = branches
|
|
53
|
+
return result
|
|
54
|
+
|
|
55
|
+
return {"anyOf": [result, {"type": "null"}]}
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _openai_strict_schema(schema: Mapping[str, Any]) -> dict[str, Any]:
|
|
59
|
+
"""Normalize a provider-neutral schema for OpenAI strict Structured Outputs.
|
|
60
|
+
|
|
61
|
+
DevAgent's core schemas intentionally permit omitted optional fields because
|
|
62
|
+
Anthropic, Gemini, and deterministic parsers support that representation.
|
|
63
|
+
OpenAI strict Structured Outputs instead requires every property of every
|
|
64
|
+
object to appear in ``required``. Preserve provider-neutral core semantics by
|
|
65
|
+
making only those originally optional properties nullable in the OpenAI
|
|
66
|
+
transport schema, while keeping deterministic parsing/compilation as the
|
|
67
|
+
final authority.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
clean = provider_schema(schema)
|
|
71
|
+
|
|
72
|
+
def normalize(value: Any) -> Any:
|
|
73
|
+
if isinstance(value, list):
|
|
74
|
+
return [normalize(item) for item in value]
|
|
75
|
+
if not isinstance(value, dict):
|
|
76
|
+
return value
|
|
77
|
+
|
|
78
|
+
result = {key: normalize(item) for key, item in value.items()}
|
|
79
|
+
properties = result.get("properties")
|
|
80
|
+
if isinstance(properties, dict):
|
|
81
|
+
required_raw = result.get("required", [])
|
|
82
|
+
required = set(required_raw) if isinstance(required_raw, list) else set()
|
|
83
|
+
normalized_properties: dict[str, Any] = {}
|
|
84
|
+
for name, property_schema in properties.items():
|
|
85
|
+
if name in required or not isinstance(property_schema, Mapping):
|
|
86
|
+
normalized_properties[name] = property_schema
|
|
87
|
+
else:
|
|
88
|
+
normalized_properties[name] = _nullable_openai_schema(property_schema)
|
|
89
|
+
result["properties"] = normalized_properties
|
|
90
|
+
result["required"] = list(normalized_properties)
|
|
91
|
+
result["additionalProperties"] = False
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
normalized = normalize(clean)
|
|
95
|
+
if not isinstance(normalized, dict): # pragma: no cover - schema contract is mapping
|
|
96
|
+
raise TypeError("openai_schema_must_be_object")
|
|
97
|
+
return normalized
|
|
98
|
+
|
|
99
|
+
|
|
20
100
|
class OpenAIProvider:
|
|
21
101
|
provider_name = "openai"
|
|
22
102
|
|
|
@@ -58,7 +138,7 @@ class OpenAIProvider:
|
|
|
58
138
|
"type": "json_schema",
|
|
59
139
|
"name": f"devagent_{request.role.value}",
|
|
60
140
|
"strict": True,
|
|
61
|
-
"schema":
|
|
141
|
+
"schema": _openai_strict_schema(request.output_schema),
|
|
62
142
|
}
|
|
63
143
|
},
|
|
64
144
|
max_output_tokens=self._max_output_tokens,
|