feat(domain): modèle de tâches de fond de 1re classe et mailbox (B1)
Entités BackgroundTask et Inbox, identifiants, événements et ports du domaine pour la récupération de complétion post-tour et la réception de messages concurrents. Fondations des lots infra/application suivants. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -28,8 +28,11 @@ use async_trait::async_trait;
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::agent::AgentManifest;
|
||||
use crate::background_task::{
|
||||
BackgroundTask, BackgroundTaskKind, BackgroundTaskResult, BackgroundTaskWakePolicy,
|
||||
};
|
||||
use crate::events::DomainEvent;
|
||||
use crate::ids::{AgentId, NodeId, ScheduleId, SessionId};
|
||||
use crate::ids::{AgentId, NodeId, ProjectId, ScheduleId, SessionId, TaskId};
|
||||
use crate::markdown::MarkdownDoc;
|
||||
use crate::memory::{Memory, MemoryIndexEntry, MemoryLink, MemorySlug};
|
||||
use crate::permission::ProjectPermissions;
|
||||
@ -196,6 +199,59 @@ pub type OutputStream = Box<dyn Iterator<Item = Vec<u8>> + Send>;
|
||||
/// A boxed stream of domain events, returned by [`EventBus::subscribe`].
|
||||
pub type EventStream = Box<dyn Iterator<Item = DomainEvent> + Send>;
|
||||
|
||||
/// A boxed stream of background task completions.
|
||||
pub type BackgroundCompletionStream = Box<dyn Iterator<Item = BackgroundTaskCompletion> + Send>;
|
||||
|
||||
/// Specification passed to a background task runner.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackgroundTaskSpec {
|
||||
/// Task id allocated by the application.
|
||||
pub task_id: TaskId,
|
||||
/// Owning project.
|
||||
pub project_id: ProjectId,
|
||||
/// Agent that owns completion delivery.
|
||||
pub owner_agent_id: AgentId,
|
||||
/// Kind-specific task payload.
|
||||
pub kind: BackgroundTaskKind,
|
||||
/// Completion wake policy.
|
||||
pub wake_policy: BackgroundTaskWakePolicy,
|
||||
/// Optional command invocation for command-backed tasks.
|
||||
pub command: Option<SpawnSpec>,
|
||||
/// Optional absolute deadline, epoch milliseconds.
|
||||
pub deadline_ms: Option<u64>,
|
||||
}
|
||||
|
||||
/// Opaque runner handle for a spawned background task.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackgroundTaskHandle {
|
||||
/// Spawned task id.
|
||||
pub task_id: TaskId,
|
||||
}
|
||||
|
||||
/// Terminal completion reported by a background runner.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct BackgroundTaskCompletion {
|
||||
/// Completed task id.
|
||||
pub task_id: TaskId,
|
||||
/// Terminal result.
|
||||
pub result: BackgroundTaskResult,
|
||||
}
|
||||
|
||||
/// Why IdeA wakes an agent outside a human/delegation submit.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum WakeReason {
|
||||
/// A persisted background task completion is ready in the owner's inbox.
|
||||
BackgroundCompletion {
|
||||
/// Completed background task.
|
||||
task_id: TaskId,
|
||||
},
|
||||
/// A generic queued inbox item should be processed.
|
||||
InboxItem {
|
||||
/// Queued item id.
|
||||
item_id: crate::mailbox::TicketId,
|
||||
},
|
||||
}
|
||||
|
||||
/// Un événement incrémental d'un tour de réponse d'un agent IA (ARCHITECTURE §17.1).
|
||||
///
|
||||
/// Universel : l'adapter (Claude/Codex) traduit SON format structuré documenté
|
||||
@ -461,6 +517,55 @@ pub enum GitError {
|
||||
Operation(String),
|
||||
}
|
||||
|
||||
/// Errors from first-class background task ports.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum BackgroundTaskPortError {
|
||||
/// The requested task was not found.
|
||||
#[error("background task not found")]
|
||||
NotFound,
|
||||
/// A task with this id already exists.
|
||||
#[error("background task already exists")]
|
||||
AlreadyExists,
|
||||
/// The task or request is invalid.
|
||||
#[error("background task invalid: {0}")]
|
||||
Invalid(String),
|
||||
/// The runner failed.
|
||||
#[error("background task runner failed: {0}")]
|
||||
Runner(String),
|
||||
/// The store failed.
|
||||
#[error("background task store failed: {0}")]
|
||||
Store(String),
|
||||
}
|
||||
|
||||
/// Errors from the owner wake port.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Error)]
|
||||
pub enum WakeError {
|
||||
/// The owner is already running a turn; the inbox item must stay queued.
|
||||
#[error("agent {agent_id} is busy; wake skipped")]
|
||||
AgentBusy {
|
||||
/// Target agent.
|
||||
agent_id: AgentId,
|
||||
},
|
||||
/// There is no queued item to drain for this wake.
|
||||
#[error("no pending inbox item for agent {agent_id}")]
|
||||
NoPendingItem {
|
||||
/// Target agent.
|
||||
agent_id: AgentId,
|
||||
},
|
||||
/// The drained inbox item does not match the requested wake reason.
|
||||
#[error("unexpected inbox item for wake: {0}")]
|
||||
UnexpectedInboxItem(String),
|
||||
/// Session lookup or launch failed.
|
||||
#[error("wake session failed: {0}")]
|
||||
Session(String),
|
||||
/// Background task store failed.
|
||||
#[error("wake store failed: {0}")]
|
||||
Store(String),
|
||||
/// Background task payload was missing or invalid for wake delivery.
|
||||
#[error("wake task invalid: {0}")]
|
||||
Task(String),
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Git port support types
|
||||
// ---------------------------------------------------------------------------
|
||||
@ -1191,6 +1296,95 @@ pub trait PermissionStore: Send + Sync {
|
||||
) -> Result<(), StoreError>;
|
||||
}
|
||||
|
||||
/// Persistence port for first-class background tasks.
|
||||
#[async_trait]
|
||||
pub trait BackgroundTaskStore: Send + Sync {
|
||||
/// Creates a new task.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] if the task already exists or cannot be stored.
|
||||
async fn create(&self, task: &BackgroundTask) -> Result<(), BackgroundTaskPortError>;
|
||||
|
||||
/// Gets a task by id.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] on store failure.
|
||||
async fn get(&self, id: TaskId) -> Result<Option<BackgroundTask>, BackgroundTaskPortError>;
|
||||
|
||||
/// Saves a task.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] on store failure.
|
||||
async fn save(&self, task: &BackgroundTask) -> Result<(), BackgroundTaskPortError>;
|
||||
|
||||
/// Lists non-terminal tasks owned by an agent.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] on store failure.
|
||||
async fn list_open_for_agent(
|
||||
&self,
|
||||
agent_id: AgentId,
|
||||
) -> Result<Vec<BackgroundTask>, BackgroundTaskPortError>;
|
||||
|
||||
/// Lists terminal tasks whose completion has not been delivered yet.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] on store failure.
|
||||
async fn list_undelivered_completions(
|
||||
&self,
|
||||
) -> Result<Vec<BackgroundTask>, BackgroundTaskPortError>;
|
||||
|
||||
/// Marks a completion as delivered.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] on store failure.
|
||||
async fn mark_completion_delivered(
|
||||
&self,
|
||||
task_id: TaskId,
|
||||
) -> Result<(), BackgroundTaskPortError>;
|
||||
}
|
||||
|
||||
/// Execution port for first-class background tasks.
|
||||
#[async_trait]
|
||||
pub trait BackgroundTaskRunner: Send + Sync {
|
||||
/// Spawns a background task.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] when the task cannot be spawned.
|
||||
async fn spawn(
|
||||
&self,
|
||||
spec: BackgroundTaskSpec,
|
||||
) -> Result<BackgroundTaskHandle, BackgroundTaskPortError>;
|
||||
|
||||
/// Cancels a running task.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`BackgroundTaskPortError`] when cancellation fails.
|
||||
async fn cancel(&self, task_id: TaskId) -> Result<(), BackgroundTaskPortError>;
|
||||
|
||||
/// Subscribes to terminal completions.
|
||||
fn subscribe_completions(&self) -> BackgroundCompletionStream;
|
||||
}
|
||||
|
||||
/// Wakes an agent to process one queued system/inbox item.
|
||||
#[async_trait]
|
||||
pub trait AgentWakePort: Send + Sync {
|
||||
/// Wakes `agent` in `project` for `reason`, if the agent is idle.
|
||||
///
|
||||
/// Implementations must drain at most one inbox item and must not start a
|
||||
/// concurrent turn when the agent is already busy.
|
||||
///
|
||||
/// # Errors
|
||||
/// [`WakeError`] when the session/store operation fails or the queued item is
|
||||
/// inconsistent with the requested wake.
|
||||
async fn wake_agent(
|
||||
&self,
|
||||
project: &Project,
|
||||
agent: AgentId,
|
||||
reason: WakeReason,
|
||||
) -> Result<(), WakeError>;
|
||||
}
|
||||
|
||||
/// Git operations for a project. Named `GitPort` to avoid clashing with the
|
||||
/// [`crate::git::GitRepository`] *entity* (state image).
|
||||
#[async_trait]
|
||||
|
||||
Reference in New Issue
Block a user