Skip to main content

objectiveai_cli/command/agents/
locks.rs

1//! Lock coordinates for the per-agent lockfiles.
2//!
3//! Two families, both under the per-state locks root and both held
4//! through [`objectiveai_sdk::lockfile`] (contents are always empty —
5//! these locks carry liveness, not data):
6//!
7//! - **Per-instance**: `<state>/locks/agents/instances/<…>` — the
8//!   `agent_instance_hierarchy` is split on `/`; every segment but
9//!   the last becomes a literal subdirectory, the last segment is the
10//!   lockfile key (the SDK escapes the key; directory segments ride
11//!   raw). A held instance lock ⇔ a live process owns that agent.
12//! - **Per-tag**: `<state>/locks/agents/tags` keyed by the tag name —
13//!   held while a spawn is materializing an un-upgraded (GROUPED)
14//!   tag, released once the spawn claims its minted AIH lock.
15//!
16//! No `create_dir_all` here — the SDK's acquire functions create the
17//! directory chain themselves.
18
19use std::path::{Path, PathBuf};
20
21/// `(lock_dir, key)` for an `agent_instance_hierarchy`.
22pub fn agent_instance_lock(state_dir: &Path, agent_instance_hierarchy: &str) -> (PathBuf, String) {
23    let mut dir = state_dir.join("locks").join("agents").join("instances");
24    let mut segments = agent_instance_hierarchy.split('/').peekable();
25    let mut key = String::new();
26    while let Some(segment) = segments.next() {
27        if segments.peek().is_some() {
28            dir.push(segment);
29        } else {
30            key = segment.to_string();
31        }
32    }
33    (dir, key)
34}
35
36/// `(lock_dir, key)` for an agent tag.
37pub fn agent_tag_lock(state_dir: &Path, agent_tag: &str) -> (PathBuf, String) {
38    (
39        state_dir.join("locks").join("agents").join("tags"),
40        agent_tag.to_string(),
41    )
42}
43
44#[cfg(test)]
45mod tests {
46    use super::*;
47
48    #[test]
49    fn agent_instance_lock_splits_hierarchy() {
50        let state = Path::new("state");
51        let (dir, key) = agent_instance_lock(state, "root/child-1/leaf-2");
52        assert_eq!(
53            dir,
54            Path::new("state")
55                .join("locks")
56                .join("agents")
57                .join("instances")
58                .join("root")
59                .join("child-1"),
60        );
61        assert_eq!(key, "leaf-2");
62
63        // Single-segment hierarchy: no subdirectories, whole string
64        // is the key.
65        let (dir, key) = agent_instance_lock(state, "UNKNOWN");
66        assert_eq!(
67            dir,
68            Path::new("state").join("locks").join("agents").join("instances"),
69        );
70        assert_eq!(key, "UNKNOWN");
71    }
72}