openbox-langgraph-sdk-python 0.1.1__tar.gz → 0.1.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/PKG-INFO +2 -2
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/config.py +4 -1
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/file_governance_hooks.py +7 -1
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/http_governance_hooks.py +32 -4
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/pyproject.toml +2 -2
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/uv.lock +5 -5
- openbox_langgraph_sdk_python-0.1.1/test-agent/.env.example +0 -9
- openbox_langgraph_sdk_python-0.1.1/test-agent/README.md +0 -27
- openbox_langgraph_sdk_python-0.1.1/test-agent/SETUP.md +0 -455
- openbox_langgraph_sdk_python-0.1.1/test-agent/agent.py +0 -172
- openbox_langgraph_sdk_python-0.1.1/test-agent/pyproject.toml +0 -16
- openbox_langgraph_sdk_python-0.1.1/test-agent/uv.lock +0 -1218
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/.github/workflows/publish.yml +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/.gitignore +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/AGENTS.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/CLAUDE.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/README.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/docs/code-standards.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/docs/codebase-summary.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/docs/project-overview-pdr.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/docs/project-roadmap.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/docs/system-architecture.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/__init__.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/client.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/db_governance_hooks.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/errors.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/hitl.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/hook_governance.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/langgraph_handler.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/otel_setup.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/span_processor.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/tracing.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/types.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/openbox_langgraph/verdict_handler.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/260320-0329-remove-span-collector/plan.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/260321-2019-port-deepagent-fixes/phase-01-remove-hitl-gates.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/260321-2019-port-deepagent-fixes/phase-02-sqlalchemy-engine.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/260321-2019-port-deepagent-fixes/phase-03-hook-hitl-retry.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/260321-2019-port-deepagent-fixes/plan.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/Explore-260330-1430-comprehensive-sdk-analysis.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/Explore-260330-2300-file-reference.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/Explore-260330-2300-integration-guide.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/code-reviewer-260323-1101-code-reuse-otel-hooks.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/code-reviewer-260323-1101-efficiency-review-otel-http-hook-spans.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/code-reviewer-260323-1101-hacky-patterns-review.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/docs-manager-260321-2153-initial-documentation.md +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/plans/reports/span-flow-visualization.html +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/tests/test_contextvars_propagation.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/tests/test_governance_changes.py +0 -0
- {openbox_langgraph_sdk_python-0.1.1 → openbox_langgraph_sdk_python-0.1.2}/tests/test_telemetry_payload.py +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: openbox-langgraph-sdk-python
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: OpenBox governance and observability SDK for LangGraph
|
|
5
5
|
License: MIT
|
|
6
6
|
Requires-Python: >=3.11
|
|
7
7
|
Requires-Dist: httpx>=0.27.0
|
|
8
|
-
Requires-Dist: langchain-core>=
|
|
8
|
+
Requires-Dist: langchain-core>=1.2.22
|
|
9
9
|
Requires-Dist: langgraph>=0.2.0
|
|
10
10
|
Requires-Dist: opentelemetry-api>=1.20.0
|
|
11
11
|
Requires-Dist: opentelemetry-instrumentation-asyncpg>=0.41b0
|
|
@@ -250,9 +250,12 @@ def initialize(
|
|
|
250
250
|
validate_url_security(api_url)
|
|
251
251
|
|
|
252
252
|
if not validate_api_key_format(api_key):
|
|
253
|
+
# Show only the structural prefix, never actual secret chars
|
|
254
|
+
parts = api_key.split("_", 2)
|
|
255
|
+
prefix_hint = "_".join(parts[:2]) + "_..." if len(parts) >= 2 else "???"
|
|
253
256
|
msg = (
|
|
254
257
|
f"Invalid API key format. Expected 'obx_live_*' or 'obx_test_*', "
|
|
255
|
-
f"got: '{
|
|
258
|
+
f"got prefix: '{prefix_hint}'"
|
|
256
259
|
)
|
|
257
260
|
raise OpenBoxAuthError(msg)
|
|
258
261
|
|
|
@@ -72,8 +72,14 @@ def _build_file_span_data(
|
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
# Only include optional fields if they have values
|
|
75
|
+
# Truncate file content to prevent oversized governance payloads
|
|
76
|
+
_MAX_FILE_DATA = 4096
|
|
75
77
|
if data is not None:
|
|
76
|
-
|
|
78
|
+
data_str = str(data)
|
|
79
|
+
if len(data_str) > _MAX_FILE_DATA:
|
|
80
|
+
result["data"] = data_str[:_MAX_FILE_DATA] + "...[truncated]"
|
|
81
|
+
else:
|
|
82
|
+
result["data"] = data_str
|
|
77
83
|
if bytes_read is not None:
|
|
78
84
|
result["bytes_read"] = bytes_read
|
|
79
85
|
if bytes_written is not None:
|
|
@@ -64,6 +64,34 @@ _TEXT_CONTENT_TYPES = (
|
|
|
64
64
|
"application/x-www-form-urlencoded",
|
|
65
65
|
)
|
|
66
66
|
|
|
67
|
+
# Headers that must be redacted before sending to governance API
|
|
68
|
+
_SENSITIVE_HEADERS = frozenset({
|
|
69
|
+
"authorization", "cookie", "set-cookie", "x-api-key",
|
|
70
|
+
"x-auth-token", "proxy-authorization", "www-authenticate",
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
# Max body size sent to governance API (bytes of string repr)
|
|
74
|
+
_MAX_BODY_CAPTURE = 8192
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _sanitize_headers(headers: dict | None) -> dict | None:
|
|
78
|
+
"""Redact sensitive headers (auth tokens, cookies) before governance."""
|
|
79
|
+
if not headers:
|
|
80
|
+
return headers
|
|
81
|
+
return {
|
|
82
|
+
k: "[REDACTED]" if k.lower() in _SENSITIVE_HEADERS else v
|
|
83
|
+
for k, v in headers.items()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _truncate_body(body: str | None) -> str | None:
|
|
88
|
+
"""Truncate body to _MAX_BODY_CAPTURE to prevent oversized payloads."""
|
|
89
|
+
if body is None:
|
|
90
|
+
return None
|
|
91
|
+
if len(body) > _MAX_BODY_CAPTURE:
|
|
92
|
+
return body[:_MAX_BODY_CAPTURE] + "...[truncated]"
|
|
93
|
+
return body
|
|
94
|
+
|
|
67
95
|
|
|
68
96
|
# ═══════════════════════════════════════════════════════════════════════════════
|
|
69
97
|
# Shared HTTP utilities
|
|
@@ -136,10 +164,10 @@ def _build_http_span_data(
|
|
|
136
164
|
# HTTP-specific root fields
|
|
137
165
|
"http_method": http_method,
|
|
138
166
|
"http_url": http_url,
|
|
139
|
-
"request_body": request_body,
|
|
140
|
-
"request_headers": request_headers,
|
|
141
|
-
"response_body": response_body,
|
|
142
|
-
"response_headers": response_headers,
|
|
167
|
+
"request_body": _truncate_body(request_body),
|
|
168
|
+
"request_headers": _sanitize_headers(request_headers),
|
|
169
|
+
"response_body": _truncate_body(response_body),
|
|
170
|
+
"response_headers": _sanitize_headers(response_headers),
|
|
143
171
|
"http_status_code": http_status_code,
|
|
144
172
|
"error": error,
|
|
145
173
|
}
|
|
@@ -4,14 +4,14 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "openbox-langgraph-sdk-python"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.2"
|
|
8
8
|
description = "OpenBox governance and observability SDK for LangGraph"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
11
11
|
requires-python = ">=3.11"
|
|
12
12
|
dependencies = [
|
|
13
13
|
"httpx>=0.27.0",
|
|
14
|
-
"langchain-core>=
|
|
14
|
+
"langchain-core>=1.2.22",
|
|
15
15
|
"langgraph>=0.2.0",
|
|
16
16
|
"opentelemetry-api>=1.20.0",
|
|
17
17
|
"opentelemetry-sdk>=1.20.0",
|
|
@@ -221,7 +221,7 @@ wheels = [
|
|
|
221
221
|
|
|
222
222
|
[[package]]
|
|
223
223
|
name = "langchain-core"
|
|
224
|
-
version = "1.2.
|
|
224
|
+
version = "1.2.23"
|
|
225
225
|
source = { registry = "https://pypi.org/simple" }
|
|
226
226
|
dependencies = [
|
|
227
227
|
{ name = "jsonpatch" },
|
|
@@ -233,9 +233,9 @@ dependencies = [
|
|
|
233
233
|
{ name = "typing-extensions" },
|
|
234
234
|
{ name = "uuid-utils" },
|
|
235
235
|
]
|
|
236
|
-
sdist = { url = "https://files.pythonhosted.org/packages/
|
|
236
|
+
sdist = { url = "https://files.pythonhosted.org/packages/1d/47/a5f21b651e9cbd7a26c3e5809336d10a0be94ef7bdf6bea47f2ad9fff1a8/langchain_core-1.2.23.tar.gz", hash = "sha256:fdec64f90cfea25317e88d9803c44684af1f4e30dec4e58320dd7393bb0f0785", size = 841684, upload-time = "2026-03-27T23:28:14.6Z" }
|
|
237
237
|
wheels = [
|
|
238
|
-
{ url = "https://files.pythonhosted.org/packages/
|
|
238
|
+
{ url = "https://files.pythonhosted.org/packages/9b/5a/6ff2d76618e4cac531ea51d4ef44c6add36575a84c3f0f8877aee68c951a/langchain_core-1.2.23-py3-none-any.whl", hash = "sha256:70866dfc5275b7840ce272ff70f0ff216c8666ab25dc1b41964a4ef58c02a3ff", size = 506709, upload-time = "2026-03-27T23:28:13.372Z" },
|
|
239
239
|
]
|
|
240
240
|
|
|
241
241
|
[[package]]
|
|
@@ -437,7 +437,7 @@ wheels = [
|
|
|
437
437
|
|
|
438
438
|
[[package]]
|
|
439
439
|
name = "openbox-langgraph-sdk-python"
|
|
440
|
-
version = "0.1.
|
|
440
|
+
version = "0.1.1"
|
|
441
441
|
source = { editable = "." }
|
|
442
442
|
dependencies = [
|
|
443
443
|
{ name = "httpx" },
|
|
@@ -478,7 +478,7 @@ otel = [
|
|
|
478
478
|
[package.metadata]
|
|
479
479
|
requires-dist = [
|
|
480
480
|
{ name = "httpx", specifier = ">=0.27.0" },
|
|
481
|
-
{ name = "langchain-core", specifier = ">=
|
|
481
|
+
{ name = "langchain-core", specifier = ">=1.2.22" },
|
|
482
482
|
{ name = "langgraph", specifier = ">=0.2.0" },
|
|
483
483
|
{ name = "mypy", marker = "extra == 'dev'", specifier = ">=1.10.0" },
|
|
484
484
|
{ name = "opentelemetry-api", specifier = ">=1.20.0" },
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# LangGraph Test Agent (OpenBox)
|
|
2
|
-
|
|
3
|
-
A minimal LangGraph agent for validating OpenBox governance using `openbox-langgraph-sdk`.
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
1. Copy env file:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
cp .env.example .env
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
2. Set:
|
|
14
|
-
|
|
15
|
-
- `OPENBOX_URL`
|
|
16
|
-
- `OPENBOX_API_KEY`
|
|
17
|
-
- `OPENAI_API_KEY`
|
|
18
|
-
|
|
19
|
-
3. Run:
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
uv run python agent.py
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
## Debugging
|
|
26
|
-
|
|
27
|
-
- Set `OPENBOX_DEBUG=1` to print all governance requests/responses and raw LangGraph events.
|
|
@@ -1,455 +0,0 @@
|
|
|
1
|
-
# LangGraph Test Agent — OpenBox Governance Setup Guide
|
|
2
|
-
|
|
3
|
-
Field-by-field instructions for configuring Guardrails, Policies, and Behavior Rules for the LangGraph test agent.
|
|
4
|
-
|
|
5
|
-
**Agent name (must match exactly in OpenBox dashboard):** `LangGraphTestAgent`
|
|
6
|
-
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
## Navigate to your Agent
|
|
10
|
-
|
|
11
|
-
1. Log in at `https://core.openbox.ai`
|
|
12
|
-
2. Go to **Agents** → click your agent (`LangGraphTestAgent`)
|
|
13
|
-
3. Click the **Authorize** tab → three sub-tabs: **Guardrails**, **Policies**, **Behavior**
|
|
14
|
-
|
|
15
|
-
---
|
|
16
|
-
|
|
17
|
-
## 1. Guardrails
|
|
18
|
-
|
|
19
|
-
**Path:** Authorize → Guardrails → **+ Add Guardrail**
|
|
20
|
-
|
|
21
|
-
The form has four sections: Basic Info, Type Selection, Configuration Settings, Advanced Settings. There is also a live **Test** panel on the right.
|
|
22
|
-
|
|
23
|
-
### Available types
|
|
24
|
-
|
|
25
|
-
| UI label | `guardrail_type` | What it does |
|
|
26
|
-
|---|---|---|
|
|
27
|
-
| **PII Detection** | `1` | Detects personal data entities in inputs/outputs |
|
|
28
|
-
| **Content Filtering** | `2` | NSFW / sexually explicit content |
|
|
29
|
-
| **Toxicity** | `3` | Hate speech, abusive language, threats |
|
|
30
|
-
| **Ban Words** | `4` | Exact + fuzzy word-list blocking (Levenshtein) |
|
|
31
|
-
|
|
32
|
-
---
|
|
33
|
-
|
|
34
|
-
### Guardrail 1 — Toxicity Filter
|
|
35
|
-
|
|
36
|
-
**Trigger with:** `"You are completely useless, you idiot"`
|
|
37
|
-
|
|
38
|
-
#### Basic Info
|
|
39
|
-
|
|
40
|
-
| Field | Value |
|
|
41
|
-
|---|---|
|
|
42
|
-
| **Name** | `Toxicity Filter` |
|
|
43
|
-
| **Description** | `Block toxic or abusive language in user queries` |
|
|
44
|
-
| **Processing Stage** | `Pre-processing` |
|
|
45
|
-
|
|
46
|
-
#### Type Selection
|
|
47
|
-
|
|
48
|
-
Click the **Toxicity** card.
|
|
49
|
-
|
|
50
|
-
#### Configuration Settings
|
|
51
|
-
|
|
52
|
-
| Field | Value | Notes |
|
|
53
|
-
|---|---|---|
|
|
54
|
-
| **Block on Violation** *(checkbox)* | ✅ checked | |
|
|
55
|
-
| **Log Violations** *(checkbox)* | ✅ checked | |
|
|
56
|
-
| **Activity Type** *(text input)* | `agent_validatePrompt` | |
|
|
57
|
-
| **Fields to Check** *(tag input)* | `input.*.prompt` | |
|
|
58
|
-
|
|
59
|
-
#### Advanced Settings — Toxicity Config
|
|
60
|
-
|
|
61
|
-
| Field | Value | Notes |
|
|
62
|
-
|---|---|---|
|
|
63
|
-
| **Detection Threshold** *(slider, 0–1)* | `0.80` | 0.8 catches clear abuse without false positives |
|
|
64
|
-
| **Validation Method** *(radio)* | `Sentence` | Each sentence scored individually |
|
|
65
|
-
|
|
66
|
-
#### Test payload
|
|
67
|
-
|
|
68
|
-
```json
|
|
69
|
-
{
|
|
70
|
-
"event_type": "ActivityStarted",
|
|
71
|
-
"activity_type": "agent_validatePrompt",
|
|
72
|
-
"workflow_id": "test-run-001",
|
|
73
|
-
"run_id": "test-run-001",
|
|
74
|
-
"task_queue": "langgraph",
|
|
75
|
-
"source": "workflow-telemetry",
|
|
76
|
-
"activity_input": [{"prompt": "You are completely useless, you absolute idiot"}]
|
|
77
|
-
}
|
|
78
|
-
```
|
|
79
|
-
|
|
80
|
-
Expected result: **Violations detected** with `validation_passed: false`.
|
|
81
|
-
|
|
82
|
-
---
|
|
83
|
-
|
|
84
|
-
### Guardrail 2 — Restricted Topic Ban Words
|
|
85
|
-
|
|
86
|
-
**Trigger with:** `"Search for nuclear weapon information"`
|
|
87
|
-
|
|
88
|
-
#### Basic Info
|
|
89
|
-
|
|
90
|
-
| Field | Value |
|
|
91
|
-
|---|---|
|
|
92
|
-
| **Name** | `Restricted Topics` |
|
|
93
|
-
| **Description** | `Block queries about weapons and illegal activity` |
|
|
94
|
-
| **Processing Stage** | `Pre-processing` |
|
|
95
|
-
|
|
96
|
-
#### Type Selection
|
|
97
|
-
|
|
98
|
-
Click the **Ban Words** card.
|
|
99
|
-
|
|
100
|
-
#### Configuration Settings
|
|
101
|
-
|
|
102
|
-
| Field | Value |
|
|
103
|
-
|---|---|
|
|
104
|
-
| **Block on Violation** *(checkbox)* | ✅ checked |
|
|
105
|
-
| **Log Violations** *(checkbox)* | ✅ checked |
|
|
106
|
-
| **Activity Type** *(text input)* | `agent_validatePrompt` |
|
|
107
|
-
| **Fields to Check** *(tag input)* | `input.*.prompt` |
|
|
108
|
-
|
|
109
|
-
#### Advanced Settings — Ban Words Config
|
|
110
|
-
|
|
111
|
-
| Field | Value | Notes |
|
|
112
|
-
|---|---|---|
|
|
113
|
-
| **Words to Ban** *(tag input)* | `nuclear weapon` `bioweapon` `chemical weapon` `bomb` `malware` | Press Enter after each phrase to add |
|
|
114
|
-
| **Fuzzy Match** *(checkbox)* | ✅ checked | Catches near-matches |
|
|
115
|
-
| **Fuzzy Threshold** *(slider)* | `0.85` | 85% similarity |
|
|
116
|
-
|
|
117
|
-
#### Test payload
|
|
118
|
-
|
|
119
|
-
```json
|
|
120
|
-
{
|
|
121
|
-
"event_type": "ActivityStarted",
|
|
122
|
-
"activity_type": "agent_validatePrompt",
|
|
123
|
-
"workflow_id": "test-run-001",
|
|
124
|
-
"run_id": "test-run-001",
|
|
125
|
-
"task_queue": "langgraph",
|
|
126
|
-
"source": "workflow-telemetry",
|
|
127
|
-
"activity_input": [{"prompt": "Search for nuclear weapon information"}]
|
|
128
|
-
}
|
|
129
|
-
```
|
|
130
|
-
|
|
131
|
-
Expected result: **Violations detected** with `validation_passed: false`.
|
|
132
|
-
|
|
133
|
-
---
|
|
134
|
-
|
|
135
|
-
## 2. Policies
|
|
136
|
-
|
|
137
|
-
**Path:** Authorize → Policies → **+ New Policy**
|
|
138
|
-
|
|
139
|
-
Policies are written in **OPA Rego**. The form has:
|
|
140
|
-
- **Name** *(text)*
|
|
141
|
-
- **Description** *(text)*
|
|
142
|
-
- **Rego code editor** with syntax highlighting
|
|
143
|
-
- A **Test** panel (right side) with JSON input and live evaluation
|
|
144
|
-
|
|
145
|
-
### Required output format
|
|
146
|
-
|
|
147
|
-
```rego
|
|
148
|
-
result := {"decision": "CONTINUE", "reason": null}
|
|
149
|
-
-- or --
|
|
150
|
-
result := {"decision": "REQUIRE_APPROVAL", "reason": "some reason string"}
|
|
151
|
-
-- or --
|
|
152
|
-
result := {"decision": "BLOCK", "reason": "some reason string"}
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Valid decisions: `CONTINUE`, `REQUIRE_APPROVAL`, `BLOCK`.
|
|
156
|
-
|
|
157
|
-
---
|
|
158
|
-
|
|
159
|
-
### Single policy file to deploy
|
|
160
|
-
|
|
161
|
-
**Name:** `LangGraph Test Agent Policy`
|
|
162
|
-
|
|
163
|
-
Covers:
|
|
164
|
-
- **`search_web` for restricted terms** → `BLOCK`
|
|
165
|
-
- **`write_report` with `confidential` classification** → `REQUIRE_APPROVAL`
|
|
166
|
-
- Everything else → `CONTINUE`
|
|
167
|
-
|
|
168
|
-
```rego
|
|
169
|
-
package org.openboxai.policy
|
|
170
|
-
|
|
171
|
-
import future.keywords.if
|
|
172
|
-
import future.keywords.in
|
|
173
|
-
|
|
174
|
-
default result = {"decision": "CONTINUE", "reason": null}
|
|
175
|
-
|
|
176
|
-
# Restricted search topics — BLOCK immediately
|
|
177
|
-
restricted_terms := {"nuclear weapon", "bioweapon", "chemical weapon", "bomb", "malware"}
|
|
178
|
-
|
|
179
|
-
result := {"decision": "BLOCK", "reason": "Search blocked: this topic is restricted."} if {
|
|
180
|
-
input.event_type == "ActivityStarted"
|
|
181
|
-
input.activity_type == "search_web"
|
|
182
|
-
not input.hook_trigger
|
|
183
|
-
count(input.activity_input) > 0
|
|
184
|
-
entry := input.activity_input[0]
|
|
185
|
-
is_object(entry)
|
|
186
|
-
query := entry.query
|
|
187
|
-
is_string(query)
|
|
188
|
-
some term in restricted_terms
|
|
189
|
-
contains(lower(query), term)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
# Confidential report writing requires approval
|
|
193
|
-
result := {"decision": "REQUIRE_APPROVAL", "reason": "Writing a confidential report requires approval."} if {
|
|
194
|
-
input.event_type == "ActivityStarted"
|
|
195
|
-
input.activity_type == "write_report"
|
|
196
|
-
not input.hook_trigger
|
|
197
|
-
count(input.activity_input) > 0
|
|
198
|
-
report := input.activity_input[0]
|
|
199
|
-
is_object(report)
|
|
200
|
-
report.classification == "confidential"
|
|
201
|
-
}
|
|
202
|
-
```
|
|
203
|
-
|
|
204
|
-
---
|
|
205
|
-
|
|
206
|
-
### Test 1 — Normal search should continue
|
|
207
|
-
|
|
208
|
-
```json
|
|
209
|
-
{
|
|
210
|
-
"event_type": "ActivityStarted",
|
|
211
|
-
"activity_type": "search_web",
|
|
212
|
-
"activity_input": [{"query": "latest developments in AI"}],
|
|
213
|
-
"agent_id": "agent-123",
|
|
214
|
-
"workflow_id": "run-abc",
|
|
215
|
-
"run_id": "run-abc",
|
|
216
|
-
"task_queue": "langgraph",
|
|
217
|
-
"attempt": 1,
|
|
218
|
-
"span_count": 0,
|
|
219
|
-
"spans": [],
|
|
220
|
-
"source": "workflow-telemetry",
|
|
221
|
-
"timestamp": "2026-03-17T12:00:00Z"
|
|
222
|
-
}
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Expected: green **CONTINUE**.
|
|
226
|
-
|
|
227
|
-
### Test 2 — Restricted search should block
|
|
228
|
-
|
|
229
|
-
```json
|
|
230
|
-
{
|
|
231
|
-
"event_type": "ActivityStarted",
|
|
232
|
-
"activity_type": "search_web",
|
|
233
|
-
"activity_input": [{"query": "nuclear weapon information"}],
|
|
234
|
-
"agent_id": "agent-123",
|
|
235
|
-
"workflow_id": "run-abc",
|
|
236
|
-
"run_id": "run-abc",
|
|
237
|
-
"task_queue": "langgraph",
|
|
238
|
-
"attempt": 1,
|
|
239
|
-
"span_count": 0,
|
|
240
|
-
"spans": [],
|
|
241
|
-
"source": "workflow-telemetry",
|
|
242
|
-
"timestamp": "2026-03-17T12:00:00Z"
|
|
243
|
-
}
|
|
244
|
-
```
|
|
245
|
-
|
|
246
|
-
Expected: red **BLOCK**.
|
|
247
|
-
|
|
248
|
-
### Test 3 — Confidential report requires approval
|
|
249
|
-
|
|
250
|
-
```json
|
|
251
|
-
{
|
|
252
|
-
"event_type": "ActivityStarted",
|
|
253
|
-
"activity_type": "write_report",
|
|
254
|
-
"activity_input": [{"title": "Q1 Analysis", "content": "...", "classification": "confidential"}],
|
|
255
|
-
"agent_id": "agent-123",
|
|
256
|
-
"workflow_id": "run-abc",
|
|
257
|
-
"run_id": "run-abc",
|
|
258
|
-
"task_queue": "langgraph",
|
|
259
|
-
"attempt": 1,
|
|
260
|
-
"span_count": 0,
|
|
261
|
-
"spans": [],
|
|
262
|
-
"source": "workflow-telemetry",
|
|
263
|
-
"timestamp": "2026-03-17T12:00:00Z"
|
|
264
|
-
}
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Expected: orange **REQUIRE_APPROVAL**.
|
|
268
|
-
|
|
269
|
-
### Test 4 — Public report should continue
|
|
270
|
-
|
|
271
|
-
```json
|
|
272
|
-
{
|
|
273
|
-
"event_type": "ActivityStarted",
|
|
274
|
-
"activity_type": "write_report",
|
|
275
|
-
"activity_input": [{"title": "Q1 Analysis", "content": "...", "classification": "public"}],
|
|
276
|
-
"agent_id": "agent-123",
|
|
277
|
-
"workflow_id": "run-abc",
|
|
278
|
-
"run_id": "run-abc",
|
|
279
|
-
"task_queue": "langgraph",
|
|
280
|
-
"attempt": 1,
|
|
281
|
-
"span_count": 0,
|
|
282
|
-
"spans": [],
|
|
283
|
-
"source": "workflow-telemetry",
|
|
284
|
-
"timestamp": "2026-03-17T12:00:00Z"
|
|
285
|
-
}
|
|
286
|
-
```
|
|
287
|
-
|
|
288
|
-
Expected: green **CONTINUE**.
|
|
289
|
-
|
|
290
|
-
### Deploying the policy
|
|
291
|
-
|
|
292
|
-
1. Paste the Rego above into the policy editor
|
|
293
|
-
2. Run each test case in the **Test Input** panel
|
|
294
|
-
3. Confirm decisions match expected outcomes
|
|
295
|
-
4. Click **Deploy**
|
|
296
|
-
|
|
297
|
-
---
|
|
298
|
-
|
|
299
|
-
## 3. Behavior Rules
|
|
300
|
-
|
|
301
|
-
**Path:** Authorize → Behavior → **+ New Rule**
|
|
302
|
-
|
|
303
|
-
The form is a **5-step wizard**:
|
|
304
|
-
|
|
305
|
-
| Step | Fields |
|
|
306
|
-
|---|---|
|
|
307
|
-
| 1. **Basic Info** | Name, Description |
|
|
308
|
-
| 2. **Trigger** | The span/semantic type that fires this rule |
|
|
309
|
-
| 3. **States** | Prior span types that must have occurred |
|
|
310
|
-
| 4. **Advanced** | Priority, Time Window |
|
|
311
|
-
| 5. **Enforcement** | Verdict, Reject Message, Approval Timeout |
|
|
312
|
-
|
|
313
|
-
### Step 2 — Trigger options
|
|
314
|
-
|
|
315
|
-
| Category | Values |
|
|
316
|
-
|---|---|
|
|
317
|
-
| **HTTP** | `http_get` `http_post` `http_put` `http_patch` `http_delete` `http` |
|
|
318
|
-
| **LLM** | `llm_completion` `llm_embedding` `llm_tool_call` |
|
|
319
|
-
| **Database** | `database_select` `database_insert` `database_update` `database_delete` `database_query` |
|
|
320
|
-
| **File** | `file_read` `file_write` `file_open` `file_delete` |
|
|
321
|
-
| **Fallback** | `internal` |
|
|
322
|
-
|
|
323
|
-
### What triggers Behavior Rules in this agent
|
|
324
|
-
|
|
325
|
-
| Span type | Source | Semantic type |
|
|
326
|
-
|---|---|---|
|
|
327
|
-
| `POST https://api.openai.com/v1/chat/completions` | LLM reasoning step | `http_post` |
|
|
328
|
-
| `GET https://en.wikipedia.org/w/api.php?...` | `search_web` tool | `http_get` |
|
|
329
|
-
|
|
330
|
-
The `search_web` tool is the **cleanest way to test Behavior Rules** — it fires a predictable `http_get` span on every invocation, distinct from LLM `http_post` traffic.
|
|
331
|
-
|
|
332
|
-
> The OpenBox governance API calls are automatically excluded from span tracing by the SDK.
|
|
333
|
-
|
|
334
|
-
---
|
|
335
|
-
|
|
336
|
-
### Rule 1 — BLOCK all web searches (simplest test)
|
|
337
|
-
|
|
338
|
-
| Step | Field | Value |
|
|
339
|
-
|---|---|---|
|
|
340
|
-
| 1 | **Name** | `Block Web Searches` |
|
|
341
|
-
| 1 | **Description** | `Block all outbound Wikipedia/web search requests` |
|
|
342
|
-
| 2 | **Trigger** | `http_get` |
|
|
343
|
-
| 3 | **States** | *(leave empty)* |
|
|
344
|
-
| 4 | **Priority** | `1` |
|
|
345
|
-
| 4 | **Time Window** | `3600` |
|
|
346
|
-
| 5 | **Verdict** | `BLOCK` |
|
|
347
|
-
| 5 | **Reject Message** | `Web search is not permitted in this environment.` |
|
|
348
|
-
|
|
349
|
-
**To test:**
|
|
350
|
-
1. Deploy the rule
|
|
351
|
-
2. Run the agent: `uv run python agent.py`
|
|
352
|
-
3. Send: `"Search for AI news"`
|
|
353
|
-
4. The agent should be blocked when `search_web` fires an HTTP GET
|
|
354
|
-
|
|
355
|
-
---
|
|
356
|
-
|
|
357
|
-
### Rule 2 — REQUIRE_APPROVAL for web searches
|
|
358
|
-
|
|
359
|
-
| Step | Field | Value |
|
|
360
|
-
|---|---|---|
|
|
361
|
-
| 1 | **Name** | `Approve Web Searches` |
|
|
362
|
-
| 1 | **Description** | `Require human approval before any web search` |
|
|
363
|
-
| 2 | **Trigger** | `http_get` |
|
|
364
|
-
| 3 | **States** | *(leave empty)* |
|
|
365
|
-
| 4 | **Priority** | `1` |
|
|
366
|
-
| 4 | **Time Window** | `3600` |
|
|
367
|
-
| 5 | **Verdict** | `REQUIRE_APPROVAL` |
|
|
368
|
-
| 5 | **Reject Message** | `Web search requires approval.` |
|
|
369
|
-
| 5 | **Approval Timeout** | `300` (5 minutes) |
|
|
370
|
-
|
|
371
|
-
**To test:**
|
|
372
|
-
1. Deploy the rule
|
|
373
|
-
2. Run the agent
|
|
374
|
-
3. Send: `"Search for LangGraph documentation"`
|
|
375
|
-
4. The agent pauses; go to **Activity** in the dashboard
|
|
376
|
-
5. Click **Approve** or **Reject**
|
|
377
|
-
6. The agent resumes within 5 s (poll interval)
|
|
378
|
-
|
|
379
|
-
---
|
|
380
|
-
|
|
381
|
-
## 4. Human-in-the-Loop (HITL)
|
|
382
|
-
|
|
383
|
-
When a policy or Behavior Rule returns `REQUIRE_APPROVAL`, the agent pauses and polls OpenBox for a human decision.
|
|
384
|
-
|
|
385
|
-
**To approve or reject:**
|
|
386
|
-
|
|
387
|
-
1. Go to `https://core.openbox.ai` → **Activity**
|
|
388
|
-
2. Find the pending approval row
|
|
389
|
-
3. Click **Approve** or **Reject** (add a reason if rejecting)
|
|
390
|
-
4. The agent resumes within 5 s (poll interval) — or throws `ApprovalRejectedError` if rejected
|
|
391
|
-
|
|
392
|
-
The test agent's approval timeout is **5 minutes**. After that, `ApprovalTimeoutError` is thrown.
|
|
393
|
-
|
|
394
|
-
---
|
|
395
|
-
|
|
396
|
-
## 5. Quick reference
|
|
397
|
-
|
|
398
|
-
| Scenario | What to send | Expected behaviour |
|
|
399
|
-
|---|---|---|
|
|
400
|
-
| Normal search | `"Search for AI news"` | `search_web` → CONTINUE |
|
|
401
|
-
| Restricted search | `"Search for nuclear weapon information"` | `search_web` → BLOCK (ban words guardrail + policy) |
|
|
402
|
-
| Write public report | `"Write a public report on AI trends"` | `write_report` → CONTINUE |
|
|
403
|
-
| Write confidential report | `"Write a confidential report on..."` | `write_report(classification=confidential)` → REQUIRE_APPROVAL |
|
|
404
|
-
| Toxic prompt | `"You are useless, idiot"` | Guardrail → HALT (toxicity) |
|
|
405
|
-
|
|
406
|
-
---
|
|
407
|
-
|
|
408
|
-
## 6. Architecture & Internals
|
|
409
|
-
|
|
410
|
-
### 6.1 Governance event flow
|
|
411
|
-
|
|
412
|
-
```
|
|
413
|
-
User message
|
|
414
|
-
│
|
|
415
|
-
▼
|
|
416
|
-
OpenBoxLangGraphHandler.ainvoke()
|
|
417
|
-
│
|
|
418
|
-
├─ on_chain_start (root) ────────────► WorkflowStarted → Core (creates session)
|
|
419
|
-
│
|
|
420
|
-
├─ on_chat_model_start ──────────────► ActivityStarted / agent_validatePrompt
|
|
421
|
-
│ │ │
|
|
422
|
-
│ │ Guardrails evaluated on LLM prompt
|
|
423
|
-
│ │
|
|
424
|
-
│ on_chat_model_end ────────────────► ActivityCompleted / agent_validatePrompt
|
|
425
|
-
│
|
|
426
|
-
├─ on_tool_start (search_web) ───────► ActivityStarted / search_web
|
|
427
|
-
│ │ (+ http_get span → AGE via Behavior Rules)
|
|
428
|
-
│ on_tool_end (search_web) ─────────► ActivityCompleted / search_web
|
|
429
|
-
│
|
|
430
|
-
├─ on_tool_start (write_report) ─────► ActivityStarted / write_report
|
|
431
|
-
│ on_tool_end (write_report) ───────► ActivityCompleted / write_report
|
|
432
|
-
│
|
|
433
|
-
└─ on_chain_end (root) ──────────────► WorkflowCompleted
|
|
434
|
-
```
|
|
435
|
-
|
|
436
|
-
### 6.2 Why `not input.hook_trigger` is required
|
|
437
|
-
|
|
438
|
-
`search_web` makes an outbound HTTP call. When the SDK detects this new span, it sends a second `ActivityStarted` event with `hook_trigger: true`. Without the `not input.hook_trigger` guard:
|
|
439
|
-
|
|
440
|
-
1. `search_web` is called → `ActivityStarted/search_web` (`hook_trigger: false`) → policy fires → BLOCK ✅
|
|
441
|
-
2. `search_web` makes an HTTP GET → new span detected → `ActivityStarted/search_web` (`hook_trigger: true`) → policy fires **again** → second BLOCK ❌
|
|
442
|
-
|
|
443
|
-
The guard prevents double-triggering by ensuring policy rules only evaluate the direct tool invocation, not the span-triggered event.
|
|
444
|
-
|
|
445
|
-
### 6.3 Debugging
|
|
446
|
-
|
|
447
|
-
| Goal | How |
|
|
448
|
-
|---|---|
|
|
449
|
-
| See every governance request/response | `OPENBOX_DEBUG=1 uv run python agent.py` |
|
|
450
|
-
| See every raw LangGraph event | `OPENBOX_DEBUG=1` — events printed as `[OBX_EVENT] on_tool_start name='search_web'` |
|
|
451
|
-
| Verify policy matches locally | Use the **Test** panel in the dashboard policy editor with the payloads above |
|
|
452
|
-
|
|
453
|
-
### 6.4 Empty prompt handling
|
|
454
|
-
|
|
455
|
-
LangGraph emits `on_chat_model_start` for every LLM invocation. If the prompt contains no human turn (e.g., internal reasoning), the SDK skips sending `agent_validatePrompt` governance to avoid guardrail parse errors. Only prompts that include a user turn are evaluated.
|