Back to all tutorials
Agent Gateway

Protect agent-to-agent workflows with Agent Gateway

Learn what the Indykite Agent Gateway (IAG) does and how to deploy it in front of each agent of the iag-demo reference app.

This tutorial is grounded in the iag-demo reference application. The demo runs a small canbank agentic workflow with three protected agents and one IAG instance in front of each.

What you will have at the end

  1. A clear mental model of how IAG validates caller, workflow, and delegation chain.
  2. A Workflow node (external_id=wf1), three Agent nodes, and INVOKES relationships in the IKG.
  3. A CAN_TRIGGER KBAC policy and a ContX IQ query that returns (workflow, agent_list) pairs.
  4. Three IAG instances running via Docker Compose: orchestrator-iag, retriever-iag, weather-iag.
  5. Auditable AUTHORIZED and NOT_AUTHORIZED records delivered to the chatbot webhook.

Who this tutorial is for

  • Developers wiring up agents over the A2A protocol who need policy enforcement in front of each agent.
  • Platform and security engineers who want traceable user-to-agent delegation.
  • AI agents consuming this doc as a runbook - every chapter uses explicit service names, ports, file paths, and commands.

Demo topology at a glance

Service Role Port
chatbot Web UI and A2A client; also receives audit webhooks. 3000
orchestrator-iag IAG in front of the orchestrator agent. 8881
orchestrator A2A orchestrator; delegates to retriever or weather. 6001
retriever-iag IAG in front of the retriever agent. 8882
retriever Answers canbank questions via MCP against the IKG. 6002
weather-iag IAG in front of the weather agent. 8884
weather Returns weather forecasts from Open-Meteo. 6004
1

Chapter 1

What is the Agent Gateway?

Understand IAG's role in an A2A workflow using the iag-demo topology as a concrete example.

Chapter 1: What is the Agent Gateway?

The Indykite Agent Gateway (IAG) is a standalone service that protects one AI agent. Deploy one IAG per protected agent - in the iag-demo that means three instances: orchestrator-iag, retriever-iag, and weather-iag.

For the full product reference, see the official Agent Gateway documentation.

IAG talks to two backends:

  • The IndyKite Platform - for workflow data (IKG), KBAC/AuthZEN policies, and ContX IQ queries.
  • An external OAuth2-compliant identity provider (IdP) - for introspecting caller tokens, obtaining actor tokens, and exchanging them into delegated tokens. The demo uses https://idsvr.indykite.one/oauth/v2/.

Not a regular proxy

IAG assumes agents speak the A2A protocol. It is not a generic reverse proxy: it tracks agent sessions so that A2A messages stream properly between Source and Target agents, and it appears as the Target from the caller's point of view and as a Source from the protected agent's point of view.

When to use IAG

Use case What IAG gives you
Protect A2A workflows A policy-controlled enforcement point in front of each protected agent.
Explicit, traceable user-to-agent delegation Delegation validation through OAuth token exchange and the token's act chain, verified against workflow definitions in the IKG.
Workflow-aware authorization Validates that the caller can trigger the workflow and that the requested sequence of agents is permitted.
Auditable decisions outside the agent runtime Per-request records of who invoked which protected agent, when, and why a request was allowed or denied.

Mental model (iag-demo)

leslie (user)
   │  login via chatbot
   ▼
chatbot:3000
   │  A2A JSON-RPC
   ▼
orchestrator-iag:8881  ── introspect / exchange ──▶  IdP (idsvr.indykite.one)
   │                    ── CAN_TRIGGER wf1      ──▶  AuthZEN
   │                    ── workflows / chains   ──▶  ContX IQ (IKG)
   │                    ── audit webhook        ──▶  chatbot:3000/api/push-update
   ▼
orchestrator:6001
   │  delegates
   ▼
retriever-iag:8882  ──▶  retriever:6002         (for canbank questions)
weather-iag:8884    ──▶  weather:6004           (for weather questions)

What comes next

Chapter 2 walks through the exact sequence of checks IAG performs for every incoming A2A request.

2

Chapter 2

How IAG handles a request at runtime

