Use Case
Can the assistant put a job into maintenance?
Imagine a teammate asks the assistant to take OPS-001 offline for scheduled work. Looking at the record is fine. Changing its status to MAINTENANCE has a real effect, so the assistant must not do it just because it was asked.
1. Read
The assistant checks the current status of OPS-001.
2. Ask
Before changing it, the assistant asks AIGP whether this change is allowed.
3. Change or stop
A yes updates the record. A no, timeout, or bad response leaves it alone.
4. Explain later
AIGP keeps a small record of the request, the answer, and what the assistant actually did.
That is the whole use case: let the assistant help with a real task, without giving it a blank cheque to change operational data.
Who does the work?
LangGraph
It reads the record and makes the update only after a valid answer.
Who says yes or no?
AIGP OSS
It checks the policy for this exact update, right before it happens.
Who keeps the receipt?
AIGP OSS
It records the decision and whether the assistant actually made the change.
For builders: AIGP keeps the execution ID, correlation ID, limited step facts, authorization decision, linked tool outcome, and final status—not the prompt, full record, or credentials.
You Will Build
A small assistant that can make one real local maintenance update.
You Will Verify
The assistant asks before it changes data, then leaves a clear trail behind it.
Limits
AIGP does not run the assistant, repair a later change, or make this example replayable.
1. Build Agent
Build First
The first checkpoint is intentionally plain: one read-only run, one local SQLite file, and no AIGP connection. No model, cloud account, or paid API is needed.
git clone https://github.com/Bhanuj-AI/labs.git
cd labs/agent-frameworks/langgraph/langgraph-governed-operations-intg
python3 -m venv .venv
source .venv/bin/activate
python -m pip install --upgrade pip
python -m pip install -r requirements.txtpython app.py readonly --database /tmp/bhanuj-langgraph-readonly.dbOutcome: READ_COMPLETED
Reason: n/a
SQLite record status: PENDING
AIGP execution ID: not delivered
AIGP evidence status: DISABLEDVerify: the local record is still PENDING. Evidence is DISABLED because the workflow has no configured endpoint yet.
Before authoritative runs: synchronize the adjacent OSS checkout. The lab virtualenv owns LangGraph; the OSS virtualenv owns the public authorization SDK and its control-plane dependencies.
cd /path/to/ai-governance-oss
uv sync2. Connect to AIGP
Connect the lab and its two identities
Start the local AIGP stack from its own checkout. This prepares Studio for the user's policy setup in the next step; the authoritative protected run has not started yet.
cd /path/to/ai-governance-oss
./servers.sh up
./servers.sh psChoose an authentication mode first. For an offline local read, confirm the platform is configured with AI_GOVERNANCE_AUTH_MODE=development and use the header-based values below. For an authoritative run, use Keycloak (or your production identity provider): valid bearer tokens authenticate each caller; organization and project headers only scope the request. Never use development authentication in a shared or production environment.
# Development authentication only. Do not use this in a shared environment.
export AI_GOVERNANCE_BASE_URL=http://localhost:8000
export AI_GOVERNANCE_ORGANIZATION_ID=org_default
export AI_GOVERNANCE_PROJECT_ID=project_default
export AI_GOVERNANCE_ACTOR_ID=local-admin
export AI_GOVERNANCE_RUNTIME_ACTOR_ID=local-adminpython app.py readonly --database /tmp/bhanuj-langgraph-registered.dbOutcome: READ_COMPLETED
SQLite record status: PENDING
AIGP execution ID: <uuid>
AIGP evidence status: COMPLETEDThe lab submits the actual LangGraph run ID as the external execution ID. AIGP returns the canonical execution_id, which is then required for authorization, evidence, retrieval, and finalization. An optional LangGraph thread ID is continuity metadata only; a new run on the same thread gets a new AIGP execution.
For Keycloak, the runtime actor must match the service-principal subject AIGP derives from the runtime token; it is checked locally against the returned decision, not sent as an authority claim. The issuer has its own short-lived token and subject. A local Studio administrator may also operate the runtime if that identity has the necessary permissions; it must still remain distinct from the delegation issuer. In production, prefer a least-privilege runtime service account.
# Run in the OSS checkout after ./servers.sh up.
# These generated files contain local development credentials. Do not print,
# commit, or copy either resulting token outside this shell.
set -a
source .env.oauth.generated
source .env.service-accounts.generated
set +a
# Issuer token: the walkthrough service account creates the delegation.
ISSUER_ACCESS_TOKEN="$(uv run python -c 'from ai_governance.oauth.client_credentials import access_token_from_environment; print(access_token_from_environment())')"
# Runtime token: the separate MCP service account asks for the action decision.
RUNTIME_ACCESS_TOKEN="$(AI_GOVERNANCE_OAUTH_TOKEN_URL="$AI_GOVERNANCE_MCP_TOKEN_URL" AI_GOVERNANCE_OAUTH_CLIENT_ID="$AI_GOVERNANCE_MCP_CLIENT_ID" AI_GOVERNANCE_OAUTH_CLIENT_SECRET="$AI_GOVERNANCE_MCP_CLIENT_SECRET" uv run python -c 'from ai_governance.oauth.client_credentials import access_token_from_environment; print(access_token_from_environment())')"
test -n "$ISSUER_ACCESS_TOKEN" && test -n "$RUNTIME_ACCESS_TOKEN"
# These are public service-account subjects, not secrets. Keep them as variables
# so the lab can check the server-derived identities without hard-coding IDs.
export AI_GOVERNANCE_DELEGATION_ISSUER_ACTOR_ID="$AI_GOVERNANCE_WALKTHROUGH_ACTOR_ID"
export AI_GOVERNANCE_RUNTIME_ACTOR_ID="$AI_GOVERNANCE_MCP_ACTOR_ID"# Keep both short-lived tokens only in this shell or your secret manager.
export AI_GOVERNANCE_BASE_URL=http://localhost:8000
export AI_GOVERNANCE_ORGANIZATION_ID=org_default
export AI_GOVERNANCE_PROJECT_ID=project_default
# Runtime: the identity that runs LangGraph and asks for authorization.
export AI_GOVERNANCE_ACCESS_TOKEN="$RUNTIME_ACCESS_TOKEN"
export AI_GOVERNANCE_RUNTIME_ACTOR_ID="$AI_GOVERNANCE_MCP_ACTOR_ID"
# Issuer: a different identity allowed to create this execution's delegation.
export AI_GOVERNANCE_DELEGATION_ISSUER_ACCESS_TOKEN="$ISSUER_ACCESS_TOKEN"
export AI_GOVERNANCE_DELEGATION_ISSUER_ACTOR_ID="$AI_GOVERNANCE_WALKTHROUGH_ACTOR_ID"
# This development-only header must not be sent in Keycloak mode.
unset AI_GOVERNANCE_ACTOR_ID3. Create Policies Before the Run
The user configures two policies in Studio
This happens once as integration setup, before anyone runs aigp. The integration owner creates and activates these two policies. The agent never creates policies, selects a policy, or supplies a policy ID to the PDP.
Policy 1 · create first
Delegation-issuance policy
This lets the named issuer service account create a short-lived delegation for the named runtime service account. It must match the permitted action, resource type, and resource reference—for this walkthrough, the protected SQLite update for OPS-001.
Why: without it, the issuer cannot hand the runtime a permission slip at all.
Policy 2 · create next
Runtime-action authorization policy
This registers operations.update_status on sqlite.row and contains the active APPROVE rule for that operation.
Why: even with a valid delegation, the PDP will not return an allow unless this policy also permits the action.
STEP 1. Open the correct page
In the Studio sidebar choose Policies Engine, then Create Policy. Complete the Policy Definition form, press Create Draft, open version 1, then press Activate.
STEP 2. Create and activate both
Create Policy 1 first, then Policy 2. Check that each appears as ACTIVE in the policy inventory before starting the protected run.
STEP 3. Do not configure policy IDs in the lab
Policies remain active setup rules. On each run, the issuer creates only an immutable execution-scoped delegation after registration; the PDP resolves the active applicable policies itself.
Current Studio authoring limit: Policy 2 can be authored entirely with the Studio rule picker. Policy 1 also needs the server-supported issuer.id and runtime_actor.id conditions; those fields are not yet offered by that picker. Studio can inspect the active Policy 1 shown below, but its exact initial definition must currently be created through the policy REST API or approved policy-administration automation. Do not substitute actor.id or delegation.*: they describe the later runtime authorization decision, not delegation issuance.
Fill these common definition fields for both policies: use your configured organization and project (the local lab uses org_default and project_default), category SECURITY, a unique descriptive name, and the integration owner as Owner and Created by. Give the one rule a unique rule ID, priority 10, severity LOW, a plain-language reason, and effect APPROVE.
Policy 1 exact scope
Target: AgentExecution. Effect: APPROVE. Conditions: issuer.id = walkthrough subject, runtime_actor.id = MCP subject, action.id = operations.update_status, resource.type = sqlite.row, and resource.reference = operational-record:OPS-001. Definition metadata: runtime_action_delegation_issuance.purpose = runtime_action_delegation_issuance.
Policy 2 exact scope
Target: AgentExecution. Effect: APPROVE. Conditions: action.id = operations.update_status, resource.type = sqlite.row, and resource.reference = operational-record:OPS-001. Copy the definition metadata shown in step 4; it registers the action and resource type with the PDP.
1. Identity provider
Keycloak in this lab authenticates the issuer and runtime service accounts. It supplies identity and platform permissions, not task authority.
2. Delegation issuer
The walkthrough service account has runtime_action_delegation.issue and an active issuance policy. It creates a time-limited permission for this canonical execution.
3. Agent runtime
The distinct MCP/runtime service account runs LangGraph and proposes the SQLite operation. It cannot issue or broaden its delegation.
After both policies are ACTIVE — in this order
- 1
External application starts the LangGraph run
It owns task initiation. AIGP does not orchestrate the task.
- 2
AIGP registers the canonical execution
The returned execution ID becomes the trusted execution reference.
- 3
Authorized issuer creates an immutable delegation
AIGP validates the issuer, issuance policy, execution, runtime identity, exact action/resource scope, expiry, then persists the delegation.
- 4
Runtime proposes one tool operation
It sends delegation ID, version, digest, registered action/resource, and a digest of the exact tool arguments. Agent wording cannot enlarge authority.
- 5
AIGP PDP resolves trusted context and decides
It independently reads the stored delegation and applicable policy set. The caller cannot select only a favourable policy.
- 6
LangGraph PEP enforces immediately before SQLite
ALLOW permits this bound operation; deny, expiry, mismatch, or an unavailable service prevents the write.
- 7
Runtime reports evidence
AIGP records the decision and runtime-reported tool outcome. It observes evidence; it does not execute or independently witness SQLite.
Create and activate this first. It identifies the issuer, runtime, action, resource type, and OPS-001 resource that may be delegated.
Create and activate this next. Permits the registered operations.update_status action on the SQLite resource after a valid delegation.
4. Run the Authoritative Action
Now start the protected run
Only after both policies are ACTIVE, run aigp. The lab registers the canonical execution; the issuer uses Policy 1 to create its immutable delegation; the runtime uses that delegation plus Policy 2 to request authorization immediately before SQLite.
For a changed scope, the user who administers the integration creates and activates a new policy version before the affected run. The prior activated version remains historical evidence. See the Policy Engine documentation for Studio policy creation and version lifecycle.
{
"runtime_action_authorization": {
"purpose": "runtime_action_authorization",
"registered_actions": ["operations.update_status"],
"registered_resource_types": ["sqlite.row"]
}
}LAB_ROOT="$(pwd)"
OSS_ROOT=/path/to/ai-governance-oss
LAB_SITE_PACKAGES="$("$LAB_ROOT/.venv/bin/python" -c 'import site; print(site.getsitepackages()[0])')"
PYTHONPATH="$LAB_ROOT:$LAB_SITE_PACKAGES" \
"$OSS_ROOT/.venv/bin/python" "$LAB_ROOT/app.py" aigp \
--database /tmp/bhanuj-langgraph-aigp.dbOutcome: WRITE_COMPLETED
Reason: ALLOW_POLICY_MATCHED
SQLite record status: MAINTENANCE
AIGP execution ID: <uuid>
AIGP evidence status: COMPLETED- 1
Execution Registration
An AIGP execution ID is assigned to the LangGraph run ID.
- 2
Workflow Facts
Each observable node transition is delivered as a bounded WORKFLOW_STEP record.
- 3
Authorization Decision
AIGP persists RUNTIME_ACTION_AUTHORIZATION for the registered action, resource, actor, and bounded context.
- 4
Actual Tool Fact
The SQLite write produces one TOOL_CALL whose causation ID is the authorization decision ID.
- 5
Finalization
The execution reaches its terminal state through the public completion route.
5. Compare Live Results
A permitted write and a safely refused one
These are two live outcomes validated against the local OSS API. Read the execution status separately from the authorization and tool results: a workflow may finish successfully because it handled a denied request safely.
Scenario A — authorized mutation
- Delegation permits
operations.update_status. - PDP returns
ALLOW_POLICY_MATCHED. OPS-001changes fromPENDINGtoMAINTENANCE.- One successful
TOOL_CALLcites the authorization decision withcausation_id. - The execution completes successfully.
Scenario B — scope blocks mutation
- Delegation permits only
operations.read_status. - The runtime proposes
operations.update_status. - PDP returns
DENY — DELEGATION_SCOPE_DENIED. OPS-001remainsPENDING; there are zero successfulTOOL_CALLevents.- The execution may still be
SUCCEEDED, because denial was handled safely.
Reproduce what the checked-in lab supports today: with the two Keycloak identities and both active policies configured above, run Scenario A with the aigp command. The checked-in CLI always asks its issuer for operations.update_status; it does not yet expose a read-only-delegation flag. Scenario B is a live-validated security result, not a command you should pretend exists. Use it as the expected assertion when adapting the public delegation API to your own runtime.
LAB_ROOT="$(pwd)"
OSS_ROOT=/path/to/ai-governance-oss
LAB_SITE_PACKAGES="$("$LAB_ROOT/.venv/bin/python" -c 'import site; print(site.getsitepackages()[0])')"
PYTHONPATH="$LAB_ROOT:$LAB_SITE_PACKAGES" \
"$OSS_ROOT/.venv/bin/python" "$LAB_ROOT/app.py" aigp \
--database /tmp/bhanuj-langgraph-aigp.dbRUNTIME_ACTION_AUTHORIZATION with ALLOW, then the successful TOOL_CALL whose causation ID points back to the decision.
PENDING SQLite result, no successful tool call, and terminal execution status.
6. Fixture Cases
Compare Offline Fixtures
All scenarios share the same LangGraph workflow and evidence adapter. Only aigp calls the authoritative policy endpoint. The fixture cases are retained for deterministic teaching and failure exercises; they never represent an AIGP policy decision.
| Scenario | Workflow Outcome | SQLite OPS-001 | Purpose |
|---|---|---|---|
| readonly | READ_COMPLETED | PENDING | Unprotected read; evidence optional. |
| aigp | Policy-dependent | Policy-dependent | Actual AIGP authorization immediately before the write. |
| allowed | WRITE_COMPLETED | MAINTENANCE | Offline fixture allows one protected write. |
| denied | WRITE_DENIED | PENDING | Offline fixture blocks the write. |
| outage | GOVERNANCE_UNAVAILABLE | PENDING | Offline fixture unavailable; write fails closed. |
| registration-failure | READ_COMPLETED | PENDING | No execution is fabricated. |
| evidence-failure | READ_COMPLETED | PENDING | Accepted start remains inspectable. |
# These decisions come from a local fixture; no authoritative policy call occurs.
python app.py allowed --database /tmp/bhanuj-langgraph-allowed.db
python app.py denied --database /tmp/bhanuj-langgraph-denied.db
python app.py outage --database /tmp/bhanuj-langgraph-outage.dbAllowed Fixture
The protected SQLite write is reachable only after the local fixture returns allow.
Blocked Fixture
No SQLite write occurs. The unavailable path fails the protected operation closed and records bounded error evidence where delivery succeeds.
7. Handle Failures
Visible Failures
The transport-failure scenarios are fault injectors around the same REST client; they do not create a second architecture. Evidence delivery can degrade a read-only result, but an authoritative protected write is stricter: any authorization denial, timeout, expiry, binding mismatch, malformed response, or missing canonical execution blocks SQLite.
# Registration never reaches OSS; the local read still completes.
python app.py registration-failure --database /tmp/bhanuj-langgraph-registration-failure.db
# Registration succeeds, then evidence delivery exhausts bounded retries.
python app.py evidence-failure --database /tmp/bhanuj-langgraph-evidence-failure.db
# The workflow stops after its read and finalizes as cancelled.
python app.py readonly --interrupt-after-read \
--database /tmp/bhanuj-langgraph-interrupted.dbRegistration Failure
READ_COMPLETED + DEGRADED. No execution ID or terminal completion is invented.
Evidence Failure
READ_COMPLETED + DEGRADED. The accepted start is still inspectable; a false completion is not sent.
Authorization Denied
WRITE_DENIED. AIGP retains the decision; no SQLite mutation or TOOL_CALL occurs.
Authorization Unavailable
GOVERNANCE_UNAVAILABLE + FAILED. The protected write fails closed and the record remains PENDING.
Interrupted Run
INTERRUPTED + CANCELLED. No database write occurs.
Status Meaning. AIGP evidence status: COMPLETED means the reporter delivered and finalized the evidence stream. It does not mean the business operation succeeded. Conversely, retryable evidence delivery is never authorization: a protected write requires one valid, current, operation-bound AIGP allow immediately before the tool call.
8. Inspect Evidence
See What Happened in Studio
After the aigp run completes, you do not need to read a database or decode raw events. Open Studio and follow the same path a reviewer would use.
- STEP 1
Open Agents Runtime
Choose Agents Runtime in the Studio sidebar, then stay on the Executions tab.
- STEP 2
Choose the LangGraph assistant
Select LangGraph governed operations assistant to reveal its recent runs.
- STEP 3
Open the new run
Choose the run you just made to see the authorization, tool call, and completion record.
Start in Studio's Agents Runtime area. The Executions tab is where observed assistants and their execution evidence appear. Select LangGraph governed operations assistant. Its recent executions appear directly below the selected-agent summary; choose the run you just created.
The execution inspector shows the ordered evidence: the authorization before the tool call, followed by the completed execution. Event payloads are intentionally not shown.
Policy 1, Delegation-issuance; Policy 2, Runtime-action authorization.
What to look for
- The returned external execution ID and every event correlation ID match the ID printed by the lab.
- The timeline includes EXECUTION_STARTED, WORKFLOW_STEP, RUNTIME_ACTION_AUTHORIZATION, TOOL_CALL, and EXECUTION_COMPLETED.
- The authorization event records ALLOW for operations.update_status on the sqlite.row resource.
- The one TOOL_CALL is the actual SQLite write, has outcome SUCCEEDED, and cites the authorization decision as causation.
- Repeating an event delivery with its deterministic idempotency key yields one logical event.
Need the exact IDs for an API check or automated test? Copy the AIGP execution ID printed by the authoritative run. The read route returns that tenant-scoped execution and its timeline; the lab never reads AIGP's SQLite or graph stores directly.
export EXECUTION_ID='<AIGP execution ID printed by the lab>'
curl "$AI_GOVERNANCE_BASE_URL/api/v1/agent-executions/$EXECUTION_ID" \
-H "Authorization: Bearer $AI_GOVERNANCE_ACCESS_TOKEN" \
-H "X-AI-Governance-Organization-Id: $AI_GOVERNANCE_ORGANIZATION_ID" \
-H "X-AI-Governance-Project-Id: $AI_GOVERNANCE_PROJECT_ID"unset ISSUER_ACCESS_TOKEN RUNTIME_ACCESS_TOKEN AI_GOVERNANCE_ACCESS_TOKEN AI_GOVERNANCE_DELEGATION_ISSUER_ACCESS_TOKENTest Live API
Deterministic unit tests run without a server. With a development-authenticated local API, explicitly opt into the HTTP integration module. It creates and archives ephemeral policies to prove the real authorization, SQLite mutation, and decision-to-tool causation path. This is separate from the two-identity walkthrough above; do not use it to create or archive a policy you intend to keep.
python -m pytest -qexport BHANUJ_LAB_INTEGRATION_ENABLED=true
export BHANUJ_LAB_INTEGRATION_BASE_URL=http://localhost:8000
export BHANUJ_LAB_INTEGRATION_ORGANIZATION_ID=org_default
export BHANUJ_LAB_INTEGRATION_PROJECT_ID=project_default
export BHANUJ_LAB_INTEGRATION_ACTOR_ID=local-admin
export BHANUJ_LAB_INTEGRATION_RUNTIME_ACTOR_ID=local-admin
LAB_ROOT="$(pwd)"
OSS_ROOT=/path/to/ai-governance-oss
LAB_SITE_PACKAGES="$("$LAB_ROOT/.venv/bin/python" -c 'import site; print(site.getsitepackages()[0])')"
PYTHONPATH="$LAB_ROOT:$LAB_SITE_PACKAGES" \
"$OSS_ROOT/.venv/bin/python" -m pytest "$LAB_ROOT/tests/test_oss_api_integration.py" -m integration -q9. Production Identities
Bring your own identity system
The lab uses Keycloak because it makes the issuer and runtime identities visible locally. In production, your identity system can authenticate those applications, issue short-lived credentials, and manage platform permissions. It does not automatically create AIGP's execution-specific delegation; the integrating application still calls the OSS public API to issue a narrowly scoped delegation.
AWS
Amazon Bedrock AgentCore Identity, IAM, and AgentCore Gateway can supply workload identity and platform access controls.
Google Cloud
Agent Identity, IAM, and Agent Gateway can authenticate an agent and manage platform-level access.
Microsoft
Entra Agent ID and application or service identities can authenticate and govern agent workloads.
OIDC / OAuth, including Keycloak
Use your existing identity provider for separate issuer and runtime principals. Keep credentials short-lived and out of source, screenshots, and command output.
Integration boundary: these are identity sources, not claimed native BHANUJ integrations. They can establish who is calling and what platform permissions apply. The application must still register the canonical execution and issue the appropriate AIGP delegation through the public API.
10. Understand Boundaries
Scope Boundaries
Execution Ownership
AIGP does not schedule nodes, call SQLite, or select a graph branch.
Authorization Ownership
AIGP independently resolves the immutable delegation and applicable policies, returns and persists an operation-bound decision; LangGraph enforces it immediately before SQLite.
Evidence Ownership
The public API registers, receives, finalizes, and returns the evidence record.
Fixture Boundary
The offline allowed, denied, and outage scenarios are not AIGP policy decisions and cannot authorize production operations.
Agent-Controlled Context
A purpose, task description, or claimed authority from the agent can only add restrictions; it cannot enlarge the stored delegation or select a favourable policy.
Integrity Binding
The tool-argument digest binds the exact proposed operation. It does not independently validate every business constraint behind that operation.
Expiry and Revocation
Revocation blocks new decisions. A short-lived ALLOW already issued remains usable only until its stated expiry.
Bounded Evidence
The lab sends identifiers, metadata, outcome codes, and references—not prompts, records, tool payloads, responses, credentials, or reasoning traces.
No Evidence Sealing
A terminal execution status is not evidence sealing, an immutable ledger claim, or a causal conclusion.
No Replay
This lab does not provide a replay provider, causal audit demonstration, or a claim that the SQLite tool is replayable.
Start Here
Clone the lab, run the read-only checkpoint, then connect it to your local OSS API and activate a matching runtime-action policy before the authoritative protected write.
