crabtalk_core/agent/id.rs
1//! Stable agent identity.
2//!
3//! An [`AgentId`] is a ULID — Crockford base32, 26 characters, sortable
4//! by creation time. Agents get a fresh ULID when they're created and
5//! keep it across renames. Phase 5 introduces the field; later phases
6//! (agent CRUD via Storage, sessions) start keying on it.
7
8use serde::{Deserialize, Serialize};
9use std::{
10 fmt::{self, Display},
11 str::FromStr,
12};
13use ulid::Ulid;
14
15/// Stable identifier for an agent. Newtype over [`Ulid`] so callers can
16/// extend the type later without touching call sites.
17#[derive(
18 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default,
19)]
20#[serde(transparent)]
21pub struct AgentId(pub Ulid);
22
23impl AgentId {
24 /// Generate a fresh ULID for a new agent.
25 pub fn new() -> Self {
26 Self(Ulid::new())
27 }
28
29 /// The nil/zero ID — used as a sentinel for "not yet backfilled".
30 /// Callers that see this on a registered agent should treat it as a
31 /// bug: the daemon's startup backfill is expected to replace any
32 /// missing `id` field before agents reach the runtime.
33 pub const fn nil() -> Self {
34 Self(Ulid::nil())
35 }
36
37 /// Is this the nil/zero sentinel?
38 pub fn is_nil(&self) -> bool {
39 self.0.is_nil()
40 }
41}
42
43impl Display for AgentId {
44 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45 Display::fmt(&self.0, f)
46 }
47}
48
49impl FromStr for AgentId {
50 type Err = ulid::DecodeError;
51
52 fn from_str(s: &str) -> Result<Self, Self::Err> {
53 Ulid::from_str(s).map(Self)
54 }
55}