Skip to content

feat(kernel): persist canonical agent UUID across respawns (#4614)#4630

Merged
houko merged 1 commit into
mainfrom
feat/4614-canonical-agent-uuid
May 5, 2026
Merged

feat(kernel): persist canonical agent UUID across respawns (#4614)#4630
houko merged 1 commit into
mainfrom
feat/4614-canonical-agent-uuid

Conversation

@houko

@houko houko commented May 5, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds a canonical agent UUID registry so respawns reuse the same
AgentId instead of orphaning history (refs #4614).

  • New AgentIdentityRegistry persists agent_name -> canonical_uuid
    at <home_dir>/agent_identities.toml. Atomic writes
    (.tmp.<pid>.<seq>.<nanos> + fsync + rename).
  • Spawn path consults the registry first; on miss, derives via
    AgentId::from_name (UUID v5) and registers as the canonical
    binding. Hand / child agents keep their existing UUID semantics.
  • kill_agent now preserves the binding (so panic restarts / hot
    reloads no longer silently orphan history). Destructive purge is
    reachable only through the explicit confirmation flow via the new
    kill_agent_with_purge(id, true) kernel helper.

API

  • DELETE /api/agents/{id} requires ?confirm=true; without it
    returns 409 Conflict with the data-loss warning text from the
    issue (mentions canonical UUID + cannot be undone). With confirm
    the agent is killed AND the canonical UUID is purged. Idempotent
    shortcut for an already-gone agent (DELETE /api/agents/{id} not idempotent — second call returns 404, breaks Terraform-style retry #3509) still applies.
  • GET /api/agents/identities lists the registry contents.
  • POST /api/agents/identities/{name}/reset?confirm=true drops the
    binding without touching a running agent. Same 409-without-confirm
    gate. 404 on a missing name.

CLI

  • librefang agent kill <id> now auto-sends ?confirm=true.
  • librefang agent delete <name> [--yes] — name resolution via the
    identity endpoint, [y/N] prompt with the warning text, then the
    confirmed DELETE.
  • librefang agent reset-uuid <name> [--yes] — purges the binding
    via the reset endpoint.
  • librefang agent merge-history <name> --from <old-uuid>
    documented stub (see Deferred).

Dashboard deleteAgent() updated to send ?confirm=true.

Tests

  • 7 unit tests in agent_identity_registry::tests (round-trip,
    first-register-wins, purge persists, malformed file is left
    intact, in-memory no-op, deterministic list ordering).
  • 6 new integration tests in
    crates/librefang-api/tests/agent_identity_registry_test.rs:
    • spawn_registers_canonical_uuid
    • delete_without_confirm_returns_409_and_preserves_identity
    • delete_with_confirm_purges_identity_and_respawn_recovers_uuid
    • delete_invalid_uuid_short_circuits_400_before_confirm_check
    • list_identities_returns_registered_entries
    • reset_identity_endpoint_gates_on_confirm
  • Existing api_integration_test / load_test /
    agents_routes_integration callers updated to use
    ?confirm=true; a delete_confirmed helper added in the latter
    so future tests don't have to open-code the URL.

Deferred to follow-up

  • librefang agent merge-history cross-table reassignment. Moving
    sessions / memories / events / kv_store / entities / relations /
    usage_events / canonical_sessions / prompt_experiments /
    audit_entries / approval_audit + the proactive memory store from
    one UUID to another under a single transaction with rollback is
    deep memory-substrate surgery and warrants its own PR with
    data-integrity tests.

Verification

  • cargo check --workspace --lib — clean
  • cargo check -p librefang-cli — clean
  • pre-push hook (clippy --workspace --all-targets -- -D warnings
    • OpenAPI drift) — passed on push
  • Scoped cargo test -p librefang-kernel / -p librefang-api
    deferred to CI per coordinator instruction

Closes #4614

@github-actions github-actions Bot added area/kernel Core kernel (scheduling, RBAC, workflows) size/XL 1000+ lines changed labels May 5, 2026
Adds an `AgentIdentityRegistry` that stores `agent_name -> canonical_uuid`
at `<home_dir>/agent_identities.toml`, written atomically (write to
`.tmp.<pid>.<seq>.<nanos>`, fsync, rename). The registry is consulted on
every top-level spawn — if a binding exists for the agent's name, that
UUID is reused verbatim; otherwise the historical
`AgentId::from_name` derivation is used and the result registered as
the canonical UUID. Hand / child agents (parent.is_some()) keep their
existing UUID-v5-from-hand-id semantics.

The kernel grows a sibling `kill_agent_with_purge(id, purge: bool)`:
the existing `kill_agent` is now a thin shim that preserves the
binding (so internal lifecycle resets — panic restart, manifest
reload, hot reload — never silently orphan history). The destructive
purge path is reachable only through the explicit confirmation flow.

API surface (refs #4614):
- `DELETE /api/agents/{id}` now requires `?confirm=true`. Without it
  the request is rejected with `409 Conflict` and the data-loss
  warning text from the issue ("permanently remove its canonical
  UUID and all associated memories and sessions"). With confirm,
  the agent is killed AND the canonical UUID is purged.
- `GET /api/agents/identities` lists the registry contents (RFC3339
  `created_at` per row).
- `POST /api/agents/identities/{name}/reset?confirm=true` drops the
  binding without killing a running agent (next spawn re-derives a
  fresh UUID). Same 409-without-confirm gate.

CLI (refs #4614):
- `librefang agent kill <id>` auto-sends `?confirm=true` (the verb is
  the user's confirmation).
- `librefang agent delete <name> [--yes]` resolves name -> canonical
  UUID via the new endpoint, prompts `[y/N]` with the warning text,
  and issues the confirmed DELETE.
- `librefang agent reset-uuid <name> [--yes]` purges the binding via
  the new reset endpoint without touching the running agent.
- `librefang agent merge-history <name> --from <old-uuid>` is a stub
  that prints the deferral rationale and exits 2.

Dashboard: `deleteAgent()` now sends `?confirm=true` so the modal
flow continues to work end-to-end.

Tests:
- Unit (in `agent_identity_registry.rs`): round-trip read/write,
  first-register-wins, purge persists, malformed file leaves the
  on-disk file alone, in-memory persist is no-op, list ordering is
  deterministic.
- Integration (`crates/librefang-api/tests/agent_identity_registry_test.rs`,
  6 cases): canonical UUID is registered on spawn; bare DELETE
  returns 409 + warning + preserves identity; confirmed DELETE
  purges + respawn re-registers; malformed UUID still 400; list
  endpoint surfaces entries; reset endpoint gates on confirm + 404
  on missing.
- Existing `api_integration_test`, `load_test`, and
  `agents_routes_integration` callers updated to send
  `?confirm=true` (a `delete_confirmed` helper was added in the
  latter so future tests don't have to open-code the URL).

Deferred to a follow-up (documented in CLI long_about):
- `librefang agent merge-history` cross-table reassignment. Moving
  sessions / memories / events / kv_store / entities / relations /
  usage_events / canonical_sessions / prompt_experiments /
  audit_entries / approval_audit + the proactive memory store from
  one UUID to another under a single transaction with rollback is
  deep memory-substrate surgery and warrants its own PR with
  data-integrity testing.

Closes #4614
@houko
houko force-pushed the feat/4614-canonical-agent-uuid branch from 3e40f24 to 28d74fa Compare May 5, 2026 09:50
@houko
houko merged commit 95ca4a7 into main May 5, 2026
16 of 17 checks passed
@houko
houko deleted the feat/4614-canonical-agent-uuid branch May 5, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/kernel Core kernel (scheduling, RBAC, workflows) size/XL 1000+ lines changed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: Persist canonical UUID per agent name to prevent silent history loss on respawn

1 participant