The pipeline, end to end.
What each stage does, what it stores, and the one place a model is allowed in.
A model between two deterministic layers.
Everything before the model is deterministic. Everything after it is too. A person decides what gets applied.
Collect
Deterministic
- Plugins read systems and take in telemetry
- Graph versioned in PostgreSQL, with evidence IDs
- Change feed an ordered, transactional outbox
- Detectors built-in checks and rules ask whether a change is new
Investigate
Model
- Queue eligible signals wait in a durable queue, sampled and budgeted
- Packet a compact summary of the change, with no telemetry in it
- Investigator queries the graph through six read-only tools
Decide
Deterministic
- Validation every claim must cite what the graph returned
- Promotion a finding is kept or dropped
- Rule match an active rule’s match is a finding with no model call
- Findings persisted, served by API, shown in the explorer
Act
You approve
- Plan the plugin resolves current state
- Dry run every write is checked first
- Approval a person submits the prepared plan
- Apply and verify the plugin writes, then re-reads
The graph is the shared language.
Every plugin describes its world as resources, facets, and relationships. Detectors and the first packet use neutral security semantics, not provider-native fields, so adding a plugin does not mean teaching the model a new format.
- Identity does not drift. Names, labels, and IP addresses are attributes. Identity is an authority plus an immutable key.
- Plugins replace, not patch. Each publishes its complete current output. Whatever it omits is retracted.
- Every publication is a version. The canonical graph is compiled from active assertions and carries a SHA-256 digest that findings are pinned to.
- Security facts are typed. Class, domain, state, effect, scope, direction, and coverage are explicit. A declared policy is not assumed to be enforced.
Identity
- scheme
- kubernetes.io/v1, Kind=Object
- authority
- stable cluster ID
- key
- lowercase(apiVersion/kind/UID)
Shown for Kubernetes. AWS resources use aws.amazon.com/v1, Kind=Resource with the owning account as the authority.
Relationships
- contains
- instantiates
- member-of
- uses-identity
- exposes
- governed-by
- communicates-with
- runs-on
- backed-by
- executes
A closed vocabulary. A new predicate is added centrally, not per plugin.
Telemetry stays out of the graph.
The graph says what exists and what is allowed. Telemetry says what is happening. They meet at one identity.
In
Istio access logs and Falco alerts arrive over OTLP/HTTP from your collector, and AWS VPC Flow Logs arrive straight from Amazon Data Firehose. Plugins map them into normalized events. Ownership comes from registration, never from the payload.
Stored
Raw events go to local JSONL or ClickHouse before anything is projected, with explicit backpressure. Nothing uncommitted reaches the graph.
Rolled up
The graph holds bounded rollups and evidence IDs, not payloads. A communicates-with edge, or an executes edge for a shell alert, is a lease: it refreshes while observations continue and retracts when they stop.
How a change becomes a finding.
The model is an advisory investigator. The service stays authoritative for graph state, retrieval bounds, evidence, and promotion.
A change lands
Each publication that matters to reasoning writes one ordered event to the outbox, in the same transaction. Telemetry-only updates do not.
Detectors ask whether it is new
Built-in checks compare the previous graph with a coherent checkpoint. A new communication path from a workload that already had traffic, a resource becoming externally addressable, typed semantics changing or disappearing, a governed-by edge being retracted, a new RBAC binding or admission webhook, and a workload taking on a new identity all raise a signal. Rules you activated run in the same window. Windows with no signal never reach a model.
Each signal is routed
A match of an active rule becomes a finding with no model call. A match of an unreviewed draft is only recorded. A built-in signal of medium risk or higher is queued for investigation, where batching, sampling, deduplication, and an hourly budget bound model spend.
The model gets a small packet
Resource IDs, kinds, typed security semantics, opaque evidence handles, and the signals. No telemetry and no provider-native configuration.
It investigates with read-only tools
Neighborhoods, paths, governance posture, telemetry chains, timelines, and baselines. Each result is bounded and pinned to the packet’s analysis token.
The harness validates
A candidate needs a selected signal, entities from the packet, returned fact IDs, and evidence IDs attached to those facts. A wrong reference gets one correction turn. Truncated retrieval blocks promotion.
A finding is promoted or dropped
Finding IDs are deterministic. A later change that touches an affected entity invalidates an investigated finding; it stays available for audit but cannot launch a remediation.
Fixes come from the plugins that can apply them.
An investigation attaches desired outcomes to a finding. It never picks a provider. You see every action that is live and choose one.
Kubernetes (Pod)
- Action
network-policy/v1- Outcome
network.declare-ingress-isolation- Result
- A pod label and an ingress-isolating NetworkPolicy
Kubernetes (Service)
- Action
service-network-policy/v1- Outcome
network.declare-ingress-isolation- Result
- An ingress-isolating NetworkPolicy for the Pods the Service selects
Cilium
- Action
cilium-network-policy/v1- Outcome
network.declare-ingress-isolation- Result
- A pod label and an ingress-isolating CiliumNetworkPolicy
Istio
- Action
peer-authentication/v1- Outcome
transport.require-mutual-tls- Result
- A pod label and a STRICT PeerAuthentication
A third outcome, gateway.prevent-route-delivery, can be suggested, but no shipped plugin implements it yet. The AWS plugin has no actions either, and a finding from a rule match carries no outcomes, so it has nothing to apply.
Preparation runs a dry run for every intended write. Execution re-reads the target, writes, and attempts to roll back a new pod label if the policy write fails. Read the remediation docs.
Where it stops today.
It is v0. These are the edges, stated plainly.
- Kubernetes and AWS EC2 networking only. The graph and plugin contract are provider-neutral; other environments will arrive as plugins.
- The AWS plugin is early and read-only. It models instances, network interfaces, and security groups, not routing, gateways, network ACLs, or IAM, so a public address or an open rule is not treated as proof of exposure.
- The graph models network and policy. Permissions, reachability from network policy, and secret access are not edges yet.
- Detection works on batches of graph changes, so it is near real time, not instant. A telemetry rule that watches for a new operation or outcome on a pair that already communicates waits for the next unrelated graph change.
- On first start, or after a restart that lags, it rebases to the current state instead of analyzing what changed while it was down.
- The HTTP API and UI have no authentication. Approval state lives in memory, mutations are served only on a loopback listener, and one process runs per database.
- The Hubble flow and agentgateway access-log normalizers exist, but no transport feeds them yet.
- Verification confirms the declared policy and label are present. It does not prove the controller accepted them or that traffic is blocked.
Run it yourself.
One binary with the explorer built in. It needs PostgreSQL and something to read: a Kubernetes context, AWS credentials, or both. The model is optional.
Needs Go, Node.js with pnpm, Docker, and kubectl.
No cluster? The demo runs the real UI against fake data in your browser. Try the demo
export POSTGRES_PASSWORD='development-only-password'make postgres-upexport AGENTSHIELD_DATABASE_URL="postgres://agentshield:${POSTGRES_PASSWORD}@127.0.0.1:5432/agentshield?sslmode=disable"make runOpen http://127.0.0.1:8088. For findings, point it at a model and turn on agentic investigation, then run make run again.
export AGENTSHIELD_MODEL_ENDPOINT='https://api.openai.com'export OPENAI_API_KEY='<your key>'export AGENTSHIELD_AGENTIC_INVESTIGATION=true