Hackathon Submission: 'DogOps' Factory Automation Dashboard#2281
Hackathon Submission: 'DogOps' Factory Automation Dashboard#2281seichris wants to merge 2 commits into
Conversation
|
Watch our video at: https://www.youtube.com/watch?v=iM8s6TYot9c warehouse.dimos-go2.demo.video.mp4 |
Greptile SummaryThis PR adds the DogOps factory-automation dashboard as a new experimental package under
Confidence Score: 3/5Not safe to merge as-is: the dogops-go2-stack blueprint entry is outright broken (wrong symbol name), and two dashboard endpoints allow unauthenticated state mutation reachable via CSRF from the local browser. The broken symbol in all_blueprints.py means dimos/robot/all_blueprints.py (wrong symbol for dogops-go2-stack) and dimos/experimental/dogops/dashboard.py (unguarded /api/work_orders and /api/operator/event endpoints)
|
| Filename | Overview |
|---|---|
| dimos/agents/mcp/mcp_server.py | Replaces remote get_skills() RPC with local class inspection (_module_class_skills) to avoid startup deadlocks; SkillInfo import moved to runtime. The dir()-based skill discovery may expose inherited skills from base classes. |
| dimos/robot/all_blueprints.py | Registers dogops-go2-stack and unitree-go2-dogops blueprints plus two new modules. The dogops-go2-stack entry references a non-existent symbol dogops_go2_stack — should be unitree_go2_dogops. |
| dimos/experimental/dogops/dashboard.py | Large new dashboard HTTP server (~2400 lines). POST /api/work_orders/{id}/ready_to_verify and POST /api/operator/event are the only mutating endpoints that lack any authorization guard, enabling CSRF-style mutations from any browser tab. |
| dimos/experimental/dogops/blueprints.py | Defines unitree_go2_dogops blueprint but calls the factory function at module-import time, triggering heavy imports during blueprint listing; exports unitree_go2_dogops, not dogops_go2_stack. |
| dimos/experimental/dogops/skills.py | DogOpsSkillContainer exposes robot navigation, scanning, and reporting skills via MCP with robust fallbacks for full DimOS vs. standalone checkout. |
| dimos/experimental/dogops/route_executor.py | Waypoint-by-waypoint route execution with retry, progress timeout, stop-on-request, and dry-run support; locks route_execution.json to prevent concurrent runs. |
| dimos/robot/unitree/go2/blueprints/agentic/unitree_go2_dogops.py | Thin blueprint wiring the Go2 hardware stack to DogOps modules; uses try/except fallback stubs so the file compiles without the full DimOS SDK installed. |
| dimos/experimental/dogops/store.py | JSON/JSONL run store for offline simulation; write operations are straightforward but lack concurrency locks (acceptable for the current single-threaded offline use case). |
| dimos/experimental/dogops/models.py | Pydantic data models for the DogOps domain (zones, assets, incidents, nav events, etc.); well-structured with explicit enums and field defaults. |
Sequence Diagram
sequenceDiagram
participant Browser
participant DashboardServer as DogOpsDashboardServer
participant MCPServer as McpServer
participant DogOpsSkillContainer
participant DogOpsStore
Note over Browser,DogOpsStore: Normal robot-control flow (authenticated)
Browser->>DashboardServer: POST /api/robot/jog + X-DogOps-Control-Token
DashboardServer->>DashboardServer: _authorize_robot_control() ok
DashboardServer->>MCPServer: HTTP call to follow_route / go_to
MCPServer->>DogOpsSkillContainer: RPC via RpcCall
DogOpsSkillContainer->>DogOpsStore: read/write state.json
Note over Browser,DashboardServer: Unguarded mutation - no auth
Browser->>DashboardServer: POST /api/work_orders/WO-001/ready_to_verify
DashboardServer->>DogOpsStore: load_existing, update_work_order, write_state, write_report
Note over MCPServer,DogOpsSkillContainer: Startup skill discovery new approach
MCPServer->>MCPServer: on_system_modules(modules)
MCPServer->>MCPServer: _module_class_skills inspects actor_class locally
MCPServer->>DogOpsSkillContainer: RPC at call time only
Reviews (1): Last reviewed commit: "Default DogOps live routes to direct dri..." | Re-trigger Greptile
| "demo-osm": "dimos.mapping.osm.demo_osm:demo_osm", | ||
| "demo-skill": "dimos.agents.skills.demo_skill:demo_skill", | ||
| "desk-marker-tf": "dimos.perception.fiducial.blueprints.desk_marker_tf:desk_marker_tf", | ||
| "dogops-go2-stack": "dimos.experimental.dogops.blueprints:dogops_go2_stack", |
There was a problem hiding this comment.
Broken blueprint symbol —
dogops_go2_stack is never defined
dimos/experimental/dogops/blueprints.py exports unitree_go2_dogops (line 48), not dogops_go2_stack. Anyone who runs dimos run dogops-go2-stack or loads this entry in the registry will get an ImportError / AttributeError at the module-import boundary. The "unitree-go2-dogops" entry below (which correctly points to unitree_go2_dogops.py) covers the same blueprint — so either the dogops-go2-stack entry needs to point to the right symbol (unitree_go2_dogops) or a dogops_go2_stack alias needs to be added to blueprints.py.
| "dogops-go2-stack": "dimos.experimental.dogops.blueprints:dogops_go2_stack", | |
| "dogops-go2-stack": "dimos.experimental.dogops.blueprints:unitree_go2_dogops", |
| def do_POST(self) -> None: | ||
| path = urlparse(self.path).path | ||
| if path.startswith("/api/work_orders/") and path.endswith("/ready_to_verify"): | ||
| work_order_id = path.split("/")[3] | ||
| self._mark_work_order_ready(work_order_id) | ||
| elif path == "/api/operator/event": | ||
| self._record_operator_event() |
There was a problem hiding this comment.
Unauthenticated state-mutation endpoints
POST /api/work_orders/{id}/ready_to_verify and POST /api/operator/event are the only two mutating POST routes that skip every authorization check. Every other mutating POST in this handler (/api/qr/events, /api/map/…, /api/robot/…) calls _authorize_map_authoring_write() or _authorize_robot_control() first. Without the loopback + origin + token checks, any page loaded in the browser can send a CSRF-style fetch to http://127.0.0.1:8765/api/work_orders/WO-001/ready_to_verify — the mutation completes even though the response is blocked by the browser's same-origin policy. _mark_work_order_ready also re-writes state.json and report.md, so the impact is data corruption rather than just a log entry.
| for name in dir(actor_class): | ||
| attr = getattr(actor_class, name, None) | ||
| if not callable(attr) or not hasattr(attr, "__skill__"): | ||
| continue | ||
| schema = tool(attr).args_schema.model_json_schema() | ||
| properties = schema.get("properties") | ||
| if isinstance(properties, dict): | ||
| properties.pop("self", None) | ||
| required = schema.get("required") | ||
| if isinstance(required, list) and "self" in required: | ||
| schema["required"] = [item for item in required if item != "self"] | ||
| skills.append( | ||
| SkillInfo( | ||
| class_name=actor_class.__name__, | ||
| func_name=name, | ||
| args_schema=json.dumps(schema), | ||
| ) | ||
| ) | ||
| return skills |
There was a problem hiding this comment.
dir() iteration may expose inherited / unrelated __skill__-marked attributes
dir(actor_class) walks the entire MRO — including all Module base-class methods and any mixin that happens to carry __skill__. If a base class ever gains a @skill-decorated method, it will appear as a skill for every deployed module rather than just the one that defines it, inflating the tool list and potentially registering a duplicate func_name key in app.state.rpc_calls. Consider restricting to vars(actor_class) to capture only the methods defined directly on the concrete class.
| ).global_config(n_workers=12, robot_model="unitree_go2") | ||
|
|
||
|
|
||
| unitree_go2_dogops = build_unitree_go2_dogops_blueprint() |
There was a problem hiding this comment.
Module-level blueprint construction triggers imports at list time
build_unitree_go2_dogops_blueprint() is called at module-import time, which means importing dimos.experimental.dogops.blueprints (e.g. when dimos list walks all_blueprints) immediately executes all the try import blocks, including dimos.agents.mcp.mcp_server and dimos.core.coordination.blueprints. This is the same pattern that the MCP change in this PR is trying to fix for deadlocks. The preferred DimOS pattern is a lazy factory function.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
|
Demo video: https://youtu.be/iM8s6TYot9c |
Team: Warehouse automation
Original project repo: https://github.com/lg66lgnb-sketch/dimos-unitree-go2
Pitch
DogOps turns a Unitree Go2 into an automated site reliability operator for factories and warehouses. Instead of sending people through the floor with clipboards, teams can use DimOS to collect real-world operational data with robot-scale spatial precision, save repeatable inspection routes, and review every run from a purpose-built dashboard.
The workflow starts by gathering a floor heatmap, then annotating the virtual map with labels, assets, and no-go zones. Operators can define routes with waypoints and attach actions at each waypoint, such as capturing images for Gemini-based asset inspection, checking machine states and error readings, or scanning QR codes and AprilTags to confirm object identity and position. Routes can be run immediately or scheduled for repeated execution, like a real-world cron job for physical operations.
Each run stores its route, events, observations, images, and analysis outputs so warehouse teams can audit what changed, compare runs over time, and turn robot inspections into operational records.
This PR also reflects a broader product direction: custom industrial apps should be a primary interface for DimOS. DogOps packages the dashboard, route engine, examples, tests, and Go2 blueprint as importable DimOS modules so this app can run inside the full DimOS checkout and serve as a template for similar applications.
Watch the demo video at https://www.youtube.com/watch?v=iM8s6TYot9c
warehouse.dimos-go2.demo.video.mp4