Skip to content

feat(agent): select a delegated identity for hook recording - #219

Merged
geekgonecrazy merged 3 commits into
devfrom
feat/agent-identity-selection
Sep 25, 2026
Merged

geekgonecrazy merged 3 commits into
devfrom
feat/agent-identity-selection

Conversation

@geekgonecrazy

Copy link
Copy Markdown
Contributor

What

atomic agent identity set <name> makes every hooked agent on this machine record turns under a delegated identity's own key instead of the plus-tag of the default identity. One identity per machine — deliberately not per repository — persisted to a new top-level agent_identity in ~/.atomic/config.toml.

New command tree under atomic agent identity:

  • set <name> — validates eagerly: the identity must resolve in the store and be an agent/delegated type (human identities are refused — signing agent work with the human's key is worse than the honest plus-tag), and a missing delegation certificate prints a warning.
  • unset — clears the selection; hooks go back to the plus-tag path.
  • show — the effective identity, its source, email, key, and active delegation URN — the same resolution the hooks run.

Resolution order (per hook invocation)

ATOMIC_AGENT_IDENTITY env var
  > global agent_identity setting
  > active server profile's agent_identity binding
  > plus-tag fallback (behavior from before agent identities existed)

The server-profile link closes a promise already written down: bind_agent_identity documents its binding as "so hooks use it by default", but only push auth ever read it — the recording path never did. Now creating an agent identity makes hooks use it with zero extra steps, and the global setting overrides it.

Design invariants

  • Backward compatible: nothing configured anywhere → recorded turns are byte-identical to today (plus-tag author, human key, no envelope URN).
  • atomic record untouched: the human path keeps using the default identity.
  • Recording never fails over identity selection: a mistyped name degrades to the plus-tag path with a debug log; eager validation lives in set, soft fallback lives in the record path.
  • atomic-agent never reads config: the resolved name crosses as plain data through TurnRecordOptions.agent_identity into build_agent_author and active_delegation_urn, so change headers carry the agent's public key and envelopes name the active delegation certificate.

Also

  • atomic agent status shows the effective identity in human output and as agent_identity: {name, source} in --json (stable source keys: env, global, server-profile).
  • Intent: ATOM::aaron::15 (vault).

Testing

  • cargo test -p atomic-config -p atomic-agent -p atomic-cli — all pass (1,356 / 1,927 / 28), including new unit tests for the resolution order, blank-value skipping, keyed-attribution threading, and never-fail fallback.
  • Clippy clean for all touched files.
  • Manually exercised the built binary against an isolated config dir: set/unset/show round-trip, human identity refused, unknown identity → IdentityNotFound, env var precedence over the global setting, status human + JSON output.

`atomic agent identity set <name>` makes every hooked agent on this
machine record under a delegated identity's own key instead of the
plus-tag of the default identity. The selection is global — one
identity per machine, deliberately not per repository — written to a
new top-level `agent_identity` in ~/.atomic/config.toml.

Hooks resolve the identity on every invocation:

  ATOMIC_AGENT_IDENTITY env var
    > global agent_identity setting
    > active server profile's agent_identity binding
    > plus-tag fallback

The server-profile link closes a promise that was already written down:
`bind_agent_identity` documents the binding as "so hooks use it by
default", but only push auth ever read it — recording never did.

`set` validates eagerly (the identity must resolve and be an
agent/delegated type; human identities are refused; a missing delegation
certificate warns) while the record path degrades softly on a bad name
— recording a turn must never fail over identity selection. `show` and
`agent status` (human + JSON) report the effective identity and its
source. With nothing configured anywhere, recorded turns are
indistinguishable from before, and `atomic record` is untouched.

atomic-agent never reads config: the resolved name crosses as plain
data through TurnRecordOptions into build_agent_author and
active_delegation_urn, so change headers carry the agent's public key
and envelopes name the active delegation certificate.
cargo fmt across the touched crates, and the `key()` doc comment
linked to `Self::fmt` — a trait impl method rustdoc cannot resolve —
which failed the Documentation job under -Dwarnings.
Every test in database_owner_integration_test spawns real atomic
subprocesses that hold the redb lock and burn CPU. On 2-core Windows CI
runners, the default test-threads parallelism lets the process-heavy
tests (the eight-process session-start hammer, the failpoint owners)
starve whichever sibling tests overlap them past their database-wait
budgets.

The failure signature is exactly that: different tests fail on
different runs — concurrent_session_starts…, crashing_second_checkpoint…,
owner_death_after_checkpoint_prepare… in this PR's runs;
concurrent_stops_publish… on another PR the same day — all in this file,
all contention-shaped (one with an explicit 'Database already open.
Cannot acquire lock'), while macOS and ubuntu pass the same suite.

#[serial] trades a few minutes of wall time for runs that only fail when
something is actually broken. Locally the serialized suite passes in 53s
(macOS); CI is the only place the starvation reproduced.
@geekgonecrazy
geekgonecrazy merged commit ba802e0 into dev Sep 25, 2026
8 checks passed
@geekgonecrazy
geekgonecrazy deleted the feat/agent-identity-selection branch September 25, 2026 20:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants