made_core/ports/agent_factory.rs
1//! [`AgentFactoryPort`] — materialize an [`AgentPort`] from a typed
2//! descriptor.
3//!
4//! Callers (the composition root and the `RegisterAgent` RPC) do not
5//! construct agent implementations directly; they hand a descriptor to
6//! this factory and receive a live handle back. That keeps the set of
7//! wired providers (vLLM, Anthropic, OpenAI, rule-based, human-in-the-
8//! loop, …) behind Cargo features at the adapter layer, exactly like
9//! every other provider-specific concern in this crate.
10//!
11//! The `kind` field in [`AgentDescriptor`] names the provider the
12//! factory should dispatch to. Adapters return
13//! [`DomainError::InvariantViolated`] when asked for a kind they do
14//! not support; the composition root is responsible for wiring a
15//! factory that recognises every kind the deployment intends to accept.
16
17use std::sync::Arc;
18
19use async_trait::async_trait;
20use serde::{Deserialize, Serialize};
21
22use crate::error::DomainError;
23use crate::ports::agent::AgentPort;
24use crate::value_objects::{AgentId, AgentKind, Attributes, Specialty};
25
26/// Everything the factory needs to build a live agent. Mirrors the
27/// `AgentSummary` proto, but kept in domain shapes so use cases never
28/// touch wire types.
29#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30pub struct AgentDescriptor {
31 pub id: AgentId,
32 pub specialty: Specialty,
33 pub kind: AgentKind,
34 pub attributes: Attributes,
35}
36
37#[async_trait]
38pub trait AgentFactoryPort: Send + Sync {
39 /// Produce a live agent matching `descriptor`.
40 ///
41 /// Adapters that do not recognise `descriptor.kind` must return
42 /// [`DomainError::InvariantViolated`] with a reason naming the
43 /// unsupported kind, so operators see the mismatch loudly.
44 async fn create(&self, descriptor: AgentDescriptor) -> Result<Arc<dyn AgentPort>, DomainError>;
45}