forge-engine 0.2.0

Causal edit attribution and structured patch evaluation engine
Documentation
use async_trait::async_trait;
use llm_tool_runtime::{ToolError, ToolErrorClass, ToolReceipt, ToolReceiptSink};
use semantic_memory_forge::{
    ForgeToolBudgetContext, ForgeToolReceiptV2, FORGE_TOOL_RECEIPT_V2_SCHEMA,
};
use std::sync::Arc;

use crate::{store::db::ToolReceiptRow, ForgeStore};

#[derive(Debug, Clone)]
pub struct ForgeToolReceiptSink {
    store: Arc<ForgeStore>,
}

impl ForgeToolReceiptSink {
    pub fn new(store: Arc<ForgeStore>) -> Self {
        Self { store }
    }
}

#[async_trait]
impl ToolReceiptSink for ForgeToolReceiptSink {
    async fn persist(&self, receipt: &ToolReceipt) -> Result<(), ToolError> {
        let row = ToolReceiptRow {
            receipt_id: receipt.receipt_id.clone(),
            tool_run_id: receipt.tool_run_id.clone(),
            tool_name: receipt.tool_name.clone(),
            tool_version: receipt.tool_version.clone(),
            backend_kind: format!("{:?}", receipt.backend_kind),
            input_digest: receipt.input_digest.hex().to_string(),
            output_digest_or_refs_json: serde_json::to_string(&receipt.output_digest_or_refs)
                .map_err(|err| {
                    ToolError::new(
                        ToolErrorClass::ReceiptPersistence,
                        format!("failed to serialize tool receipt outputs: {}", err),
                    )
                })?,
            policy_hash: receipt.policy_hash.hex().to_string(),
            approval_state: format!("{:?}", receipt.approval_state),
            host_identity: receipt.host_identity.clone(),
            started_at: receipt.started_at.clone(),
            finished_at: receipt.finished_at.clone(),
            trace_id: receipt.trace_ctx.trace_id.clone(),
            trace_ctx_json: serde_json::to_string(&receipt.trace_ctx).map_err(|err| {
                ToolError::new(
                    ToolErrorClass::ReceiptPersistence,
                    format!("failed to serialize trace context: {}", err),
                )
            })?,
            attempt_id: receipt.attempt_id.as_str().to_string(),
            trial_id: receipt.trial_id.as_str().to_string(),
            error_class: receipt
                .error_class
                .as_ref()
                .map(|class| format!("{:?}", class)),
            retry_owner: format!("{:?}", receipt.retry_owner),
            replay_link: receipt.replay_link.clone(),
            provider_call_id: receipt.provider_call_id.clone(),
            raw_payload_json: serde_json::to_string(&forge_tool_receipt(receipt)).map_err(
                |err| {
                    ToolError::new(
                        ToolErrorClass::ReceiptPersistence,
                        format!("failed to serialize Forge tool receipt payload: {}", err),
                    )
                },
            )?,
            recorded_at: chrono::Utc::now().to_rfc3339(),
        };
        self.store.insert_tool_receipt(&row).map_err(|err| {
            ToolError::new(
                ToolErrorClass::ReceiptPersistence,
                format!("failed to persist Forge tool receipt: {}", err),
            )
        })?;
        Ok(())
    }
}

fn forge_tool_receipt(receipt: &ToolReceipt) -> ForgeToolReceiptV2 {
    ForgeToolReceiptV2 {
        schema_version: FORGE_TOOL_RECEIPT_V2_SCHEMA.into(),
        receipt_id: receipt.receipt_id.clone(),
        tool_run_id: receipt.tool_run_id.clone(),
        tool_name: receipt.tool_name.clone(),
        tool_version: receipt.tool_version.clone(),
        backend_kind: format!("{:?}", receipt.backend_kind),
        input_digest: receipt.input_digest.clone(),
        output_digest_or_refs: receipt.output_digest_or_refs.clone(),
        policy_hash: receipt.policy_hash.clone(),
        approval_state: format!("{:?}", receipt.approval_state),
        host_identity: receipt.host_identity.clone(),
        started_at: receipt.started_at.clone(),
        finished_at: receipt.finished_at.clone(),
        trace_ctx: receipt.trace_ctx.clone(),
        attempt_id: receipt.attempt_id.clone(),
        trial_id: receipt.trial_id.clone(),
        planner_stage: format!("{:?}", receipt.planner_stage),
        deadline: receipt.deadline.clone(),
        workload_class: receipt.workload_class.clone(),
        budget_context: receipt
            .budget_context
            .as_ref()
            .map(|budget| ForgeToolBudgetContext {
                budget_kind: budget.budget_kind.clone(),
                max_steps: budget.max_steps,
                time_budget_ms: budget.time_budget_ms,
                cost_budget_units: budget.cost_budget_units,
            }),
        parent_receipt_id: receipt.parent_receipt_id.clone(),
        family_receipt_id: receipt.family_receipt_id.clone(),
        replay_parent_receipt_id: receipt.replay_parent_receipt_id.clone(),
        error_class: receipt
            .error_class
            .as_ref()
            .map(|class| format!("{:?}", class)),
        retry_owner: format!("{:?}", receipt.retry_owner),
        replay_link: receipt.replay_link.clone(),
        provider_call_id: receipt.provider_call_id.clone(),
        raw_payload: serde_json::to_value(receipt).unwrap_or_else(|_| serde_json::json!({})),
    }
}