/** * Agent Assignment Service * Based on: docs/foundation/microdao_Governance_And_Permissions_v1.md * * Manages agent work assignments to other MicroDAO/District/City */ import { db } from '../../infra/db/client'; import { logger } from '../../infra/logger/logger'; import type { AgentAssignment, CreateAssignmentRequest, AssignmentScope, } from '../../domain/ontology/types'; import { v4 as uuidv4 } from 'uuid'; export class AssignmentService { /** * Create a new agent assignment */ async createAssignment(request: CreateAssignmentRequest): Promise { try { const result = await db.query( `INSERT INTO agent_assignments (agent_id, target_microdao_id, scope, role, metadata) VALUES ($1, $2, $3, $4, $5) RETURNING *`, [ request.agentId, request.targetMicrodaoId, request.scope, request.role, JSON.stringify(request.metadata || {}), ] ); const assignment = result.rows[0]; // Publish event to outbox await this.publishEvent('dagion.agent.assignment_created', { assignmentId: assignment.id, agentId: request.agentId, targetMicrodaoId: request.targetMicrodaoId, scope: request.scope, role: request.role, timestamp: new Date().toISOString(), }); logger.info(`Created assignment: ${assignment.id} for agent ${request.agentId}`); return assignment; } catch (error) { logger.error('Failed to create assignment', error); throw error; } } /** * End an agent assignment */ async endAssignment(assignmentId: string): Promise { try { const result = await db.query( `UPDATE agent_assignments SET end_ts = now() WHERE id = $1 RETURNING *`, [assignmentId] ); if (result.rows.length === 0) { throw new Error(`Assignment not found: ${assignmentId}`); } const assignment = result.rows[0]; // Publish event to outbox await this.publishEvent('dagion.agent.assignment_ended', { assignmentId, agentId: assignment.agentId, timestamp: new Date().toISOString(), }); logger.info(`Ended assignment: ${assignmentId}`); } catch (error) { logger.error(`Failed to end assignment: ${assignmentId}`, error); throw error; } } /** * Get all active assignments for an agent */ async getAgentAssignments(agentId: string): Promise { try { const result = await db.query( `SELECT * FROM agent_assignments WHERE agent_id = $1 AND end_ts IS NULL ORDER BY created_at DESC`, [agentId] ); return result.rows; } catch (error) { logger.error(`Failed to get assignments for agent: ${agentId}`, error); throw error; } } /** * Get all assignments for a MicroDAO */ async getMicrodaoAssignments(microdaoId: string): Promise { try { const result = await db.query( `SELECT a.*, ag.name as agent_name FROM agent_assignments a JOIN agents ag ON ag.id = a.agent_id WHERE a.target_microdao_id = $1 AND a.end_ts IS NULL ORDER BY a.created_at DESC`, [microdaoId] ); return result.rows; } catch (error) { logger.error(`Failed to get assignments for microdao: ${microdaoId}`, error); throw error; } } /** * Get citywide assignments (DAARION108) */ async getCitywideAssignments(): Promise { try { const result = await db.query( `SELECT a.*, ag.name as agent_name FROM agent_assignments a JOIN agents ag ON ag.id = a.agent_id WHERE a.scope = 'city' AND a.end_ts IS NULL ORDER BY a.created_at DESC`, [] ); return result.rows; } catch (error) { logger.error('Failed to get citywide assignments', error); throw error; } } /** * Check if agent has assignment to target */ async hasAssignment(agentId: string, targetMicrodaoId: string): Promise { try { const result = await db.query( `SELECT 1 FROM agent_assignments WHERE agent_id = $1 AND target_microdao_id = $2 AND end_ts IS NULL LIMIT 1`, [agentId, targetMicrodaoId] ); return result.rows.length > 0; } catch (error) { logger.error('Failed to check assignment', error); throw error; } } /** * Get agent's effective scope (home + assignments) */ async getAgentScope(agentId: string): Promise<{ homeMicrodaoId: string | null; assignments: AgentAssignment[]; effectiveScope: AssignmentScope; }> { try { // Get agent's home MicroDAO const agent = await db.query( `SELECT home_microdao_id, agent_service_scope FROM agents WHERE id = $1`, [agentId] ); if (agent.rows.length === 0) { throw new Error(`Agent not found: ${agentId}`); } const assignments = await this.getAgentAssignments(agentId); // Determine effective scope let effectiveScope: AssignmentScope = 'microdao'; if (agent.rows[0].agent_service_scope === 'city') { effectiveScope = 'city'; } else if (assignments.some(a => a.scope === 'city')) { effectiveScope = 'city'; } else if (assignments.some(a => a.scope === 'district')) { effectiveScope = 'district'; } return { homeMicrodaoId: agent.rows[0].home_microdao_id, assignments, effectiveScope, }; } catch (error) { logger.error(`Failed to get agent scope: ${agentId}`, error); throw error; } } /** * Publish event to outbox for NATS */ private async publishEvent(eventType: string, payload: Record): Promise { await db.query( `INSERT INTO event_outbox (event_type, subject, payload) VALUES ($1, $2, $3)`, [eventType, eventType, JSON.stringify(payload)] ); } } export const assignmentService = new AssignmentService();