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}