Skip to content

Go2 agentic architecture layers and self-evolution review#2727

Open
z1050297972-spec wants to merge 41 commits into
dimensionalOS:mainfrom
z1050297972-spec:refactor/go2-architecture-layers
Open

Go2 agentic architecture layers and self-evolution review#2727
z1050297972-spec wants to merge 41 commits into
dimensionalOS:mainfrom
z1050297972-spec:refactor/go2-architecture-layers

Conversation

@z1050297972-spec

@z1050297972-spec z1050297972-spec commented Jul 4, 2026

Copy link
Copy Markdown

Hi DimOS maintainers,

I am opening this as a draft PR for architecture review and discussion. The goal is to share my current thinking around a Go2 agentic architecture extension, especially around context scheduling, skill-interface evolution, and reviewable self-evolution artifacts. I am not asking for an immediate merge before the design direction is reviewed.

Architecture overview

The main review figure is below. It combines the repo-wide DimOS runtime architecture with the Go2 agentic extension in one diagram, so the agent layer can be reviewed in the context of blueprints, runtime modules, transports, skills, memory, and robot targets.

DimOS project architecture with Go2 agentic self-evolution extension

Editable SVG source:
https://github.com/z1050297972-spec/dimos/blob/refactor/go2-architecture-layers/docs/reviews/2026-07-05-dimos-agentic-architecture.svg

PNG render in the branch:
https://github.com/z1050297972-spec/dimos/blob/refactor/go2-architecture-layers/docs/reviews/2026-07-05-dimos-agentic-architecture.png

Detailed review notes:

What this branch explores

This branch proposes a more explicit Go2 agentic layering model:

  • Layer 3: Agent Brain: prompt routing, context-provider invocation, feasibility evaluation, MCP/tool selection, outcome recording, causal-state use, and skill-proposal generation.
  • Layer 4: World State: structured runtime/body state, semantic temporal map, spatial/RAG memory access, temporal summaries, and snapshot metadata.
  • Layer 5: Skill Interface: static skill contracts, preflight metadata, argument validation, MCP-tool comparison, and skill proposal artifacts.
  • Layer 6: Robot Body: Go2 connection, sensors/odom, motion-control evidence, and lower-level runtime readiness.

The important design boundary is that self-evolution is represented as reviewable artifacts, not automatic code mutation. The agent can record events and proposals, but those artifacts still need human or downstream tooling review before becoming implementation changes.

Main technical changes

  • Added Go2 self-evolution proposal tools and a local evolution ledger for structured event/proposal artifacts.
  • Added context-provider evidence schemas so selected context, dropped context, source confidence, and policy effects can be reviewed after a task.
  • Added feasibility evaluation surfaces so the agent can classify a requested task as feasible, infeasible, or uncertain using available skills, missing context, and safety risks.
  • Added skill-interface contracts and validation so the agent has a clearer boundary between “existing tool can be used” and “new skill/interface should be proposed”.
  • Added memory/backend status reporting to make it explicit which spatial, temporal, RAG, feedback, outcome, or world-model providers are actually wired and available.
  • Added lightweight causal/world-model state plumbing as an advisory evidence source. This is not a separately trained model in this branch; it is a structured evidence and outcome interface for later learning/review loops.
  • Added optional observability/dashboard contracts, while documenting that the current unitree_go2_agentic blueprint does not wire WebsocketVisModule directly.
  • Expanded English and Chinese review documents with implementation logic, interface inputs/outputs, storage boundaries, decision owners, failure modes, and validation notes.
  • Merged current upstream main into this branch and preserved upstream infrastructure changes while keeping the local Go2 layered architecture work.

Review questions

I would especially appreciate feedback on these points:

  1. Is the Layer 3/4/5/6 boundary compatible with how DimOS maintainers expect agentic robot stacks to evolve?
  2. Should self-evolution artifacts live in a local Git-backed ledger, an existing memory/RAG backend, or both depending on artifact type?
  3. Is the ContextProvider boundary the right place to decide which memory/context is effective, with the LLM remaining the main judgment owner after context is selected?
  4. Is the skill-contract/proposal flow a reasonable way to separate existing skill use from proposing a new skill interface?
  5. Are there parts of this that should be moved out of Go2-specific code into a robot-agnostic agent subsystem?

