Skip to content

feat(agents): add exclusive cabilities#2279

Open
paul-nechifor wants to merge 1 commit into
mainfrom
paul/feat/capabilities
Open

feat(agents): add exclusive cabilities#2279
paul-nechifor wants to merge 1 commit into
mainfrom
paul/feat/capabilities

Conversation

@paul-nechifor
Copy link
Copy Markdown
Contributor

@paul-nechifor paul-nechifor commented May 28, 2026

Problem

Closes DIM-XXX

Solution

How to Test

Contributor License Agreement

  • I have read and approved the CLA.

@paul-nechifor paul-nechifor marked this pull request as draft May 28, 2026 05:50
@codecov
Copy link
Copy Markdown

codecov Bot commented May 28, 2026

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 28, 2026

Greptile Summary

This PR introduces an exclusive capability system for skill-level mutual exclusion: skills declare @skill(uses=["movement"]) and the MCP server consults a CapabilityRegistry before each tools/call, either waiting (for instant skills) or refusing with a human-readable message (for background skills). The end-to-end token lifecycle — MCP server acquires, ToolStream stamps the stop frame, _fan_out_to_sse_queues releases — is well designed, with same-tool takeover handled cleanly via rebind_acquire_token.

  • CapabilityRegistry: thread-safe all-or-nothing multi-cap acquire with per-invocation token scoping; stale takeover tokens no longer leak the hold.
  • start_tool changed from raising on duplicate to re-stamping the token, enabling background skills to call it unconditionally before any early-return path.
  • PatrollingModule, WavefrontFrontierExplorer, PersonFollowSkillContainer, and NavigationSkillContainer updated to declare CAP_MOVEMENT; a hardware-free demo and thorough unit tests accompany the change.
  • humancli.py gains live per-tool streaming boxes with tool-result anchoring via direct mutation of Textual private internals (_line_cache).

Confidence Score: 5/5

Safe to merge; the capability system is well-structured with no correctness gaps in the core acquisition/release path.

The token-scoped registry, same-tool takeover, and always-emitting stop frame work correctly together. All early-return paths in the updated skills call stop_tool or rely on the re-stamp mechanism. The only fragility is the direct use of Textual's private _line_cache in the UI layer, which is cosmetic and isolated.

dimos/utils/cli/human/humancli.py — the _insert_after method couples to Textual private internals.

Important Files Changed

Filename Overview
dimos/agents/capabilities.py New CapabilityRegistry: thread-safe all-or-nothing acquire with per-invocation token scoping and same-tool takeover; design is solid.
dimos/agents/annotation.py Refactored skill decorator to support uses and lifecycle params, with lifecycle validation before the bare-form check; correct and well-typed.
dimos/agents/mcp/mcp_server.py Integrates CapabilityRegistry into tool dispatch: acquires before call, releases on failure or delegates to tool-stream for background skills; exception path correctly releases caps via finally block.
dimos/agents/mcp/tool_stream.py Always emits dimos/tool_stopped on close (spinning up a transport if needed), adds rebind_acquire_token() for same-tool takeover; stop is idempotent.
dimos/core/module.py Changed start_tool from raising on duplicate to re-stamping the acquire token on the live stream, enabling same-tool takeover semantics; old error message removed correctly.
dimos/utils/cli/human/humancli.py Adds live per-tool streaming boxes and tool-result anchoring; _insert_after directly accesses RichLog._line_cache (private Textual attribute) and mutates log.lines/log.virtual_size, coupling tightly to internal Textual state.
dimos/agents/skills/person_follow.py Moves start_tool to the parent skill before early returns so all code paths release capability; removes now-redundant patrol auto-stop (handled by capability system).
dimos/navigation/patrolling/module.py Adds @skill(uses=[CAP_MOVEMENT], lifecycle="background"), calls start_tool before early-return and stop_tool in _stop_patrolling; double-stop on both explicit and main() teardown paths remains safe (idempotent pop).
dimos/navigation/frontier_exploration/wavefront_frontier_goal_selector.py Extracts _run_exploration_loop so _exploration_loop can wrap it with a finally: stop_tool that releases movement on every exit path, including self-termination.

Sequence Diagram

sequenceDiagram
    participant LLM as LLM / McpClient
    participant MCP as McpServer
    participant REG as CapabilityRegistry
    participant SKILL as Skill (background)
    participant STREAM as ToolStream
    participant SSE as _fan_out_to_sse_queues

    LLM->>MCP: "tools/call {name, args}"
    MCP->>REG: acquire(caps, tool_name, token, timeout)
    alt cap free or same-tool takeover
        REG-->>MCP: None (success)
        MCP->>SKILL: "rpc_call(_mcp_context={acquire_token})"
        SKILL->>STREAM: start_tool(name) stamps token
        SKILL-->>MCP: "Patrol started."
        Note over MCP: caps_held=False (background) cap stays with ToolStream
        MCP-->>LLM: result text
        loop background work
            SKILL->>STREAM: tool_update(name, msg)
            STREAM->>SSE: notifications/message
        end
        SKILL->>STREAM: stop_tool(name)
        STREAM->>SSE: "dimos/tool_stopped {token}"
        SSE->>REG: release_by_token(token)
        REG-->>SSE: ["movement"] released
    else cap held by different tool
        REG-->>MCP: (cap, holder_tool)
        MCP-->>LLM: "Cannot start X: capability Y is held by Z. Call stop tool first."
    end
Loading

Reviews (3): Last reviewed commit: "feat(agents): add exclusive cabilities" | Re-trigger Greptile

Comment thread dimos/agents/test_capabilities.py Outdated
@paul-nechifor paul-nechifor force-pushed the paul/feat/capabilities branch 4 times, most recently from 6831efb to 635255d Compare May 31, 2026 01:01
@paul-nechifor paul-nechifor marked this pull request as ready for review May 31, 2026 01:01
@paul-nechifor paul-nechifor changed the title WIP: feat(agents): add exclusive cabilities feat(agents): add exclusive cabilities May 31, 2026

if lifecycle == "background":
# Hand ownership of the caps off to the tool-stream lifecycle.
caps_held = False
@paul-nechifor paul-nechifor force-pushed the paul/feat/capabilities branch from 635255d to 0abba03 Compare June 1, 2026 22:58
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.

2 participants