Skip to main content

objectiveai_cli/db/
agent_refs.rs

1//! Definition-source registry keyed by `agent_instance_hierarchy`:
2//! EITHER the `RemotePath` the agent's WF was fetched from, OR the
3//! inline WF spec itself — exactly one per row.
4//!
5//! Single function: [`upsert`] — blind, last-write-wins, no
6//! read-before-write (mirrors [`super::agent_continuations`]). Two
7//! writers:
8//!
9//! - `agents spawn`, for spawns by SPEC only (never via tag or AIH),
10//!   at the moment the AIH lock is acquired (first-chunk identity
11//!   capture).
12//! - The log writer, whenever a chunk carries `agent_inline` (the
13//!   first chunk of every agent completion, at any tier/nesting):
14//!   `agent_remote` present → [`AgentRefValue::Remote`], else
15//!   [`AgentRefValue::Inline`].
16//!
17//! No read API today — write-only scaffolding for surfacing agent
18//! definition sources (e.g. `agents instances get`) later.
19
20use serde::Serialize;
21
22use super::{Error, Pool};
23
24/// One row's payload: the remote path's wire string, or the inline
25/// WF spec as JSON.
26pub enum AgentRefValue {
27    Remote(String),
28    Inline(serde_json::Value),
29}
30
31impl AgentRefValue {
32    /// The remote path's wire form (`RemotePath` and its
33    /// commit-optional twin both serialize as plain strings).
34    pub fn remote<T: Serialize>(remote: &T) -> Option<Self> {
35        match serde_json::to_value(remote).ok()? {
36            serde_json::Value::String(s) => Some(Self::Remote(s)),
37            other => Some(Self::Remote(other.to_string())),
38        }
39    }
40
41    /// The inline WF spec's JSON form.
42    pub fn inline<T: Serialize>(spec: &T) -> Option<Self> {
43        serde_json::to_value(spec).ok().map(Self::Inline)
44    }
45}
46
47fn now_seconds() -> i64 {
48    use std::time::{SystemTime, UNIX_EPOCH};
49    SystemTime::now()
50        .duration_since(UNIX_EPOCH)
51        .map(|d| d.as_secs() as i64)
52        .unwrap_or(0)
53}
54
55/// Insert-or-replace the definition source for an AIH. Blind: prior
56/// values are overwritten unconditionally, whichever variant they
57/// held.
58pub async fn upsert(
59    pool: &Pool,
60    agent_instance_hierarchy: &str,
61    value: AgentRefValue,
62) -> Result<(), Error> {
63    let (remote, inline) = match value {
64        AgentRefValue::Remote(remote) => (Some(remote), None),
65        AgentRefValue::Inline(inline) => (None, Some(inline)),
66    };
67    sqlx::query(
68        "INSERT INTO objectiveai.agent_refs \
69             (agent_instance_hierarchy, remote, inline, updated_at) \
70         VALUES ($1, $2, $3, $4) \
71         ON CONFLICT (agent_instance_hierarchy) DO UPDATE SET \
72             remote     = EXCLUDED.remote, \
73             inline     = EXCLUDED.inline, \
74             updated_at = EXCLUDED.updated_at",
75    )
76    .bind(agent_instance_hierarchy)
77    .bind(remote)
78    .bind(inline)
79    .bind(now_seconds())
80    .execute(&**pool)
81    .await?;
82    Ok(())
83}