/ src / utils / agentId.ts
agentId.ts
 1  /**
 2   * Deterministic Agent ID System
 3   *
 4   * This module provides helper functions for formatting and parsing deterministic
 5   * agent IDs used in the swarm/teammate system.
 6   *
 7   * ## ID Formats
 8   *
 9   * **Agent IDs**: `agentName@teamName`
10   * - Example: `team-lead@my-project`, `researcher@my-project`
11   * - The @ symbol acts as a separator between agent name and team name
12   *
13   * **Request IDs**: `{requestType}-{timestamp}@{agentId}`
14   * - Example: `shutdown-1702500000000@researcher@my-project`
15   * - Used for shutdown requests, plan approvals, etc.
16   *
17   * ## Why Deterministic IDs?
18   *
19   * Deterministic IDs provide several benefits:
20   *
21   * 1. **Reproducibility**: The same agent spawned with the same name in the same team
22   *    always gets the same ID, enabling reconnection after crashes/restarts.
23   *
24   * 2. **Human-readable**: IDs are meaningful and debuggable (e.g., `tester@my-project`).
25   *
26   * 3. **Predictable**: Team leads can compute a teammate's ID without looking it up,
27   *    simplifying message routing and task assignment.
28   *
29   * ## Constraints
30   *
31   * - Agent names must NOT contain `@` (it's used as the separator)
32   * - Use `sanitizeAgentName()` from TeammateTool.ts to strip @ from names
33   */
34  
35  /**
36   * Formats an agent ID in the format `agentName@teamName`.
37   */
38  export function formatAgentId(agentName: string, teamName: string): string {
39    return `${agentName}@${teamName}`
40  }
41  
42  /**
43   * Parses an agent ID into its components.
44   * Returns null if the ID doesn't contain the @ separator.
45   */
46  export function parseAgentId(
47    agentId: string,
48  ): { agentName: string; teamName: string } | null {
49    const atIndex = agentId.indexOf('@')
50    if (atIndex === -1) {
51      return null
52    }
53    return {
54      agentName: agentId.slice(0, atIndex),
55      teamName: agentId.slice(atIndex + 1),
56    }
57  }
58  
59  /**
60   * Formats a request ID in the format `{requestType}-{timestamp}@{agentId}`.
61   */
62  export function generateRequestId(
63    requestType: string,
64    agentId: string,
65  ): string {
66    const timestamp = Date.now()
67    return `${requestType}-${timestamp}@${agentId}`
68  }
69  
70  /**
71   * Parses a request ID into its components.
72   * Returns null if the request ID doesn't match the expected format.
73   */
74  export function parseRequestId(
75    requestId: string,
76  ): { requestType: string; timestamp: number; agentId: string } | null {
77    const atIndex = requestId.indexOf('@')
78    if (atIndex === -1) {
79      return null
80    }
81  
82    const prefix = requestId.slice(0, atIndex)
83    const agentId = requestId.slice(atIndex + 1)
84  
85    const lastDashIndex = prefix.lastIndexOf('-')
86    if (lastDashIndex === -1) {
87      return null
88    }
89  
90    const requestType = prefix.slice(0, lastDashIndex)
91    const timestampStr = prefix.slice(lastDashIndex + 1)
92    const timestamp = parseInt(timestampStr, 10)
93  
94    if (isNaN(timestamp)) {
95      return null
96    }
97  
98    return { requestType, timestamp, agentId }
99  }