Metadata-Version: 2.5
Name: datalayer-agent-shield
Version: 0.1.2
Summary: Datalayer Agent Shield: a runtime firewall for every agent
Project-URL: Home, https://github.com/datalayer/agent-shield
Project-URL: Documentation, https://agent-shield.datalayer.tech
Author-email: Datalayer <info@datalayer.io>
License: BSD 3-Clause License
        
        Copyright (c) 2025, Datalayer
        All rights reserved.
        
        Redistribution and use in source and binary forms, with or without
        modification, are permitted provided that the following conditions are met:
        
        1. Redistributions of source code must retain the above copyright notice, this
           list of conditions and the following disclaimer.
        
        2. Redistributions in binary form must reproduce the above copyright notice,
           this list of conditions and the following disclaimer in the documentation
           and/or other materials provided with the distribution.
        
        3. Neither the name of the copyright holder nor the names of its
           contributors may be used to endorse or promote products derived from
           this software without specific prior written permission.
        
        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
        AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
        IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
        DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
        FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
        DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
        SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
        CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
        OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
        OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
License-File: LICENSE
Keywords: ACP,AI,Agents,Datalayer,Firewall,Policy,Shield
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: System Administrators
Classifier: License :: OSI Approved :: BSD License
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.10
Requires-Dist: agent-client-protocol<0.8,>=0.7.1
Requires-Dist: pydantic>=2.0
Requires-Dist: pyyaml>=6.0
Requires-Dist: typer>=0.9
Provides-Extra: all
Requires-Dist: agentspecs>=0.0.100; extra == 'all'
Requires-Dist: websockets>=13; extra == 'all'
Provides-Extra: claude-code
Requires-Dist: agentspecs>=0.0.100; extra == 'claude-code'
Provides-Extra: codex
Requires-Dist: agentspecs>=0.0.100; extra == 'codex'
Provides-Extra: gemini-cli
Requires-Dist: agentspecs>=0.0.100; extra == 'gemini-cli'
Provides-Extra: goose
Requires-Dist: agentspecs>=0.0.100; extra == 'goose'
Provides-Extra: lint
Requires-Dist: mdformat-gfm>=0.3.5; extra == 'lint'
Requires-Dist: mdformat>0.7; extra == 'lint'
Requires-Dist: ruff; extra == 'lint'
Provides-Extra: opencode
Requires-Dist: agentspecs>=0.0.100; extra == 'opencode'
Provides-Extra: openhands
Requires-Dist: agentspecs>=0.0.100; extra == 'openhands'
Provides-Extra: register
Requires-Dist: agentspecs>=0.0.100; extra == 'register'
Provides-Extra: test
Requires-Dist: agentspecs>=0.0.100; extra == 'test'
Requires-Dist: pytest-asyncio>=0.21; extra == 'test'
Requires-Dist: pytest-timeout>=2.0; extra == 'test'
Requires-Dist: pytest>=7.0; extra == 'test'
Requires-Dist: tomli>=2; (python_version < '3.11') and extra == 'test'
Requires-Dist: websockets>=13; extra == 'test'
Provides-Extra: typing
Requires-Dist: mypy>=0.990; extra == 'typing'
Provides-Extra: websocket
Requires-Dist: websockets>=13; extra == 'websocket'
Description-Content-Type: text/markdown

<!--
  ~ Copyright (c) 2025-2026 Datalayer, Inc.
  ~
  ~ BSD 3-Clause License
-->

[![Datalayer](https://images.datalayer.io/legacy/datalayer-25.svg)](https://datalayer.ai)

[![Become a Sponsor](https://img.shields.io/static/v1?label=Become%20a%20Sponsor&message=%E2%9D%A4&logo=GitHub&style=flat&color=1ABC9C)](https://github.com/sponsors/datalayer)

# 🤖 🛡️ Agent Shield

[![PyPI - Version](https://img.shields.io/pypi/v/datalayer-agent-shield)](https://pypi.org/project/datalayer-agent-shield)
[![npm](https://img.shields.io/npm/v/%40datalayer%2Fagent-shield)](https://www.npmjs.com/package/@datalayer/agent-shield)

**A runtime firewall for every agent.** Deploy a shield with each agent. Enforce
permissions before actions execute, manage policies centrally, and retain an
audit trail.

The Shield stands between an agent and the world. Its first shippable is the
**proxy**: `agent-shield run -- <an ACP agent's command>` starts the agent as
its subprocess and stands as an [Agent Client Protocol](https://agentclientprotocol.com)
agent on its own stdio, so any ACP client — Zed, `vscode-acp`, Marimo,
Datalayer's runtimes — talks to the Shield and the Shield talks to the agent.
In between it decides:

- `session/request_permission` by the policy: *do it* is answered without the
  client, *ask me first* is forwarded with the same options and answered as
  the person chose, *leave it to me* is refused in a sentence the person sees;
- the agent's `tool_call` notifications are classed (ACP `kind` → `read`,
  `write`, `delete`, `execute`; `other` fails closed to `write`) and held to
  the session's limits; a refusal cancels the turn and says why;
- `fs/*` and `terminal/*` are answered by the Shield against the **workspace**
  — a path outside it is refused, a command runs with a trimmed environment
  — and every one is a tool row the client sees;
- `mcpServers` are rewritten so the agent gets only what the policy lends;
- every action and decision is a line of `~/.agent-shield/audit.jsonl`,
  credentials redacted, a file's content never in it.

## The first five minutes

```bash
pip install "datalayer-agent-shield[claude-code]"   # one extra per harness
agent-shield setup        # finds node, the vendor CLIs and the credentials present; installs the pinned adapter into ~/.agent-shield/harnesses/<id>@<version>; writes agent-shield.yaml beside the project
agent-shield run -- claude-agent-acp               # Claude Code behind the Shield, on this directory, with the default policy
```

Measured on 15 October 2026 from a clean prefix: `setup` took **27 s**
(the adapter `@agentclientprotocol/claude-agent-acp@0.91.0`, 325 MB, through
`npm install --prefix`, never a global `npx`); `run` initialized the adapter
and opened a session in **1.5 s**; the first decision followed the first tool
call. The whole, from `pip install` to a decision seen in the terminal, is
under a minute, five times under the five minutes of SHIELD §5.3.

`setup` asks for nothing it finds: it says which credentials are present by
name (never a value), which are missing and how to export them, and stores
no secret. It refuses, in a sentence, a credential the register forbids — a
Claude Code or ChatGPT subscription login where only an API key serves to
third parties, a Google login on Gemini's individuals tier.

Then:

```bash
agent-shield log                 # the audit, one line per action and decision
agent-shield policy show         # every line of the policy in force, and where it came from
agent-shield policy schema       # the document's JSON Schema
agent-shield harnesses           # the register: each harness, its terms, where they were read
agent-shield upgrade --check     # a newer Shield or adapter?
agent-shield upgrade             # move the adapters to the register's pinned versions
agent-shield uninstall           # previews; --yes removes the adapters and the config, keeps the audit and the policy
```

Zed, `settings.json`:

```json
"agent_servers": {
  "Claude Code behind the Shield": { "command": "agent-shield", "args": ["run", "--", "claude-agent-acp"] }
}
```

## The policy

One document per scope — `organization`, `team`, `session` — in
`agent-shield.yaml` beside the project (else `~/.agent-shield/agent-shield.yaml`),
with the schema in [`schema/policy.schema.json`](schema/policy.schema.json).
The strictest scope decides first and **the first decision wins**; a scope
that says nothing passes the action down; nothing a rule names falls to
*ask me first*, never to *do it*.

The default `setup` writes: read and search inside the workspace — do it;
edit and move inside it — do it, shown as diffs; delete, run a command, and
any path outside the workspace — ask me first; a command naming a credential,
`sudo`, a package install from the network, or `git push` — ask me first with
the command shown; a fetch — through `network.fetch`'s allow-list, which is
empty, so refused; send or buy — ask me first. Limits: 100 then 200 tool
calls, 4 then 8 terminals, 5 then 20 MiB written — a **soft** threshold asks
once, a **hard** one refuses each call that would cross it and says what would
let the session go on.

```yaml
scope: session
rules:
  - name: read inside the workspace
    decision: allow
    classes: [read]
    where: inside
  - name: git push
    decision: ask
    classes: [execute]
    commands: ['\bgit\b[^;&|]*\bpush\b']
limits:
  tool_calls: {soft: 100, hard: 200}
network:
  fetch: []
mcp:
  allow: []
```

`agent_shield.policy.decide(action, context, policies)` is pure: it answers
`allow | ask | deny`, the sentence, and the scope and rule that decided.

Three levels hold (SHIELD.md §6.3): on Datalayer the **organization's**
document comes from IAM, the **Appspec's** Rules are the team's, and the
**session's** is the default above plus what a person allows or tightens
while it runs. A lower document only tightens: `agent_shield.policy.widening`
says in sentences where it would permit more, IAM refuses a team document
with them, and the runtime refuses a session rule with them. Two more limits
hold the model: `model_tokens` (the budget) and `spend` (credits, 100 a USD),
soft then hard; `decide_budget` decides the next model request against them.
A `harnesses` section says which harnesses a scope admits and whose
credentials run them.

Managed from Datalayer (`agent_shield.saas`), the policy is pulled from IAM
at the agent's creation, kept until its `expires`, refreshed every 30 s or
when pushed, and **fails closed**: with IAM unreachable and no unexpired
copy, the harness is refused in a sentence naming IAM.

## What is built, and what is not

Built (0.1.2, SHIELD.md Phase 0 and the policy side of Phase 2): the policy
engine and its schema, the three levels with `widening`, the model limits and
`decide_budget`, the `harnesses` section, the SaaS client pulling from IAM
and failing closed; the
register read from [agentspecs](https://github.com/datalayer/agentspecs)
(`claude-code`, `codex`, `gemini-cli`, `goose`, `opencode`, `openhands`,
`fast-agent`); the stdio and WebSocket client channels on the official
`agent-client-protocol` SDK (protocol version 1); the proxy, which passes the
[ACP Test Compatibility Kit](https://github.com/agentclientprotocol/acp-tck)
in front of the test agent (`VERDICT: CONFORMANT`); the audit; the
`agent-shield` command; `@datalayer/agent-shield`'s policy types.

Not built: `agent-shield serve` (the proxy over WebSocket, SH-52);
registration of the standalone proxy with Datalayer (SH-47: the SaaS client
exists, `setup --register` does not use it yet); the OTEL export (SH-51); the
Shield report and conformance (SH-36, SH-60); the TypeScript renderers of
diffs and terminals (SH-53).

Claude Code has been run behind the proxy (`claude-agent-acp` 0.91.0:
initialize, session, modes); a turn needs a funded Anthropic API key.

## Development

```bash
pip install -e ".[test]"
pytest tests/ -q
npm install && npm test            # the TypeScript types against schema/policy.schema.json
make tck                            # the ACP TCK against the proxy (uv tool install acp-tck first)
```

## Documentation

[agent-shield.datalayer.tech](https://agent-shield.datalayer.tech)
