Identify (ID) · Identity
Identity at the in agent layer
advisory · Bypassable by language alone
What agents exist, what they touch, what threats they face?
What this cell does
Agent registers on startup with name, charter ref, owner email, instance ID. Heartbeats. Deregister on shutdown. Dead-mans-switch alerts on heartbeat lapse.
Artifacts (1)
agent-register.pyview on GitHub#!/usr/bin/env python3
# ABOUTME: Minimal agent self-registration daemon. POSTs registration on start, heartbeats every N sec, deregisters on shutdown.
# ABOUTME: Refuses to start if the agent's charter is missing or expired. Wrap your agent process so this runs alongside.
import argparse
import datetime as dt
import json
import os
import signal
import socket
import sys
import time
import urllib.error
import urllib.request
from pathlib import Path
from typing import Optional
REGISTRY_URL = os.environ.get("INVENTORY_REGISTRY_URL", "https://inventory.example.com")
HEARTBEAT_INTERVAL = int(os.environ.get("INVENTORY_HEARTBEAT_SEC", "60"))
INSTANCE_ID = os.environ.get("INVENTORY_INSTANCE_ID", f"{socket.gethostname()}-{os.getpid()}")
class RegistrationError(Exception):
pass
def load_charter(charter_path: Path) -> dict:
"""Load and minimally validate the charter file. Refuse to register if charter is expired."""
if not charter_path.exists():
raise RegistrationError(f"Charter not found: {charter_path}")
import yaml # local import so the rest of this script does not require pyyaml unless a charter exists
charter = yaml.safe_load(charter_path.read_text())
next_review = charter.get("next_review_due")
if next_review:
# next_review is a date string YYYY-MM-DD per agent-charter.yaml
if dt.date.fromisoformat(str(next_review)) < dt.date.today():
raise RegistrationError(
f"Charter review is overdue ({next_review}). Refusing to start agent."
)
return charter
def http_post(path: str, body: dict, token: str) -> dict:
req = urllib.request.Request(
f"{REGISTRY_URL}{path}",
data=json.dumps(body).encode(),
headers={
"Authorization": f"Bearer {token}",
"Content-Type": "application/json",
},
method="POST",
)
with urllib.request.urlopen(req, timeout=10) as resp:
return json.loads(resp.read().decode())
def register(charter: dict, charter_ref: str, token: str) -> dict:
body = {
"agent_identifier": charter["agent"]["identifier"],
"charter_ref": charter_ref,
"charter_version": charter["charter_version"],
"owner_email": charter["ownership"]["owner_email"],
"instance_id": INSTANCE_ID,
"hostname": socket.gethostname(),
"pid": os.getpid(),
"started_at": dt.datetime.now(dt.timezone.utc).isoformat(),
"heartbeat_interval_seconds": HEARTBEAT_INTERVAL,
}
return http_post("/api/v1/agents/register", body, token)
def heartbeat(agent_identifier: str, token: str) -> None:
body = {
"agent_identifier": agent_identifier,
"instance_id": INSTANCE_ID,
"ts": dt.datetime.now(dt.timezone.utc).isoformat(),
}
try:
http_post("/api/v1/agents/heartbeat", body, token)
except urllib.error.URLError as exc:
# Backoff is the caller's responsibility; log and keep going.
print(f"agent-register: heartbeat failed: {exc}", file=sys.stderr, flush=True)
def deregister(agent_identifier: str, token: str) -> None:
body = {
"agent_identifier": agent_identifier,
"instance_id": INSTANCE_ID,
"ts": dt.datetime.now(dt.timezone.utc).isoformat(),
}
try:
http_post("/api/v1/agents/deregister", body, token)
except urllib.error.URLError as exc:
print(f"agent-register: deregister failed: {exc}", file=sys.stderr, flush=True)
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--charter", required=True, type=Path, help="Path to the agent charter YAML.")
parser.add_argument("--charter-ref", required=True, help="Source-of-truth path of the charter (e.g., charters/claude-code-prod.yaml).")
args = parser.parse_args()
token = os.environ.get("INVENTORY_TOKEN")
if not token:
print("INVENTORY_TOKEN not set. Refusing to start.", file=sys.stderr)
return 2
try:
charter = load_charter(args.charter)
except RegistrationError as exc:
print(f"REFUSING: {exc}", file=sys.stderr)
return 3
register(charter, args.charter_ref, token)
agent_identifier = charter["agent"]["identifier"]
stop = False
def _shutdown(_signo, _frame):
nonlocal stop
stop = True
signal.signal(signal.SIGTERM, _shutdown)
signal.signal(signal.SIGINT, _shutdown)
try:
while not stop:
heartbeat(agent_identifier, token)
for _ in range(HEARTBEAT_INTERVAL):
if stop:
break
time.sleep(1)
finally:
deregister(agent_identifier, token)
return 0
if __name__ == "__main__":
sys.exit(main())
Cell notes
Inventory, Identity / Self-declared
What this cell records. The agent's own report of its identity, charter binding, and liveness.
Fields captured at registration
- -
agent_identifier(must match the Charteragent.identifier) - -
charter_ref(path to the charter file in source control) - -
charter_version(version on the charter at startup) - -
owner_email(named owner from the charter) - -
instance_id(per-process or per-pod identifier) - -
pid,hostname,started_at - -
heartbeat_interval_seconds
Reference tooling
- -
agent-register.py, minimal registration daemon. Sends aPOST /api/v1/agents/registeron startup, then heartbeats every N seconds, thenPOST /api/v1/agents/deregisteron shutdown. Refuses to start ifcharter_refis missing or charter is expired.
Cross-layer cross-references
- - Should appear in operator-declared (
inventory/identity/operator-declared/). Mismatch = charter-integrity failure. - - Should match a discovered identity (
inventory/identity/discovered/). Heartbeat lapse without deregister = dead-mans-switch alert.
Common failure modes
- - Agent never implements the registration protocol. Treat as a runtime requirement, not optional.
- - Heartbeat dropped silently on network failure. Use exponential backoff with explicit logging.
- - Registration is best-effort and the agent runs even if registration fails. Refuse to start without a successful registration ack.
Citation
NIST CSF 2.0 ID.AM-01, ID.AM-02. NIST AI RMF MAP 1.1, MAP 4.1. CSA MAESTRO Layer 7. NIST NCCoE Concept Paper on AI Agent Identity (Feb 5, 2026).
Primary failure modes
Documented, not hypothetical. A control whose bypass is undocumented is worse than no control, because somebody trusted it.
- agent never implements registration protocol
- registers once, never heartbeats
- registration silently drops on network failure
Crosswalk
| NIST CSF 2 0 | ID.AM-01, ID.AM-02 |
|---|---|
| NIST AI RMF | MAP 1.1, MAP 4.1 |
| CSA MAESTRO | Layer 7 |
| OTHER | NIST NCCoE Concept Paper on AI Agent Identity (Feb 5, 2026) |
Cite this cell:
https://agenticcovenants.com/identify/identity/in-agent/