SDK for Permiso Custom Hooks API — send hook events with automatic run handling
Project description
permiso-custom-hooks-sdk
Python SDK for the Permiso Custom Hooks API. Send hook events from your application with automatic run handling: a run_id is generated when the client is constructed and sent on every request so events are correlated in the Agent Transaction Dashboard. Call end_run() to close out a run and rotate to a fresh run_id after a successful stop request.
Install
pip install permiso-custom-hooks-sdk
Quick start
from permiso_custom_hooks import PermisoCustomHooksClient, PermisoCustomHooksConfig
config = PermisoCustomHooksConfig(api_key="your-api-secret")
client = PermisoCustomHooksClient(config)
print(client.get_run_id()) # run_id generated in the constructor
client.send_event("user_prompt", {"source": "user", "type": "text", "text": "Hello World"})
# Close out this run: sends a "stop" event, then rotates to a new run_id after success
client.end_run()
# Subsequent calls use the new run_id automatically
client.send_event(
"web_fetch",
{
"source": "agent",
"type": "tool_use",
"name": "WebFetch",
"toolUseId": "toolu_01abc",
"input": {"url": "https://example.com"},
},
)
Sub-agents (parent and child runs)
from permiso_custom_hooks import (
PermisoAgentContext,
PermisoCustomHooksClient,
PermisoCustomHooksConfig,
)
parent = PermisoCustomHooksClient(PermisoCustomHooksConfig(api_key="your-api-secret"))
parent.send_event("user_prompt", {"source": "user", "type": "text", "text": "Hello"})
parent_run_id = parent.get_run_id()
sub = PermisoCustomHooksClient(
PermisoCustomHooksConfig(
api_key="your-api-secret",
parent_run_id=parent_run_id,
agent=PermisoAgentContext(name="ResearchSubAgent", id="sub-1"),
)
)
sub.send_event("user_prompt", {"source": "user", "type": "text", "text": "Dig into details"})
Fire-and-forget events
When you do not need to wait for the HTTP request to finish (and do not need the response dict), use send_event_background(...) instead of send_event(...). The SDK performs the same POST on a daemon background thread and returns immediately.
client.send_event_background("my_metric", {"kind": "latency_ms", "value": 42})
Tradeoffs:
- No return value and no way to observe failures on the calling thread. Errors are never raised to the caller from
send_event_background, even whenraise_on_errorisTrue(failures are handled inside the worker thread only). - Uses one OS thread per call; for very high throughput, prefer batching or a dedicated queue/worker instead.
Request body shape
Every request POSTs to {base_url}/hooks with a JSON body shaped like this:
{
"hookEvent": "my_custom_event",
"runId": "b1f0c3d4-....-uuid",
"parentRunId": "optional-parent-run-uuid",
"bourneVersion": "v2",
"sessionId": "optional-if-set",
"user": { "email": "jane@example.com", "id": "user-123", "name": "Jane" },
"agent": {
"systemPrompt": "optional-system-instructions",
"name": "optional-agent-name",
"id": "optional-custom-agent-id"
},
"event": { }
}
hookEvent— the event name passed tosend_eventorsend_event_background.runId— the current run ID, at the top level of the body.parentRunId— (optional) included when the client is configured withparent_run_id(same level asrunId).bourneVersion— always"v2"; set by the SDK on every request.sessionId— (optional) included only if configured via thesession_idoption orset_session_id.user— (optional) end-user metadata; any subset ofemail,id, andname.agent— (optional) included when at least one ofsystemPrompt,name, oridis set; sent on every event (no separatesystem_prompthook).event— the payload for this hook; see Event payload (event) below. When you omitdatainsend_event, the SDK sends"event": {}.
Event payload (event)
The backend expects event to match one of the shapes below. For source "user" and "agent", type selects the variant. For source "stop", there is no type field.
On every variant, these fields are optional unless noted otherwise:
| Field | Description |
|---|---|
eventId |
Stable id for this event (string). |
timestamp |
Epoch milliseconds (number) or ISO-8601 string. |
Blob objects (for image / document events):
| Field | Required | Description |
|---|---|---|
source |
yes | Origin label for the blob (string). |
data |
yes | Payload (e.g. base64). |
mediaType |
no | MIME type (string). |
Agent-only optional fields (allowed only when source is "agent"; omit on source "user"):
model (string), temperature, maxTokens, topP, topK (numbers) — all optional.
source: "user" (content)
type |
Required fields | Optional fields |
|---|---|---|
"text" |
text |
— |
"thinking" |
thinking |
thinkingBudget, signature |
"tool_use" |
name, toolUseId |
input (any JSON) |
"tool_result" |
toolUseId |
content, isError |
"image" |
image (blob) |
— |
"document" |
document (blob) |
— |
source: "agent" (content)
Same rows as for "user", plus the agent-only optional fields above on the same dict.
source: "stop" (run end)
No type property.
| Field | Required | Description |
|---|---|---|
source |
yes | Must be "stop". |
stopReason |
no | One of: "end_turn", "max_tokens", "stop_sequence", "tool_use", "content_filter". The client's end_run(stop_reason=...) defaults to "end_turn". |
usage |
no | Token usage object (see below). |
usage when present:
{
"inputTokens": 0,
"outputTokens": 0,
"totalTokens": 0,
"cacheReadTokens": 0,
"cacheWriteTokens": 0,
"cost": {
"inputTokensCost": 0,
"outputTokensCost": 0,
"cacheReadTokensCost": 0,
"cacheWriteTokensCost": 0,
"currency": "USD"
}
}
cacheReadTokens, cacheWriteTokens, and cost are optional; when cost is set, all of its fields are required as shown.
Do not send properties that are not listed for a variant if your integration is validated strictly (unknown keys may be rejected).
Configuration
| Option | Description |
|---|---|
api_key |
Your Permiso agent API secret (from the A2M Keys tab in the dashboard). |
base_url |
(Optional.) Base URL of the Permiso API (without /hooks). The client POSTs to {base_url}/hooks. Defaults to https://alb.permiso.io. |
parent_run_id |
(Optional.) Parent run UUID. Sent as top-level parentRunId on every request next to runId (e.g. sub-agents). |
agent |
(Optional.) PermisoAgentContext with optional system_prompt, name, and id (agent id, not end-user id). Merged with system_prompt when that field is not None. |
system_prompt |
(Optional.) After agent is applied, sets or overrides agent.systemPrompt on each request. |
session_id |
(Optional.) Session identifier. When set, it is attached as a top-level sessionId field on every request. |
user |
(Optional.) PermisoUser instance (fields: email, id, name) attached as a top-level user object on every request. |
raise_on_error |
(Optional.) Defaults to False. When False, send_event and end_run return {} on failure instead of raising; a failed end_run does not rotate run_id. When True, failures raise PermisoCustomHooksError. |
system_prompt, session_id, user, and agent fields can be updated after construction via set_system_prompt, set_session_id, set_user, and set_agent.
Run lifecycle
- Construction — A
run_id(UUID) is generated in the constructor. Access it viaget_run_id(). - Sending events — Every call to
send_event(...)orsend_event_background(...)includes the currentrun_idat the top level of the body, so all events are tied to the same run. - Ending a run — Call
end_run()to send astopevent for the current run. After the request succeeds, the client rotates to a newrun_id. If the stop request fails andraise_on_errorisFalse(the default),end_runreturns{}and keeps the samerun_id.
Run state is kept in memory only. If your process restarts, a new run_id is generated automatically when the next client is constructed.
API
PermisoCustomHooksClient
__init__(config: PermisoCustomHooksConfig)— Creates a client fromapi_keyand optionalbase_url,parent_run_id,agent,system_prompt,session_id,user, andraise_on_error. Generates an initialrun_id.send_event(event_name: str, data: dict | None = None) -> dict— Sends a hook event. POSTs JSON includinghookEvent,runId,event,bourneVersion, and when configuredparentRunId,sessionId,user, andagent. On success returns the API response dict. Whenraise_on_errorisFalse, failures return{}instead of raising.send_event_background(event_name: str, data: dict | None = None) -> None— Same request assend_event, but runs on a background thread and returns immediately. Does not return the API response; errors are never raised on the calling thread (see Fire-and-forget events).end_run(stop_reason: str = "end_turn") -> dict— Sends astophook withevent{"source": "stop", "stopReason": <stop_reason>}, then rotates to a newrun_idafter a successful request. Whenraise_on_errorisFalse, a failed stop returns{}and does not rotaterun_id.get_run_id() -> str— Returns the current run ID (useful for logging, correlating logs, or passing asparent_run_idfor a child client).set_system_prompt(prompt: str | None) -> None— Sets or clears the system prompt in the top-levelagentobject on every subsequent request.set_agent(*, system_prompt=..., name=..., id=...)— Merges agent fields; omit a keyword to leave that field unchanged, or passNoneto clear it from the outboundagentpayload. (Uses keyword-only arguments.)set_session_id(session_id: str | None) -> None— Sets (or clears) the session id attached as a top-levelsessionIdon every subsequent request.set_user(user: PermisoUser) -> None— Merges partial end-user metadata into the client's user state.
PermisoUser
Dataclass describing optional user metadata: email, id, name (all str | None). Instances are passed to the user config option and to set_user.
PermisoAgentContext
Dataclass with optional system_prompt, name, and id (agent identity; JSON keys are camelCase: systemPrompt, name, id). Used for the agent config option and merged with top-level system_prompt on construction.
PermisoCustomHooksError
Raised when raise_on_error is True and the API returns a non-2xx or the request fails. Attributes:
message— Error message.status— HTTP status code (optional).body— Response body (optional).
When raise_on_error is True, the client does not rotate its run_id after a failed send_event or failed end_run, so you can retry the same run. When raise_on_error is False, failures return {} and run_id is unchanged except after a successful end_run.
Examples
Smoke-test against the API. Put PERMISO_API_KEY=... in a .env file at custom-hooks-sdk-py/.env, the repository root, or your current working directory (first file found is used). If python-dotenv is installed it is used; otherwise the script parses simple KEY=VALUE lines without extra dependencies.
cd custom-hooks-sdk-py
# optional: pip install python-dotenv
# Add PERMISO_API_KEY=... to .env
python examples/send_test_event.py
Publishing
This package can be published to PyPI (public) or a private index. If using a private index, configure pip (e.g. pip.conf or --index-url) and authentication as needed for your organization.
Requirements
- Python >= 3.9
- No runtime dependencies for the SDK (uses standard library
urllib,json, anduuid). - The example script can use python-dotenv when installed; otherwise it reads
.envwith a small stdlib parser.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file permiso_custom_hooks_sdk-0.1.3.tar.gz.
File metadata
- Download URL: permiso_custom_hooks_sdk-0.1.3.tar.gz
- Upload date:
- Size: 9.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.21
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a7b55bdbdbe82509e784f1ad180443c8e9da85591137601dc7f6bee10c00cb06
|
|
| MD5 |
69a1e5d6c6c2a8dcb0406a7a635af5ba
|
|
| BLAKE2b-256 |
7701c561b8099abb66ef266c311a8dba2b27431c4bb5ea26384daf77cd73b26f
|
File details
Details for the file permiso_custom_hooks_sdk-0.1.3-py3-none-any.whl.
File metadata
- Download URL: permiso_custom_hooks_sdk-0.1.3-py3-none-any.whl
- Upload date:
- Size: 11.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.21
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
5f7631ea2d70f0e9a7dcbe44fcfac878a56a33151c754248fdebdf32da360e49
|
|
| MD5 |
eb6c3e3a93cdc1ee8440c5f922fd9e56
|
|
| BLAKE2b-256 |
657bfaf0eb0d15528e2b6f50a4582dbb977f7683895cafd0b8e653ebdd03dcaf
|