intended
Python SDK for Intended — the intent verification runtime for autonomous agents.
Evaluate every AI agent action against your organization's policies before execution. Fail-closed by default: if the authority service is unreachable, all actions are denied.
Installation
pip install intended
Quick Start
Synchronous
from intended import IntendedSdk
client = IntendedSdk(api_key="intended_live_...", tenant_id="tenant-a")
# If the action is escalated, this waits (up to 10 minutes) for a human decision.
decision = client.authorize("deploy to production", actor="ci-agent", system="github")
if decision.allowed:
print(f"Approved — token: {decision.token}")
elif decision.escalated:
print(f"Still awaiting human approval: intent {decision.intent_id}")
else:
print(f"Denied: {decision.reason}")
Asynchronous
from intended.client import AsyncIntendedClient
async with AsyncIntendedClient(api_key="intended_live_...", tenant_id="tenant-a") as client:
decision = await client.authorize("scale cluster to 100 nodes", actor="auto-scaler")
if decision.allowed:
await scale_cluster(100)
MCP Integration
Wrap any MCP tool function with Intended authority checks:
from intended.mcp import IntendedMCPMiddleware
middleware = IntendedMCPMiddleware(api_key="intended_live_...", tenant_id="tenant-a")
@middleware.protect
def execute_sql(query: str) -> str:
# Only runs if Intended approves
return db.execute(query)
# Or check manually
decision = middleware.check("execute_sql", {"query": "DROP TABLE users"})
print(decision.decision) # "DENY"
LangChain Integration
Guard any LangChain tool with Intended:
from intended.langchain import IntendedToolGuard
from langchain.tools import ShellTool
guard = IntendedToolGuard(api_key="intended_live_...", tenant_id="tenant-a")
shell = ShellTool()
safe_shell = guard.wrap(shell)
# Now shell.run() checks with INTENDED before executing
agent = create_react_agent(llm, tools=[safe_shell])
Argument values and policies
The integrations send a tool call as two parts:
- the action text: the tool name and the argument names only, for example
Call MCP tool "execute_sql" with args: query(MCP) orCall LangChain tool "delete_database" with args: target(LangChain); arguments: the argument values. Intended evaluates them in memory and stores only a sha256 digest.
Argument values are never sent in the action text or in metadata. To make a
policy depend on a value, use an args.<path> condition (for example
args.target EQ staging), not a match on the action text. When you call
authorize() directly, pass tool-call values the same way:
client.authorize(
'Call tool "execute_sql" with args: query',
actor="my-agent",
arguments={"query": "select 1"},
)
PydanticAI Integration
Use as a decorator on PydanticAI tool functions:
from intended.pydantic_ai import IntendedPydanticGuard
from pydantic_ai import Agent
guard = IntendedPydanticGuard(api_key="intended_live_...", tenant_id="tenant-a")
agent = Agent('openai:gpt-4o')
@agent.tool
@guard.protect
async def deploy(env: str) -> str:
return f"Deployed to {env}"
AuthorityDecision
Every call to authorize() returns an AuthorityDecision:
| Field | Type | Description |
|---|---|---|
decision |
str |
"ALLOW", "DENY", or "ESCALATE" |
intent_id |
str |
Unique ID of the evaluated intent |
risk_score |
int |
0-100 risk assessment |
confidence |
float |
Confidence in the risk assessment |
reason |
str |
Human-readable explanation |
token |
str | None |
Authority token for approved actions |
escalation_id |
str | None |
ID for tracking escalated actions |
expired |
bool |
True when an escalation expired before an approver decided (decision is "DENY") |
outcome |
DecisionOutcome | None |
The wait result, when authorize() waited on an escalation |
Convenience properties: decision.allowed, decision.denied, decision.escalated.
Escalations: what your agent gets back
When a policy escalates an action to a human approver, authorize() waits for
the approver's decision by default and returns the final answer:
| What happened | authorize() returns |
|---|---|
| Approver approved | decision="ALLOW", allowed=True, token set (the single-use token minted at approval), same intent_id |
| Approver rejected, or the escalation was canceled | decision="DENY", reason from the approver |
| No approver decided before the escalation expired | decision="DENY", expired=True |
Still undecided after wait_timeout_seconds |
decision="ESCALATE", reason says it is still awaiting approval, with intent_id and escalation_id |
Options on authorize() (sync and async):
on_escalate="wait"(default) or"return"."return"returns theESCALATEdecision immediately, as versions before 0.3.0 did.wait_timeout_seconds=600(10 minutes) bounds the wait.
To keep waiting later, or to wait on an intent from another process, call
wait_for_decision:
outcome = client.wait_for_decision(decision.intent_id, timeout_seconds=600)
if outcome.allowed:
run_action(token=outcome.token)
elif outcome.pending:
... # still undecided; outcome.timed_out is True
else:
print(outcome.status, outcome.reason) # DENY or EXPIRED
wait_for_decision long-polls GET /v1/decisions/{intent_id}/wait (needs the
intent:read scope) and returns a DecisionOutcome with status (ALLOW,
DENY, PENDING or EXPIRED), reason, decided_by, decided_at,
token, token_expires_at, receipt_url, escalation_id, expires_at,
timed_out and error. It retries rate limits (429), server errors and
network errors with backoff until the deadline, and stops at once on 401, 403
or 404 with a DENY result whose error names the problem.
Treat EXPIRED exactly like DENY (outcome.denied is True for both). Only
ALLOW permits the action; the SDK never reports allowed for anything else.
The framework guards (IntendedMCPMiddleware, IntendedToolGuard,
IntendedOpenAIGuard, IntendedPydanticGuard) wait the same way: an approved
tool call runs; anything else raises, and a call that is still pending says it
is awaiting human approval in Intended and gives the intent id. Each guard
accepts on_escalate and wait_timeout_seconds. The async guards
(PydanticAI, async LangChain tools) wait in a worker thread so the event loop
is not blocked.
Business Intent APIs
The Python SDK also exposes the business-intent compiler and runtime feedback surfaces:
from intended import IntendedClient
client = IntendedSdk(api_key="intended_live_...", tenant_id="tenant-a", api_url="http://localhost:3101")
compiled = client.compile_business_intent_and_plan(
"Shift the nightly ETL window to 3am",
workflow_family="enterprise-operations",
target_system="airflow",
metadata={"environment": "production"},
)
print(compiled["prediction"]["canonicalIntent"])
print(compiled["intentObject"]["process"]["stages"])
object_evals = client.get_business_intent_object_evals()
print(object_evals["report"]["summary"]["passedCases"])
feedback = client.capture_business_intent_feedback(
input_text="Shift the nightly ETL window to 3am",
predicted_intent="ops.batch.schedule",
workflow_family="enterprise-operations",
outcome="accepted",
confidence=0.93,
target_system="airflow",
execution_status="succeeded",
verification_status="verified",
)
evals = client.get_business_intent_feedback_evals(workflow_family="enterprise-operations")
print(evals["evals"]["verification"]["verificationRate"])
summary = client.get_business_intent_feedback_summary(workflow_family="enterprise-operations")
print(summary["summary"]["approvalPredictionHits"])
report = client.get_business_intent_feedback_report(workflow_family="enterprise-operations")
print(report["report"]["metadataDrift"])
Available helper methods:
compile_business_intent()compile_business_intent_and_plan()capture_business_intent_feedback()get_business_intent_object_evals()get_business_intent_feedback_summary()get_business_intent_feedback_report()get_business_intent_feedback_evals()get_business_intent_feedback_backlog()review_business_intent_feedback()promote_business_intent_feedback()
Example review and promote flow:
backlog = client.get_business_intent_feedback_backlog(workflow_family="enterprise-operations")
for record in backlog["backlog"]:
client.review_business_intent_feedback(record["id"], "approved")
promotion = client.promote_business_intent_feedback("enterprise-operations")
print(promotion["snapshotVersion"])
print(promotion["snapshotPath"])
Fail-Closed Behavior
If the INTENDED API is unreachable or returns an error, the SDK returns a DENY decision with risk_score=100 and confidence=0. This ensures that network issues or misconfigurations never result in unauthorized actions.
License
Apache-2.0
Metadata
Release files for intended 0.3.0
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 |
|---|---|---|---|---|
| intended-0.3.0.tar.gz | 36.9 kB | Without provenance | Details for intended-0.3.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| intended-0.3.0-py3-none-any.whl | Python 3 | none | any | Details for intended-0.3.0-py3-none-any.whl |
Total release size: 65.2 kB
Release files / intended-0.3.0.tar.gz
| Download URL | intended-0.3.0.tar.gz |
|---|---|
| Size | 36.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
86e58f38419b324c959a320217242a4f836e704acf334a67529b0d92ae1895d6
|
|
BLAKE2b-256 checksum How to use checksums |
8dc37aa09d1d93c96d61b5ef6838184de534ad8dcb67046c06ee3f7aa28e9a28
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Release files / intended-0.3.0-py3-none-any.whl
| Download URL | intended-0.3.0-py3-none-any.whl |
|---|---|
| Size | 28.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2d51b12ac49527f91627e083c37897fa2b771fc9e758143a4e88d1c97b3fd522
|
|
BLAKE2b-256 checksum How to use checksums |
1077c627a4e017397c05e672543b893b8e18812c62285b7620623c352a6832e1
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Security for intended 0.3.0
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.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A 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.