Skip to content

feat(agents): add exclusive cabilities#2279

Merged
paul-nechifor merged 2 commits into
mainfrom
paul/feat/capabilities
Jun 3, 2026
Merged

feat(agents): add exclusive cabilities#2279
paul-nechifor merged 2 commits 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 agent skills, allowing skills to declare resources they occupy (e.g., movement) so the MCP server can enforce mutual exclusion and give the LLM actionable refusal messages instead of silently overwriting ongoing actions.

  • New CapabilityRegistry (capabilities.py) provides a thread-safe, per-invocation-token-scoped lock with atomic all-or-nothing multi-cap acquire, same-tool takeover semantics, and a blocking wait path for instant holders.
  • @skill decorator extended with uses and lifecycle params; McpServer reads these to enforce caps before dispatch and releases them either directly (instant skills) or via the dimos/tool_stopped LCM frame emitted by ToolStream.stop() (background skills).
  • Real skills updated: start_patrol, begin_exploration, and follow_person all declare uses=[CAP_MOVEMENT], lifecycle="background" and call start_tool before any early-return path so the capability is always carried by a live stream.

Confidence Score: 5/5

Safe to merge. The capability system is well-designed with comprehensive tests; the only concern is a missing exception guard in PatrollingModule._patrol_loop that is unlikely to trigger in practice.

The core CapabilityRegistry, token-scoped release, and same-tool takeover mechanism are all correct and well-tested. The one rough edge is _patrol_loop in PatrollingModule: unlike _exploration_loop and follow_person, _patrol_loop has no guard to close the tool-stream on exception. In practice the loop body only calls next_goal() and publish(), which are unlikely to throw, and the cap would be recovered by the next stop_patrol or module shutdown anyway.

dimos/navigation/patrolling/module.py — _patrol_loop has no try/finally to close the tool-stream on exception, unlike the analogous loops in the other two background skills.

Important Files Changed

Filename Overview
dimos/agents/capabilities.py New CapabilityRegistry — atomic all-or-nothing acquire, per-invocation token scoping for same-tool takeover, blocking acquire with can_wait predicate. Logic and thread-safety are solid.
dimos/agents/mcp/mcp_server.py Adds capability enforcement in _handle_tools_call (blocking acquire, refusal messages with lifecycle-aware advice) and cap release via _fan_out_to_sse_queues on dimos/tool_stopped frames. Logic is correct.
dimos/agents/mcp/tool_stream.py Adds _acquire_token capture on construction, rebind_acquire_token() for same-tool takeover, and make_stopped_notification with optional token. stop() now always emits a dimos/tool_stopped frame even when no send() occurred.
dimos/core/module.py start_tool now does a same-tool re-stamp instead of raising on duplicate; get_skills extracts uses/lifecycle from __skill_uses__/__skill_lifecycle__. Clean and well-tested.
dimos/navigation/patrolling/module.py start_patrol updated with uses=[CAP_MOVEMENT], lifecycle="background" and start_tool called before early-return. _stop_patrolling closes the stream. _patrol_loop has no try/finally to close the stream on exception, unlike _exploration_loop.
dimos/navigation/frontier_exploration/wavefront_frontier_goal_selector.py begin_exploration now calls start_tool before explore(), ensuring the early-return path re-stamps the live stream and is covered by the _exploration_loop's finally block.
dimos/agents/skills/person_follow.py follow_person updated with uses=[CAP_MOVEMENT], lifecycle="background"; uses a background_launched flag in a try/finally to close the stream on all early-return paths while leaving it open for the background thread.
dimos/agents/demos/demo_capabilities.py New self-contained demo showing instant vs background skills, capability conflicts, and self-terminating background tools. Background loops correctly wrap body in try/finally: stop_tool.

Sequence Diagram

sequenceDiagram
    participant LLM as LLM Agent
    participant MCP as McpServer
    participant REG as CapabilityRegistry
    participant SKILL as Skill
    participant STREAM as ToolStream

    LLM->>MCP: tools/call start_patrol
    MCP->>REG: "acquire([movement], token=T1)"
    REG-->>MCP: None (success)
    MCP->>SKILL: "rpc_call(_mcp_context={acquire_token: T1})"
    SKILL->>STREAM: start_tool stamps T1
    SKILL-->>MCP: Patrol started
    Note over MCP: lifecycle=background, caps_held=False

    LLM->>MCP: tools/call turn_in_place
    MCP->>REG: "acquire([movement], token=T2)"
    REG-->>MCP: conflict (movement held by start_patrol)
    MCP-->>LLM: Cannot start turn_in_place — call stop tool first

    LLM->>MCP: tools/call stop_patrol
    MCP->>SKILL: rpc_call stop_patrol
    SKILL->>STREAM: "stop_tool emits dimos/tool_stopped token=T1"
    STREAM->>MCP: dimos/tool_stopped via LCM
    MCP->>REG: release_by_token(T1)
    MCP-->>LLM: Patrol stopped

    LLM->>MCP: tools/call turn_in_place
    MCP->>REG: "acquire([movement], token=T3)"
    REG-->>MCP: None (success)
    MCP->>SKILL: rpc_call turn_in_place
    SKILL-->>MCP: Turned 90 degrees
    Note over MCP: lifecycle=instant, release in finally
    MCP->>REG: release_by_token(T3)
Loading

Reviews (4): Last reviewed commit: "Merge branch 'main' into paul/feat/capab..." | 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
Comment thread dimos/agents/mcp/mcp_server.py Dismissed
@paul-nechifor paul-nechifor force-pushed the paul/feat/capabilities branch from 635255d to 0abba03 Compare June 1, 2026 22:58
@paul-nechifor paul-nechifor merged commit 5f9e0a8 into main Jun 3, 2026
21 checks passed
@paul-nechifor paul-nechifor deleted the paul/feat/capabilities branch June 3, 2026 01:43
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