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}