The currently-selected delegated agent identity, if one resolves. Shared by the author path ([`delegated_agent_author`]) and the signing path ([`resolve_turn_signer`]) so attribution and proof can never name different identities: same name chain (explicit option, else [`AGENT_IDENTITY_ENV`]), same store, same refusal of human identities. Returns the store alongside the identity — callers need it
(
agent_identity: Option<&str>,
identity_dir: Option<&Path>,
)
| 175 | /// Returns the store alongside the identity — callers need it to load the |
| 176 | /// keypair without re-opening (and possibly disagreeing about) the store. |
| 177 | fn load_selected_agent_identity( |
| 178 | agent_identity: Option<&str>, |
| 179 | identity_dir: Option<&Path>, |
| 180 | ) -> Option<(atomic_identity::IdentityStore, atomic_identity::Identity)> { |
| 181 | let name = agent_identity |
| 182 | .map(str::to_string) |
| 183 | .or_else(|| std::env::var(AGENT_IDENTITY_ENV).ok()) |
| 184 | .map(|n| n.trim().to_string()) |
| 185 | .filter(|n| !n.is_empty())?; |
| 186 | |
| 187 | let store = open_identity_store(identity_dir)?; |
| 188 | |
| 189 | let identity = match store.load_by_name(&name) { |
| 190 | Ok(identity) => identity, |
| 191 | Err(e) => { |
| 192 | log::debug!("Agent identity '{name}' not usable ({e}); falling back to plus-tag"); |
| 193 | return None; |
| 194 | } |
| 195 | }; |
| 196 | |
| 197 | // A human identity here would silently sign agent work as the human with |
| 198 | // no delegation behind it — worse than the plus-tag fallback, which at |
| 199 | // least does not claim to be keyed to an agent. |
| 200 | if !identity.identity_type.is_delegated() && !identity.identity_type.is_agent() { |
| 201 | log::debug!("'{name}' is not an agent identity; falling back to plus-tag"); |
| 202 | return None; |
| 203 | } |
| 204 | |
| 205 | Some((store, identity)) |
| 206 | } |
| 207 | |
| 208 | /// Resolve the signer for a recorded turn: the identity whose public key |
| 209 | /// the header author claims. |
no test coverage detected