The nine-step path a request takes through orchestrator-iag to the protected agent.

Chapter 2: How IAG handles a request at runtime

Every IAG instance in the iag-demo performs the same sequence. This chapter uses orchestrator-iag as the concrete example.

The nine steps

  1. Receive an A2A JSON-RPC request (for example POST /v1/message/send) from the chatbot on port 8881.
  2. Introspect the caller's token at the IdP's oauth-introspect endpoint.
  3. Obtain an actor token for the protected agent via client credentials (ORCHESTRATOR_IDP_CLIENT_ID / ORCHESTRATOR_IDP_CLIENT_SECRET) at oauth-token.
  4. Exchange the caller token and actor token for a delegated token (oauth-token).
  5. Query ContX IQ with the configured CIQ_QUERY_ID to get the workflows the protected agent belongs to and the allowed agent chains for each.
  6. Check the subject: call AuthZEN with action CAN_TRIGGER on each candidate Workflow.
  7. Check the chain: confirm the requested act delegation chain matches an allowed agent_list.
  8. Forward the request to orchestrator:6001 with the delegated token.
  9. Return the protected agent's response to the caller, and write an audit record.

Where each question is answered

Step Question Service
Introspect Is the caller token valid and active? IdP
Client credentials Can IAG authenticate as the protected agent? IdP
Token exchange Can the caller delegate to the protected agent? IdP
ContX IQ query Which workflows is this agent part of, and what chains are allowed? ContX IQ (IKG)
AuthZEN Can the subject CAN_TRIGGER at least one candidate workflow? AuthZEN / KBAC
Chain check Does the requested act chain match an allowed agent chain? IAG (in-process)

HTTP responses

Code Meaning
400 Bad request. The request cannot be processed because of its content.
401 Unauthorized. IAG cannot identify the caller.
403 Forbidden. Caller is authenticated but not allowed.
500 Internal error. Unexpected internal failure.
502 Bad gateway. Upstream or gateway-side processing issue.

IAG may also translate upstream errors where appropriate (for example, a 404 returned by ContX IQ).

Supported endpoints

  • Any method on / for generic JSON-RPC (message/send, tasks/get).
  • POST /v1/message:send and POST /v1/message/send for A2A SendMessage.
  • POST /v1/tasks:get and POST /v1/tasks/get for A2A GetTask.
3

Chapter 3

Prerequisites and agent registration

Exact list of tools, IdP clients, and IndyKite artifacts you need before running iag-demo.

Chapter 3: Prerequisites and agent registration

Gather the items in this chapter before touching configuration. The iag-demo expects all of them to exist.

Tooling

  • Docker + Docker Compose v2.
  • Python 3.11+ with a package manager - pipenv is recommended for the in-repo services.
  • A POSIX OS is preferred.
  • Optional: a Gemini API key, or a local Ollama instance reachable at http://host.docker.internal:11434.

IdP clients

Create one OAuth2 client per service. The demo uses four clients and maps them to these env vars:

Client Used by Env vars
console chatbot (login flow) CHATBOT_IDP_CLIENT_ID / _SECRET
indykiteagent orchestrator ORCHESTRATOR_IDP_CLIENT_ID / _SECRET
indykiteagent-2 retriever RETRIEVER_IDP_CLIENT_ID / _SECRET
indykiteagent-3 weather WEATHER_IDP_CLIENT_ID / _SECRET

Your IdP must support token introspection, client credentials, and token exchange. The chatbot client's redirect URL must match http://${CHATBOT_HOST}:${CHATBOT_PORT}/auth/callback, otherwise the login will fail with an OAuth redirect mismatch.

IndyKite artifacts

  • An IndyKite project with the canbank graph data ingested.
  • A Workflow node with external_id=wf1.
  • A ContX IQ knowledge query - its GID or name populates CIQ_QUERY_ID.
  • A Token Introspect configuration - created as part of the canbank app setup. See the Token Introspect guide for details.
  • An App Agent whose credentials token populates APP_AGENT_CREDENTIALS_TOKEN.

Register protected agents in two places

Each protected agent is authenticated through the IdP using the client credentials flow, and each must exist as a digital twin in the IKG so IAG can evaluate workflows against it.

  1. In the IdP: create the client (see the table above).
  2. In IndyKite: pre-register and catalog the agent in the IKG, using a URI-style identifier as external_id.

Pre-flight checklist

  • Four IdP clients exist and you have their IDs and secrets.
  • Canbank data is visible in your IKG.
  • Workflow with external_id=wf1 exists and links to the three Agent nodes.
  • You have the ContX IQ query ID and the App Agent credentials token.
  • You can reach ${INDYKITE_BASE_URL}/contx-iq/v1 and ${INDYKITE_BASE_URL}/access/v1.
4

Chapter 4

Model the wf1 workflow in the IKG

The Workflow, Agent nodes, and INVOKES relationships that back the iag-demo canbank workflow.

Chapter 4: Model the wf1 workflow in the IKG

IAG validates whether the requested act delegation chain matches a workflow-defined agent chain. The iag-demo uses a single workflow with external_id=wf1 and three agents.

Required data shape

  • A Workflow node identified by external_id - here, wf1.
  • Agent nodes identified by external_id - orchestrator, retriever, weather.
  • INVOKES relationships between agents.
  • A workflow_name property on each INVOKES - set to wf1 for this demo.

Demo workflow graph

(Workflow {external_id: "wf1"})

(Agent {external_id: “orchestrator”}) -[INVOKES {workflow_name: “wf1”}]-> (Agent {external_id: “retriever”})

(Agent {external_id: “orchestrator”}) -[INVOKES {workflow_name: “wf1”}]-> (Agent {external_id: “weather”})

Why workflow_name is mandatory

An agent may participate in several workflows. The workflow_name property on the INVOKES relationship identifies which workflow a given invocation belongs to. The ContX IQ query in Chapter 5 only returns relationships whose workflow_name matches the Workflow.external_id, so chains without this property are silently excluded.

Valid delegation chains in wf1

chatbot -> orchestrator -> retriever
chatbot -> orchestrator -> weather

Any chain that skips the orchestrator (for example chatbot -> retriever) or adds an agent not modeled in wf1 is rejected with 403 Forbidden.

How to capture the data

Use whichever ingestion path you already use in your project:

  • The Capture API (POST /capture/v1/nodes and POST /capture/v1/relationships).
  • The IndyKite Hub UI.
  • An existing Terraform or identity pipeline.

The bruno/iag-demo folder of the iag-demo repo contains ready-made sample requests you can replay against your project.

Common pitfalls

  • Missing workflow_name: the ContX IQ query returns nothing and every request is denied.
  • Identifier mismatch: the external_id on each Agent must match the identifier carried in the act chain and used in the IdP.
  • No link between the subject and the workflow: even with a correct chain, the request is denied at the AuthZEN step if the subject cannot CAN_TRIGGER wf1.
5

Chapter 5

Create the KBAC policy and ContX IQ query

The CAN_TRIGGER policy and knowledge query that wire the iag-demo subject User to the wf1 workflow.

Chapter 5: Create the KBAC policy and ContX IQ query

IAG needs two authorization artifacts:

  1. A KBAC / AuthZEN policy that answers "can this subject trigger this workflow?".
  2. A ContX IQ query that returns, for the protected agent, the workflows it belongs to and the allowed agent chains inside each.

Subject type used by iag-demo

The demo configures JARVIS_AUTHZEN_SUBJECT_TYPES=User, so the KBAC policy below uses User as the subject type. If you add more subject types, create one policy per type and list them all in JARVIS_AUTHZEN_SUBJECT_TYPES (or authzen.subject_types in a YAML config).

KBAC / AuthZEN policy

{
  "meta": {
    "policyVersion": "1.0-kbac"
  },
  "subject": {
    "type": "User"
  },
  "actions": [
    "CAN_TRIGGER"
  ],
  "resource": {
    "type": "Workflow"
  },
  "condition": {
    "cypher": "MATCH (subject)-[:CAN_TRIGGER]->(resource:Workflow)"
  }
}

This policy assumes your IKG contains a relationship like:

(:User)-[:CAN_TRIGGER]->(:Workflow {external_id: "wf1"})

In the canbank demo, users leslie, roy, and rebecca each have this relationship to wf1.

ContX IQ query

The query returns one row per (workflow, agent_list) pair for the protected agent identified by $agent_id:

{
  "meta": {
    "policy_version": "1.0-ciq"
  },
  "subject": {
    "type": "_Application"
  },
  "condition": {
    "cypher": "MATCH (subject:_Application) MATCH (wf:Workflow)-[rels:INVOKES*]->(a:Agent {external_id: $agent_id}) WHERE ALL(r IN rels WHERE r.workflow_name = wf.external_id AND endNode(r):Agent) WITH subject, wf.external_id AS workflow, [r IN rels | endNode(r).external_id] AS agent_list",
    "filter": []
  },
  "allowed_reads": {
    "nodes": [],
    "relationships": [],
    "aggregate_values": [
      "workflow",
      "agent_list"
    ]
  }
}

Record the query GID or name - it populates CIQ_QUERY_ID in your .env.

Enforced response shape

IAG expects the response in this exact shape:

{
  "data": [
    {
      "aggregate_values": {
        "agent_list": ["orchestrator", "retriever"],
        "workflow": "wf1"
      }
    },
    {
      "aggregate_values": {
        "agent_list": ["orchestrator", "weather"],
        "workflow": "wf1"
      }
    }
  ]
}

In iag-demo, the same wf1 workflow appears twice - once per allowed chain.

How IAG combines the two

  1. IAG runs the ContX IQ query and gets the candidate (workflow, agent_list) pairs.
  2. For each workflow, IAG asks AuthZEN whether the subject has CAN_TRIGGER.
  3. If at least one workflow is triggerable and the requested delegation chain matches its agent_list, IAG forwards the request.

If either check fails, IAG responds with 403 and the audit record's decision is NOT_AUTHORIZED.

6

Chapter 6

Configure IAG: YAML fields and the iag-demo env vars

Both ways to configure IAG - a full config.yaml and the JARVIS_* environment variables used by iag-demo.

Chapter 6: Configure IAG

IAG accepts either a YAML config file passed via --config=/app/config.yaml or a set of environment variables. The iag-demo uses the environment-variable form so a single shared file (iag-base-docker.yaml) can be reused by three services.

Configuration sections

The configuration is grouped by concern. The same keys appear in both forms - YAML fields use dots (service.name), and environment variables use the JARVIS_ prefix with underscores (JARVIS_SERVICE_NAME).

Section Purpose
service Runtime: name, port, environment, log_level.
identity_provider IdP base_url and endpoints: introspect_endpoint, client_credential_endpoint, exchange_endpoint.
protected_agent Target agent base_url and its client credentials (authentication.client_id, authentication.client_secret, authentication.type=credentials).
authzen base_url, action (typically CAN_TRIGGER), subject_types, and cache tuning.
contx_iq base_url, query_id, app_agent_credentials_token, optional allowed_workflow_id, and cache tuning.
audit Optional. Either delivery: webhook (with http.url, http.method, http.auth) or delivery: file (with storage_path, format, rotation_strategy).

Defaults worth knowing

  • Caches (cache_ttl, cache_update_after) default to 5m; cache_update_after_error defaults to 10s.
  • Audit file rotation defaults: rotation_interval=24h, rotation_max_bytes=100MiB.
  • Webhook API key auth defaults api_key_header to X-API-Key.

Form 1 - Full config.yaml

---
service:
  name: agent-gateway
  port: 1234
  environment: prod
  log_level: debug

identity_provider: base_url: http://idp.example.com:1234 introspect_endpoint: /token/introspect client_credential_endpoint: /token exchange_endpoint: /token/exchange

protected_agent: base_url: http://agent.example.com:5678 authentication: type: credentials client_secret: secret-password client_id: idp-actor-client-id

authzen: base_url: https://us.api.indykite.com/access/v1 action: CAN_TRIGGER subject_types: - Person - User - Agent cache_ttl: 5m cache_update_after: 5m cache_update_after_error: 10s

contx_iq: base_url: https://us.api.indykite.com/contx-iq/v1 query_id: gid:abc123def456 app_agent_credentials_token: jwtheader.payload.signature cache_ttl: 5m cache_update_after: 5m cache_update_after_error: 10s allowed_workflow_id: my-awesome-workflow

audit: delivery: webhook http: url: https://example.com:9090/audit method: POST auth: type: mTLS mtls_certificate_file_path: cert.pem mtls_private_key_path: key.pem

Form 2 - environment variables (iag-demo)

The iag-demo shares a single base service via iag-base-docker.yaml. Each IAG instance extends it and overrides only the per-agent values.

# iag-base-docker.yaml (excerpt, verbatim from the demo)
services:
  iag-base:
    image: indykite/agent-gateway:latest
    environment:
      JARVIS_SERVICE_LOG_LEVEL: debug
      JARVIS_SERVICE_ENVIRONMENT: demo
      JARVIS_IDENTITY_PROVIDER_BASE_URL: "https://idsvr.indykite.one/oauth/v2/"
      JARVIS_IDENTITY_PROVIDER_INTROSPECT_ENDPOINT: "oauth-introspect"
      JARVIS_IDENTITY_PROVIDER_CLIENT_CREDENTIAL_ENDPOINT: "oauth-token"
      JARVIS_IDENTITY_PROVIDER_EXCHANGE_ENDPOINT: "oauth-token"
      JARVIS_CONTX_IQ_BASE_URL: ${INDYKITE_BASE_URL}/contx-iq/v1
      JARVIS_CONTX_IQ_QUERY_ID: ${CIQ_QUERY_ID}
      JARVIS_CONTX_IQ_APP_AGENT_CREDENTIALS_TOKEN: ${APP_AGENT_CREDENTIALS_TOKEN}
      JARVIS_CONTX_IQ_ALLOWED_WORKFLOW_ID: ${WORKFLOW_ID}
      JARVIS_AUTHZEN_BASE_URL: ${INDYKITE_BASE_URL}/access/v1
      JARVIS_AUTHZEN_ACTION: CAN_TRIGGER
      JARVIS_AUTHZEN_SUBJECT_TYPES: User
    extra_hosts:
      - "host.docker.internal:host-gateway"

Per-agent overrides in the demo

Each IAG service in docker-compose.yaml sets its own JARVIS_SERVICE_NAME, JARVIS_SERVICE_PORT, JARVIS_PROTECTED_AGENT_BASE_URL, JARVIS_PROTECTED_AGENT_AUTHENTICATION_CLIENT_ID, and JARVIS_PROTECTED_AGENT_AUTHENTICATION_CLIENT_SECRET. For example:

IAG instance Protected agent URL Client ID env var
orchestrator-iag (:8881) http://orchestrator:6001 ORCHESTRATOR_IDP_CLIENT_ID
retriever-iag (:8882) http://retriever:6002 RETRIEVER_IDP_CLIENT_ID
weather-iag (:8884) http://weather:6004 WEATHER_IDP_CLIENT_ID

Audit configuration in the demo

The demo ships a minimal audit-config.yaml that every IAG mounts and points all three gateways at the chatbot's webhook endpoint:

audit:
  delivery: webhook
  http:
    url: http://chatbot:3000/api/push-update
    method: post
    auth:
      type: no-auth

That is why audit decisions appear in the chatbot UI in real time.

7

Chapter 7

Deploy: iag-demo with Docker Compose, and Kubernetes variants

Build the in-repo services, pin the IAG image, bring up the compose stack, and see how the same wiring maps to Kubernetes standalone and sidecar patterns.

Chapter 7: Deploy IAG

IAG is delivered as a Docker image. Deploy one IAG instance per protected agent, regardless of topology. The iag-demo uses three instances: orchestrator-iag, retriever-iag, weather-iag.

Step-by-step: run the iag-demo

  1. Clone the repo and enter a2a/iag-demo.
  2. Copy the env file:
    cp .example.env .env
  3. Fill in the required variables: INDYKITE_BASE_URL, CIQ_QUERY_ID, WORKFLOW_ID=wf1, APP_AGENT_CREDENTIALS_TOKEN, and the four *_IDP_CLIENT_ID / *_IDP_CLIENT_SECRET pairs.
  4. Generate a Flask secret:
    python -c "import secrets; print(secrets.token_hex(32))"
    Paste the output into FLASK_SECRET_KEY in .env.
  5. Build the in-repo services:
    make
    This builds the chatbot, orchestrator, retriever, and weather images.
  6. Pin the IAG image. In iag-base-docker.yaml, replace latest with a specific tag - if you leave it as latest Docker may fail with manifest not found:
    image: indykite/agent-gateway:1.783.1
  7. Start the stack:
    docker compose up
  8. Open http://localhost:3000 and log in as a demo user (leslie, roy, or rebecca).

Tail the gateway logs

When a request is denied, look here first:

docker compose logs -f orchestrator-iag retriever-iag weather-iag

Shut down

docker compose down

Option: run a single IAG with docker run

Outside the demo, a single IAG instance can be started with one command and a config.yaml:

docker run -d \
  --name agent-gateway \
  -p <YOUR_PORT>:<SERVICE_PORT> \
  --add-host host.docker.internal:host-gateway \
  -v $(pwd)/config.yaml:/app/config.yaml \
  -v $(pwd)/audit:/app/audit \
  indykite/agent-gateway:latest \
  --config=/app/config.yaml

The Docker user must have read permission on config.yaml.

Kubernetes - standalone Pod

The gateway and the protected agent run in different Pods. Use this when they have independent lifecycles or scaling.

  • A Deployment for IAG (single container, indykite/agent-gateway:<version>).
  • A Service to expose the gateway internally (and externally if needed).
  • A ConfigMap and/or Secret mounted as /app/config.yaml.
  • An optional Ingress or LoadBalancer.

Set protected_agent.base_url to the in-cluster DNS name, for example:

http://protected-agent.<namespace>.svc.cluster.local:8080

Kubernetes - sidecar

IAG and the protected agent run in the same Pod and share a network namespace. Traffic between them stays on localhost.

  • One Deployment with two containers: agent-gateway and protected-agent.
  • A Service that exposes only the gateway port.

Set protected_agent.base_url to the local instance so traffic stays inside the Pod:

http://127.0.0.1:<AGENT_PORT>

Configuration management tips

  • Simple setups: store the whole config.yaml in a Secret.
  • Stricter setups: split non-sensitive values into a ConfigMap, credentials into a Secret, combine them at runtime.
  • The cluster must allow egress to the audit endpoint or webhook delivery will fail.
  • Use NetworkPolicy to constrain traffic between gateway, protected agent, IdP, and IndyKite.

Troubleshooting quick reference

Symptom Likely cause
manifest not found on docker compose up latest tag in iag-base-docker.yaml; pin a real version.
OAuth redirect mismatch at login IdP client redirect URL doesn't match http://${CHATBOT_HOST}:${CHATBOT_PORT}/auth/callback.
401 from a gateway Caller token can't be introspected. Check the IdP and the token.
403 from a gateway Subject lacks CAN_TRIGGER on wf1, or the CIQ query returns no matching chain.
8

Chapter 8

Exercise iag-demo and read the audit logs

Send real prompts through the canbank chatbot, watch IAG decisions in real time, and interpret audit records in CSV, JSON, and TXT formats.

Chapter 8: Exercise iag-demo and read the audit logs

Prerequisite: complete Chapter 7 so the full Docker Compose stack is up. This chapter assumes docker compose up is running and all three IAG instances are healthy.

Log in at http://localhost:3000 as leslie. Send the prompts below and watch the audit messages appear - the chatbot UI receives them on /api/push-update.

Demo users

The canbank dataset ships with three personas. They all have CAN_TRIGGER on wf1, so any of them can log into the chatbot. The scenarios differ:

