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.
What happens
Two agents, each with a goal named
done, but meaning different things:Both deploy.
agents()returns both. Nothing is logged. One goal no longerexists. Same for actions and conditions.
Why it matters
PerGoalToolFactorypublishes one MCP tool per goal, so a dropped goal is atool 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.ktaggregates all three withdistinctBy { it.name }:Annotation agents mostly escape it, because
AgentMetadataReadernames goalsClassName.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.