Skip to main content

objectiveai_cli/db/
tag_groups.rs

1//! `tag_groups` table — explicit grouping container that lets
2//! many `tags` rows share one resolved `AgentSpec` + parent
3//! lineage.
4//!
5//! Inserts happen inline in `db::tags::apply`'s
6//! `ResolvedApplyTarget::Agent` arm (kept there so the
7//! `tags`-side write and the `tag_groups`-side write live in the
8//! same transaction). This module only exposes the read path:
9//! [`fetch`] returns the [`TagGroup`] for a given id, used by
10//! `agents spawn` when it resolves a `--agent-tag`
11//! invocation into a runnable `AgentSpec` + parent.
12
13use objectiveai_sdk::agent::InlineAgentBaseWithFallbacksOrRemoteCommitOptional;
14use sqlx::Row as _;
15
16use super::{Error, Pool};
17
18#[derive(Debug, Clone)]
19pub struct TagGroup {
20    pub id: i64,
21    pub agent_spec: InlineAgentBaseWithFallbacksOrRemoteCommitOptional,
22    pub parent_agent_instance_hierarchy: String,
23}
24
25/// Look up a tag_group row by id. Returns `Ok(None)` when no row
26/// matches (typically only happens if the caller raced a
27/// `DELETE` from the parent `tags` cascade).
28pub async fn fetch(pool: &Pool, id: i64) -> Result<Option<TagGroup>, Error> {
29    let row = sqlx::query(
30        "SELECT id, agent_spec, parent_agent_instance_hierarchy \
31         FROM objectiveai.tag_groups \
32         WHERE id = $1",
33    )
34    .bind(id)
35    .fetch_optional(&**pool)
36    .await?;
37    let Some(row) = row else { return Ok(None) };
38    let id: i64 = row.try_get(0)?;
39    let spec_value: serde_json::Value = row.try_get(1)?;
40    let agent_spec: InlineAgentBaseWithFallbacksOrRemoteCommitOptional =
41        serde_json::from_value(spec_value)?;
42    let parent: String = row.try_get(2)?;
43    Ok(Some(TagGroup {
44        id,
45        agent_spec,
46        parent_agent_instance_hierarchy: parent,
47    }))
48}