[project] name = "attestor" version = "4.1.11" description = "Governed memory for multi-agent production meshes: role-based access, provenance on every memory, per-agent budgets, deterministic retrieval with no LLM in scoring by default, temporal supersession with as_of replay, hard tenant isolation, auditable forget and retention." readme = "README.md" license = "MIT" requires-python = ">=3.10,<4.0" authors = [{ name = "aarjay" }] keywords = ["ai", "agent", "memory", "multi-agent", "llm", "rbac", "namespace", "provenance", "postgres", "pinecone", "neo4j", "graph", "vector-search", "mcp", "agentic"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.14", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Software Development :: Libraries", "Topic :: Scientific/Engineering :: Artificial Intelligence", ] dependencies = [ "mcp>=1.0.0", "tomlkit>=0.12.0", "starlette>=0.27.0", "uvicorn>=0.23.0", # PyYAML is REQUIRED, not optional: attestor/config/loader.py imports # `yaml` on every get_stack() (embedder/model resolution), so a base # install without it dies with "No module named 'yaml'" on first use. "PyYAML>=6.0", # The default embedder route goes through the OpenAI SDK (also how the # local Ollama OpenAI-compatible endpoint is reached), so `openai` must # be present for any write to embed. Without it: "No module named 'openai'". "openai>=1.0.0", # Canonical stack: Postgres (document) + Pinecone (vector) + Neo4j (graph). # The codebase imports postgres + neo4j eagerly # (attestor/conversation/episodes.py, store/postgres_backend.py, # store/neo4j_backend.py), so they must be present at install time for # `import attestor.conversation` to even resolve. "psycopg2-binary>=2.9.0", "neo4j>=5.0.0", # Pinecone is the DEFAULT vector role (incl. Pinecone Local via `attestor # quickstart`), so it must ship in the base install — not an optional extra. # The local emulator path goes through `pinecone.grpc.PineconeGRPC`, and # pinecone 9.x DROPPED the `[grpc]` extra, so `grpcio` must be declared # explicitly or the local vector lane dies with "No module named 'pinecone'" # / a missing-grpc import. grpcio ships cp310–cp314 wheels (no source build). "pinecone>=5.0.0", "grpcio>=1.60.0", # `requests` is used by health probes and ad-hoc HTTP calls # throughout the codebase (e.g. cloud doctor, MCP health pings). "requests>=2.28.0", "python-dotenv (>=1.2.2,<2.0.0)", ] [project.urls] Homepage = "https://attestor.dev" Repository = "https://github.com/bolnet/attestor" Issues = "https://github.com/bolnet/attestor/issues" [project.optional-dependencies] extraction = ["openai>=1.0.0"] postgres = ["psycopg2-binary>=2.9.0"] neo4j = ["neo4j>=5.0.0"] pinecone = ["pinecone>=5.0.0"] cloud-embeddings = ["openai>=1.0.0"] voyage = ["voyageai>=0.3.0; python_version < '3.14'"] lambda = ["mangum>=0.17.0", "starlette>=0.27.0"] eval = ["braintrust>=0.0.150", "anthropic>=0.30.0", "PyYAML>=6.0"] # Durable governance jobs via Temporal (docs/plans/temporal-integration.md). # Opt-in: recall + hooks never import this; SDK needs Python >= 3.10. durable = ["temporalio>=1.32,<2"] all = [ "openai>=1.0.0", "psycopg2-binary>=2.9.0", "neo4j>=5.0.0", "pinecone>=5.0.0", "voyageai>=0.3.0; python_version < '3.14'", ] [project.scripts] attestor = "attestor.cli:main" [tool.poetry] packages = [{ include = "attestor" }] # Externalized prompt templates ship as .md files next to the loader. # Poetry includes data files inside packages by default once an # __init__.py exists, but listing them here makes the contract explicit # and survives any future packaging-format changes. include = [ { path = "attestor/extraction/prompts/*.md", format = ["sdist", "wheel"] }, # 2026 agent SDK distribution surface — Anthropic skills-2025-10-02 + # OpenAI Responses API both auto-discover SKILL.md by canonical path. # Shipping it inside the wheel means `pip install attestor` is enough # for any 2026 agent harness to pick the skill up. { path = "skills/*/SKILL.md", format = ["sdist", "wheel"] }, # Web UI assets — read-only dashboard, recall explorer, audit dashboard. # FileResponse serves them directly so they MUST be in the wheel. { path = "attestor/ui/static/*", format = ["sdist", "wheel"] }, { path = "attestor/ui/templates/*.html", format = ["sdist", "wheel"] }, # Local-default stack config — `attestor init` copies this to # ~/.attestor/attestor.yaml; it MUST be in the wheel or a fresh install # has no stack config and get_stack() fails. { path = "attestor/config/defaults/*.yaml", format = ["sdist", "wheel"] }, # Local Docker backends — `attestor quickstart` runs `docker compose` against # this bundled compose (+ its postgres build context), so the compose file, # the postgres Dockerfile, and the extension-init SQL MUST ship in the wheel. { path = "attestor/infra/local/docker-compose.yml", format = ["sdist", "wheel"] }, { path = "attestor/infra/local/postgres.Dockerfile", format = ["sdist", "wheel"] }, { path = "attestor/infra/local/init-extensions.sql", format = ["sdist", "wheel"] }, ] [tool.poetry.group.dev.dependencies] pytest = ">=7.0" pytest-cov = ">=4.0" pytest-asyncio = ">=0.23" requests = ">=2.28.0" twine = "^6.2.0" ruff = ">=0.6" mypy = ">=1.10" hypothesis = ">=6.100" [tool.ruff] line-length = 100 target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I", "N", "UP", "B", "SIM", "RUF", "ANN", "ASYNC", "S", "PT", "RET", "TCH", "FA"] ignore = [ "ANN101", # missing type for self "ANN102", # missing type for cls "ANN401", # Any in signatures (legitimate at boundaries) "S101", # assert (allowed in tests) "S603", # subprocess (we control inputs) "S607", # subprocess partial-path "B008", # function call in default arg (FastAPI/Starlette pattern) "RET504", # unnecessary assign-then-return (often clearer) ] [tool.ruff.lint.per-file-ignores] "tests/*" = ["ANN", "S", "PLR2004", "RUF"] "attestor/longmemeval*.py" = ["BLE001"] # eval harness intentionally swallows "attestor/locomo.py" = ["BLE001"] "attestor/cli.py" = ["T201"] # CLI prints "attestor/doctor_v4.py" = ["T201"] # doctor prints diagnostics "attestor/init_wizard.py" = ["T201"] # interactive wizard prints "attestor/_branding.py" = ["T201"] "experiments/*" = ["ANN", "T201", "BLE001"] [tool.mypy] python_version = "3.10" strict = true warn_unreachable = true warn_redundant_casts = true files = [ "attestor/__init__.py", "attestor/context.py", "attestor/models.py", "attestor/client.py", ] # expand `files` module-by-module as the codebase tightens [[tool.mypy.overrides]] module = ["psycopg2.*", "neo4j.*", "pinecone.*", "voyageai.*", "mcp.*", "starlette.*", "uvicorn.*", "tomlkit.*"] ignore_missing_imports = true [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" markers = [ "docker: tests requiring Docker (deselect with '-m not docker')", "unit: fast, dependency-free unit tests", "integration: tests that touch real services or the filesystem beyond fixtures", "live: tests that hit the live attestor_v4_test Postgres DB and clobber its schema; deselect during bench runs with '-m \"not live\"'", ]