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.
Files changed (126) hide show
  1. agenthandler-0.94.0/.github/workflows/ci.yml +75 -0
  2. agenthandler-0.94.0/.github/workflows/publish.yml +75 -0
  3. {agenthandler-0.93.0 → agenthandler-0.94.0}/.gitignore +2 -6
  4. {agenthandler-0.93.0 → agenthandler-0.94.0}/CHANGELOG.md +74 -0
  5. {agenthandler-0.93.0 → agenthandler-0.94.0}/CONTRIBUTING.md +21 -6
  6. agenthandler-0.93.0/README.md → agenthandler-0.94.0/PKG-INFO +241 -18
  7. agenthandler-0.93.0/PKG-INFO → agenthandler-0.94.0/README.md +193 -65
  8. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/__init__.py +60 -1
  9. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/a2a.py +64 -9
  10. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/approval.py +84 -4
  11. agenthandler-0.94.0/agenthandler/auth.py +262 -0
  12. agenthandler-0.94.0/agenthandler/completion.py +37 -0
  13. agenthandler-0.94.0/agenthandler/compression.py +185 -0
  14. agenthandler-0.94.0/agenthandler/connectors.py +600 -0
  15. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/context.py +2 -0
  16. agenthandler-0.94.0/agenthandler/edge_reduce.py +239 -0
  17. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/mcp_server.py +2 -1
  18. agenthandler-0.94.0/agenthandler/model_router.py +265 -0
  19. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/observe.py +103 -19
  20. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/openclaw.py +31 -2
  21. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/pipeline.py +20 -7
  22. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/policy.py +37 -1
  23. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/redact.py +54 -11
  24. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/reflection.py +138 -14
  25. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/sdk_adapters.py +6 -2
  26. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/server.py +94 -36
  27. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/session.py +209 -73
  28. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/store.py +146 -13
  29. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/streams.py +41 -17
  30. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/supervisor.py +293 -92
  31. agenthandler-0.94.0/agenthandler/task.py +462 -0
  32. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/triggers.py +96 -60
  33. agenthandler-0.94.0/agenthandler/verification.py +194 -0
  34. {agenthandler-0.93.0 → agenthandler-0.94.0}/dashboard.html +318 -14
  35. {agenthandler-0.93.0 → agenthandler-0.94.0}/docs/architecture.svg +1 -1
  36. agenthandler-0.94.0/docs/durable-tasks.md +202 -0
  37. agenthandler-0.94.0/docs/verification-gates.md +94 -0
  38. agenthandler-0.94.0/examples/edge_reduce.py +101 -0
  39. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/mcp_server.py +35 -2
  40. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/openclaw_bridge.py +1 -2
  41. {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/package.json +1 -1
  42. {agenthandler-0.93.0 → agenthandler-0.94.0}/pyproject.toml +18 -8
  43. agenthandler-0.94.0/tests/dashboard.test.cjs +82 -0
  44. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_a2a.py +3 -3
  45. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_approval.py +82 -0
  46. agenthandler-0.94.0/tests/test_auth.py +232 -0
  47. agenthandler-0.94.0/tests/test_call_approved.py +64 -0
  48. agenthandler-0.94.0/tests/test_completion.py +212 -0
  49. agenthandler-0.94.0/tests/test_compression.py +65 -0
  50. agenthandler-0.94.0/tests/test_connector_regressions.py +142 -0
  51. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_connectors.py +1 -1
  52. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_cost_attribution.py +72 -0
  53. agenthandler-0.94.0/tests/test_gate_durable_recovery.py +63 -0
  54. agenthandler-0.94.0/tests/test_integration_regressions.py +129 -0
  55. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_replay_reflection.py +5 -3
  56. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_store.py +196 -0
  57. agenthandler-0.94.0/tests/test_stream_delivery.py +117 -0
  58. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_supervisor.py +66 -0
  59. agenthandler-0.94.0/tests/test_task.py +744 -0
  60. agenthandler-0.94.0/tests/test_trigger_regressions.py +98 -0
  61. agenthandler-0.94.0/tests/test_verification.py +222 -0
  62. agenthandler-0.93.0/.github/workflows/ci.yml +0 -41
  63. agenthandler-0.93.0/.github/workflows/claude-pr-review.yml +0 -28
  64. agenthandler-0.93.0/agenthandler/auth.py +0 -188
  65. agenthandler-0.93.0/agenthandler/connectors.py +0 -425
  66. agenthandler-0.93.0/tests/test_session.py +0 -561
  67. {agenthandler-0.93.0 → agenthandler-0.94.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  68. {agenthandler-0.93.0 → agenthandler-0.94.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  69. {agenthandler-0.93.0 → agenthandler-0.94.0}/LICENSE +0 -0
  70. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/audit.py +0 -0
  71. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/budget.py +0 -0
  72. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/builder.py +0 -0
  73. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/circuit_breaker.py +0 -0
  74. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/errors.py +0 -0
  75. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/__init__.py +0 -0
  76. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/deterministic.py +0 -0
  77. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/llm_based.py +0 -0
  78. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/guardrails/types.py +0 -0
  79. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/pipeline_poml.py +0 -0
  80. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/py.typed +0 -0
  81. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/replay.py +0 -0
  82. {agenthandler-0.93.0 → agenthandler-0.94.0}/agenthandler/skill.py +0 -0
  83. {agenthandler-0.93.0 → agenthandler-0.94.0}/docs/manifesto.md +0 -0
  84. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/a2a_agents.py +0 -0
  85. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/approval_flow.py +0 -0
  86. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/audit_logging.py +0 -0
  87. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/basic.py +0 -0
  88. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/circuit_breaker.py +0 -0
  89. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/context_compression.py +0 -0
  90. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/cost_attribution.py +0 -0
  91. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/data_flows.py +0 -0
  92. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/from_config.py +0 -0
  93. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/guardrails.py +0 -0
  94. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/long_running_agent.py +0 -0
  95. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/pii_redaction.py +0 -0
  96. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/pipeline.py +0 -0
  97. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/poml_pipeline.py +0 -0
  98. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/sdk_adapters.py +0 -0
  99. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/session_lifecycle.py +0 -0
  100. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/skill_builder.py +0 -0
  101. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/streaming.py +0 -0
  102. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/tool_scoping.py +0 -0
  103. {agenthandler-0.93.0 → agenthandler-0.94.0}/examples/triggers.py +0 -0
  104. {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/README.md +0 -0
  105. {agenthandler-0.93.0 → agenthandler-0.94.0}/npm/bin/agenthandler.js +0 -0
  106. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/__init__.py +0 -0
  107. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_audit.py +0 -0
  108. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_budget.py +0 -0
  109. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_circuit_breaker.py +0 -0
  110. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_context.py +0 -0
  111. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_errors.py +0 -0
  112. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_guardrails.py +0 -0
  113. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_llm_decisions.py +0 -0
  114. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_observe.py +0 -0
  115. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_openclaw.py +0 -0
  116. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_pipeline.py +0 -0
  117. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_pipeline_poml.py +0 -0
  118. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_policy.py +0 -0
  119. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_redact.py +0 -0
  120. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_schema_discovery.py +0 -0
  121. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_scopes.py +0 -0
  122. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_sdk_adapters.py +0 -0
  123. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_server.py +0 -0
  124. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_skill.py +0 -0
  125. {agenthandler-0.93.0 → agenthandler-0.94.0}/tests/test_streams.py +0 -0
  126. {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 (for openclaw-plugin)
26
+ # Node
27
27
  node_modules/
28
- openclaw-plugin/dist/
29
28
 
30
29
  # Environment
31
30
  .env
32
31
  .env.*
33
32
 
34
- # Internal docs (not for public repos)
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/AgentHandler.git
9
- cd AgentHandler
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/ Python library (zero dependencies)
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
- openclaw-plugin/ TypeScript plugin template for OpenClaw
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
- # AgentHandler
2
-
3
-
4
-
5
-
6
- <img width="1536" height="1024" alt="image" src="https://github.com/user-attachments/assets/162a4e5e-ccdd-4152-b7e4-4addd2ef5743" />
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
- [![CI](https://github.com/arunvenkatadri/AgentHandler/actions/workflows/ci.yml/badge.svg)](https://github.com/arunvenkatadri/AgentHandler/actions/workflows/ci.yml)
57
+ [![CI](https://github.com/arunvenkatadri/agenthandler/actions/workflows/ci.yml/badge.svg)](https://github.com/arunvenkatadri/agenthandler/actions/workflows/ci.yml)
11
58
  [![Python 3.10+](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
12
59
  [![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-green.svg)](LICENSE)
13
- [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)]()
14
- [![Tests](https://img.shields.io/badge/tests-658-brightgreen.svg)]()
60
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-brightgreen.svg)](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 — use [Artemis](https://github.com/arunvenkatadri/artemis) or provide your own `/chat` and `/generate-prompt` endpoints.
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="Sdd" src="https://github.com/user-attachments/assets/b4fec9fa-59d4-4476-93ac-19684c835ab0" />
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. Each phase is its own LLM call, each phase is audited, and the reflection output feeds into the next think phase.
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
- Each cycle is fully supervised. Guardrails apply. Crashes are recoverable. Every reflection is in the audit log. This is what distinguishes "agent ran for 72 hours doing useful work" from "agent ran in circles for 72 hours."
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
- All three use at-least-once delivery — messages are acknowledged only after the handler completes successfully. If the agent crashes mid-processing, the message is redelivered.
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