Validation performed

  • Synced this branch with upstream main at 6e813a72 and resolved merge conflicts while preserving upstream infrastructure updates.
  • Added detailed review docs for both English and Chinese readers.
  • Added deterministic editable SVG architecture figures and PNG renders, then visually checked them for label/arrow/optional-path ambiguity.
  • Parsed the SVG diagrams as XML.
  • Ran git diff --check / git diff --cached --check on the changed docs, SVGs, and changelog updates during the documentation work.
  • Cleaned local runtime noise by ignoring MUJOCO_LOG.TXT.

Notes

This is intentionally opened as a draft PR. I expect some parts may need to be split, renamed, or moved to more general DimOS agent packages after maintainers review the architecture direction.

@z1050297972-spec z1050297972-spec marked this pull request as ready for review July 4, 2026 18:17
@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This draft PR introduces a Go2 agentic architecture extension (Layers 3–6) covering agent brain, world state, skill interfaces, and robot body, along with a local evolution ledger for reviewable self-evolution artifacts. It also carries several upstream fixes: async MCP client initialization, direct RPC tool dispatch, task-scoped outcome recording, JS-based Rerun iframe URL construction, and MuJoCo headless mode.

  • Layer 3–6 modules (causal_world_model, context_provider, skill_outcome_store, evolution_ledger, etc.) are all new and tested; previous-thread P1s around proposal file collision, InterventionLog load-then-clear, and outcome task isolation have been addressed.
  • MCP client gains a background init thread, automatic tool-outcome recording with task propagation, and direct-RPC short-circuit for tools from known modules.
  • McpClientConfig.model default is changed from "gpt-4o" to "ollama:qwen3.6:latest", which silently breaks any deployment that omits an explicit model setting and does not have Ollama running.

Confidence Score: 4/5

Safe to merge after the default model value in McpClientConfig is restored or made explicit-only; the rest of the changes are well-tested and the previous-thread bugs have been addressed.

The one issue that could affect every existing McpClient user is the default model switch: any deployment that omits an explicit model setting now silently targets a local Ollama server instead of gpt-4o, and the new async init loop will retry indefinitely without surfacing the misconfiguration. All other concerns — proposal collision, InterventionLog clear-then-load, outcome task isolation, dashboard localhost hardcoding — were resolved in earlier review rounds and the fixes are present in this diff.

dimos/agents/mcp/mcp_client.py — the McpClientConfig.model default change is the one item that needs a decision before merging.

Important Files Changed

