feat(orchestrator): MS23 agent lifecycle ingestion service (#701)
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Some checks failed
ci/woodpecker/push/ci Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
This commit was merged in pull request #701.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import { Injectable, Logger, Inject, forwardRef } from "@nestjs/common";
|
||||
import { Injectable, Logger, Inject, Optional, forwardRef } from "@nestjs/common";
|
||||
import { ValkeyService } from "../valkey/valkey.service";
|
||||
import { AgentSpawnerService } from "./agent-spawner.service";
|
||||
import { AgentIngestionService } from "../agent-ingestion/agent-ingestion.service";
|
||||
import type { AgentState, AgentStatus, AgentEvent } from "../valkey/types";
|
||||
import { isValidAgentTransition } from "../valkey/types/state.types";
|
||||
|
||||
@@ -32,7 +33,8 @@ export class AgentLifecycleService {
|
||||
constructor(
|
||||
private readonly valkeyService: ValkeyService,
|
||||
@Inject(forwardRef(() => AgentSpawnerService))
|
||||
private readonly spawnerService: AgentSpawnerService
|
||||
private readonly spawnerService: AgentSpawnerService,
|
||||
@Optional() private readonly agentIngestionService?: AgentIngestionService
|
||||
) {
|
||||
this.logger.log("AgentLifecycleService initialized");
|
||||
}
|
||||
@@ -55,6 +57,25 @@ export class AgentLifecycleService {
|
||||
return createdState;
|
||||
}
|
||||
|
||||
private async recordLifecycleIngestion(
|
||||
agentId: string,
|
||||
event: "started" | "completed" | "failed" | "killed",
|
||||
record: (ingestionService: AgentIngestionService) => Promise<void>
|
||||
): Promise<void> {
|
||||
if (!this.agentIngestionService) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await record(this.agentIngestionService);
|
||||
} catch (error: unknown) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(
|
||||
`Failed to record agent ${event} ingestion for ${agentId}: ${errorMessage}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquire a per-agent mutex to serialize state transitions.
|
||||
* Uses promise chaining: each caller chains onto the previous lock,
|
||||
@@ -118,6 +139,10 @@ export class AgentLifecycleService {
|
||||
// Emit event
|
||||
await this.publishStateChangeEvent("agent.running", updatedState);
|
||||
|
||||
await this.recordLifecycleIngestion(agentId, "started", (ingestionService) =>
|
||||
ingestionService.recordAgentStarted(agentId)
|
||||
);
|
||||
|
||||
this.logger.log(`Agent ${agentId} transitioned to running`);
|
||||
return updatedState;
|
||||
});
|
||||
@@ -155,6 +180,10 @@ export class AgentLifecycleService {
|
||||
// Emit event
|
||||
await this.publishStateChangeEvent("agent.completed", updatedState);
|
||||
|
||||
await this.recordLifecycleIngestion(agentId, "completed", (ingestionService) =>
|
||||
ingestionService.recordAgentCompleted(agentId)
|
||||
);
|
||||
|
||||
// Schedule session cleanup
|
||||
this.spawnerService.scheduleSessionCleanup(agentId);
|
||||
|
||||
@@ -192,6 +221,10 @@ export class AgentLifecycleService {
|
||||
// Emit event
|
||||
await this.publishStateChangeEvent("agent.failed", updatedState, error);
|
||||
|
||||
await this.recordLifecycleIngestion(agentId, "failed", (ingestionService) =>
|
||||
ingestionService.recordAgentFailed(agentId, error)
|
||||
);
|
||||
|
||||
// Schedule session cleanup
|
||||
this.spawnerService.scheduleSessionCleanup(agentId);
|
||||
|
||||
@@ -228,6 +261,10 @@ export class AgentLifecycleService {
|
||||
// Emit event
|
||||
await this.publishStateChangeEvent("agent.killed", updatedState);
|
||||
|
||||
await this.recordLifecycleIngestion(agentId, "killed", (ingestionService) =>
|
||||
ingestionService.recordAgentKilled(agentId)
|
||||
);
|
||||
|
||||
// Schedule session cleanup
|
||||
this.spawnerService.scheduleSessionCleanup(agentId);
|
||||
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { Injectable, Logger, HttpException, HttpStatus, OnModuleDestroy } from "@nestjs/common";
|
||||
import {
|
||||
Injectable,
|
||||
Logger,
|
||||
HttpException,
|
||||
HttpStatus,
|
||||
OnModuleDestroy,
|
||||
Optional,
|
||||
} from "@nestjs/common";
|
||||
import { ConfigService } from "@nestjs/config";
|
||||
import Anthropic from "@anthropic-ai/sdk";
|
||||
import { randomUUID } from "crypto";
|
||||
@@ -8,6 +15,7 @@ import {
|
||||
AgentSession,
|
||||
AgentType,
|
||||
} from "./types/agent-spawner.types";
|
||||
import { AgentIngestionService } from "../agent-ingestion/agent-ingestion.service";
|
||||
|
||||
/**
|
||||
* Default delay in milliseconds before cleaning up sessions after terminal states
|
||||
@@ -30,7 +38,10 @@ export class AgentSpawnerService implements OnModuleDestroy {
|
||||
private readonly sessionCleanupDelayMs: number;
|
||||
private readonly cleanupTimers = new Map<string, NodeJS.Timeout>();
|
||||
|
||||
constructor(private readonly configService: ConfigService) {
|
||||
constructor(
|
||||
private readonly configService: ConfigService,
|
||||
@Optional() private readonly agentIngestionService?: AgentIngestionService
|
||||
) {
|
||||
const configuredProvider = this.configService.get<string>("orchestrator.aiProvider");
|
||||
this.aiProvider = this.normalizeAiProvider(configuredProvider);
|
||||
|
||||
@@ -98,6 +109,19 @@ export class AgentSpawnerService implements OnModuleDestroy {
|
||||
this.cleanupTimers.clear();
|
||||
}
|
||||
|
||||
private recordSpawnedAgentIngestion(agentId: string, request: SpawnAgentRequest): void {
|
||||
if (!this.agentIngestionService) {
|
||||
return;
|
||||
}
|
||||
|
||||
void this.agentIngestionService
|
||||
.recordAgentSpawned(agentId, undefined, undefined, request.taskId, request.agentType)
|
||||
.catch((error: unknown) => {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(`Failed to record spawned ingestion for ${agentId}: ${errorMessage}`);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawn a new agent with the given configuration
|
||||
* @param request Agent spawn request
|
||||
@@ -130,6 +154,8 @@ export class AgentSpawnerService implements OnModuleDestroy {
|
||||
// Store session
|
||||
this.sessions.set(agentId, session);
|
||||
|
||||
this.recordSpawnedAgentIngestion(agentId, request);
|
||||
|
||||
this.logger.log(`Agent spawned successfully: ${agentId} (type: ${request.agentType})`);
|
||||
|
||||
// NOTE: Actual Claude SDK integration will be implemented in next iteration (see issue #TBD)
|
||||
|
||||
@@ -3,9 +3,10 @@ import { AgentSpawnerService } from "./agent-spawner.service";
|
||||
import { AgentLifecycleService } from "./agent-lifecycle.service";
|
||||
import { DockerSandboxService } from "./docker-sandbox.service";
|
||||
import { ValkeyModule } from "../valkey/valkey.module";
|
||||
import { AgentIngestionModule } from "../agent-ingestion/agent-ingestion.module";
|
||||
|
||||
@Module({
|
||||
imports: [ValkeyModule],
|
||||
imports: [ValkeyModule, AgentIngestionModule],
|
||||
providers: [AgentSpawnerService, AgentLifecycleService, DockerSandboxService],
|
||||
exports: [AgentSpawnerService, AgentLifecycleService, DockerSandboxService],
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user