-
Notifications
You must be signed in to change notification settings - Fork 666
Hackathon Submission: 'DogOps' Factory Automation Dashboard #2281
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -86,3 +86,6 @@ htmlcov/ | |
|
|
||
| # Memory2 autorecord | ||
| recording*.db | ||
|
|
||
| # DogOps local run state and hardware logs | ||
| /.dogops/ | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| """DogOps SiteOps Agent offline core.""" | ||
|
|
||
| from dimos.experimental.dogops.config_loader import load_dogops_config | ||
| from dimos.experimental.dogops.mission_engine import run_offline_simulation | ||
|
|
||
| __all__ = ["load_dogops_config", "run_offline_simulation"] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from dataclasses import dataclass | ||
|
|
||
| from dimos.experimental.dogops.dashboard import DogOpsDashboardModule | ||
| from dimos.experimental.dogops.nav_eval import DogOpsNavEvalModule | ||
| from dimos.experimental.dogops.observation_module import DogOpsObservationModule | ||
| from dimos.experimental.dogops.skills import DogOpsSkillContainer | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class DogOpsBlueprintMetadata: | ||
| name: str | ||
| modules: tuple[str, ...] | ||
| robot_model: str = "unitree_go2" | ||
| requires_mcp_client: bool = False | ||
| fallback: bool = True | ||
|
|
||
|
|
||
| def build_unitree_go2_dogops_blueprint() -> object: | ||
| try: # pragma: no cover - exercised only inside a full DimOS checkout. | ||
| from dimos.agents.mcp.mcp_server import McpServer | ||
| from dimos.core.coordination.blueprints import autoconnect | ||
| from dimos.robot.unitree.go2.blueprints.smart.unitree_go2 import unitree_go2_markers | ||
| except ModuleNotFoundError: | ||
| return DogOpsBlueprintMetadata( | ||
| name="unitree-go2-dogops", | ||
| modules=( | ||
| "unitree_go2_markers", | ||
| "DogOpsObservationModule", | ||
| "DogOpsSkillContainer", | ||
| "McpServer", | ||
| "DogOpsDashboardModule", | ||
| "DogOpsNavEvalModule", | ||
| ), | ||
| ) | ||
|
|
||
| return autoconnect( | ||
| unitree_go2_markers, | ||
| DogOpsObservationModule.blueprint(), | ||
| DogOpsSkillContainer.blueprint(), | ||
| DogOpsDashboardModule.blueprint(), | ||
| DogOpsNavEvalModule.blueprint(), | ||
| McpServer.blueprint(), | ||
| ).global_config(n_workers=12, robot_model="unitree_go2") | ||
|
|
||
|
|
||
| unitree_go2_dogops = build_unitree_go2_dogops_blueprint() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
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! |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,96 @@ | ||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| from pathlib import Path | ||
|
|
||
| from dimos.experimental.dogops.config_loader import ( | ||
| DEFAULT_MANIFEST, | ||
| DEFAULT_MISSION, | ||
| DEFAULT_POLICY, | ||
| DEFAULT_SITE, | ||
| load_dogops_config, | ||
| ) | ||
| from dimos.experimental.dogops.dashboard import serve_dashboard | ||
| from dimos.experimental.dogops.mission_engine import run_offline_simulation | ||
| from dimos.experimental.dogops.report import render_report_markdown | ||
| from dimos.experimental.dogops.store import DogOpsStore | ||
|
|
||
|
|
||
| def build_parser() -> argparse.ArgumentParser: | ||
| parser = argparse.ArgumentParser(prog="dogops", description="DogOps offline CLI") | ||
| subparsers = parser.add_subparsers(dest="command", required=True) | ||
|
|
||
| validate = subparsers.add_parser("validate", help="Validate DogOps YAML configs") | ||
| _add_config_args(validate) | ||
|
|
||
| simulate = subparsers.add_parser("simulate", help="Run the offline DogOps mission") | ||
| _add_config_args(simulate) | ||
| simulate.add_argument("--out", default=".dogops/runs/latest") | ||
|
|
||
| report = subparsers.add_parser("report", help="Regenerate a report from a run directory") | ||
| report.add_argument("--run", default=".dogops/runs/latest") | ||
| report.add_argument("--out", default=None) | ||
|
|
||
| serve = subparsers.add_parser("serve", help="Serve a local dashboard for a run directory") | ||
| serve.add_argument("--run", default=".dogops/runs/latest") | ||
| serve.add_argument("--host", default="127.0.0.1") | ||
| serve.add_argument("--port", type=int, default=8765) | ||
|
|
||
| return parser | ||
|
|
||
|
|
||
| def _add_config_args(parser: argparse.ArgumentParser) -> None: | ||
| parser.add_argument("--site", default=str(DEFAULT_SITE)) | ||
| parser.add_argument("--manifest", default=str(DEFAULT_MANIFEST)) | ||
| parser.add_argument("--mission", default=str(DEFAULT_MISSION)) | ||
| parser.add_argument("--policy", default=str(DEFAULT_POLICY)) | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| parser = build_parser() | ||
| args = parser.parse_args(argv) | ||
|
|
||
| if args.command == "validate": | ||
| config = load_dogops_config(args.site, args.manifest, args.mission, args.policy) | ||
| print( | ||
| "validated " | ||
| f"site={config.site.site_id} " | ||
| f"manifest={config.manifest.manifest_id} " | ||
| f"mission={config.mission.mission_id}" | ||
| ) | ||
| return 0 | ||
|
|
||
| if args.command == "simulate": | ||
| state = run_offline_simulation( | ||
| site=args.site, | ||
| manifest=args.manifest, | ||
| mission=args.mission, | ||
| policy=args.policy, | ||
| out=args.out, | ||
| ) | ||
| print(f"run_id={state.run.id}") | ||
| print(f"state={getattr(state.run.state, 'value', state.run.state)}") | ||
| print(f"report={Path(args.out) / 'report.md'}") | ||
| return 0 | ||
|
|
||
| if args.command == "report": | ||
| store = DogOpsStore.load_existing(args.run) | ||
| state = store.state | ||
| assert state is not None | ||
| content = render_report_markdown(state) | ||
| out_path = Path(args.out) if args.out else Path(args.run) / "report.md" | ||
| out_path.write_text(content, encoding="utf-8") | ||
| store.write_report(state.run.id) | ||
| print(f"report={out_path}") | ||
| return 0 | ||
|
|
||
| if args.command == "serve": | ||
| serve_dashboard(args.run, host=args.host, port=args.port) | ||
| return 0 | ||
|
|
||
| parser.error(f"unknown command: {args.command}") | ||
| return 2 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| import yaml | ||
|
|
||
| from dimos.experimental.dogops.models import ( | ||
| DogOpsConfig, | ||
| Manifest, | ||
| MissionConfig, | ||
| PolicyConfig, | ||
| SiteConfig, | ||
| ) | ||
|
|
||
| DEFAULT_SITE = Path("examples/dogops/site_demo.yaml") | ||
| DEFAULT_MANIFEST = Path("examples/dogops/manifest_demo.yaml") | ||
| DEFAULT_POLICY = Path("examples/dogops/policy_demo.yaml") | ||
| DEFAULT_MISSION = Path("examples/dogops/mission_demo.yaml") | ||
|
|
||
|
|
||
| def _read_yaml(path: Path) -> dict[str, Any]: | ||
| if not path.exists(): | ||
| raise FileNotFoundError(f"DogOps config not found: {path}") | ||
| with path.open("r", encoding="utf-8") as handle: | ||
| data = yaml.safe_load(handle) or {} | ||
| if not isinstance(data, dict): | ||
| raise ValueError(f"DogOps config must be a mapping: {path}") | ||
| return data | ||
|
|
||
|
|
||
| def load_site_config(path: str | Path = DEFAULT_SITE) -> SiteConfig: | ||
| return SiteConfig.model_validate(_read_yaml(Path(path))) | ||
|
|
||
|
|
||
| def load_manifest(path: str | Path = DEFAULT_MANIFEST) -> Manifest: | ||
| return Manifest.model_validate(_read_yaml(Path(path))) | ||
|
|
||
|
|
||
| def load_policy(path: str | Path = DEFAULT_POLICY) -> PolicyConfig: | ||
| return PolicyConfig.model_validate(_read_yaml(Path(path))) | ||
|
|
||
|
|
||
| def load_mission(path: str | Path = DEFAULT_MISSION) -> MissionConfig: | ||
| return MissionConfig.model_validate(_read_yaml(Path(path))) | ||
|
|
||
|
|
||
| def load_dogops_config( | ||
| site_path: str | Path = DEFAULT_SITE, | ||
| manifest_path: str | Path = DEFAULT_MANIFEST, | ||
| mission_path: str | Path = DEFAULT_MISSION, | ||
| policy_path: str | Path = DEFAULT_POLICY, | ||
| ) -> DogOpsConfig: | ||
| site = load_site_config(site_path) | ||
| manifest = load_manifest(manifest_path) | ||
| policy = load_policy(policy_path) | ||
| mission = load_mission(mission_path) | ||
| return DogOpsConfig( | ||
| site=site, | ||
| manifest=manifest, | ||
| policy=policy, | ||
| mission=mission, | ||
| paths={ | ||
| "site": Path(site_path), | ||
| "manifest": Path(manifest_path), | ||
| "policy": Path(policy_path), | ||
| "mission": Path(mission_path), | ||
| }, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import pytest | ||
|
|
||
| from dimos.experimental.dogops.config_loader import load_site_config | ||
|
|
||
|
|
||
| @pytest.fixture | ||
| def dogops_site(): | ||
| return load_site_config() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dir()iteration may expose inherited / unrelated__skill__-marked attributesdir(actor_class)walks the entire MRO — including allModulebase-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 duplicatefunc_namekey inapp.state.rpc_calls. Consider restricting tovars(actor_class)to capture only the methods defined directly on the concrete class.