Repository navigation
feat(registry): the agent register — every agent that has acted - #77
Merged
Merged
Conversation
`agents` has been written on every session creation since migration 001
(`Gate.register_agent`, reached from POST /v1/sessions and the delegation
path) and **nothing has ever read it**. Meanwhile sessions, audit_log,
approval_requests and the delegation tree all carry `agent_id`. The register
already existed as state; this is the surface.
It is also the artifact the assurance story rests on: the question an auditor,
a customer or a regulator asks is "show me your register of AI systems" —
which agents exist, what each is allowed to do, what each actually did, and
which of them can spawn others.
* `haldir_registry.py` — five grouped queries over one tenant, no N+1.
Derived from **activity as well as registration**: an agent that acted but
was never explicitly registered is still listed, because a register that
silently omits agents reads as complete and is not.
* `GET /v1/agents` and `GET /v1/agents/<id>` — tenant-scoped, `admin:read`.
A miss is the same 404 whether the agent belongs to another tenant or to
nobody.
* `/cloud/overview` gains an **Agents** page (summary stats + table), and
the contract test parses the real template against a real payload.
* `haldir agents [--agent ID] [--json]` — the register as a terminal table.
* The **evidence pack** gains an "Agent register" section (markdown + HTML),
which is the form an auditor receives. Its recency fields
(`last_seen`, `last_action_at`, `sessions.active`) are excluded from the
digest exactly as `access_control.last_used` is — a session TTL expires
with no write, and an auditor re-verifying an archived pack must not read
that drift as tampering. Its substance (agent, scopes, caps, counts,
spend, flags, delegation) stays in the digest; the test asserts both
directions.
* The register reports **two spend figures**: `session_spend_usd` (what the
Gate metered against caps) and `audited_cost_usd` (what the log recorded
per action). They move independently, and calling either one "spend"
would quietly pick a side.
Verified: 1106 tests, flake8 clean, mypy clean over 30 files (the module is
in scope), wheel built and inspected. `openapi.json` regenerated for the two
routes. The `/docs` page lists them.
Co-Authored-By: Claude Code <noreply@anthropic.com>
This was referenced Oct 4, 2026
Conflicts: `mypy.ini` and the packaging lists both branches extend — main added `haldir_client_ip.py`, this branch adds `haldir_registry.py`, and the resolution keeps both. `openapi.json` regenerated rather than trusted to a textual auto-merge, which is what the committed-spec test exists for. Co-Authored-By: Claude Code <noreply@anthropic.com>
ExposureGuard
pushed a commit
that referenced
this pull request
Oct 4, 2026
main moved when #77 landed after my first merge, and GitHub recomputes the merge against the current base — so the conflict returned. Same union resolution on `mypy.ini`, spec regenerated, suite run. Co-Authored-By: Claude Code <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Independent of #73–#76; branched from
main. This is the spine of the assurance model — the artifact an auditor, a customer or a regulator asks for: "show me your register of AI systems."The state it fixes
agentshas been written on every session creation since migration 001 —Gate.register_agent, reached fromPOST /v1/sessions(api.py:930) and the delegation path — and nothing has ever read it. No list method, no route, no dashboard page, no CLI, no export. The register already existed as state; this is the surface.What's in it
haldir_registry.py— five grouped queries over one tenant, no N+1, no Flask. Derived from activity as well as registration: an agent that acted but was never explicitly registered is still listed. A register that silently omits agents reads as complete and isn't, which is worse than none.Per agent:
registered,default_scopes,max_spend,first_seen/last_seen, sessions (total/active/revoked), spend, activity (actions, cost, flagged), approvals, and delegation in both directions —delegates_toandspawned_by, because "which of my agents can spawn others" is what the tree exists to answer.Two spend figures, deliberately.
session_spend_usdis what the Gate metered against caps;audited_cost_usdis what the append-only log recorded per action. A payment moves both, a tool call that only logs cost moves the second. Reporting one as "spend" would quietly pick a side.GET /v1/agentsandGET /v1/agents/<id>— tenant-scoped,admin:read. A miss returns the same 404 whether the agent belongs to another tenant or to nobody, so the route can't be used as an existence oracle.The dashboard gains an Agents page (summary stats + table). The contract test parses the real
dashboard.jstemplate against a real payload — same guarantee as #75's pages. Note: #75 introducestests/test_dashboard_contract.pywith the same helpers and three other pages; whichever lands second should keep both sets — they're additive.haldir agents [--agent ID] [--json]— the register as a terminal table.The evidence pack gains an "Agent register" section in both rendered forms. This is the form an auditor receives, so it is signed like everything else — with the recency fields (
last_seen,last_action_at,sessions.active) excluded from the digest, exactly asaccess_control.last_usedis. A session TTL expires with no write; without the exclusion, an auditor re-verifying an archived pack reads that drift as tampering, which is the failure the digest exists to prevent. The register's substance stays in the digest, and the test asserts both directions — recency changes leave it fixed, an action-count change moves it.Verification
haldir_registry.pyshipsopenapi.jsonregenerated for the two routes;/docslists themWhat this unlocks
The register is what makes the rest of the assurance model sellable:
POST /v1/compliance/schedulesand the evidence pack already exist, and this gives them the section that answers the actual buyer question. The natural next two pieces are framework coverage (ISO 42001 and EU AI Act Art. 12/26 mappings alongside SOC 2) and an opt-in capability card for an agent, so a register entry can also be a discovery entry — owner-published, capability-only.🤖 Generated with Claude Code