fullcourtdefense-agent
FullCourtDefense checks every tool call of an AI agent against your
organisation's action policy before the tool runs. This package is the
small agent hook: standard library only (no httpx, requests or openai),
so adding it to a deployed agent does not change the versions of anything the
agent already uses.
Install
Pin it exactly. Do not add --hash: pip's hash checking is all-or-nothing, so
one hashed line makes every other line of the file need a hash too, and an
agent's unpinned requirements stop building.
fullcourtdefense-agent==0.2.2
Configure
| Variable | Meaning |
|---|---|
FCD_SHIELD_ID |
The agent's shield (sh_...). Required. |
FCD_SHIELD_KEY |
The shield key. |
FCD_API_URL |
FullCourtDefense API (default https://api.fullcourtdefense.ai). |
FCD_AGENT_ID |
The agent's identity in the console (also read: FULLCOURTDEFENSE_AGENT_ID, FCD_AGENT_NAME, FCD_WORKLOAD_NAME). Unset: the name of the service the process runs as (OTEL_SERVICE_NAME, Cloud Run K_SERVICE, AWS_LAMBDA_FUNCTION_NAME, Azure CONTAINER_APP_NAME / WEBSITE_SITE_NAME, ...), else the main script, so agents never collapse into unknown-agent. Every call says which (agentIdSource: config, env or default). A name passed in code (agent_id= or agent_name= on the call or on protect()) is declared: config. |
FCD_FAIL_MODE |
What a block-mode shield does while FullCourtDefense cannot be reached. tiered (default when unset; owner decision 2026-10-27, replaces closed): only the agent's never-allowed rules (cached from the runtime bundle) stop a call; any other action runs, is logged as a warning and is reported to FullCourtDefense as "ran without a check" (ranDuringOutage) once it answers again, and a rule that needs a manager runs unattended with wouldHaveNeeded: "manager". The built-in emergency list no longer stops anything. closed: no tool runs. open: nothing is stopped. An unknown value is read as closed. |
FCD_MODE_AT_PROTECT |
monitor or block: the shield's mode when the agent was protected. Used only until the live mode has been read once, so a monitor-mode agent that starts while the API is down still never blocks. Set it to avoid the one-time mode lookup on the first call (the cloud Protect and the init wizard already set it); without it the first call waits for that lookup (at most 1.5 s), so an Enforce shield's first call is checked. A check waits at most 1.5 s before the fail mode answers (timeout=); If FullCourtDefense hangs or cannot be reached, at most one call waits up to 1.5 s; after that, calls answer instantly (from the fail mode) until we're back. A hang or a network error opens the circuit breaker at once; while it is open one background probe runs every 10 s (calls never wait on it) and a successful probe restores live checks. HTTP answers (4xx / 5xx with a body) are handled as before. |
FCD_SDK_SPOOL / FCD_SDK_SPOOL_DIR |
Records a short-lived process could not send before it ended (the end of the process waits at most 1.5 s for them) are kept in a small file in the user cache directory (~/.cache/fullcourtdefense/sdk-spool, %LOCALAPPDATA%, ~/Library/Caches) and sent by the next start with the same shield. FCD_SDK_SPOOL=off disables it; FCD_SDK_SPOOL_DIR moves it. |
FCD_AUTOGUARD |
adk: add the gate to every Google ADK Runner at start-up (below). Unset: the start-up hook does nothing. |
FCD_APPROVAL_MODE |
ADK plugin: block (default) stops a call that needs a manager's approval; wait holds it until a person decides (FCD_APPROVAL_TIMEOUT_S, default 900). |
A monitor-mode shield never blocks and never waits: the tool runs at once and the call is recorded in the background.
Use
from fullcourtdefense_agent import ToolGate, ToolCallBlocked
gate = ToolGate.from_env()
# A function, checked before every call (sync or async def):
safe_refund = gate.protect(issue_refund, operation="refund")
# Or one call:
gate.guard_tool_call("issue_refund", lambda: issue_refund("o_1", 120),
tool_args={"order": "o_1", "amount": 120}, operation="refund")
A blocked call raises ToolCallBlocked; the tool did not run.
A wrong key, a refused or unknown shield, a refused request (HTTP 401 / 403 / 404 / 400)
is not an outage. The fail mode does not apply: the tool does not run, the check is not
reported later as "ran without a check", and FullCourtDefenseApiError (a
ToolCallBlocked with status and code: FCD_UNAUTHORIZED, FCD_FORBIDDEN,
FCD_SHIELD_NOT_FOUND, FCD_BAD_REQUEST) says what to change. It is also logged once on the
fullcourtdefense logger. Any other 4xx except 408 / 429 (a 413, for example) is answered the same
way. Only a real outage (no answer, 5xx, 408, 429) is answered by the fail mode.
On a monitor shield (owner decision O4) a refused key or shield does not stop the tool: the mode
the gate knows (a live answer earlier in the process, else FCD_MODE_AT_PROTECT) is monitor, so the
tool runs and the call is still reported, and FullCourtDefense raises a "wrong or missing shield key"
alert. With no known mode the shield is treated as block.
Arguments too deep or too large to check (nested deeper than 32 levels, more than 20,000 values)
are never sent: in block mode the call is stopped (ToolCallBlocked, "too large to check safely"), in
monitor mode it runs and the record says what was not sent. Never an outage.
OpenAI Agents SDK and LangChain
from fullcourtdefense_agent.frameworks import protect_openai_agents_tool, protect_langchain_base_tool
agent = Agent(name="support", tools=[protect_openai_agents_tool(gate, lookup_customer)]) # still a FunctionTool
safe_sql = protect_langchain_base_tool(gate, sql_query) # still a BaseTool
The full SDK (pip install fullcourtdefense) does the same with fcd.protect(tool).
Google ADK
from google.adk.apps import App
from fullcourtdefense_agent.adk import FcdToolGuardPlugin
app = App(name="billing", root_agent=root_agent, plugins=[FcdToolGuardPlugin()])
A blocked call is answered to the model as
{"blocked": true, "reason": "...", "by": "FullCourtDefense"} and the tool does
not run. Gemini's built-in tools (Google Search, built-in code execution,
Vertex AI Search grounding) run inside Google and are not checked.
Start-up hook (no code change)
The wheel installs fullcourtdefense_agent_autoguard.pth into site-packages.
Python runs its one line at start-up. Without FCD_AUTOGUARD it only reads
that variable and stops. With FCD_AUTOGUARD=adk it waits for
google.adk.runners to load and adds the plugin to every Runner the process
creates, after the agent's own plugins. Security scanners flag start-up lines
in .pth files as a supply-chain technique; this one is disclosed here, pinned
by exact version and inert unless the variable is set. If the plugin cannot be
added (an unsupported ADK version), the hook logs an error on the
fullcourtdefense logger and stderr; it never stops the agent from starting.
What leaves the agent
The tool name, the operation, the agent identity, the session id and the tool arguments (the policy input; the server minimises them before storing). Never the tool's result and never the text of an error the tool raised.
Metadata
Release files for fullcourtdefense-agent 0.2.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| fullcourtdefense_agent-0.2.2.tar.gz | 89.5 kB | With provenance | Details for fullcourtdefense_agent-0.2.2.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| fullcourtdefense_agent-0.2.2-py3-none-any.whl | Python 3 | none | any | Details for fullcourtdefense_agent-0.2.2-py3-none-any.whl |
Total release size: 147.6 kB
Release files / fullcourtdefense_agent-0.2.2.tar.gz
| Download URL | fullcourtdefense_agent-0.2.2.tar.gz |
|---|---|
| Size | 89.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cfd69ea304193feded4ce21c7f3658ab37e96e36d453a7e772071967a1931ef0
|
|
BLAKE2b-256 checksum How to use checksums |
d8983d2ca5ac0f169dc06172bd41ca90c01896f85456d74fdf07160c1e0b9ded
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logRelease files / fullcourtdefense_agent-0.2.2-py3-none-any.whl
| Download URL | fullcourtdefense_agent-0.2.2-py3-none-any.whl |
|---|---|
| Size | 58.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ee452e396c5793a63cb2f803ecf46385973d667e95abb71de144b493431745da
|
|
BLAKE2b-256 checksum How to use checksums |
03fdd995270a4084aba100897faf038485f17a5d3c879daff404bda5ac06eec5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logSecurity for fullcourtdefense-agent 0.2.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for fullcourtdefense_agent-0.2.2.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 28, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.