Agent Communication
TnsAI agents communicate through structured, immutable messages following established multi-agent protocols. The communication system supports direct messaging, Contract Net Protocol (CNP) for task allocation, group membership management, and leader election.
AgentMessage
com.tnsai.communication.AgentMessage is the core message type for inter-agent communication. It is an immutable value object (all fields are final, maps are unmodifiable copies).
Structure
| Field | Type | Description |
|---|---|---|
messageId | String | Unique message ID (auto-generated via UUID) |
senderId | String | ID of the sending agent (required) |
receiverId | String | ID of the receiving agent (required) |
messageType | MessageTypeMarker | Type of message (CNP, Task, etc.) |
content | Map<String, Object> | Message payload (immutable copy) |
timestamp | long | Message creation time |
inReplyTo | String | ID of the message being replied to (null for originals) |
metadata | Map<String, Object> | Additional metadata (immutable copy) |
Creating Messages
// Basic message
AgentMessage msg = AgentMessage.create(
"agent-A", // sender
"agent-B", // receiver
CNPMessageType.CFP, // type
Map.of("task", "analyze data", "deadline", "2h") // content
);
// Reply (automatically swaps sender/receiver, sets inReplyTo)
AgentMessage reply = AgentMessage.createReply(
originalMessage,
CNPMessageType.PROPOSAL,
Map.of("bid", 100, "eta", "1h")
);
// Add metadata to existing message
AgentMessage withMeta = msg.withSpec("priority", "high");Query Methods
boolean isReply = msg.isReply();
boolean isCfp = msg.isType(CNPMessageType.CFP);
boolean isCnp = msg.isCategory("CNP");
Map<String, Object> spec = msg.getSpec(); // alias for getMetadata()MessageTypeMarker
com.tnsai.communication.MessageTypeMarker is the marker interface that all message type enums implement. It provides a category() method used for filtering messages by protocol.
CNPMessageType
com.tnsai.communication.CNPMessageType implements the Contract Net Protocol (Smith, 1980) for distributed task allocation.
Protocol Flow:
Phase 1: Call for Proposals
Initiator -> CFP (broadcast task requirements)
Phase 2: Contractor Response
Contractors -> PROPOSAL (submit bid) or REFUSE (decline)
Phase 3: Selection
Initiator -> ACCEPT or REJECT
Phase 4: Negotiation (optional)
COUNTER_OFFER exchanges
Phase 5: Finalization
Initiator -> AWARD
Contractor -> CONFIRM
Execution:
RESULT (success) or FAILURE (error)Enum values:
| Value | Phase | Description |
|---|---|---|
CFP | 1 | Call for proposals (broadcast) |
PROPOSAL | 2 | Submit bid/proposal |
REFUSE | 2 | Decline to bid |
ACCEPT | 3 | Accept a proposal |
REJECT | 3 | Reject a proposal |
COUNTER_OFFER | 4 | Propose modified terms |
AWARD | 5 | Final contract award |
CONFIRM | 5 | Confirm acceptance |
CANCEL | 5 | Cancel the process |
RESULT | Execution | Execution result |
FAILURE | Execution | Execution failure |
CNP Example
// Initiator broadcasts CFP
AgentMessage cfp = AgentMessage.create(
initiator.id(), "broadcast",
CNPMessageType.CFP,
Map.of("task", "translate document", "language", "Japanese")
);
// Contractor submits proposal
AgentMessage proposal = AgentMessage.createReply(cfp,
CNPMessageType.PROPOSAL,
Map.of("bid", 50, "eta", "30min", "quality", "high")
);
// Initiator accepts
AgentMessage accept = AgentMessage.createReply(proposal,
CNPMessageType.ACCEPT,
Map.of("contractId", "c-123")
);
// Contractor reports result
AgentMessage result = AgentMessage.createReply(accept,
CNPMessageType.RESULT,
Map.of("translation", translatedText, "wordCount", 1500)
);MembershipManager
com.tnsai.coordination.groups.MembershipManager manages agent group membership lifecycle including registration, health monitoring, capability-based lookup, and task tracking.
Registration
// Register an agent
Optional<MemberInfo> info = membershipManager.register(agent);
// Validate before registration
MembershipValidation validation = membershipManager.validate(agent);
if (!validation.valid()) {
System.err.println("Rejected: " + validation.reason());
}
// Unregister
boolean removed = membershipManager.unregister(agentId, "task completed");Health Monitoring
// Record heartbeat
membershipManager.heartbeat(agentId);
// Check health
Optional<MemberHealth> health = membershipManager.getHealth(agentId);
// Get unhealthy members
List<MemberInfo> unhealthy = membershipManager.getUnhealthyMembers();
// Auto-remove unhealthy
int removed = membershipManager.checkAndRemoveUnhealthy();
// Start/stop automatic health checking
membershipManager.startHealthCheck(Duration.ofSeconds(30));
membershipManager.stopHealthCheck();MemberHealth enum: HEALTHY, DEGRADED, UNHEALTHY, UNKNOWN.
Member Queries
List<MemberInfo> all = membershipManager.getMembers();
Optional<MemberInfo> member = membershipManager.getMember(agentId);
List<MemberInfo> searchers = membershipManager.findByCapability("search");
int count = membershipManager.getMemberCount();Task Tracking
// Record task start/complete
membershipManager.recordTaskStart(agentId);
membershipManager.recordTaskComplete(agentId);
// Find available members (healthy + can accept more tasks)
List<MemberInfo> available = membershipManager.getAvailableMembers();
// Available with specific capability
List<MemberInfo> available = membershipManager.getAvailableMembersWithCapability("coding");MemberInfo Record
record MemberInfo(
String agentId,
String agentName,
List<String> capabilities,
Instant joinedAt,
Instant lastHeartbeat,
MemberHealth health,
Map<String, Object> metadata,
int priority, // default 5
int maxConcurrentTasks, // default 1
int activeTasks, // current active count
String role // default "member"
)Key methods on MemberInfo:
| Method | Description |
|---|---|
isHealthy() | health == HEALTHY |
canAcceptTask() | activeTasks < maxConcurrentTasks |
getMembershipDuration() | Time since joinedAt |
withTaskStarted() | New MemberInfo with activeTasks + 1 |
withTaskCompleted() | New MemberInfo with activeTasks - 1 |
withHeartbeat(Instant) | New MemberInfo with updated heartbeat |
withHealth(MemberHealth) | New MemberInfo with updated health |
Statistics and History
MembershipStats stats = membershipManager.getStats();
// stats.currentCount(), stats.healthyCount(), stats.averageMembershipDuration()
List<MembershipHistoryEntry> history = membershipManager.getHistory(50);
// Each entry: agentId, agentName, action (JOINED/LEFT/REMOVED_UNHEALTHY/REMOVED_POLICY),
// reason, timestampLeaderElectionStrategy
com.tnsai.coordination.groups.LeaderElectionStrategy is a @FunctionalInterface for selecting a leader from group members.
@FunctionalInterface
public interface LeaderElectionStrategy {
Agent elect(List<Agent> members);
default Optional<Agent> electOptional(List<Agent> members) { ... }
}Built-in Strategies
| Strategy | Constant | Behavior |
|---|---|---|
| Random | RANDOM | Random selection from members |
| First Joined | FIRST_JOINED | First member in the list |
| Last Joined | LAST_JOINED | Last member in the list |
| Round Robin | ROUND_ROBIN | Rotating (delegates to FIRST_JOINED; state managed by group) |
| Role-Based | ROLE_BASED | Agent with most roles (most capabilities) |
| None | NONE | Always returns null |
Custom Strategies
// Lambda-based custom strategy
LeaderElectionStrategy byCapabilities = members ->
members.stream()
.max(Comparator.comparingInt(a -> a.getCapabilities().size()))
.orElse(null);
// Usage
Agent leader = strategy.elect(groupMembers);
Optional<Agent> leaderOpt = strategy.electOptional(groupMembers);Related Documentation
- Advanced Agent Features -- hierarchy and delegation
- Events -- TnsAI event system
- SPI Reference -- MessageBroker SPI for pluggable transports
Communication Protocols
TnsAI provides annotation-driven inter-agent communication through ProtocolManager. Annotate your agent class with one or more communication paradigms and the framework auto-configures handlers, transports, and discovery.
AutoTeamBuilder
TnsAI.Intelligence provides LLM-driven automatic team composition. Given a task description, AutoTeamBuilder decomposes it into subtasks, generates agent configurations, and selects the optimal coordination topology. Package: com.tnsai.autoteam.