Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions docs/architecture/agent-registry-grant-resolution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# AgentRegistryGrant Resolution

Agent Machine now has a local Agent Registry grant resolver for bootstrap and dry-run activation flows. This is a deterministic local-store resolver, not a production Agent Registry client.

## Purpose

Activation evaluation should not require callers to manually select an `AgentRegistryGrant` forever. The resolver lets Agent Machine scan explicit files or directories and select the matching grant by request shape.

The resolver supports:

- explicit grant files;
- local grant store directories;
- request matching by AgentPod ID, requested agent identity, session, optional AgentMachine, workroom, and topic;
- disambiguation by grant ID or expected grant status;
- fail-closed missing-grant stub generation;
- semantic validation through governance rules;
- activation evaluation using a locally resolved grant.

## Current commands

Resolve a grant from a local store by explicit grant ID:

```bash
agent-machine registry resolve \
examples/local-podman-llama-cpp.agent-pod.json \
--grant-dir examples \
--grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--agent-machine-id urn:srcos:agent-machine:m2-asahi-local \
--pretty
```

Resolve a grant by status and request shape:

```bash
agent-machine registry resolve \
examples/local-podman-llama-cpp.agent-pod.json \
--grant-dir examples \
--expected-status revoked \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--agent-machine-id urn:srcos:agent-machine:m2-asahi-local \
--workroom-ref urn:srcos:workroom:local-default \
--topic-ref urn:srcos:topic:agent-machine \
--pretty
```

Evaluate activation using a resolved registry grant:

```bash
agent-machine activate evaluate \
examples/local-podman-llama-cpp.agent-pod.json \
examples/policy-admission.allowed-activation.json \
--grant-dir examples \
--grant-id urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation \
--deployment-receipt-id urn:srcos:agent-machine:deployment-receipt:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa \
--requested-agent-identity-ref urn:srcos:agent:local-inference-provider \
--session-ref urn:srcos:session:local-bootstrap \
--agent-machine-id urn:srcos:agent-machine:m2-asahi-local \
--provider-id urn:srcos:agent-machine:inference-provider:asahi-llama-cpp \
--storage-receipt-dir examples \
--decided-at 2026-05-04T12:51:00Z \
--decision-id urn:srcos:agent-machine:activation-decision:local-llama-cpp-allowed \
--pretty
```

## Fail-closed behavior

If no matching grant is found and missing stubs are allowed, the resolver emits a synthetic `AgentRegistryGrant` with:

```text
grant.status = missing
grant.authorizationGranted = false
grant.revocationStatus = unavailable
```

That stub denies requested scopes and causes `ActivationDecision` to fail closed.

If `--no-missing-stub` is provided and no grant matches, resolution fails.

## Ambiguity behavior

If multiple grants match the request, resolution fails unless the caller disambiguates with:

```text
--grant-id <id>
```

or:

```text
--expected-status active|revoked|expired|denied|missing|unknown
```

This is deliberate. Silent selection among conflicting grants would be unsafe.

## Scope behavior

The resolver can construct a requested scope for generated missing stubs from CLI arguments:

```text
--provider-id
--model-ref
--tool-ref
--storage-scope-ref
--evidence-scope-ref
```

For resolved real grants, schema and governance validation ensure `scope.allowed` does not exceed `request.requestedScope`.

## Bootstrap boundary

This resolver is not a production Agent Registry client. It does not:

- call a remote Agent Registry endpoint;
- verify grant signatures;
- resolve revocations online;
- prove session freshness;
- bind identity to live proof-of-life;
- prove grant freshness beyond the contents of local artifacts.

It is the bootstrap adapter shape that a real Agent Registry client can replace.

## Validation

Agent Registry resolver validation is part of:

```bash
make validate-agent-registry
make validate
```

The validation path checks:

- directory scanning;
- schema and semantic validation;
- ambiguity rejection;
- active activation grant resolution;
- revoked grant resolution;
- generated missing stubs;
- grant-id disambiguation;
- CLI registry resolution;
- activation evaluation using a resolved local grant.
17 changes: 17 additions & 0 deletions scripts/resolve-agent-registry-grant.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env python3
"""Resolve AgentRegistryGrant from local Agent Registry files/stores."""