User Role Use when you want to...
leslie Customer Service Rep (CSR 2) Ask policy and past-decision questions on behalf of customers. Default login for the refund-policy flow.
roy Retail trader Exercise trader-side prompts from a non-CSR perspective.
rebecca Customer with a credit card and a trading account Be the subject of holdings queries (for example, NVDA shares Rebecca can purchase). Can also log in directly as the customer.

Sample prompts and expected routes

Prompt Routed through Expected decision
"What policy documents pertain to refunds?" chatbot → orchestrator → retriever AUTHORIZED on orchestrator-iag and retriever-iag.
"Retrieve past decisions that incorporated the 'refund_policy' document." chatbot → orchestrator → retriever AUTHORIZED.
"Tell me how many shares of NVDA the user with id rebecca can purchase." chatbot → orchestrator → retriever AUTHORIZED if leslie has CAN_TRIGGER on wf1.
"What's the weather in London?" chatbot → orchestrator → weather AUTHORIZED on orchestrator-iag and weather-iag.

Make IAG reject requests on purpose

Each of these should end with 403 Forbidden and a NOT_AUTHORIZED audit record:

  • Skip the orchestrator: call retriever-iag (:8882) directly without the orchestrator in the act chain. Chain chatbot -> retriever is not in wf1.
  • Break the graph: remove the workflow_name property from one INVOKES relationship and retry - the ContX IQ query no longer returns that chain.
  • Remove the subject link: delete the (:User {external_id:"leslie"})-[:CAN_TRIGGER]->(:Workflow {external_id:"wf1"}) edge. AuthZEN now says no.
  • Wrong subject type: log in with a user whose type is not User. It is not listed in JARVIS_AUTHZEN_SUBJECT_TYPES, so no policy matches.

Service logs vs audit logs

Service logs are written in JSON to standard output (docker compose logs -f orchestrator-iag). Audit messages are a separate stream and can be delivered as:

  • A webhook - what the iag-demo uses, pointed at http://chatbot:3000/api/push-update.
  • A file in CSV, JSON, or TXT format with size-, time-, or size-and-time-based rotation.

Audit formats

Webhook body (and JSON file)

{
  "decision": "AUTHORIZED",
  "reason": "subject can trigger workflows wf1 and the actors in the delegation chain",
  "subject": "leslie",
  "actor": "orchestrator",
  "action": "invoke",
  "service": "orchestrator-iag",
  "timestamp": "2026-01-02T15:04:05.999999999Z07:00",
  "traceID": "c323b688-3c01-4559-838d-59ac7a81ee1a"
}

CSV file

decision,reason,subject,actor,action,service,timestamp,traceID
AUTHORIZED,'subject can trigger workflows wf1 and the actors in the delegation chain',leslie,orchestrator,invoke,orchestrator-iag,2026-01-02T15:04:05.999999999Z07:00,c323b688-3c01-4559-838d-59ac7a81ee1a
NOT_AUTHORIZED,no workflow matches the actors chain,leslie,retriever,invoke,retriever-iag,2026-01-02T15:04:06.999999999Z07:00,2d17528b-5005-4f5b-8d39-5207c394ae9b

TXT file

[2026-01-02T15:04:05.999999999Z07:00] decision=AUTHORIZED reason=subject can trigger workflows wf1 and the actors in the delegation chain subject=leslie actor=orchestrator action=invoke service=orchestrator-iag traceID=c323b688-3c01-4559-838d-59ac7a81ee1

Reading an audit record

Field What to check
decision AUTHORIZED or NOT_AUTHORIZED. The binary outcome.
reason Human-readable explanation; useful for diagnosing failed requests.
subject / actor Who initiated the request and which protected agent was targeted.
service Which IAG wrote the record - orchestrator-iag, retriever-iag, or weather-iag.
traceID Correlate with protected-agent logs and upstream client traces.

Where to go next

  • Add a fourth agent to wf1 and watch the allowed chains grow in the ContX IQ response.
  • Switch audit delivery from the chatbot webhook to your SIEM.
  • Migrate from ENV VARS to a full config.yaml for environments that require stricter configuration management.
  • Move to Kubernetes: start with standalone Pods for independent scaling, or go sidecar for tighter coupling between each agent and its IAG.