Skip to main content

conversation_api/execution/
facade.rs

1//! App Facade contract used by the canonical Agent implementation.
2
3use async_trait::async_trait;
4use serde::{Deserialize, Serialize};
5use serde_json::Value;
6
7use crate::execution::{ActionId, ExternalError, InvocationContext, OperationId};
8
9/// Canonical App Facade request selected by Agent-owned tool code.
10#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
11#[serde(deny_unknown_fields)]
12pub struct FacadeRequest {
13    /// Stable idempotency identifier for this tool operation.
14    pub operation_id: OperationId,
15    /// Canonical `/app/...` target. The Agent owns every tool-to-target mapping.
16    pub target: String,
17    /// Structured input validated and normalized by Agent-owned tool code.
18    pub input: Value,
19    /// Output projection, separate from the immutable business input.
20    pub response_detail: crate::execution::ResponseDetail,
21}
22
23pub use crate::{InteractionCard, InteractionKind, InteractionTone};
24
25/// Prepared operation awaiting an explicit commit or rejection.
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
27#[serde(deny_unknown_fields)]
28pub struct PreparedAction {
29    /// Stable action identifier used for idempotent commit and rejection.
30    pub action_id: ActionId,
31    /// Idempotency identifier supplied by Runtime.
32    pub operation_id: OperationId,
33    /// App Facade-generated preview suitable for user confirmation.
34    pub card: InteractionCard,
35    /// Opaque execution plan. Runtime persists it but never exposes it to the model or UI.
36    #[serde(default)]
37    pub payload: Value,
38    /// App Facade revision that must still be current when committing.
39    pub revision: u64,
40}
41
42/// Structured result returned by an App Facade operation.
43#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
44#[serde(deny_unknown_fields)]
45pub struct FacadeResult {
46    /// Canonical serialized `FacadeResponse`; never a bare business DTO.
47    #[serde(deserialize_with = "deserialize_response")]
48    pub value: Value,
49    /// New prompt-context generation when this operation changed Agent-visible facts.
50    #[serde(default, skip_serializing_if = "Option::is_none")]
51    pub context_invalidation: Option<crate::execution::ContextInvalidation>,
52}
53
54/// Environment-specific implementation of the App Facade boundary.
55///
56/// Implementations may execute in-process (`MeowCore`) or through RPC (agent-cloud/Lion), but they
57/// cannot supply prompts, tools, workflows, or model-facing schemas.
58#[async_trait]
59pub trait AppFacade: Send + Sync {
60    /// Executes a direct App Facade request.
61    async fn invoke(
62        &self,
63        context: &InvocationContext,
64        request: FacadeRequest,
65    ) -> Result<FacadeResult, ExternalError>;
66
67    /// Prepares a mutating operation without applying it.
68    async fn prepare(
69        &self,
70        context: &InvocationContext,
71        request: FacadeRequest,
72    ) -> Result<PreparedAction, ExternalError>;
73
74    /// Commits a prepared operation idempotently.
75    async fn commit(
76        &self,
77        context: &InvocationContext,
78        action: PreparedAction,
79    ) -> Result<FacadeResult, ExternalError>;
80
81    /// Rejects a prepared operation idempotently.
82    async fn reject(
83        &self,
84        context: &InvocationContext,
85        action: PreparedAction,
86    ) -> Result<(), ExternalError>;
87}
88
89fn deserialize_response<'de, D: serde::Deserializer<'de>>(
90    deserializer: D,
91) -> Result<Value, D::Error> {
92    use serde::de::Error;
93    let response = crate::execution::FacadeResponse::deserialize(deserializer)?;
94    serde_json::to_value(response).map_err(D::Error::custom)
95}