autonomous-agent-algos 0.1.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.
- autonomous_agent_algos-0.1.0/.github/workflows/ci.yml +103 -0
- autonomous_agent_algos-0.1.0/.github/workflows/docs.yml +63 -0
- autonomous_agent_algos-0.1.0/.github/workflows/release.yml +111 -0
- autonomous_agent_algos-0.1.0/.gitignore +7 -0
- autonomous_agent_algos-0.1.0/LICENSE +21 -0
- autonomous_agent_algos-0.1.0/PKG-INFO +276 -0
- autonomous_agent_algos-0.1.0/README.md +229 -0
- autonomous_agent_algos-0.1.0/docs/about/changelog.md +81 -0
- autonomous_agent_algos-0.1.0/docs/about/contributing.md +89 -0
- autonomous_agent_algos-0.1.0/docs/about/design-decisions.md +79 -0
- autonomous_agent_algos-0.1.0/docs/about/license.md +7 -0
- autonomous_agent_algos-0.1.0/docs/about/limitations.md +102 -0
- autonomous_agent_algos-0.1.0/docs/concepts/architecture.md +76 -0
- autonomous_agent_algos-0.1.0/docs/concepts/data-model.md +95 -0
- autonomous_agent_algos-0.1.0/docs/concepts/delivery-and-idempotency.md +57 -0
- autonomous_agent_algos-0.1.0/docs/concepts/events-and-the-log.md +105 -0
- autonomous_agent_algos-0.1.0/docs/concepts/projections-and-snapshots.md +100 -0
- autonomous_agent_algos-0.1.0/docs/concepts/runs-and-the-engine.md +134 -0
- autonomous_agent_algos-0.1.0/docs/getting-started/installation.md +72 -0
- autonomous_agent_algos-0.1.0/docs/getting-started/mental-model.md +66 -0
- autonomous_agent_algos-0.1.0/docs/getting-started/quickstart.md +99 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/custom.md +84 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/ensembles-rag.md +64 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/index.md +61 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/plan-and-execute.md +81 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/refine.md +92 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/router-supervisor.md +74 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/tool-loop-react.md +104 -0
- autonomous_agent_algos-0.1.0/docs/guides/algorithms/workflow-fsm.md +80 -0
- autonomous_agent_algos-0.1.0/docs/guides/clusters.md +151 -0
- autonomous_agent_algos-0.1.0/docs/guides/context.md +81 -0
- autonomous_agent_algos-0.1.0/docs/guides/extending.md +93 -0
- autonomous_agent_algos-0.1.0/docs/guides/human-approval.md +67 -0
- autonomous_agent_algos-0.1.0/docs/guides/large-payloads.md +61 -0
- autonomous_agent_algos-0.1.0/docs/guides/llm-gateways.md +196 -0
- autonomous_agent_algos-0.1.0/docs/guides/mcp.md +110 -0
- autonomous_agent_algos-0.1.0/docs/guides/memory.md +194 -0
- autonomous_agent_algos-0.1.0/docs/guides/persistence.md +90 -0
- autonomous_agent_algos-0.1.0/docs/guides/privacy.md +170 -0
- autonomous_agent_algos-0.1.0/docs/guides/streaming.md +50 -0
- autonomous_agent_algos-0.1.0/docs/guides/testing.md +122 -0
- autonomous_agent_algos-0.1.0/docs/guides/tool-guard.md +157 -0
- autonomous_agent_algos-0.1.0/docs/guides/tools.md +128 -0
- autonomous_agent_algos-0.1.0/docs/index.md +107 -0
- autonomous_agent_algos-0.1.0/docs/operations/configuration.md +81 -0
- autonomous_agent_algos-0.1.0/docs/operations/deployment.md +63 -0
- autonomous_agent_algos-0.1.0/docs/operations/performance.md +60 -0
- autonomous_agent_algos-0.1.0/docs/operations/security.md +77 -0
- autonomous_agent_algos-0.1.0/docs/operations/troubleshooting.md +113 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/algos.md +56 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/context.md +35 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/core.md +43 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/data.md +43 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/events.md +59 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/index.md +18 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/llm.md +68 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/mcp.md +15 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/memory.md +55 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/runtime.md +37 -0
- autonomous_agent_algos-0.1.0/docs/reference/api/tools.md +27 -0
- autonomous_agent_algos-0.1.0/docs/reference/event-catalog.md +82 -0
- autonomous_agent_algos-0.1.0/docs/reference/outcomes-and-errors.md +56 -0
- autonomous_agent_algos-0.1.0/examples/cluster_client.py +30 -0
- autonomous_agent_algos-0.1.0/examples/cluster_worker.py +38 -0
- autonomous_agent_algos-0.1.0/examples/context_management.py +66 -0
- autonomous_agent_algos-0.1.0/examples/custom_algorithm.py +77 -0
- autonomous_agent_algos-0.1.0/examples/data_codecs.py +56 -0
- autonomous_agent_algos-0.1.0/examples/ensembles.py +35 -0
- autonomous_agent_algos-0.1.0/examples/generate_test_repair.py +42 -0
- autonomous_agent_algos-0.1.0/examples/inspect_log.py +35 -0
- autonomous_agent_algos-0.1.0/examples/large_payloads.py +42 -0
- autonomous_agent_algos-0.1.0/examples/llm_gateways.py +53 -0
- autonomous_agent_algos-0.1.0/examples/mcp_tools.py +71 -0
- autonomous_agent_algos-0.1.0/examples/memory_agent.py +63 -0
- autonomous_agent_algos-0.1.0/examples/model_router.py +58 -0
- autonomous_agent_algos-0.1.0/examples/persistence_recovery.py +69 -0
- autonomous_agent_algos-0.1.0/examples/plan_and_execute.py +56 -0
- autonomous_agent_algos-0.1.0/examples/privacy_tenancy.py +67 -0
- autonomous_agent_algos-0.1.0/examples/quickstart.py +38 -0
- autonomous_agent_algos-0.1.0/examples/rag_agent.py +43 -0
- autonomous_agent_algos-0.1.0/examples/router_supervisor.py +66 -0
- autonomous_agent_algos-0.1.0/examples/streaming.py +26 -0
- autonomous_agent_algos-0.1.0/examples/testing_replay.py +41 -0
- autonomous_agent_algos-0.1.0/examples/tool_approval.py +47 -0
- autonomous_agent_algos-0.1.0/examples/tool_guard.py +59 -0
- autonomous_agent_algos-0.1.0/examples/workflow_fsm.py +70 -0
- autonomous_agent_algos-0.1.0/mkdocs.yml +130 -0
- autonomous_agent_algos-0.1.0/pyproject.toml +67 -0
- autonomous_agent_algos-0.1.0/scripts/bench_growth.py +82 -0
- autonomous_agent_algos-0.1.0/scripts/live_smoke.py +445 -0
- autonomous_agent_algos-0.1.0/src/aaa/__init__.py +10 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/__init__.py +26 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/base.py +78 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/control.py +183 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/ensemble.py +173 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/loops.py +333 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/plan.py +265 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/refine.py +163 -0
- autonomous_agent_algos-0.1.0/src/aaa/algos/route.py +93 -0
- autonomous_agent_algos-0.1.0/src/aaa/context/__init__.py +30 -0
- autonomous_agent_algos-0.1.0/src/aaa/context/policy.py +199 -0
- autonomous_agent_algos-0.1.0/src/aaa/context/strategies.py +353 -0
- autonomous_agent_algos-0.1.0/src/aaa/context/tokens.py +115 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/__init__.py +33 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/aio.py +50 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/errors.py +30 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/ids.py +32 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/outcome.py +30 -0
- autonomous_agent_algos-0.1.0/src/aaa/core/retry.py +152 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/__init__.py +27 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/blob.py +87 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/capabilities.py +40 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/codec.py +102 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/index.py +115 -0
- autonomous_agent_algos-0.1.0/src/aaa/data/item.py +41 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/__init__.py +33 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/bus.py +277 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/cloudevents.py +78 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/distributed.py +500 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/event.py +81 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/guard.py +67 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/postgres.py +428 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/projection.py +95 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/redis.py +361 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/schema.py +55 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/snapshot.py +24 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/sqlite.py +222 -0
- autonomous_agent_algos-0.1.0/src/aaa/events/store.py +157 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/__init__.py +45 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/client.py +26 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/config.py +39 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/embedder.py +14 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/errors.py +41 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/factory.py +30 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/fake.py +93 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/gateways.py +75 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/handler.py +257 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/litellm_client.py +119 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/middleware.py +113 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/openai_compat.py +261 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/streaming.py +36 -0
- autonomous_agent_algos-0.1.0/src/aaa/llm/types.py +70 -0
- autonomous_agent_algos-0.1.0/src/aaa/mcp/__init__.py +6 -0
- autonomous_agent_algos-0.1.0/src/aaa/mcp/client.py +364 -0
- autonomous_agent_algos-0.1.0/src/aaa/mcp/memory.py +125 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/__init__.py +46 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/base.py +96 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/callable.py +152 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/fake.py +116 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/graphiti.py +180 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/handler.py +226 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/langgraph.py +179 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/mem0.py +158 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/native.py +177 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/supermemory.py +264 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/tools.py +76 -0
- autonomous_agent_algos-0.1.0/src/aaa/memory/types.py +138 -0
- autonomous_agent_algos-0.1.0/src/aaa/privacy.py +264 -0
- autonomous_agent_algos-0.1.0/src/aaa/py.typed +0 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/__init__.py +18 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/budget.py +57 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/engine.py +834 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/recovery.py +136 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/retention.py +171 -0
- autonomous_agent_algos-0.1.0/src/aaa/runtime/tenancy.py +106 -0
- autonomous_agent_algos-0.1.0/src/aaa/tools/__init__.py +29 -0
- autonomous_agent_algos-0.1.0/src/aaa/tools/guard.py +104 -0
- autonomous_agent_algos-0.1.0/src/aaa/tools/handler.py +436 -0
- autonomous_agent_algos-0.1.0/src/aaa/tools/rag.py +28 -0
- autonomous_agent_algos-0.1.0/src/aaa/tools/tool.py +225 -0
- autonomous_agent_algos-0.1.0/src/aaa/validate/__init__.py +79 -0
- autonomous_agent_algos-0.1.0/tests/cluster.py +101 -0
- autonomous_agent_algos-0.1.0/tests/conftest.py +219 -0
- autonomous_agent_algos-0.1.0/tests/helpers/mcp_server.py +93 -0
- autonomous_agent_algos-0.1.0/tests/helpers/memory_stubs.py +382 -0
- autonomous_agent_algos-0.1.0/tests/helpers/victim.py +46 -0
- autonomous_agent_algos-0.1.0/tests/helpers/worker.py +70 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_aio.py +96 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_algos.py +372 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_cluster_engine.py +222 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_context.py +423 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_data.py +49 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_distributed_bus.py +288 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_docs_snippets.py +40 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_engine.py +95 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_events.py +104 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_examples.py +163 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_growth.py +276 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_llm.py +313 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_loops.py +216 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_mcp.py +267 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_memory_adapters.py +253 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_memory_contract.py +250 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_memory_runtime.py +278 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_memory_signatures.py +137 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_multiprocess.py +235 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_privacy.py +327 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_recovery.py +353 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_servers_smoke.py +10 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_snapshots.py +172 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_storage.py +50 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_store_contract.py +267 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_store_outage.py +373 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_store_outage_cluster.py +211 -0
- autonomous_agent_algos-0.1.0/tests/unit/test_tool_guard.py +445 -0
- autonomous_agent_algos-0.1.0/uv.lock +4340 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
# Lint + type-check, then the full test suite against REAL Postgres and Redis, on Python 3.11 and 3.12.
|
|
4
|
+
# (Docs have their own workflow, docs.yml, which also runs the docs tests before publishing.)
|
|
5
|
+
|
|
6
|
+
on:
|
|
7
|
+
push:
|
|
8
|
+
branches: [master]
|
|
9
|
+
pull_request:
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
schedule:
|
|
12
|
+
- cron: "17 5 * * 1" # weekly: do the memory libraries' newest releases still match our adapters?
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
concurrency:
|
|
18
|
+
group: ci-${{ github.ref }}
|
|
19
|
+
cancel-in-progress: true
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
lint:
|
|
23
|
+
runs-on: ubuntu-latest
|
|
24
|
+
timeout-minutes: 10
|
|
25
|
+
steps:
|
|
26
|
+
- uses: actions/checkout@v4
|
|
27
|
+
- uses: astral-sh/setup-uv@v5
|
|
28
|
+
with:
|
|
29
|
+
enable-cache: true
|
|
30
|
+
- run: uv sync --frozen
|
|
31
|
+
- name: ruff (lint)
|
|
32
|
+
run: uv run ruff check .
|
|
33
|
+
- name: ruff (format)
|
|
34
|
+
run: uv run ruff format --check .
|
|
35
|
+
- name: pyright (src, examples, scripts)
|
|
36
|
+
run: uv run pyright
|
|
37
|
+
|
|
38
|
+
test:
|
|
39
|
+
runs-on: ubuntu-latest
|
|
40
|
+
timeout-minutes: 15
|
|
41
|
+
strategy:
|
|
42
|
+
fail-fast: false
|
|
43
|
+
matrix:
|
|
44
|
+
python: ["3.11", "3.12"]
|
|
45
|
+
services:
|
|
46
|
+
postgres:
|
|
47
|
+
image: postgres:16
|
|
48
|
+
env:
|
|
49
|
+
POSTGRES_PASSWORD: aaa
|
|
50
|
+
ports: ["5432:5432"]
|
|
51
|
+
options: >-
|
|
52
|
+
--health-cmd "pg_isready -U postgres"
|
|
53
|
+
--health-interval 5s
|
|
54
|
+
--health-timeout 5s
|
|
55
|
+
--health-retries 10
|
|
56
|
+
redis:
|
|
57
|
+
image: redis:7
|
|
58
|
+
ports: ["6379:6379"]
|
|
59
|
+
options: >-
|
|
60
|
+
--health-cmd "redis-cli ping"
|
|
61
|
+
--health-interval 5s
|
|
62
|
+
--health-timeout 5s
|
|
63
|
+
--health-retries 10
|
|
64
|
+
env:
|
|
65
|
+
# The suite creates a throwaway database per test and isolates Redis by key prefix.
|
|
66
|
+
AAA_TEST_POSTGRES_URL: postgresql://postgres:aaa@localhost:5432/postgres
|
|
67
|
+
AAA_TEST_REDIS_URL: redis://localhost:6379/0
|
|
68
|
+
steps:
|
|
69
|
+
- uses: actions/checkout@v4
|
|
70
|
+
- uses: astral-sh/setup-uv@v5
|
|
71
|
+
with:
|
|
72
|
+
enable-cache: true
|
|
73
|
+
python-version: ${{ matrix.python }}
|
|
74
|
+
- run: uv sync --frozen
|
|
75
|
+
- name: tests
|
|
76
|
+
# -v names every test, and a test stuck for 2 minutes dumps all thread stacks, so a hang is
|
|
77
|
+
# diagnosable from the log instead of just a cancelled job.
|
|
78
|
+
run: uv run pytest -v -p no:cacheprovider -o faulthandler_timeout=120
|
|
79
|
+
|
|
80
|
+
adapters:
|
|
81
|
+
# The memory adapters are tested against stand-ins; this job replays their calls against the REAL
|
|
82
|
+
# Mem0, Graphiti and LangGraph signatures (tests/unit/test_memory_signatures.py), which are skipped
|
|
83
|
+
# in the main job because those libraries are not dev dependencies.
|
|
84
|
+
runs-on: ubuntu-latest
|
|
85
|
+
timeout-minutes: 15
|
|
86
|
+
steps:
|
|
87
|
+
- uses: actions/checkout@v4
|
|
88
|
+
- uses: astral-sh/setup-uv@v5
|
|
89
|
+
with:
|
|
90
|
+
enable-cache: true
|
|
91
|
+
- name: Locked versions
|
|
92
|
+
run: |
|
|
93
|
+
uv sync --frozen --extra mem0 --extra graphiti --extra langgraph --extra langmem --extra mcp
|
|
94
|
+
uv run pytest -v -p no:cacheprovider tests/unit/test_memory_signatures.py tests/unit/test_memory_adapters.py tests/unit/test_memory_contract.py tests/unit/test_mcp.py tests/unit/test_examples.py -k "not cluster"
|
|
95
|
+
- name: MCP SDK 1.x (the lock resolves 2.x; both are supported)
|
|
96
|
+
run: |
|
|
97
|
+
uv pip install "mcp<2"
|
|
98
|
+
uv run --no-sync pytest -v -p no:cacheprovider tests/unit/test_mcp.py
|
|
99
|
+
- name: Newest releases (scheduled and manual runs only)
|
|
100
|
+
if: github.event_name != 'pull_request' && github.event_name != 'push'
|
|
101
|
+
run: |
|
|
102
|
+
uv pip install --upgrade mem0ai graphiti-core langgraph langmem mcp
|
|
103
|
+
uv run --no-sync pytest -v -p no:cacheprovider tests/unit/test_memory_signatures.py tests/unit/test_mcp.py
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
name: docs
|
|
2
|
+
|
|
3
|
+
# Builds the documentation (strictly) and publishes it to GitHub Pages on every push to master that
|
|
4
|
+
# touches the docs, the examples they embed, the source their API reference is generated from, or
|
|
5
|
+
# this workflow. A broken example or an invalid fragment blocks publishing.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [master]
|
|
10
|
+
paths:
|
|
11
|
+
- "docs/**"
|
|
12
|
+
- "examples/**"
|
|
13
|
+
- "src/**"
|
|
14
|
+
- "mkdocs.yml"
|
|
15
|
+
- "pyproject.toml"
|
|
16
|
+
- "uv.lock"
|
|
17
|
+
- "LICENSE"
|
|
18
|
+
- ".github/workflows/docs.yml"
|
|
19
|
+
workflow_dispatch:
|
|
20
|
+
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
pages: write
|
|
24
|
+
id-token: write
|
|
25
|
+
|
|
26
|
+
concurrency:
|
|
27
|
+
group: pages
|
|
28
|
+
cancel-in-progress: false
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
build:
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
|
|
36
|
+
- uses: astral-sh/setup-uv@v5
|
|
37
|
+
with:
|
|
38
|
+
enable-cache: true
|
|
39
|
+
|
|
40
|
+
- name: Install (docs extra + dev group)
|
|
41
|
+
run: uv sync --extra docs --frozen
|
|
42
|
+
|
|
43
|
+
- name: Every embedded example runs, and every inline fragment is valid Python
|
|
44
|
+
run: uv run pytest -q -p no:cacheprovider tests/unit/test_examples.py tests/unit/test_docs_snippets.py
|
|
45
|
+
|
|
46
|
+
- name: "Build (strict - warnings fail the build)"
|
|
47
|
+
run: uv run mkdocs build --strict
|
|
48
|
+
|
|
49
|
+
- uses: actions/configure-pages@v5
|
|
50
|
+
|
|
51
|
+
- uses: actions/upload-pages-artifact@v3
|
|
52
|
+
with:
|
|
53
|
+
path: site
|
|
54
|
+
|
|
55
|
+
deploy:
|
|
56
|
+
needs: build
|
|
57
|
+
runs-on: ubuntu-latest
|
|
58
|
+
environment:
|
|
59
|
+
name: github-pages
|
|
60
|
+
url: ${{ steps.deployment.outputs.page_url }}
|
|
61
|
+
steps:
|
|
62
|
+
- id: deployment
|
|
63
|
+
uses: actions/deploy-pages@v4
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
name: release
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI when a GitHub Release is published, using PyPI "Trusted Publishing" (OIDC): no API
|
|
4
|
+
# token is stored in this repository. One-time setup is described in docs/about/contributing.md.
|
|
5
|
+
#
|
|
6
|
+
# * publish a GitHub Release tagged vX.Y.Z -> builds, verifies, publishes to PyPI
|
|
7
|
+
# * run manually (workflow_dispatch) -> builds, verifies, publishes to TestPyPI (a dry run)
|
|
8
|
+
|
|
9
|
+
on:
|
|
10
|
+
release:
|
|
11
|
+
types: [published]
|
|
12
|
+
workflow_dispatch:
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
contents: read
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
timeout-minutes: 15
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: astral-sh/setup-uv@v5
|
|
24
|
+
- name: Tag matches the package version
|
|
25
|
+
if: github.event_name == 'release'
|
|
26
|
+
run: |
|
|
27
|
+
tag="${GITHUB_REF_NAME#v}"
|
|
28
|
+
version="$(uv run --no-project python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])")"
|
|
29
|
+
if [ "$tag" != "$version" ]; then
|
|
30
|
+
echo "::error::release tag '$GITHUB_REF_NAME' does not match pyproject version '$version'"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
- name: Build sdist and wheel
|
|
34
|
+
run: uv build
|
|
35
|
+
- name: Check metadata and README rendering
|
|
36
|
+
run: uvx twine check --strict dist/*
|
|
37
|
+
- name: The built wheel installs and runs in a clean environment
|
|
38
|
+
run: |
|
|
39
|
+
uv venv /tmp/smoke --python 3.12
|
|
40
|
+
uv pip install --python /tmp/smoke/bin/python dist/*.whl
|
|
41
|
+
/tmp/smoke/bin/python -I examples/quickstart.py | grep -q "success - 17 x 23 = 391"
|
|
42
|
+
/tmp/smoke/bin/python -I -c "import aaa; print('aaa', aaa.__version__)"
|
|
43
|
+
- uses: actions/upload-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
if-no-files-found: error
|
|
48
|
+
|
|
49
|
+
test:
|
|
50
|
+
# The release gate: the same checks as CI, on the commit being released.
|
|
51
|
+
needs: build
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
timeout-minutes: 20
|
|
54
|
+
services:
|
|
55
|
+
postgres:
|
|
56
|
+
image: postgres:16
|
|
57
|
+
env:
|
|
58
|
+
POSTGRES_PASSWORD: aaa
|
|
59
|
+
ports: ["5432:5432"]
|
|
60
|
+
options: >-
|
|
61
|
+
--health-cmd "pg_isready -U postgres" --health-interval 5s --health-timeout 5s --health-retries 10
|
|
62
|
+
redis:
|
|
63
|
+
image: redis:7
|
|
64
|
+
ports: ["6379:6379"]
|
|
65
|
+
options: >-
|
|
66
|
+
--health-cmd "redis-cli ping" --health-interval 5s --health-timeout 5s --health-retries 10
|
|
67
|
+
env:
|
|
68
|
+
AAA_TEST_POSTGRES_URL: postgresql://postgres:aaa@localhost:5432/postgres
|
|
69
|
+
AAA_TEST_REDIS_URL: redis://localhost:6379/0
|
|
70
|
+
steps:
|
|
71
|
+
- uses: actions/checkout@v4
|
|
72
|
+
- uses: astral-sh/setup-uv@v5
|
|
73
|
+
with:
|
|
74
|
+
enable-cache: true
|
|
75
|
+
- run: uv sync --frozen
|
|
76
|
+
- run: uv run ruff check . && uv run ruff format --check . && uv run pyright
|
|
77
|
+
- run: uv run pytest -v -p no:cacheprovider -o faulthandler_timeout=120
|
|
78
|
+
|
|
79
|
+
publish-pypi:
|
|
80
|
+
if: github.event_name == 'release'
|
|
81
|
+
needs: [build, test]
|
|
82
|
+
runs-on: ubuntu-latest
|
|
83
|
+
environment:
|
|
84
|
+
name: pypi
|
|
85
|
+
url: https://pypi.org/p/autonomous-agent-algos
|
|
86
|
+
permissions:
|
|
87
|
+
id-token: write # required for Trusted Publishing
|
|
88
|
+
steps:
|
|
89
|
+
- uses: actions/download-artifact@v4
|
|
90
|
+
with:
|
|
91
|
+
name: dist
|
|
92
|
+
path: dist/
|
|
93
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
94
|
+
|
|
95
|
+
publish-testpypi:
|
|
96
|
+
if: github.event_name == 'workflow_dispatch'
|
|
97
|
+
needs: [build, test]
|
|
98
|
+
runs-on: ubuntu-latest
|
|
99
|
+
environment:
|
|
100
|
+
name: testpypi
|
|
101
|
+
url: https://test.pypi.org/p/autonomous-agent-algos
|
|
102
|
+
permissions:
|
|
103
|
+
id-token: write
|
|
104
|
+
steps:
|
|
105
|
+
- uses: actions/download-artifact@v4
|
|
106
|
+
with:
|
|
107
|
+
name: dist
|
|
108
|
+
path: dist/
|
|
109
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
110
|
+
with:
|
|
111
|
+
repository-url: https://test.pypi.org/legacy/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Innerkore Technologies
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: autonomous-agent-algos
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async, event-driven library of production-deployable agent algorithms over arbitrary data.
|
|
5
|
+
Project-URL: Homepage, https://github.com/innerkorehq/AutonomousAgentAlgos
|
|
6
|
+
Project-URL: Documentation, https://innerkorehq.github.io/AutonomousAgentAlgos/
|
|
7
|
+
Project-URL: Source, https://github.com/innerkorehq/AutonomousAgentAlgos
|
|
8
|
+
Project-URL: Issues, https://github.com/innerkorehq/AutonomousAgentAlgos/issues
|
|
9
|
+
Author: Innerkore Technologies
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: agents,asyncio,event-sourcing,llm,planning,react,tool-calling,workflow
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Framework :: AsyncIO
|
|
15
|
+
Classifier: Intended Audience :: Developers
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.11
|
|
22
|
+
Requires-Dist: anyio>=4.4
|
|
23
|
+
Requires-Dist: httpx>=0.27
|
|
24
|
+
Requires-Dist: pydantic>=2.7
|
|
25
|
+
Provides-Extra: docs
|
|
26
|
+
Requires-Dist: mkdocs-material>=9.5; extra == 'docs'
|
|
27
|
+
Requires-Dist: mkdocs<2,>=1.6; extra == 'docs'
|
|
28
|
+
Requires-Dist: mkdocstrings[python]>=0.26; extra == 'docs'
|
|
29
|
+
Provides-Extra: graphiti
|
|
30
|
+
Requires-Dist: graphiti-core>=0.30; extra == 'graphiti'
|
|
31
|
+
Provides-Extra: langgraph
|
|
32
|
+
Requires-Dist: langgraph>=1.0; extra == 'langgraph'
|
|
33
|
+
Provides-Extra: langmem
|
|
34
|
+
Requires-Dist: langgraph>=1.0; extra == 'langmem'
|
|
35
|
+
Requires-Dist: langmem>=0.0.30; extra == 'langmem'
|
|
36
|
+
Provides-Extra: litellm
|
|
37
|
+
Requires-Dist: litellm; extra == 'litellm'
|
|
38
|
+
Provides-Extra: mcp
|
|
39
|
+
Requires-Dist: mcp<3,>=1.12; extra == 'mcp'
|
|
40
|
+
Provides-Extra: mem0
|
|
41
|
+
Requires-Dist: mem0ai>=2.0; extra == 'mem0'
|
|
42
|
+
Provides-Extra: postgres
|
|
43
|
+
Requires-Dist: asyncpg>=0.29; extra == 'postgres'
|
|
44
|
+
Provides-Extra: redis
|
|
45
|
+
Requires-Dist: redis>=5.0; extra == 'redis'
|
|
46
|
+
Description-Content-Type: text/markdown
|
|
47
|
+
|
|
48
|
+
# AutonomousAgentAlgos (AAA)
|
|
49
|
+
|
|
50
|
+
Async, **event-driven** library of production-deployable agent algorithms that run over
|
|
51
|
+
**arbitrary user data**, with a pluggable LLM gateway layer.
|
|
52
|
+
|
|
53
|
+
**Documentation: <https://innerkorehq.github.io/AutonomousAgentAlgos/>** — quickstart, concepts, guides for every
|
|
54
|
+
algorithm, clustering, the tool execution guard, operations, security, API reference, and an honest
|
|
55
|
+
[status and limitations](https://innerkorehq.github.io/AutonomousAgentAlgos/about/limitations/) page.
|
|
56
|
+
The README below is a condensed overview; the docs are the reference.
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
async with Engine() as eng:
|
|
60
|
+
LLMHandler(build_client(gateways.portkey("gpt-4o"))).attach(eng)
|
|
61
|
+
ToolHandler(registry).attach(eng)
|
|
62
|
+
outcome = await eng.run(
|
|
63
|
+
ToolLoop(registry), {"task": "..."}, budget=Budget(max_cost=0.50, deadline_s=60)
|
|
64
|
+
)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Design decisions (data first)
|
|
68
|
+
1. **The event log is the source of truth.** Every run is an append-only stream of immutable
|
|
69
|
+
CloudEvents-compatible `Event`s. State, plans, conversation, spend are folds of the log.
|
|
70
|
+
Algorithms hold *no* run state, so runs replay, resume and audit by construction.
|
|
71
|
+
2. **User data is a payload, never the algorithm's type.** `Item[T]` + per-`kind` `Codec`
|
|
72
|
+
(Pydantic / dataclass / dict / str / custom `FunctionCodec`) render, serialize and
|
|
73
|
+
fingerprint any type. Algorithms ask only for the capabilities they need.
|
|
74
|
+
3. **Large payloads are claim-checked** into a content-addressed `BlobStore`; events stay small.
|
|
75
|
+
Handlers and algorithms see hydrated data transparently.
|
|
76
|
+
4. **Failures are facts, not exceptions.** `aaa.llm.failed`, `aaa.tool.failed` are events the
|
|
77
|
+
algorithm reasons about. Outcomes are distinct: success / no_solution / timeout /
|
|
78
|
+
budget_exceeded / invalid_input / cancelled / failed.
|
|
79
|
+
5. **Safety lives outside the model**: budgets, deadlines, approval gates, and idempotency are
|
|
80
|
+
enforced by the Engine/ToolHandler, not by prompts.
|
|
81
|
+
6. **Ordering**: each algorithm sees its events serially in log order; delivery is in-process
|
|
82
|
+
at-most-once (distributed at-least-once adapters are future work).
|
|
83
|
+
7. **Durable and crash-resumable.** `SQLiteEventStore` (stdlib, WAL, atomic OCC append,
|
|
84
|
+
idempotent on event id) persists the log. After a crash, `engine.recover([...algorithms])`
|
|
85
|
+
rebuilds budgets, spend and deadlines from the log, then redelivers events nothing reacted to.
|
|
86
|
+
This is safe because algorithms decide from folded state, and every derived event has a
|
|
87
|
+
deterministic id, so a repeated reaction is absorbed by the store instead of duplicating.
|
|
88
|
+
|
|
89
|
+
## MCP and context management
|
|
90
|
+
`aaa.mcp.MCPClient` connects to any MCP server (stdio, HTTP, or your own session; mcp SDK 1.x and 2.x) and turns its tools into guarded AAA tools,
|
|
91
|
+
with MCP's annotations driving the retry/approval policy. `LLMHandler(client, context=ContextPolicy(max_input_tokens=...))` keeps long runs inside the
|
|
92
|
+
model's window by clearing old tool output, summarizing old turns (summaries are persisted events) or dropping them, without rewriting the log.
|
|
93
|
+
|
|
94
|
+
## Long-term memory
|
|
95
|
+
`aaa.memory` gives agents memory across runs behind one small protocol, with adapters for **Mem0**, **Graphiti** (Zep's engine),
|
|
96
|
+
**Supermemory** and **LangGraph/LangMem** stores, a built-in `NativeMemory`, and `CallableMemory` to wrap any other library.
|
|
97
|
+
Memory is scoped by the run's tenant and user (never by model output), goes through the event log (replayable, redacted), and
|
|
98
|
+
is erasable per user.
|
|
99
|
+
```python
|
|
100
|
+
MemoryHandler(Mem0Memory(AsyncMemory()), write_back=WriteBack()).attach(engine)
|
|
101
|
+
algo = ToolLoop(registry, recall=True) # recall before the first model call
|
|
102
|
+
await engine.tenant("acme").run(algo, {"task": "..."}, user="ann")
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Layout
|
|
106
|
+
`core` ids/outcomes/errors · `events` envelope, stores (memory/SQLite/Postgres/Redis), buses (in-process/distributed), projections+snapshots, schemas,
|
|
107
|
+
CloudEvents · `data` Item/Codec/BlobStore/Index · `llm` client protocol, OpenAI-compatible
|
|
108
|
+
adapter + gateway presets, LiteLLM, retry/breaker, streaming, fakes · `tools` typed registry +
|
|
109
|
+
handler · `validate` deterministic validators · `runtime` Engine/Budget · `algos`.
|
|
110
|
+
|
|
111
|
+
## Algorithms (v0.1)
|
|
112
|
+
ToolLoop, ReAct, PlanAndExecute (DAG, parallel child runs, replan), Router, GenerateTestRepair,
|
|
113
|
+
GeneratorCritic, WorkflowGraph (deterministic DAG), FSM, SelfConsistency, RAGAgent, Supervisor.
|
|
114
|
+
|
|
115
|
+
## Persistence, snapshots, and running as a cluster
|
|
116
|
+
|
|
117
|
+
| Store | Use for | Notes |
|
|
118
|
+
|---|---|---|
|
|
119
|
+
| `InMemoryEventStore` | tests, notebooks | nothing survives the process |
|
|
120
|
+
| `SQLiteEventStore` | one machine, durable | stdlib, WAL; single engine process |
|
|
121
|
+
| `PostgresEventStore` | production system of record | `pip install autonomous-agent-algos[postgres]` |
|
|
122
|
+
| `RedisEventStore` | low-latency, ephemeral-ish logs | `[redis]`; durability = your Redis AOF settings |
|
|
123
|
+
|
|
124
|
+
**Single process, restart safe** (`InProcessBus` + a durable store):
|
|
125
|
+
```python
|
|
126
|
+
async with Engine(store=SQLiteEventStore("agent.db")) as eng:
|
|
127
|
+
LLMHandler(client).attach(eng)
|
|
128
|
+
ToolHandler(registry).attach(eng)
|
|
129
|
+
handles = await eng.recover([ToolLoop(registry), PlanAndExecute(...)]) # after a crash
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Multi-process** (`DistributedBus` over Postgres or Redis; run the same worker code N times):
|
|
133
|
+
```python
|
|
134
|
+
store = await PostgresEventStore.create(DSN, partitions=16) # or RedisEventStore.create(URL, ...)
|
|
135
|
+
bus = DistributedBus(store.bus_backend())
|
|
136
|
+
async with Engine(store=store, bus=bus, sweep_interval_s=5) as eng:
|
|
137
|
+
LLMHandler(client).attach(eng)
|
|
138
|
+
ToolHandler(registry).attach(eng)
|
|
139
|
+
eng.register(ToolLoop(registry))
|
|
140
|
+
await asyncio.Event().wait() # a worker
|
|
141
|
+
# anywhere else (API server, CLI): submit and observe only
|
|
142
|
+
client = Engine(store=store, bus=ClientBus())
|
|
143
|
+
outcome = await client.run(ToolLoop(registry), {"task": "..."})
|
|
144
|
+
```
|
|
145
|
+
How it works: an event is appended to the store *and* the bus outbox in one atomic step (a
|
|
146
|
+
transaction in Postgres, one Lua script in Redis), so nothing stored can fail to be published. Events
|
|
147
|
+
go to partition `crc32(run_id) % P`. Within a consumer *group* (an algorithm, the LLM handler, the
|
|
148
|
+
tool handler...) each partition is owned by exactly one process at a time, consumed in order, and
|
|
149
|
+
acknowledged with a cursor; processes joining or dying rebalance partitions. Delivery is
|
|
150
|
+
at-least-once; derived events have deterministic ids and results are one-per-request
|
|
151
|
+
(`<request id>:result`), so repeats are absorbed by the store's idempotent append. Any process can
|
|
152
|
+
host any run: engine state (spend, completion, open children) is a fold of the log, rebuilt on
|
|
153
|
+
demand from a snapshot plus the tail.
|
|
154
|
+
|
|
155
|
+
**Snapshots**: every algorithm's state is an incremental `Projection`; state is snapshotted every 64
|
|
156
|
+
events and a load costs O(new events). A 40-turn run does ~16x fewer state folds, and the gap
|
|
157
|
+
grows with run length. A snapshot is derived data: a corrupt, stale-versioned, or too-new one is
|
|
158
|
+
ignored and the state is replayed from the log. Bump a projection's `version` if you change its fold.
|
|
159
|
+
|
|
160
|
+
### Tool execution guard (stalled and crashed workers)
|
|
161
|
+
Bus delivery is at-least-once, and a worker can die or stall (GC pause, VM freeze, network partition)
|
|
162
|
+
while a tool call is running. Three layers protect against a repeated side effect; each is configurable.
|
|
163
|
+
|
|
164
|
+
| Layer | What it does |
|
|
165
|
+
|---|---|
|
|
166
|
+
| **Durable ack** | The request stays unacknowledged on the bus until the tool finishes, so a worker that dies mid-call has the call redelivered to another. |
|
|
167
|
+
| **Exclusion** | A per-call execution lease with a heartbeat: two workers never run the same call concurrently while the first is alive. |
|
|
168
|
+
| **Fencing** | Every run is a numbered attempt recorded *before* it executes (`aaa.tool.started`, id `<request>:attempt:<n>`). A worker that loses its lease cancels its tool and records nothing; a result from a superseded attempt is rejected (`aaa.tool.stale_result`). |
|
|
169
|
+
| **Effect safety** | The tool receives `ctx.idempotency_key` (stable across attempts and workers), `ctx.attempt` and `ctx.fence` to make the external effect idempotent. |
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
from aaa.tools import GuardConfig, ToolContext, ToolHandler, tool
|
|
173
|
+
|
|
174
|
+
# global default for every tool
|
|
175
|
+
ToolHandler(registry, guard=GuardConfig(lease_ttl_s=30, max_attempts=3)).attach(engine)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@tool # inherits the default: re-run a lost call, up to 3 attempts
|
|
179
|
+
async def search(q: str) -> list: ...
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
@tool(delivery="at_most_once") # never re-run once an attempt started
|
|
183
|
+
async def send_email(to: str, body: str, ctx: ToolContext) -> str:
|
|
184
|
+
return await ses.send(to, body, dedupe_key=ctx.idempotency_key)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@tool(
|
|
188
|
+
idempotency_key=lambda a: f"refund:{a['order_id']}", # business-level key, stable across
|
|
189
|
+
guard={"max_attempts": 1, "lease_ttl_s": 60},
|
|
190
|
+
) # the model re-issuing the call
|
|
191
|
+
async def refund(order_id: str, ctx: ToolContext) -> str: ...
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
| `GuardConfig` field | Default | Meaning |
|
|
195
|
+
|---|---|---|
|
|
196
|
+
| `enabled` | `True` | `False`: no lease, no attempt records (the bus still redelivers a lost request, so a tool can run twice and nothing notices). |
|
|
197
|
+
| `delivery` | `at_least_once` | `at_least_once`: re-run a lost call up to `max_attempts`. `at_most_once`: a lost attempt becomes an `OutcomeUnknown` failure (the effect may or may not have happened) for the algorithm or a human to reconcile. |
|
|
198
|
+
| `max_attempts` | `3` | Cap on executions of one call; beyond it the call fails with `ExecutionAbandoned` (a poison call must not loop forever). |
|
|
199
|
+
| `lease_ttl_s` / `heartbeat_s` | `15` / `ttl/3` | How long a silent worker keeps its claim / how often it renews. Lower = faster takeover but more false positives from slow heartbeats. |
|
|
200
|
+
| `wait_poll_s` | `0.5` | How often a worker that found the call busy re-checks. |
|
|
201
|
+
| `on_lost_lease` | `cancel` | `cancel`: a worker that wakes up and finds it lost the lease stops its tool. `finish`: let it complete (its result is still fenced). |
|
|
202
|
+
| `fence_results` | `True` | Reject (and audit) a result from a superseded attempt. `False`: the first finisher wins. |
|
|
203
|
+
|
|
204
|
+
Per-tool settings override the handler default; inconsistent combinations (e.g. `heartbeat_s >= lease_ttl_s`)
|
|
205
|
+
fail at startup. **Backend semantics differ**: Redis and in-memory leases expire by TTL, so a *frozen*
|
|
206
|
+
worker is replaced after `lease_ttl_s`. Postgres leases are advisory locks bound to the database session:
|
|
207
|
+
a worker that crashes or loses its connection frees them instantly, but one that is frozen *and still
|
|
208
|
+
connected* keeps them, so nobody takes over until it resumes or the connection dies (use TCP keepalives /
|
|
209
|
+
`idle_session_timeout`, or Redis, if you need TTL takeover of frozen processes). The fence check
|
|
210
|
+
(`has(attempt n+1)` then append) is not one atomic store operation; its residual window is microseconds
|
|
211
|
+
and the result id is first-writer-wins, so the worst case is one recorded result, never two.
|
|
212
|
+
|
|
213
|
+
### Guarantees and caveats (read these)
|
|
214
|
+
- **Tool execution is guarded, and the guard is configurable** (next section). The library makes a
|
|
215
|
+
call run *at most once at a time* and *record exactly one result*; it cannot make an arbitrary
|
|
216
|
+
external effect (an email, a payment) exactly-once. That part belongs to the tool, via the
|
|
217
|
+
idempotency key the guard hands it.
|
|
218
|
+
- **Partition count is fixed** for the life of a cluster (checked on connect). Changing it needs a new
|
|
219
|
+
cluster or migration. Pick headroom (default 16).
|
|
220
|
+
- **Algorithm names must be unique and identical on every worker**, and every worker in a group must
|
|
221
|
+
register the same tools. Heterogeneous worker pools are not supported yet.
|
|
222
|
+
- **Outbox growth**: the bus log is not trimmed automatically. Call `store.trim_outbox(keep_last=...)`
|
|
223
|
+
from a periodic job, sized to your longest tolerable consumer outage.
|
|
224
|
+
- **Postgres**: a dropped LISTEN connection degrades latency to polling, never correctness. Appends to
|
|
225
|
+
the same partition serialize on one row lock (P-way write parallelism).
|
|
226
|
+
- **Redis**: with no persistence a restart loses the log. Works with Redis Cluster (hash-tagged keys)
|
|
227
|
+
but is only tested on a single node.
|
|
228
|
+
- Each consumer group scans all events of its partitions (read amplification ~ number of groups).
|
|
229
|
+
- Algorithms' `Engine.sweep()` (set `sweep_interval_s`) enforces deadlines of dead owners and repairs
|
|
230
|
+
lost child announcements; without it a run with a deadline whose host died waits for its next event.
|
|
231
|
+
- Tested with real separate OS processes and `SIGKILL` (including mid-tool-call) on Postgres and Redis;
|
|
232
|
+
not yet load tested.
|
|
233
|
+
|
|
234
|
+
## Scaling with run length
|
|
235
|
+
A continuing LLM request stores only the *new* messages plus a pointer to the previous request
|
|
236
|
+
(`conv: {prev, delta}`); algorithm state holds a request id, not the transcript. The LLM handler
|
|
237
|
+
rebuilds the full transcript the provider needs from an in-memory cache, falling back to one pass
|
|
238
|
+
over the run's log after a restart/failover/different worker. Tool-call idempotency is an O(1)
|
|
239
|
+
`store.has(stream, id)` lookup. `scripts/bench_growth.py` (SQLite, fake LLM, ~400-byte tool results):
|
|
240
|
+
|
|
241
|
+
| turns | log size before -> after | wall time before -> after |
|
|
242
|
+
|---|---|---|
|
|
243
|
+
| 50 | 999 KB -> 170 KB | 0.22 s -> 0.07 s |
|
|
244
|
+
| 100 | 3,667 KB -> 337 KB | 1.17 s -> 0.14 s |
|
|
245
|
+
| 200 | 14,032 KB -> 673 KB | 7.53 s -> 0.28 s |
|
|
246
|
+
| 400 | (not measured) -> 1,344 KB | -> 0.56 s |
|
|
247
|
+
|
|
248
|
+
Log size, state size and per-turn work are now constant per turn (doubling the turns doubles the
|
|
249
|
+
totals). What is *not* reduced is the provider payload itself: each call must still send the whole
|
|
250
|
+
transcript, so token cost per call grows with the conversation. Use provider prompt caching or
|
|
251
|
+
summarization for very long conversations. Custom algorithms should build continuing requests with
|
|
252
|
+
`llm_request(..., prev=<previous request id>)` and pass only the new messages.
|
|
253
|
+
|
|
254
|
+
## Live gateway smoke test
|
|
255
|
+
`scripts/live_smoke.py` checks the library against a real OpenAI-compatible endpoint: completion +
|
|
256
|
+
usage, streaming, structured output, native tool calls (also streamed), a full ToolLoop through the
|
|
257
|
+
Engine, PlanAndExecute with a real planner, generate-test-repair, delta events, and error mapping
|
|
258
|
+
(unknown model, bad key, unreachable host). It spends a few thousand tokens. Configure with env vars:
|
|
259
|
+
```bash
|
|
260
|
+
AAA_LIVE_BASE_URL=http://127.0.0.1:11434/v1 AAA_LIVE_MODEL=gemma4:latest uv run python scripts/live_smoke.py
|
|
261
|
+
# a cloud gateway: use the base URL / headers from aaa.llm.gateways, e.g. Portkey
|
|
262
|
+
AAA_LIVE_BASE_URL=https://api.portkey.ai/v1 AAA_LIVE_MODEL=gpt-4o-mini AAA_LIVE_API_KEY=$OPENAI_API_KEY \
|
|
263
|
+
AAA_LIVE_HEADERS='{"x-portkey-api-key": "...", "x-portkey-provider": "openai"}' uv run python scripts/live_smoke.py
|
|
264
|
+
```
|
|
265
|
+
Verified so far: a local Ollama (`gemma4:latest`, 8B) — 11/11 applicable checks. Not yet verified
|
|
266
|
+
against any hosted gateway (Portkey, Cloudflare, Vercel, Bifrost, Kong, MLflow, LiteLLM proxy): their
|
|
267
|
+
base URLs and headers follow vendor docs and are covered only by mocked-transport tests.
|
|
268
|
+
|
|
269
|
+
## Known limitations
|
|
270
|
+
- No sandboxed code-execution validator is shipped (deliberately).
|
|
271
|
+
- Structured-output `strict` mode may need schema adjustments per provider.
|
|
272
|
+
- Only a local Ollama has been tested live; hosted gateways are untested (see above).
|
|
273
|
+
|
|
274
|
+
## License
|
|
275
|
+
|
|
276
|
+
[MIT](https://github.com/innerkorehq/AutonomousAgentAlgos/blob/master/LICENSE) © 2026 Innerkore Technologies.
|