from __future__ import annotations

import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_ROOT = REPO_ROOT / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))

from agent_machine.agent_registry import main # noqa: E402

if __name__ == "__main__":
raise SystemExit(main())
128 changes: 128 additions & 0 deletions scripts/validate-agent-registry.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
#!/usr/bin/env python3
"""Validate local Agent Registry grant resolution behavior."""

from __future__ import annotations

import sys
from pathlib import Path

REPO_ROOT = Path(__file__).resolve().parents[1]
SRC_ROOT = REPO_ROOT / "src"
if str(SRC_ROOT) not in sys.path:
sys.path.insert(0, str(SRC_ROOT))

from agent_machine.agent_registry import ( # noqa: E402
load_agent_registry_grants,
requested_scope_from_inputs,
resolve_agent_registry_grant,
validate_agent_registry_grant_payload,
)

AGENTPOD_ID = "urn:srcos:agent-machine:agent-pod:local-podman-llama-cpp"
AGENT_MACHINE_ID = "urn:srcos:agent-machine:m2-asahi-local"
IDENTITY_REF = "urn:srcos:agent:local-inference-provider"
SESSION_REF = "urn:srcos:session:local-bootstrap"
WORKROOM_REF = "urn:srcos:workroom:local-default"
TOPIC_REF = "urn:srcos:topic:agent-machine"
PROVIDER_ID = "urn:srcos:agent-machine:inference-provider:asahi-llama-cpp"
ISSUED_AT = "2026-05-04T12:51:00Z"


def expect_status(grant: dict, expected: str, label: str) -> None:
observed = grant.get("grant", {}).get("status")
if observed != expected:
raise AssertionError(f"{label}: expected status={expected}, observed {observed}")
validate_agent_registry_grant_payload(grant, REPO_ROOT, source=label)
print(f"VALID registry resolve {label} status={expected}")


def expect_ambiguous(grants: list[dict]) -> None:
try:
resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
allow_missing_stub=False,
root=REPO_ROOT,
)
except AssertionError as exc:
if "ambiguous AgentRegistryGrant" not in str(exc):
raise
print("VALID registry resolve ambiguous grant requires disambiguation")
return
raise AssertionError("expected ambiguous AgentRegistryGrant resolution to fail")


def main() -> int:
grants = load_agent_registry_grants(directories=[REPO_ROOT / "examples"], root=REPO_ROOT)
if len(grants) < 4:
raise AssertionError("expected at least four AgentRegistryGrant examples")

expect_ambiguous(grants)

active_activation = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
grant_id="urn:srcos:agent-machine:agent-registry-grant:active-loopback-activation",
root=REPO_ROOT,
)
expect_status(active_activation, "active", "active-activation")

revoked = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
expected_status="revoked",
root=REPO_ROOT,
)
expect_status(revoked, "revoked", "revoked")

missing = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref="urn:srcos:agent:unresolved-provider",
session_ref=SESSION_REF,
agent_machine_id=AGENT_MACHINE_ID,
workroom_ref=WORKROOM_REF,
topic_ref=TOPIC_REF,
allow_missing_stub=True,
requested_scope=requested_scope_from_inputs(
provider_id=PROVIDER_ID,
tool_refs=["urn:srcos:tool:start-provider", "urn:srcos:tool:mount-cache"],
),
issued_at=ISSUED_AT,
root=REPO_ROOT,
)
expect_status(missing, "missing", "generated-missing-stub")

by_id = resolve_agent_registry_grant(
grants=grants,
agentpod_id=AGENTPOD_ID,
requested_agent_identity_ref=IDENTITY_REF,
session_ref=SESSION_REF,
grant_id="urn:srcos:agent-machine:agent-registry-grant:active-render-only",
root=REPO_ROOT,
)
expect_status(by_id, "active", "grant-id")
return 0


if __name__ == "__main__":
try:
raise SystemExit(main())
except (AssertionError, RuntimeError) as exc:
print(str(exc), file=sys.stderr)
raise SystemExit(1) from exc
Loading
Loading