Filename Overview
dimos/agents/mcp/mcp_client.py Major refactor: async init thread, direct RPC tool dispatch, automatic task-scoped outcome recording. Default model changed from gpt-4o to ollama:qwen3.6:latest — breaks existing deployments without explicit model config.
dimos/robot/unitree/go2/blueprints/layers/layer_3_agent_brain/causal_world_model.py New Layer 3 in-memory world model with atomic persistence (temp-file swap), safe load (builds in temporaries before replacing), and intervention log. Previous-thread load-then-clear and autosave race P1s have been addressed.
dimos/robot/unitree/go2/blueprints/layers/layer_3_agent_brain/evolution_proposal.py Proposal file writer with SHA1-anchored stem to prevent sanitization collisions and suffix-increment to prevent reuse overwrites. Both previous-thread P1s (silent overwrite, id collision) have been addressed.
dimos/robot/unitree/go2/blueprints/layers/layer_3_agent_brain/evolution_ledger.py Git-backed audit ledger using subprocess with list-form args (no shell injection). Falls back gracefully when outside a git repo. Git commit message includes user-provided event_type but passed as list arg, not shell string.
dimos/robot/unitree/go2/blueprints/layers/layer_3_agent_brain/skill_outcome_store.py New bounded in-memory outcome store with task-scoped filtering. The task isolation fix from the previous thread is present in the dataclass and get_recent_outcomes signature.
dimos/web/websocket_vis/websocket_vis_module.py Adds world-model panel endpoint and state polling API. _bounded_jsonable silently truncates lists to 10 items without a truncation indicator, which can cause the dashboard and summary to silently miss data.
dimos/web/templates/rerun_dashboard.html Rerun iframe now uses window.location.hostname via JS rather than hardcoded localhost, fixing the remote-viewer issue. Command-center iframe switched to relative URL /command-center. Previous-thread P1 has been addressed.
dimos/simulation/mujoco/mujoco_process.py Refactored into _run_simulation_loop with headless mode gated on DIMOS_MUJOCO_HEADLESS env var. The viewer import is now deferred to the non-headless path. Renderers are created inside the loop function in both paths.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant User
    participant McpClient
    participant InitThread as Init Thread
    participant McpServer
    participant SkillOutcomeStore
    participant CausalWorldModel

    User->>McpClient: on_system_modules(modules)
    McpClient->>InitThread: spawn _delayed_register_and_initialize_agent_graph
    InitThread->>InitThread: _register_direct_tools (builds _direct_rpc_calls)
    InitThread->>McpServer: _try_fetch_tools (HTTP)
    McpServer-->>InitThread: tools/list
    InitThread->>McpClient: "_state_graph = create_agent(model, tools)"
    InitThread->>McpClient: start _thread_loop

    User->>McpClient: human_input(go to kitchen)
    McpClient->>McpClient: "_last_user_task = go to kitchen"
    McpClient->>McpClient: queue HumanMessage

    McpClient->>McpServer: _mcp_tool_call(navigate_with_text, args)
    Note over McpClient,McpServer: Direct RPC path if tool in _direct_rpc_calls
    McpServer-->>McpClient: result

    McpClient->>SkillOutcomeStore: "record_skill_outcome(task=go to kitchen, ...)"
    McpClient->>CausalWorldModel: "record_causal_transition(task=..., skill_name=...)"
    CausalWorldModel->>SkillOutcomeStore: "get_recent_outcomes(skill_name=..., task=...)"
    SkillOutcomeStore-->>CausalWorldModel: filtered outcomes
    CausalWorldModel-->>McpClient: transition recorded
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant User
    participant McpClient
    participant InitThread as Init Thread
    participant McpServer
    participant SkillOutcomeStore
    participant CausalWorldModel

    User->>McpClient: on_system_modules(modules)
    McpClient->>InitThread: spawn _delayed_register_and_initialize_agent_graph
    InitThread->>InitThread: _register_direct_tools (builds _direct_rpc_calls)
    InitThread->>McpServer: _try_fetch_tools (HTTP)
    McpServer-->>InitThread: tools/list
    InitThread->>McpClient: "_state_graph = create_agent(model, tools)"
    InitThread->>McpClient: start _thread_loop

    User->>McpClient: human_input(go to kitchen)
    McpClient->>McpClient: "_last_user_task = go to kitchen"
    McpClient->>McpClient: queue HumanMessage

    McpClient->>McpServer: _mcp_tool_call(navigate_with_text, args)
    Note over McpClient,McpServer: Direct RPC path if tool in _direct_rpc_calls
    McpServer-->>McpClient: result

    McpClient->>SkillOutcomeStore: "record_skill_outcome(task=go to kitchen, ...)"
    McpClient->>CausalWorldModel: "record_causal_transition(task=..., skill_name=...)"
    CausalWorldModel->>SkillOutcomeStore: "get_recent_outcomes(skill_name=..., task=...)"
    SkillOutcomeStore-->>CausalWorldModel: filtered outcomes
    CausalWorldModel-->>McpClient: transition recorded
Loading

Reviews (11): Last reviewed commit: "Merge branch 'main' into refactor/go2-ar..." | Re-trigger Greptile

Comment thread dimos/web/templates/rerun_dashboard.html Outdated
Comment thread dimos/agents/mcp/mcp_client.py
write_skill_proposal_file derived the artifact path solely from
proposal_id via _safe_filename, then atomically replaced it. A reused
stable proposal_id (e.g. improve_navigation) would silently overwrite
the earlier review artifact, losing the proposal reviewers were meant
to inspect. Mirror the events path's _unique_event_path approach:
append -1, -2, ... when the path exists so both artifacts are kept.
The author's proposal_id stays verbatim in the JSON; only the filename
is disambiguated.
_safe_filename maps every non-alphanumeric character to '_', so
distinct proposal_ids such as skill-v2 and skill.v2 both reduce to
skill_v2 and land on the same path. The prior -N suffix guard only
treated genuine same-id reuses; it would file the second distinct
proposal as skill_v2-1.json, conflating unrelated proposals as
iterations and risking overwrite of the first when ordering changes.

