Packages
nous
0.12.9
0.17.0
0.16.6
0.16.5
0.16.4
0.16.3
0.16.2
0.16.1
0.16.0
0.15.8
0.15.7
0.15.6
0.15.5
0.15.4
0.15.3
0.15.2
0.15.1
0.15.0
0.14.3
0.14.2
0.14.1
0.14.0
0.13.3
0.13.2
0.13.1
0.13.0
0.12.17
0.12.16
0.12.15
0.12.14
0.12.13
0.12.12
0.12.11
0.12.9
0.12.7
0.12.6
0.12.5
0.12.3
0.12.2
0.12.0
0.11.3
0.11.0
0.10.1
0.10.0
0.9.0
0.8.1
0.8.0
0.7.2
0.7.1
0.7.0
0.5.0
AI agent framework for Elixir with multi-provider LLM support
Current section
Files
Jump to
Current section
Files
lib/nous/plugins/team_tools.ex
defmodule Nous.Plugins.TeamTools do
@moduledoc """
Plugin that provides team communication and coordination tools for agents.
When an agent is part of a team, this plugin adds tools for peer messaging,
broadcasting, sharing discoveries, listing team members, and claiming file
regions for editing.
## Capabilities
- Direct messaging between team agents
- Team-wide broadcasts
- Shared discovery board
- File region claiming to prevent edit conflicts
- Team member listing
## Usage
agent = Agent.new("openai:gpt-4",
plugins: [Nous.Plugins.TeamTools],
deps: %{
team_id: "team_1",
agent_name: "alice",
shared_state_pid: shared_state_pid
}
)
## Configuration
The plugin reads the following keys from `ctx.deps`:
- `:team_id` — the team identifier (required)
- `:agent_name` — this agent's name in the team (required)
- `:shared_state_pid` — pid of the team's SharedState process
- `:team_coordinator_pid` — pid of the team's Coordinator process
## Tools Provided
| Tool | Description |
|------|-------------|
| `peer_message` | Send a direct message to a named agent |
| `broadcast_message` | Broadcast a message to all team agents |
| `share_discovery` | Store a finding in SharedState and broadcast |
| `list_team` | List team members and their status |
| `claim_region` | Claim file lines before editing |
## PubSub Events
- `{:peer_message, from, to, content}` — sent on agent's direct topic
- `{:team_broadcast, from, content}` — sent on team-wide topic
- `{:discovery, from, discovery}` — sent on team-wide topic
"""
@behaviour Nous.Plugin
alias Nous.Tool
alias Nous.Teams.{Comms, SharedState, Coordinator}
# ===========================================================================
# Plugin callbacks
# ===========================================================================
@impl true
def init(_agent, ctx) do
team_id = ctx.deps[:team_id]
agent_name = ctx.deps[:agent_name]
pubsub = ctx.pubsub
if team_id && agent_name do
Comms.subscribe_team(pubsub, team_id)
Comms.subscribe_agent(pubsub, team_id, agent_name)
end
ctx
end
@impl true
def tools(_agent, _ctx) do
[
peer_message_tool(),
broadcast_message_tool(),
share_discovery_tool(),
list_team_tool(),
claim_region_tool()
]
end
@impl true
def system_prompt(_agent, ctx) do
team_id = ctx.deps[:team_id]
agent_name = ctx.deps[:agent_name]
role = ctx.deps[:team_role]
role_info =
if role do
"Your role: #{role.name}. #{role.system_prompt || ""}"
else
""
end
"""
## Team Context
You are agent "#{agent_name || "unknown"}" on team "#{team_id || "unknown"}".
#{role_info}
### Team Communication Tools
- `peer_message` — send a direct message to another agent by name
- `broadcast_message` — send a message to all agents on the team
- `share_discovery` — store an important finding for the team
- `list_team` — see who is on the team and their status
- `claim_region` — claim a file region before editing to prevent conflicts
### Guidelines
- Share important findings with the team via `share_discovery`
- Claim file regions before editing with `claim_region`
- Use `peer_message` for targeted communication
- Use `broadcast_message` sparingly, only for team-wide announcements
"""
end
@impl true
def before_request(_agent, ctx, tools) do
# Inject recent team messages into context if available
{ctx, tools}
end
# ===========================================================================
# Tool definitions
# ===========================================================================
defp peer_message_tool do
%Tool{
name: "peer_message",
description: "Send a direct message to a named agent on your team.",
parameters: %{
"type" => "object",
"properties" => %{
"to" => %{
"type" => "string",
"description" => "Name of the agent to send the message to"
},
"content" => %{
"type" => "string",
"description" => "The message content"
}
},
"required" => ["to", "content"]
},
function: &__MODULE__.peer_message/2,
takes_ctx: true
}
end
defp broadcast_message_tool do
%Tool{
name: "broadcast_message",
description:
"Broadcast a message to all agents on the team. Use sparingly for important announcements.",
parameters: %{
"type" => "object",
"properties" => %{
"content" => %{
"type" => "string",
"description" => "The message to broadcast"
}
},
"required" => ["content"]
},
function: &__MODULE__.broadcast_message/2,
takes_ctx: true
}
end
defp share_discovery_tool do
%Tool{
name: "share_discovery",
description:
"Share an important finding with the team. Stores it in shared state and broadcasts to all agents.",
parameters: %{
"type" => "object",
"properties" => %{
"topic" => %{
"type" => "string",
"description" => "Short topic/title for the discovery"
},
"content" => %{
"type" => "string",
"description" => "Detailed content of the discovery"
}
},
"required" => ["topic", "content"]
},
function: &__MODULE__.share_discovery/2,
takes_ctx: true
}
end
defp list_team_tool do
%Tool{
name: "list_team",
description: "List all agents on the team and their current status.",
parameters: %{
"type" => "object",
"properties" => %{},
"required" => []
},
function: &__MODULE__.list_team/2,
takes_ctx: true
}
end
defp claim_region_tool do
%Tool{
name: "claim_region",
description:
"Claim a file region (line range) before editing to prevent conflicts with other agents.",
parameters: %{
"type" => "object",
"properties" => %{
"file" => %{
"type" => "string",
"description" => "File path to claim"
},
"start_line" => %{
"type" => "integer",
"description" => "Start line number"
},
"end_line" => %{
"type" => "integer",
"description" => "End line number"
}
},
"required" => ["file", "start_line", "end_line"]
},
function: &__MODULE__.claim_region/2,
takes_ctx: true
}
end
# ===========================================================================
# Tool implementations
# ===========================================================================
@doc false
def peer_message(ctx, %{"to" => to, "content" => content}) do
team_id = ctx.deps[:team_id]
from = ctx.deps[:agent_name]
pubsub = ctx.pubsub
Comms.send_to_agent(pubsub, team_id, to, {:peer_message, from, to, content})
%{status: "sent", from: from, to: to}
end
@doc false
def broadcast_message(ctx, %{"content" => content}) do
team_id = ctx.deps[:team_id]
from = ctx.deps[:agent_name]
pubsub = ctx.pubsub
Comms.broadcast_team(pubsub, team_id, {:team_broadcast, from, content})
%{status: "broadcast", from: from}
end
@doc false
def share_discovery(ctx, %{"topic" => topic, "content" => content}) do
team_id = ctx.deps[:team_id]
from = ctx.deps[:agent_name]
pubsub = ctx.pubsub
shared_state = ctx.deps[:shared_state_pid]
discovery = %{topic: topic, content: content}
if is_pid(shared_state) and Process.alive?(shared_state) do
SharedState.share_discovery(shared_state, from, discovery)
end
Comms.broadcast_team(pubsub, team_id, {:discovery, from, discovery})
%{status: "shared", topic: topic, from: from}
end
@doc false
def list_team(ctx, _args) do
coordinator = ctx.deps[:team_coordinator_pid]
if is_pid(coordinator) and Process.alive?(coordinator) do
agents = Coordinator.list_agents(coordinator)
%{
team_id: ctx.deps[:team_id],
agents:
Enum.map(agents, fn a ->
%{name: a.name, status: a.status}
end)
}
else
%{team_id: ctx.deps[:team_id], agents: [], note: "coordinator unavailable"}
end
end
@doc false
def claim_region(ctx, %{"file" => file, "start_line" => start_line, "end_line" => end_line}) do
agent_name = ctx.deps[:agent_name]
shared_state = ctx.deps[:shared_state_pid]
if is_pid(shared_state) and Process.alive?(shared_state) do
case SharedState.claim_region(shared_state, agent_name, file, start_line, end_line) do
:ok ->
%{status: "claimed", file: file, start_line: start_line, end_line: end_line}
{:error, :conflict} ->
%{status: "conflict", file: file, message: "Region overlaps with another agent's claim"}
end
else
%{status: "error", message: "shared state unavailable"}
end
end
end