Skip to main content

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}