Anchor the stem on an injective digest of the original (un-sanitized)
proposal_id: {slug}-{sha1(id)[:8]}. Distinct ids now always map to
distinct files; the -1/-2 suffix still handles true same-id reuse.
JSON proposal_id stays verbatim. This mirrors how the events path uses
a timestamp prefix to sidestep lossy sanitization.
payload = json.dumps(normalized, indent=2, sort_keys=True) + "\n"
tmp_path = path.with_name(f".{path.name}.tmp-{os.getpid()}")
tmp_path.write_text(payload, encoding="utf-8")
tmp_path.replace(path)

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.

P1 Concurrent Proposals Overwrite The path is chosen with _unique_proposal_path() before this replace(), but the final write still overwrites if another agent creates the same filename between the exists() check and the replace. Two concurrent calls with the same stable proposal_id can both choose the base file, both return success, and only the later proposal remains in the ledger. The suffix selection needs to claim the final filename atomically, such as with an exclusive create or a lock around path selection and write.

…ct RPC routing

Direct tool registration was routing calls to skill.class_name/skill.func_name
instead of the module's actual RPCClient.remote_name.  When workers register
with aliased or remapped names the direct path sends tool calls to a route
that does not match the running module, causing failures or empty results.
… hardcoding localhost

The iframe src was hardcoded to localhost:9090 and localhost:9876, ignoring
DIMOS_RERUN_HOST / --rerun-host and listen_host.  When the dashboard is served
from a remote machine the embedded Rerun viewer connects to the browser user's
localhost and the stream never appears.

Read the template at serve time, substitute the correct host and ports
(RERUN_WEB_VIEWER_PORT=9878, RERUN_GRPC_PORT=9877).
write_text() directly on the shared JSON state file is not atomic:
- a crash during write truncates the file
- concurrent agent instances interleave writes, losing transitions

Write to a temp file in the same directory, then os.replace() for an
atomic swap that guarantees readers always see a complete file.
_load_world_model_state_from_path and InterventionLog.load_dict both cleared
in-memory state (transitions, records) before parsing the payload.  If any
item failed to parse the running model was left corrupted.

Build all new objects in temporaries first, swap in only after all parsing
succeeds.  A bad schema, malformed transition, or float() conversion failure
no longer destroys the running model's state.
…k evidence leakage

get_recent_outcomes() filtered only by skill_name, so unrelated tasks shared
outcome history.  When the causal world model recorded a transition without
an explicit outcome_json it fell back to _latest_outcome(skill_name), which
could grab another task's navigate_with_text success/failure and feed it as
evidence for the current task.

Add a 'task' dimension to SkillOutcome, record_skill_outcome, and
get_recent_outcomes.  _latest_outcome now filters by both skill_name and
task so that 'go to kitchen' outcomes never leak into 'go to office'
predictions.
Comment thread dimos/agents/mcp/mcp_client.py
Auto-recorded outcomes from McpClient._record_tool_outcome were missing
the 'task' field, so they were stored with task="".  Later,
_latest_outcome(skill_name, task="go to the kitchen") filtered by the
non-empty task and never matched, causing the causal world model to
record transitions with outcome_success=None (unknown).

Capture the last user input as _last_user_task and inject it into both
_outcome_payload_from_result and _record_tool_exception so auto-recorded
outcomes carry the same task key that _latest_outcome filters by.
Comment thread dimos/web/websocket_vis/websocket_vis_module.py Outdated
1050297972 and others added 2 commits July 6, 2026 13:57
…nd address

The server-side resolved host (listen_host=127.0.0.1 or rerun_host) was
used as the iframe target.  When the browser is on a different machine:
- 127.0.0.1 → browser tries to reach its own loopback
- 0.0.0.0 → not a routable address

Use window.location.hostname in JavaScript to build the iframe URL
client-side, so the browser always connects to the same host it used
to load the dashboard page.  Ports (9877 gRPC, 9878 web viewer) are
hardcoded constants matching the Rerun bridge defaults.
@spomichter

Copy link
Copy Markdown
Contributor

Thanks for sharing! Much of this including spatial memory and skill interfaces are already implemented. We're working on new agent features somewhat along these lines now, woudl reccomend expanding / double clicking on that area and exactly the set of requirements that would be interesting, and what is the end use case you're trying to achieve. Send the desired prompts on your wishlist that don't work today. For example "hey robot go sit down to the left side of the couch over there"

@greptile-apps

greptile-apps Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Want your agent to iterate on Greptile's feedback? Try greploops.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants