Skip to content

Two agents using the same goal name: one goal is silently dropped #1834

Description

@tuannx

What happens

Two agents, each with a goal named done, but meaning different things:

platform.deploy(agentA)   // goal "done", description "what A means"
platform.deploy(agentB)   // goal "done", description "what B means"

platform.agents().size    // 2
platform.goals.size       // 1   <- one goal is gone

Both deploy. agents() returns both. Nothing is logged. One goal no longer
exists. Same for actions and conditions.

Why it matters

PerGoalToolFactory publishes one MCP tool per goal, so a dropped goal is a
tool missing from the MCP server, with no error.

Which one survives depends on agent name order, since agents() sorts by name.
Deploying a new unrelated agent can therefore change which goal an existing
tool points at.

Where

AgentPlatform.kt aggregates all three with distinctBy { it.name }:

override val actions: List<Action>
    get() = agents().filterNot { it.opaque }.flatMap { it.actions }.distinctBy { it.name }

override val goals: Set<Goal>
    get() = agents().flatMap { it.goals }.distinctBy { it.name }.toSet()

override val conditions: Set<Condition>
    get() = agents().filterNot { it.opaque }.flatMap { it.conditions }.distinctBy { it.name }.toSet()

Annotation agents mostly escape it, because AgentMetadataReader names goals
ClassName.methodName. DSL agents use whatever name the developer types.

How it turned up

Found while writing the MCP end-to-end tests in #1832. Two test fixtures
happened to share a goal name, and one goal never reached tools/list.

Fix

PR #1833.

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions