agenthandler 0.93.0__tar.gz → 0.94.0__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.
- agenthandler-0.94.0/.github/workflows/ci.yml +75 -0
- agenthandler-0.94.0/.github/workflows/publish.yml +75 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/.gitignore +2 -6
- {agenthandler-0.93.0 → agenthandler-0.94.0}/CHANGELOG.md +74 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/CONTRIBUTING.md +21 -6
- agenthandler-0.93.0/README.md → agenthandler-0.94.0/PKG-INFO +241 -18
- agenthandler-0.93.0/PKG-INFO → agenthandler-0.94.0/README.md +193 -65
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/__init__.py +60 -1
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/a2a.py +64 -9
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/approval.py +84 -4
- agenthandler-0.94.0/agenthandler/auth.py +262 -0
- agenthandler-0.94.0/agenthandler/completion.py +37 -0
- agenthandler-0.94.0/agenthandler/compression.py +185 -0
- agenthandler-0.94.0/agenthandler/connectors.py +600 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/context.py +2 -0
- agenthandler-0.94.0/agenthandler/edge_reduce.py +239 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/mcp_server.py +2 -1
- agenthandler-0.94.0/agenthandler/model_router.py +265 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/observe.py +103 -19
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/openclaw.py +31 -2
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/pipeline.py +20 -7
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/policy.py +37 -1
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/redact.py +54 -11
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/reflection.py +138 -14
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/sdk_adapters.py +6 -2
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/server.py +94 -36
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/session.py +209 -73
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/store.py +146 -13
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/streams.py +41 -17
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/supervisor.py +293 -92
- agenthandler-0.94.0/agenthandler/task.py +462 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/triggers.py +96 -60
- agenthandler-0.94.0/agenthandler/verification.py +194 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/dashboard.html +318 -14
- {agenthandler-0.93.0 → agenthandler-0.94.0}/docs/architecture.svg +1 -1
- agenthandler-0.94.0/docs/durable-tasks.md +202 -0
- agenthandler-0.94.0/docs/verification-gates.md +94 -0
- agenthandler-0.94.0/examples/edge_reduce.py +101 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/mcp_server.py +35 -2
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/openclaw_bridge.py +1 -2
- {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/package.json +1 -1
- {agenthandler-0.93.0 → agenthandler-0.94.0}/pyproject.toml +18 -8
- agenthandler-0.94.0/tests/dashboard.test.cjs +82 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_a2a.py +3 -3
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_approval.py +82 -0
- agenthandler-0.94.0/tests/test_auth.py +232 -0
- agenthandler-0.94.0/tests/test_call_approved.py +64 -0
- agenthandler-0.94.0/tests/test_completion.py +212 -0
- agenthandler-0.94.0/tests/test_compression.py +65 -0
- agenthandler-0.94.0/tests/test_connector_regressions.py +142 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_connectors.py +1 -1
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_cost_attribution.py +72 -0
- agenthandler-0.94.0/tests/test_gate_durable_recovery.py +63 -0
- agenthandler-0.94.0/tests/test_integration_regressions.py +129 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_replay_reflection.py +5 -3
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_store.py +196 -0
- agenthandler-0.94.0/tests/test_stream_delivery.py +117 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_supervisor.py +66 -0
- agenthandler-0.94.0/tests/test_task.py +744 -0
- agenthandler-0.94.0/tests/test_trigger_regressions.py +98 -0
- agenthandler-0.94.0/tests/test_verification.py +222 -0
- agenthandler-0.93.0/.github/workflows/ci.yml +0 -41
- agenthandler-0.93.0/.github/workflows/claude-pr-review.yml +0 -28
- agenthandler-0.93.0/agenthandler/auth.py +0 -188
- agenthandler-0.93.0/agenthandler/connectors.py +0 -425
- agenthandler-0.93.0/tests/test_session.py +0 -561
- {agenthandler-0.93.0 → agenthandler-0.94.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/LICENSE +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/audit.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/budget.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/builder.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/circuit_breaker.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/errors.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/__init__.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/deterministic.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/llm_based.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/types.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/pipeline_poml.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/py.typed +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/replay.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/skill.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/docs/manifesto.md +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/a2a_agents.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/approval_flow.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/audit_logging.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/basic.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/circuit_breaker.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/context_compression.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/cost_attribution.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/data_flows.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/from_config.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/guardrails.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/long_running_agent.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/pii_redaction.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/pipeline.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/poml_pipeline.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/sdk_adapters.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/session_lifecycle.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/skill_builder.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/streaming.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/tool_scoping.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/triggers.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/README.md +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/bin/agenthandler.js +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/__init__.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_audit.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_budget.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_circuit_breaker.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_context.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_errors.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_guardrails.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_llm_decisions.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_observe.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_openclaw.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_pipeline.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_pipeline_poml.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_policy.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_redact.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_schema_discovery.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_scopes.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_sdk_adapters.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_server.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_skill.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_streams.py +0 -0
- {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_triggers.py +0 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
permissions:
|
|
10
|
+
contents: read
|
|
11
|
+
|
|
12
|
+
concurrency:
|
|
13
|
+
group: ci-${{ github.event.pull_request.number || github.ref }}
|
|
14
|
+
cancel-in-progress: true
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
lint:
|
|
18
|
+
name: Lint and typecheck
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 10
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
23
|
+
with:
|
|
24
|
+
persist-credentials: false
|
|
25
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: '3.12'
|
|
28
|
+
cache: pip
|
|
29
|
+
- run: python -m pip install -e '.[dev,server]'
|
|
30
|
+
- run: ruff check .
|
|
31
|
+
- run: ruff format --check .
|
|
32
|
+
- run: mypy agenthandler/
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
timeout-minutes: 15
|
|
38
|
+
strategy:
|
|
39
|
+
fail-fast: false
|
|
40
|
+
matrix:
|
|
41
|
+
python-version: ['3.10', '3.11', '3.12', '3.13']
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
44
|
+
with:
|
|
45
|
+
persist-credentials: false
|
|
46
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: ${{ matrix.python-version }}
|
|
49
|
+
cache: pip
|
|
50
|
+
- run: python -m pip install -e '.[dev,server]'
|
|
51
|
+
- run: python -m pytest -q --tb=short --cov=agenthandler --cov-report=term-missing --cov-fail-under=75
|
|
52
|
+
|
|
53
|
+
build:
|
|
54
|
+
name: Package build
|
|
55
|
+
runs-on: ubuntu-latest
|
|
56
|
+
timeout-minutes: 10
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4
|
|
59
|
+
with:
|
|
60
|
+
persist-credentials: false
|
|
61
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
|
62
|
+
with:
|
|
63
|
+
python-version: '3.12'
|
|
64
|
+
- run: python -m pip install build twine
|
|
65
|
+
- run: python -m build
|
|
66
|
+
- run: python -m twine check dist/*
|
|
67
|
+
- name: Smoke test the wheel without optional dependencies
|
|
68
|
+
run: |
|
|
69
|
+
python -m venv "$RUNNER_TEMP/wheel-test"
|
|
70
|
+
"$RUNNER_TEMP/wheel-test/bin/python" -m pip install dist/*.whl
|
|
71
|
+
cd "$RUNNER_TEMP"
|
|
72
|
+
"$RUNNER_TEMP/wheel-test/bin/python" -c "import agenthandler; from importlib.resources import files; assert files('agenthandler').joinpath('py.typed').is_file(); print(agenthandler.__version__)"
|
|
73
|
+
- name: Check the npm placeholder package
|
|
74
|
+
working-directory: npm
|
|
75
|
+
run: npm pack --dry-run
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
name: Publish
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI and npm when a GitHub release is published, using trusted
|
|
4
|
+
# publishing (OIDC): no API tokens are stored in the repository.
|
|
5
|
+
#
|
|
6
|
+
# One-time setup:
|
|
7
|
+
# PyPI: pypi.org > agenthandler > Settings > Publishing > add a GitHub publisher
|
|
8
|
+
# owner arunvenkatadri, repo agenthandler, workflow publish.yml, environment pypi.
|
|
9
|
+
# npm: npmjs.com > agenthandler > Settings > Trusted publishing > GitHub Actions,
|
|
10
|
+
# same owner/repo, workflow publish.yml.
|
|
11
|
+
# GitHub: Settings > Environments > create "pypi" and "npm" (optionally with required reviewers).
|
|
12
|
+
|
|
13
|
+
on:
|
|
14
|
+
release:
|
|
15
|
+
types: [published]
|
|
16
|
+
workflow_dispatch:
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
build:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
26
|
+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
27
|
+
with:
|
|
28
|
+
python-version: "3.13"
|
|
29
|
+
- name: Check the tag matches the version
|
|
30
|
+
if: github.event_name == 'release'
|
|
31
|
+
run: |
|
|
32
|
+
v=$(python -c "import tomllib;print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")
|
|
33
|
+
test "${GITHUB_REF_NAME#v}" = "$v" || { echo "tag $GITHUB_REF_NAME != version $v"; exit 1; }
|
|
34
|
+
test "$(node -p "require('./npm/package.json').version")" = "$v"
|
|
35
|
+
- run: python -m pip install build twine && python -m build && twine check dist/*
|
|
36
|
+
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
pypi:
|
|
42
|
+
needs: build
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
environment: pypi
|
|
45
|
+
permissions:
|
|
46
|
+
id-token: write
|
|
47
|
+
steps:
|
|
48
|
+
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
49
|
+
with:
|
|
50
|
+
name: dist
|
|
51
|
+
path: dist/
|
|
52
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
53
|
+
with:
|
|
54
|
+
skip-existing: true
|
|
55
|
+
|
|
56
|
+
npm:
|
|
57
|
+
needs: build
|
|
58
|
+
runs-on: ubuntu-latest
|
|
59
|
+
environment: npm
|
|
60
|
+
permissions:
|
|
61
|
+
id-token: write
|
|
62
|
+
contents: read
|
|
63
|
+
steps:
|
|
64
|
+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
|
65
|
+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
|
66
|
+
with:
|
|
67
|
+
node-version: "22"
|
|
68
|
+
registry-url: "https://registry.npmjs.org"
|
|
69
|
+
- name: Publish (skipped when this version is already on npm)
|
|
70
|
+
working-directory: npm
|
|
71
|
+
run: |
|
|
72
|
+
npm install -g npm@latest
|
|
73
|
+
v=$(node -p "require('./package.json').version")
|
|
74
|
+
if npm view "agenthandler@$v" version >/dev/null 2>&1; then echo "agenthandler@$v already published"; exit 0; fi
|
|
75
|
+
npm publish --provenance --access public
|
|
@@ -23,16 +23,12 @@ htmlcov/
|
|
|
23
23
|
*.swo
|
|
24
24
|
.DS_Store
|
|
25
25
|
|
|
26
|
-
# Node
|
|
26
|
+
# Node
|
|
27
27
|
node_modules/
|
|
28
|
-
openclaw-plugin/dist/
|
|
29
28
|
|
|
30
29
|
# Environment
|
|
31
30
|
.env
|
|
32
31
|
.env.*
|
|
33
32
|
|
|
34
|
-
#
|
|
35
|
-
ROADMAP.internal.md
|
|
36
|
-
|
|
37
|
-
# SQLite databases (created by clawboss sessions)
|
|
33
|
+
# SQLite databases (created by local sessions)
|
|
38
34
|
*.db
|
|
@@ -2,6 +2,80 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to AgentHandler are documented here.
|
|
4
4
|
|
|
5
|
+
## Unreleased
|
|
6
|
+
|
|
7
|
+
## [0.94.0] — 2026-11-11
|
|
8
|
+
|
|
9
|
+
The open-source relaunch of the AgentHandler governance library, published from a
|
|
10
|
+
new repository with fresh history. Everything shipped in 0.93.0 is still here; the
|
|
11
|
+
entries below cover what changed in the library since then.
|
|
12
|
+
|
|
13
|
+
### Breaking
|
|
14
|
+
- **OAuth requires an explicit allow-list.** `add_oauth_routes()` now takes
|
|
15
|
+
`allowed_subjects` and raises at startup when it is empty; the REST control plane
|
|
16
|
+
reads `AGENTHANDLER_OAUTH_ALLOWED_SUBJECTS`. Previously any identity that completed
|
|
17
|
+
the provider flow was admitted — authentication was treated as authorization.
|
|
18
|
+
Deployments that relied on that must list the provider user IDs allowed in, or the
|
|
19
|
+
server will refuse to start. Failing closed here is deliberate.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- **Durable tasks**: `DurableTaskRunner` with `SqliteTaskStore`, `Milestone`,
|
|
23
|
+
`TaskLimits`, `CallBudget` and `TaskContext` persist milestone progress, operation
|
|
24
|
+
IDs, verification evidence and lifetime budget reservations, so a restartable job
|
|
25
|
+
resumes without repeating side effects (`docs/durable-tasks.md`).
|
|
26
|
+
- **Verification gates**: `VerificationGate` requires named validators to pass before
|
|
27
|
+
an agent's proposal counts as verified; `CompletionStatus` and `VerificationResult`
|
|
28
|
+
for reflection loops (`docs/verification-gates.md`).
|
|
29
|
+
- **Model routing**: `ModelRouter`, `RoutingRule` and `RoutingDecision` route each
|
|
30
|
+
query to a model by keywords, patterns or a default.
|
|
31
|
+
- **Context compression** (optional, `pip install agenthandler[compression]`):
|
|
32
|
+
`CompressedSupervisor`, `compress_output` and `compress_messages`.
|
|
33
|
+
- **Local/cloud routing** (optional): `EdgeReduceLLM` runs LLM calls routed by
|
|
34
|
+
edge-reduce-llm under supervision.
|
|
35
|
+
- **Approvals**: expiry (`Policy.approval_ttl_s`, per-tool `approval_ttls`) and
|
|
36
|
+
`ApprovalQueue.revoke()`; `Supervisor.call_approved()` dispatches a call against a
|
|
37
|
+
specific approval.
|
|
38
|
+
- **Supervisor**: `preflight()`, `call_with_breaker()`, `reset_circuit_breaker()`,
|
|
39
|
+
`restore_rate_history()` and `begin_request()`.
|
|
40
|
+
- **Atomic checkpoints**: the `AtomicCheckpointStore` protocol; the SQLite store
|
|
41
|
+
writes the supervisor checkpoint and session state in one transaction.
|
|
42
|
+
- **Cost attribution**: prompt-cache read and write rates and batch discounts in
|
|
43
|
+
`ModelPricing` and `PricingTable.set_model()`.
|
|
44
|
+
- `Redactor.redact_value()` redacts nested JSON-like values.
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- `SessionManager` binds a running session to its supervisor generation, so a stale
|
|
48
|
+
process cannot overwrite a resumed session.
|
|
49
|
+
- REST control-plane request models bound the size of every string field.
|
|
50
|
+
|
|
51
|
+
### Fixed
|
|
52
|
+
- **Dashboard XSS**: `esc()` escaped only `&`, `<` and `>` by way of
|
|
53
|
+
`textContent`/`innerHTML`, leaving quotes intact while six `onclick=` attributes
|
|
54
|
+
interpolated its output — a `'` escaped into the handler. It now escapes quotes,
|
|
55
|
+
and `jsArg()` encodes a JavaScript string argument for that attribute context.
|
|
56
|
+
- **Stream message loss**: a failed handler dropped its message. The Kafka connector
|
|
57
|
+
committed the consumer's current position rather than the handled offset, so the
|
|
58
|
+
next success committed past the failure; the Redis connector skipped `XACK` but
|
|
59
|
+
nothing reclaimed the pending entry. Deliveries now retry before advancing.
|
|
60
|
+
- **Connectors**: read-only enforcement and transaction handling.
|
|
61
|
+
- **Triggers**: recurring cron steps (`*/N`), webhook enable/disable, and scheduler
|
|
62
|
+
shutdown through a FastAPI `lifespan`.
|
|
63
|
+
- **Routing, pipeline approval and cancellation** regressions.
|
|
64
|
+
|
|
65
|
+
### Security
|
|
66
|
+
- `require_auth=True` no longer accepts a placeholder key; OAuth login states are
|
|
67
|
+
single-use, expire and are bound to the browser; OAuth sessions are scoped per app;
|
|
68
|
+
WebSocket connections authenticate with API keys and OAuth sessions alike.
|
|
69
|
+
- `OpenClawBridge` takes `allowed_origins`; the A2A client takes an `auth_token`.
|
|
70
|
+
|
|
71
|
+
## [0.93.0] — 2026-07-11
|
|
72
|
+
|
|
73
|
+
### Added
|
|
74
|
+
- **MCP server mode**: `SupervisedMCPServer` exposes supervised tools over the Model
|
|
75
|
+
Context Protocol (`pip install agenthandler[mcp]`).
|
|
76
|
+
- **A2A protocol**: supervised agent-to-agent calls and agent cards.
|
|
77
|
+
- **SDK adapters** for the OpenAI Agents SDK and the Claude Agent SDK.
|
|
78
|
+
|
|
5
79
|
## [0.92.0] — 2026-07-11
|
|
6
80
|
|
|
7
81
|
### Added
|
|
@@ -5,9 +5,9 @@ Thanks for your interest in contributing.
|
|
|
5
5
|
## Setup
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
|
-
git clone https://github.com/arunvenkatadri/
|
|
9
|
-
cd
|
|
10
|
-
pip install -e ".[dev]"
|
|
8
|
+
git clone https://github.com/arunvenkatadri/agenthandler.git
|
|
9
|
+
cd agenthandler
|
|
10
|
+
pip install -e ".[dev,server]"
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
## Running checks
|
|
@@ -16,6 +16,9 @@ pip install -e ".[dev]"
|
|
|
16
16
|
# Tests
|
|
17
17
|
pytest tests/ -v
|
|
18
18
|
|
|
19
|
+
# Include the coverage check used in CI
|
|
20
|
+
pytest tests/ --cov=agenthandler --cov-fail-under=75
|
|
21
|
+
|
|
19
22
|
# Lint
|
|
20
23
|
ruff check .
|
|
21
24
|
ruff format --check .
|
|
@@ -33,19 +36,31 @@ All three must pass before submitting a PR.
|
|
|
33
36
|
3. Make sure all checks pass.
|
|
34
37
|
4. Open a PR with a clear description of what changed and why.
|
|
35
38
|
|
|
39
|
+
### Changelog
|
|
40
|
+
|
|
41
|
+
Add an entry under `## Unreleased` in `CHANGELOG.md` for any user-visible change.
|
|
42
|
+
Call out breaking changes to public APIs (anything exported from `agenthandler`,
|
|
43
|
+
the REST control plane, policy keys) explicitly.
|
|
44
|
+
|
|
45
|
+
## Review and merging
|
|
46
|
+
|
|
47
|
+
PRs need passing CI (tests on Python 3.10–3.13, lint, formatting, type checking
|
|
48
|
+
and the package build) and a maintainer's approval. Resolve review conversations
|
|
49
|
+
before merging.
|
|
50
|
+
|
|
36
51
|
## Project structure
|
|
37
52
|
|
|
38
53
|
```
|
|
39
|
-
agenthandler/
|
|
54
|
+
agenthandler/ Python library (zero dependencies)
|
|
40
55
|
tests/ Test suite (pytest)
|
|
41
56
|
examples/ Runnable examples
|
|
57
|
+
docs/ Guides and design notes
|
|
42
58
|
dashboard.html Agent management UI (single-file, no build step)
|
|
43
|
-
|
|
59
|
+
npm/ npm placeholder that points to the Python package
|
|
44
60
|
```
|
|
45
61
|
|
|
46
62
|
- **`agenthandler/`** — the core library. Skills are capabilities (tool collections). Agents use skills and are supervised by policies.
|
|
47
63
|
- **`dashboard.html`** — standalone UI for managing agents, skills, and conversations. Pure HTML/CSS/JS, no build tools. Open directly in a browser.
|
|
48
|
-
- **`openclaw-plugin/`** — TypeScript plugin that bridges agenthandler to OpenClaw.
|
|
49
64
|
|
|
50
65
|
## Design principles
|
|
51
66
|
|
|
@@ -1,17 +1,63 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: agenthandler
|
|
3
|
+
Version: 0.94.0
|
|
4
|
+
Summary: A way of controlling deployed agents to keep your (and their) sanity.
|
|
5
|
+
Project-URL: Homepage, https://github.com/arunvenkatadri/agenthandler
|
|
6
|
+
Project-URL: Repository, https://github.com/arunvenkatadri/agenthandler
|
|
7
|
+
Project-URL: Issues, https://github.com/arunvenkatadri/agenthandler/issues
|
|
8
|
+
Author: Arun Venkatadri
|
|
9
|
+
License: Apache-2.0
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: agents,ai,audit,budget,circuit-breaker,supervision
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Provides-Extra: compression
|
|
23
|
+
Requires-Dist: headroom-ai>=0.20; extra == 'compression'
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: build>=1.0; extra == 'dev'
|
|
26
|
+
Requires-Dist: httpx>=0.25; extra == 'dev'
|
|
27
|
+
Requires-Dist: mypy>=1.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == 'dev'
|
|
29
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
30
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
31
|
+
Requires-Dist: ruff==0.16.6; extra == 'dev'
|
|
32
|
+
Requires-Dist: twine>=5.0; extra == 'dev'
|
|
33
|
+
Provides-Extra: kafka
|
|
34
|
+
Requires-Dist: aiokafka>=0.10; extra == 'kafka'
|
|
35
|
+
Provides-Extra: kinesis
|
|
36
|
+
Requires-Dist: aioboto3>=12.0; extra == 'kinesis'
|
|
37
|
+
Provides-Extra: mcp
|
|
38
|
+
Requires-Dist: mcp>=1.0; extra == 'mcp'
|
|
39
|
+
Provides-Extra: redis
|
|
40
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
41
|
+
Provides-Extra: server
|
|
42
|
+
Requires-Dist: aiosqlite>=0.19; extra == 'server'
|
|
43
|
+
Requires-Dist: fastapi>=0.100; extra == 'server'
|
|
44
|
+
Requires-Dist: uvicorn[standard]>=0.20; extra == 'server'
|
|
45
|
+
Provides-Extra: streams
|
|
46
|
+
Requires-Dist: aioboto3>=12.0; extra == 'streams'
|
|
47
|
+
Requires-Dist: aiokafka>=0.10; extra == 'streams'
|
|
48
|
+
Requires-Dist: redis>=5.0; extra == 'streams'
|
|
49
|
+
Description-Content-Type: text/markdown
|
|
50
|
+
|
|
51
|
+
# AgentHandler
|
|
8
52
|
|
|
53
|
+
<p align="center">
|
|
54
|
+
<img alt="AgentHandler banner" src="docs/banner.png" width="900" />
|
|
55
|
+
</p>
|
|
9
56
|
|
|
10
|
-
[](https://github.com/arunvenkatadri/agenthandler/actions/workflows/ci.yml)
|
|
11
58
|
[](https://www.python.org/downloads/)
|
|
12
59
|
[](LICENSE)
|
|
13
|
-
[]()
|
|
14
|
-
[]()
|
|
60
|
+
[](pyproject.toml)
|
|
15
61
|
|
|
16
62
|
*Every agent needs a handler.*
|
|
17
63
|
|
|
@@ -66,6 +112,9 @@ async def web_search(query: str) -> str:
|
|
|
66
112
|
return f"Results for: {query}"
|
|
67
113
|
|
|
68
114
|
async def main():
|
|
115
|
+
# Count this round against max_iterations (raises when the limit is hit)
|
|
116
|
+
supervisor.record_iteration()
|
|
117
|
+
|
|
69
118
|
# Supervise a tool call
|
|
70
119
|
result = await supervisor.call("web_search", web_search, query="python async")
|
|
71
120
|
|
|
@@ -84,6 +133,12 @@ async def main():
|
|
|
84
133
|
asyncio.run(main())
|
|
85
134
|
```
|
|
86
135
|
|
|
136
|
+
`record_iteration()` and `record_tokens()` are explicit because AgentHandler
|
|
137
|
+
does not run your loop — it supervises the calls your loop makes. One
|
|
138
|
+
`supervisor.call()` is not necessarily one iteration, and only you know how many
|
|
139
|
+
tokens your LLM call actually spent. Both raise when they cross a policy limit,
|
|
140
|
+
so the loop stops at the boundary rather than after it.
|
|
141
|
+
|
|
87
142
|
## Dashboard
|
|
88
143
|
|
|
89
144
|
Open `dashboard.html` in a browser for a full management UI:
|
|
@@ -100,10 +155,10 @@ Open `dashboard.html` in a browser for a full management UI:
|
|
|
100
155
|
- **Costs** — track spend, set budgets with hard stops, view usage over time
|
|
101
156
|
- **Policies** — see all active supervision rules at a glance
|
|
102
157
|
|
|
103
|
-
The Sessions tab connects to the REST control plane (`uvicorn agenthandler.server:app`) and shows real-time session data. Agent cards show live status and controls work against the real API. The LLM features (prompt generation, chat, pipeline generation) require a backend with an LLM API key
|
|
158
|
+
The Sessions tab connects to the REST control plane (`uvicorn agenthandler.server:app`) and shows real-time session data. Agent cards show live status and controls work against the real API. The LLM features (prompt generation, chat, pipeline generation) require a backend with an LLM API key: provide your own `/chat` and `/generate-prompt` endpoints.
|
|
104
159
|
|
|
105
160
|
|
|
106
|
-
<img width="1383" height="1270" alt="
|
|
161
|
+
<img width="1383" height="1270" alt="AgentHandler dashboard — agents, skills, sessions, approvals and costs in one view" src="https://github.com/user-attachments/assets/b4fec9fa-59d4-4476-93ac-19684c835ab0" />
|
|
107
162
|
|
|
108
163
|
|
|
109
164
|
|
|
@@ -127,6 +182,11 @@ The Sessions tab connects to the REST control plane (`uvicorn agenthandler.serve
|
|
|
127
182
|
| **16 guardrails** | No layered safety — rule-based + LLM-backed checks for every tool call |
|
|
128
183
|
| **Reflection loops** | Agents looping without thinking about whether they're making progress |
|
|
129
184
|
| **Session replay** | No way to reconstruct what an agent did after the fact |
|
|
185
|
+
| **Model routing** | Paying frontier prices for simple queries — route to cheap models automatically |
|
|
186
|
+
| **Context compression** | Sending 10x more tokens than needed — compress tool outputs before they hit the LLM |
|
|
187
|
+
| **MCP server mode** | No standard way to expose supervised tools to any MCP client |
|
|
188
|
+
| **A2A protocol** | No supervision on inter-agent communication |
|
|
189
|
+
| **SDK adapters** | No way to add guardrails to OpenAI/Claude SDK tools without rewriting |
|
|
130
190
|
| **Streaming inputs** | No way to react to real-time events (Kafka/Kinesis/Redis) |
|
|
131
191
|
| **Triggers & scheduling** | No way to run agents on schedule or on data events |
|
|
132
192
|
| **Pipeline orchestration** | No structured way to chain tool calls |
|
|
@@ -372,7 +432,7 @@ The REST endpoint `GET /pipelines/schema` returns the schema for all registered
|
|
|
372
432
|
|
|
373
433
|
## Reflection loops (agents that actually think)
|
|
374
434
|
|
|
375
|
-
Long-duration agents don't just loop `run()` forever. They think → act → observe → reflect → think again.
|
|
435
|
+
Long-duration agents don't just loop `run()` forever. They think → act → observe → reflect → think again. Think, observe, and reflect use your LLM callback; tool execution is supervised and audited. Reflections feed into the next think phase.
|
|
376
436
|
|
|
377
437
|
```python
|
|
378
438
|
from agenthandler import ReflectionLoop, SessionManager, MemoryStore
|
|
@@ -394,7 +454,9 @@ for c in result.cycles:
|
|
|
394
454
|
print(f" {c.thought} → {c.tool_called} → {c.reflection}")
|
|
395
455
|
```
|
|
396
456
|
|
|
397
|
-
|
|
457
|
+
A model completion claim produces `status="proposed"` and `completed=False`. Supply a trusted async `verifier` returning `VerificationResult` to certify completion with acceptance evidence. Malformed responses are invalid; blocked work remains incomplete. Reflection history is local to this run.
|
|
458
|
+
|
|
459
|
+
For restartable jobs, use the optional `DurableTaskRunner` with `SqliteTaskStore`. It persists milestone progress, operation IDs, verification evidence, and lifetime budget reservations. Uncertain external operations require reconciliation before replay. See [durable tasks and completion semantics](docs/durable-tasks.md) for the API, migration notes, and adapter requirements.
|
|
398
460
|
|
|
399
461
|
See `examples/long_running_agent.py` for a full reference implementation.
|
|
400
462
|
|
|
@@ -503,13 +565,31 @@ Three streaming backends — install only what you need:
|
|
|
503
565
|
|
|
504
566
|
| Connector | Install | Use for |
|
|
505
567
|
|-----------|---------|---------|
|
|
506
|
-
| `KafkaStreamConnector` | `pip install agenthandler[kafka]` | Self-hosted, enterprise, replay, consumer groups |
|
|
507
|
-
| `KinesisStreamConnector` | `pip install agenthandler[kinesis]` | AWS-native event streams |
|
|
568
|
+
| `KafkaStreamConnector` | `pip install agenthandler[kafka]` | Self-hosted, enterprise, replay, consumer groups. **Recommended for long-running agents.** |
|
|
508
569
|
| `RedisStreamConnector` | `pip install agenthandler[redis]` | Simpler deployments, already-have-Redis |
|
|
570
|
+
| `KinesisStreamConnector` | `pip install agenthandler[kinesis]` | AWS-native event streams. Live-tail only — see the caveat below. |
|
|
509
571
|
|
|
510
572
|
Or install all three: `pip install agenthandler[streams]`
|
|
511
573
|
|
|
512
|
-
|
|
574
|
+
**Delivery semantics.** A message is acknowledged only after its handler
|
|
575
|
+
succeeds, and a failed handler is retried rather than skipped — the connector
|
|
576
|
+
does not advance past a record it could not process.
|
|
577
|
+
|
|
578
|
+
- **Kafka** commits the handled offset to the consumer group, so a restart
|
|
579
|
+
resumes where it left off.
|
|
580
|
+
- **Redis Streams** acknowledges through the consumer group with `XACK`. A
|
|
581
|
+
handler that fails leaves the entry pending; nothing reclaims it
|
|
582
|
+
automatically yet, so recovery is manual.
|
|
583
|
+
- **Kinesis** retries within the running process, but **keeps no durable
|
|
584
|
+
position**. It enumerates shards once at startup and resumes from
|
|
585
|
+
`shard_iterator_type` on restart — so a redeploy skips whatever arrived while
|
|
586
|
+
it was down, and a reshard is never noticed. Use it for live tailing, not for
|
|
587
|
+
anything that must not miss a record. Checkpointing and reshard discovery are
|
|
588
|
+
not implemented.
|
|
589
|
+
|
|
590
|
+
A failed handler currently retries indefinitely with no attempt ceiling and no
|
|
591
|
+
dead-letter queue, so a message that always fails will stall its partition
|
|
592
|
+
([#4](https://github.com/arunvenkatadri/agenthandler/issues/4)).
|
|
513
593
|
|
|
514
594
|
## Triggers and scheduling
|
|
515
595
|
|
|
@@ -688,9 +768,19 @@ For production deployments, configure OAuth2 via environment variables:
|
|
|
688
768
|
AGENTHANDLER_OAUTH_PROVIDER=github \
|
|
689
769
|
AGENTHANDLER_OAUTH_CLIENT_ID=your-client-id \
|
|
690
770
|
AGENTHANDLER_OAUTH_CLIENT_SECRET=your-client-secret \
|
|
771
|
+
AGENTHANDLER_OAUTH_ALLOWED_SUBJECTS=1234567,7654321 \
|
|
691
772
|
uvicorn agenthandler.server:app
|
|
692
773
|
```
|
|
693
774
|
|
|
775
|
+
`AGENTHANDLER_OAUTH_ALLOWED_SUBJECTS` is **required** — a comma-separated list of
|
|
776
|
+
the provider user IDs allowed in (GitHub or Google numeric account IDs, not
|
|
777
|
+
usernames or email addresses). The server refuses to start without it.
|
|
778
|
+
|
|
779
|
+
That is deliberate. Completing an OAuth flow proves who someone is, not that
|
|
780
|
+
they should be allowed to drive your agents; without an allow-list, anyone with
|
|
781
|
+
a GitHub account would qualify. Authentication is not authorization, so the
|
|
782
|
+
allow-list has no default.
|
|
783
|
+
|
|
694
784
|
This adds `/auth/login` (get the OAuth redirect URL), `/auth/callback` (exchange code for session token), and `/auth/me` (current user info). Session tokens are Bearer tokens — use them the same way as API keys. Supported providers: `github`, `google`.
|
|
695
785
|
|
|
696
786
|
Endpoints:
|
|
@@ -789,6 +879,131 @@ Every tool call through MCP goes through the Supervisor — timeouts, budgets, c
|
|
|
789
879
|
|
|
790
880
|
See `examples/mcp_server.py` for a full working example.
|
|
791
881
|
|
|
882
|
+
## Model routing (cost optimization)
|
|
883
|
+
|
|
884
|
+
Route queries to different models based on complexity. Simple queries go to cheap models; complex queries go to expensive ones. Configure rules in code or via the dashboard.
|
|
885
|
+
|
|
886
|
+
```python
|
|
887
|
+
from agenthandler import ModelRouter, RoutingRule, Supervisor, Policy
|
|
888
|
+
|
|
889
|
+
supervisor = Supervisor(Policy(token_budget=100000))
|
|
890
|
+
|
|
891
|
+
router = ModelRouter(
|
|
892
|
+
api_key="sk-ant-...",
|
|
893
|
+
rules=[
|
|
894
|
+
RoutingRule(name="simple", match_default=True, model="claude-haiku-4-5"),
|
|
895
|
+
RoutingRule(name="complex", keywords=["analyze", "compare", "architecture"],
|
|
896
|
+
model="claude-sonnet-4-6"),
|
|
897
|
+
RoutingRule(name="critical", keywords=["audit", "security", "legal"],
|
|
898
|
+
model="claude-opus-4-6"),
|
|
899
|
+
],
|
|
900
|
+
supervisor=supervisor,
|
|
901
|
+
)
|
|
902
|
+
|
|
903
|
+
await router("What is 2+2?") # → Haiku ($0.25/M input)
|
|
904
|
+
await router("Analyze this codebase") # → Sonnet ($3/M input)
|
|
905
|
+
await router("Audit the security") # → Opus ($15/M input)
|
|
906
|
+
```
|
|
907
|
+
|
|
908
|
+
Rules are evaluated top-down (first match wins). Each rule supports keyword matching, regex patterns, or a default fallback. The dashboard **Routing** tab provides a visual editor with model dropdowns, drag-to-reorder, test queries, and YAML export.
|
|
909
|
+
|
|
910
|
+
For local/cloud hybrid routing (run a local model and only escalate to cloud when needed), see the [edge-reduce integration](https://github.com/arunvenkatadri/edge-reduce-llm).
|
|
911
|
+
|
|
912
|
+
## Context compression (Headroom)
|
|
913
|
+
|
|
914
|
+
Compress tool outputs before they reach the LLM — 60-95% fewer tokens for JSON, 15-20% for code. Powered by [Headroom](https://github.com/chopratejas/headroom) (Netflix).
|
|
915
|
+
|
|
916
|
+
```bash
|
|
917
|
+
pip install agenthandler[compression]
|
|
918
|
+
```
|
|
919
|
+
|
|
920
|
+
```python
|
|
921
|
+
from agenthandler import CompressedSupervisor, Policy
|
|
922
|
+
|
|
923
|
+
sv = CompressedSupervisor(
|
|
924
|
+
Policy(token_budget=50000),
|
|
925
|
+
compression="auto", # "auto", "always", or "off"
|
|
926
|
+
target_model="claude-sonnet-4-6",
|
|
927
|
+
)
|
|
928
|
+
|
|
929
|
+
result = await sv.call("search", search_fn, query="quantum computing")
|
|
930
|
+
# result.output is compressed — same information, fewer tokens
|
|
931
|
+
```
|
|
932
|
+
|
|
933
|
+
Or compress individual outputs:
|
|
934
|
+
|
|
935
|
+
```python
|
|
936
|
+
from agenthandler import compress_output
|
|
937
|
+
|
|
938
|
+
raw_data = {"rows": [{"id": i, "name": f"item_{i}"} for i in range(1000)]}
|
|
939
|
+
compressed = compress_output(raw_data, model="claude-sonnet-4-6")
|
|
940
|
+
# 60-95% smaller for JSON payloads
|
|
941
|
+
```
|
|
942
|
+
|
|
943
|
+
Compression is lossless and reversible — Headroom caches originals locally. If the LLM needs full detail, it retrieves via the `headroom_retrieve` tool. Combine with model routing for maximum cost reduction.
|
|
944
|
+
|
|
945
|
+
## SDK adapters
|
|
946
|
+
|
|
947
|
+
Wrap tools from any agent SDK with AgentHandler supervision — no rewrite needed.
|
|
948
|
+
|
|
949
|
+
```python
|
|
950
|
+
from agenthandler import supervised_tool_registry, SessionManager, MemoryStore
|
|
951
|
+
|
|
952
|
+
# Bulk-wrap a dict of tools — creates a supervised session automatically
|
|
953
|
+
supervised = supervised_tool_registry(
|
|
954
|
+
{"search": search_fn, "write": write_fn},
|
|
955
|
+
SessionManager(MemoryStore()),
|
|
956
|
+
policy={"tool_timeout": 30, "token_budget": 50000},
|
|
957
|
+
)
|
|
958
|
+
|
|
959
|
+
# Use supervised["search"] in your OpenAI/Claude/Mastra agent
|
|
960
|
+
```
|
|
961
|
+
|
|
962
|
+
Individual wrapping for finer control:
|
|
963
|
+
|
|
964
|
+
```python
|
|
965
|
+
from agenthandler import wrap_openai_tool, wrap_claude_tool
|
|
966
|
+
|
|
967
|
+
# For OpenAI Agents SDK
|
|
968
|
+
supervised_search = wrap_openai_tool(search_fn, supervisor, tool_name="search")
|
|
969
|
+
|
|
970
|
+
# For Claude Agent SDK
|
|
971
|
+
supervised_write = wrap_claude_tool(write_fn, supervisor, tool_name="write")
|
|
972
|
+
```
|
|
973
|
+
|
|
974
|
+
Convert AgentHandler guardrails to OpenAI format:
|
|
975
|
+
|
|
976
|
+
```python
|
|
977
|
+
from agenthandler import openai_guardrail_adapter, UrlGuard, RecursionDetector
|
|
978
|
+
|
|
979
|
+
oai_guardrails = openai_guardrail_adapter([
|
|
980
|
+
UrlGuard(allowlist=["*.example.com"]),
|
|
981
|
+
RecursionDetector(max_repeats=3),
|
|
982
|
+
])
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
## A2A protocol (agent-to-agent)
|
|
986
|
+
|
|
987
|
+
Supervise inter-agent communication via Google's Agent2Agent protocol. Every outbound call goes through the Supervisor.
|
|
988
|
+
|
|
989
|
+
```python
|
|
990
|
+
from agenthandler import A2AClient, A2AAgentCard, Supervisor, Policy
|
|
991
|
+
|
|
992
|
+
supervisor = Supervisor(Policy(tool_timeout=30, token_budget=50000))
|
|
993
|
+
client = A2AClient(supervisor=supervisor)
|
|
994
|
+
|
|
995
|
+
# Call another agent
|
|
996
|
+
result = await client.send_task(
|
|
997
|
+
agent_url="https://other-agent.example.com",
|
|
998
|
+
task={"skill": "research", "input": "quantum computing"},
|
|
999
|
+
)
|
|
1000
|
+
|
|
1001
|
+
# Make your agent discoverable
|
|
1002
|
+
card = A2AAgentCard.from_supervisor(
|
|
1003
|
+
supervisor, name="my-agent", url="https://my-agent.example.com"
|
|
1004
|
+
)
|
|
1005
|
+
```
|
|
1006
|
+
|
|
792
1007
|
## Policy from config
|
|
793
1008
|
|
|
794
1009
|
Load policy from a dictionary (YAML, JSON, database — whatever you use):
|
|
@@ -1289,11 +1504,19 @@ obs = Observer(otlp_endpoint="http://localhost:4317")
|
|
|
1289
1504
|
# Spans and metrics exported automatically to your collector
|
|
1290
1505
|
```
|
|
1291
1506
|
|
|
1507
|
+
## Independent acceptance checks
|
|
1508
|
+
|
|
1509
|
+
Use `VerificationGate` to require named application validators before treating an
|
|
1510
|
+
agent proposal as verified. Missing validators, empty evidence, rejected checks,
|
|
1511
|
+
exceptions, and timeouts cannot produce success. Every attempt executes fresh
|
|
1512
|
+
checks. See [verification gates](docs/verification-gates.md) for artifact binding,
|
|
1513
|
+
restart handling, and an independent regression-test example.
|
|
1514
|
+
|
|
1292
1515
|
## Contributing
|
|
1293
1516
|
|
|
1294
1517
|
```bash
|
|
1295
1518
|
# Install dev dependencies
|
|
1296
|
-
pip install -e ".[dev]"
|
|
1519
|
+
pip install -e ".[dev,server]"
|
|
1297
1520
|
|
|
1298
1521
|
# Run tests
|
|
1299
1522
|
pytest tests/ -v
|