use async_trait::async_trait;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DecisionReceipt {
pub ts: i64,
pub decision_cid: String,
pub intent_kind: String,
pub model_id: String,
pub input_tokens: u32,
pub output_tokens: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallReceipt {
pub ts: i64,
pub tool: String,
pub args_cid: String,
pub gate_decision: String,
pub outcome: String,
pub latency_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DreamReceipt {
pub ts: i64,
pub events_consolidated: u32,
pub memory_before: usize,
pub memory_after: usize,
pub duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct HandoverReceipt {
pub ts: i64,
pub summary: String,
pub pending_tasks: Vec<String>,
pub decisions_count: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QuotaBreachReceipt {
pub ts: i64,
pub quota_type: String,
pub limit: u64,
pub attempted: u64,
}
#[async_trait]
pub trait OfficeHooks: Send + Sync {
async fn on_start(&self, tenant_id: &str) {
let _ = tenant_id;
}
async fn on_decision(&self, receipt: DecisionReceipt) {
let _ = receipt;
}
async fn on_tool_call(&self, receipt: ToolCallReceipt) {
let _ = receipt;
}
async fn on_dream(&self, receipt: DreamReceipt) {
let _ = receipt;
}
async fn on_handover(&self, receipt: HandoverReceipt) {
let _ = receipt;
}
async fn on_quota_breach(&self, receipt: QuotaBreachReceipt) {
let _ = receipt;
}
async fn on_shutdown(&self, tenant_id: &str) {
let _ = tenant_id;
}
async fn on_error(&self, error: &str) {
let _ = error;
}
}
pub struct NoopHooks;
#[async_trait]
impl OfficeHooks for NoopHooks {}
pub struct TracingHooks;
#[async_trait]
impl OfficeHooks for TracingHooks {
async fn on_start(&self, tenant_id: &str) {
tracing::info!(tenant_id, "office.start");
}
async fn on_decision(&self, receipt: DecisionReceipt) {
tracing::info!(
intent = receipt.intent_kind,
model = receipt.model_id,
input_tokens = receipt.input_tokens,
output_tokens = receipt.output_tokens,
"office.decision"
);
}
async fn on_tool_call(&self, receipt: ToolCallReceipt) {
tracing::info!(
tool = receipt.tool,
gate = receipt.gate_decision,
outcome = receipt.outcome,
latency_ms = receipt.latency_ms,
"office.tool_call"
);
}
async fn on_dream(&self, receipt: DreamReceipt) {
tracing::info!(
events = receipt.events_consolidated,
memory_before = receipt.memory_before,
memory_after = receipt.memory_after,
duration_ms = receipt.duration_ms,
"office.dream"
);
}
async fn on_handover(&self, receipt: HandoverReceipt) {
tracing::info!(
decisions = receipt.decisions_count,
pending = receipt.pending_tasks.len(),
"office.handover"
);
}
async fn on_quota_breach(&self, receipt: QuotaBreachReceipt) {
tracing::warn!(
quota_type = receipt.quota_type,
limit = receipt.limit,
attempted = receipt.attempted,
"office.quota_breach"
);
}
async fn on_shutdown(&self, tenant_id: &str) {
tracing::info!(tenant_id, "office.shutdown");
}
async fn on_error(&self, error: &str) {
tracing::error!(error, "office.error");
}
}