[build-system] requires = ["setuptools>=68.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "agent-coherence" dynamic = ["version"] description = "Vendor-neutral MESI + optimistic-concurrency coordinator for multi-agent state — a stale write is denied or returned as a typed conflict, never silently applied; safety invariants model-checked in TLA+" readme = "README.md" license = { text = "Apache-2.0" } authors = [{ name = "agent-coherence contributors" }] keywords = [ "multi-agent", "agents", "llm", "cache-coherence", "mesi", "optimistic-concurrency", "lost-update", "consistency", "coordination", "mcp", "langgraph", "crewai", "token-efficiency", ] classifiers = [ "Development Status :: 3 - Alpha", "Intended Audience :: Developers", "Intended Audience :: Science/Research", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", ] requires-python = ">=3.11" dependencies = ["pyyaml>=6.0"] [project.scripts] ccs-simulate = "ccs.cli.simulate:main" ccs-compare = "ccs.cli.compare:main" ccs-check-architecture = "ccs.hardening.architecture:main" ccs-benchmark = "ccs.cli.benchmark:main" ccs-diagnose = "ccs.cli.diagnose:console_main" # Claude Code coordinator console scripts. The plugin's bin/ensure-coordinator # shim wraps the first; the latter three back the /agent-coherence # {status,track,untrack} slash commands. agent-coherence-coordinator = "ccs.cli.coherence_coordinator:main" agent-coherence-status = "ccs.cli.coherence_status:main" agent-coherence-track = "ccs.cli.coherence_track:main" agent-coherence-untrack = "ccs.cli.coherence_untrack:main" # The four targeted-grant-handoff verbs (#185), one module with one entry point # per verb. Each acts as a named Claude Code session (--session, defaulting to # CLAUDE_CODE_SESSION_ID) and presents that session's stored caller principal. # Served by the Python coordinator only: against the Node coordinator they exit # 4. Exit codes are listed in the module docstring. agent-coherence-transfer = "ccs.cli.coherence_handoff:transfer_main" agent-coherence-accept = "ccs.cli.coherence_handoff:accept_main" agent-coherence-decline = "ccs.cli.coherence_handoff:decline_main" agent-coherence-withdraw = "ccs.cli.coherence_handoff:withdraw_main" # Scans workspace CLAUDE.md for prose tool-class rules ("use rg, not grep", # "never sudo") and proposes permissions.deny entries. Flag-only by default; # --apply writes to settings.local.json after a confirmation prompt. agent-coherence-migrate-rules = "ccs.cli.coherence_migrate_rules:main" # Stricter sibling to migrate-rules. STDOUT only (never writes), # symlink-contained (canonical-path containment check), never invokes an LLM, # never scans files outside the workspace root. Intended for security-sensitive # operators who want the helper output but not the auto-apply surface. agent-coherence-migrate-deny = "ccs.adapters.claude_code.migrate_deny:main" # Claude Code rejects hooks.json URLs containing ${COHERENCE_PORT} at LOAD # TIME, so HTTP-type hooks are not viable; the plugin uses command-type hooks # that invoke this client, which reads .coherence/server.pid directly. agent-coherence-hook-client = "ccs.cli.coherence_hook_client:main" # Invariant replay CLI. Walks a captured session directory through the # predicate engine (single-writer / monotonic-version / stale-read / # lost-write) and emits human or JSON findings with per-reason exit codes. agent-coherence-replay = "ccs.cli.coherence_replay:main" # Workspace-Versioning operator CLI (WV R6): checkpoint / list / restore / # status over heterogeneous workspace members, driving an in-process # coordinator service (pin legs have no HTTP surface by design). agent-coherence-workspace = "ccs.cli.workspace:main" # The stale-write-guard-fs stdio MCP server (requires the `mcp` extra). # Single-host coherence guard over CoherentVolume; configured via SWG_ROOT / # SWG_MANAGED. See src/ccs/mcp/. stale-write-guard-fs = "ccs.mcp.server:main" # Note: ccs-check-release is intentionally NOT exposed as a public console # script. It's a maintainer-only pre-tag-push verifier that queries this # repo's GitHub admin settings (hardcoded defaults to Cohexa-ai/agent-coherence). # End users have no use case. CI invokes it directly via # `python tools/check_release_readiness.py`. Maintainers run the same path. [project.optional-dependencies] # ``setuptools`` is already the build backend; naming it here as well puts it # in the *test* environment, where tests/test_packaging_data_files.py pins its # package-data replay against the real thing. Neither a modern venv nor # GitHub's setup-python leaves it importable on 3.12, so without this the # cross-check silently skips everywhere and the pin never actually holds. dev = ["pytest>=7.0", "pytest-xdist>=3.8", "pytest-split>=0.11", "ruff>=0.4", "setuptools>=68.0"] # The packaged conformance corpus (ccs.testing.substrate_conformance) imports # and runs WITHOUT pytest; this extra adds it so scenario skips report as # skips when a foreign implementation runs the corpus under its test suite. conformance = ["pytest>=7.0"] langgraph = ["langgraph>=0.2"] crewai = ["crewai>=0.80"] # `opentelemetry-api` is the runtime dep (the OtelExporter no-ops without an # SDK). `opentelemetry-sdk` is required for metrics to actually be collected/ # exported — and for tests/adapters/telemetry/test_otel_exporter.py to read # them back via MeterProvider + InMemoryMetricReader. otel = ["opentelemetry-api>=1.0", "opentelemetry-sdk>=1.0"] langsmith = ["langsmith>=0.1"] benchmark = ["langchain-core>=0.2"] diagnose = ["agent-coherence[langgraph]", "jinja2>=3.1", "langchain-core>=0.2"] # OpenAI stack: `openai` carries the Conversations client + raw httpx for the # demo broken case; `openai-agents` (pinned <0.18 — 0.x surface churns) is the # Agents SDK adapter dep and composes the `openai` extra. `mistral` is the # second Conversations vendor for the stale-read demo. openai = ["openai>=2.0", "httpx>=0.27"] openai-agents = ["agent-coherence[openai]", "openai-agents>=0.17,<0.18"] mistral = ["mistralai>=2.0"] # The stale-write-guard-fs stdio MCP server (ccs.mcp). The official `mcp` SDK # is the only new runtime dep; v2-alpha renames FastMCP, so pin <2. mcp = ["mcp>=1.27,<2"] # BYO-substrate native-CAS bindings. `psycopg[binary]` (v3) is the CoherentRow # driver; `boto3` is the CoherentObject driver. Both are lazily imported, so the # adapters package stays importable without them. coherent-row = ["psycopg[binary]>=3.1"] coherent-object = ["boto3>=1.34"] all = ["agent-coherence[langgraph,crewai,otel,langsmith,benchmark,diagnose,openai-agents,mistral,mcp,coherent-row,coherent-object]"] # Lock-only (#408): crewai 1.15.28 pins `mcp~=1.28.1` and, through crewai-cli, # `uv~=0.11.6`, which keeps both below their security fixes. crewai's main # already requires these ranges, but no release carries them yet. An override # replaces every requirement on the package, our own `<2` included, so the # bound is repeated here. Published metadata is unaffected. Delete this block # and re-lock once a crewai release past 1.15.28 is locked. [tool.uv] override-dependencies = ["mcp>=1.30,<2", "uv>=0.13.4"] [tool.pytest.ini_options] testpaths = ["tests"] pythonpath = ["src", "."] # ``src`` makes the ccs package importable. ``.`` adds the repo root so tests # under ``tests/`` can ``from examples.refactor_demo import ...`` for the # refactor demo test suite. Test files must not live under any directory # whose name shadows a top-level package — for example, do NOT create # ``tests/examples/`` (would shadow the real ``examples/`` package). markers = [ "launch_gate: Hard launch-gate. Requires live claude CLI; ~$4/run. Run via `pytest -m launch_gate`.", "launch_gate_pilot: Pilot launch-gate run (N=4, one per category). Requires live claude CLI; ~$0.40. Run via `pytest -m launch_gate_pilot`.", "launch_gate_strict: Multi-model strict-mode launch gate (10 scenarios × {haiku, sonnet, opus} × 2 consecutive runs). Requires live claude CLI; ~$8-13/cycle × 2 = ~$16-26 per full gate run. Operator runs once consecutively before a strict-mode release. Run via `pytest -m launch_gate_strict`.", "launch_gate_pilot_matrix: Per-PR cheap launch-gate pilot (1 scenario × {haiku, sonnet, opus}). Requires live claude CLI; ~$0.50 per run. Run via `pytest -m launch_gate_pilot_matrix`.", "protocol_corpus: Cross-implementation wire-shape parity corpus. Spawns Python + Node coordinators against shared JSON fixtures. Requires the agent-coherence-plugin checkout built (`npm ci && npm run build`) or `AGENT_COHERENCE_PLUGIN_DIST_PATH` set; Node scenarios xfail with a clear reason if the dist path can't resolve. Run via `pytest -m protocol_corpus`.", "live_api: OpenAI/Mistral Conversations live-API tests (stale-read probe + adapter smoke). Requires OPENAI_API_KEY and/or MISTRAL_API_KEY and the `[openai,mistral]` extras; ~$10 per full probe run. Excluded from default `pytest -q`. Run via `pytest -m live_api`.", "real_substrate: BYO-substrate conformance against a REAL Postgres / S3 (no Moto/LocalStack — they serialize and false-green concurrency). Requires the `[coherent-row]`/`[coherent-object]` extras plus credentials (CCS_TEST_PG_DSN, CCS_REAL_S3_BUCKET, etc.). Excluded from default `pytest -q`. Run via `pytest -m real_substrate`.", "serial: Not safe beside other xdist workers (real processes that race each other). CI runs these in the serial lane with -n 0; see tests/conftest.py.", ] # Skip launch_gate + protocol_corpus + live_api + real_substrate markers in # default unit test runs. live_api / real_substrate need credentials and real # backends; keep them opt-in so the normal `pytest -q` loop stays fast, offline, # and free. addopts = "-m 'not launch_gate and not launch_gate_pilot and not launch_gate_strict and not launch_gate_pilot_matrix and not protocol_corpus and not live_api and not real_substrate'" # Dump every thread's stack when one test runs this long, so a hung xdist # worker leaves a traceback in the CI log instead of only a job timeout. faulthandler_timeout = 300 [tool.setuptools] package-dir = { "" = "src" } license-files = [] [tool.setuptools.dynamic] version = { attr = "ccs.__version__" } [tool.setuptools.packages.find] where = ["src"] # The HTML report templates are read off the filesystem at runtime # (``Path(__file__).with_name("templates")`` in ccs/output/report.py and # ccs/diagnose/render.py), so they MUST ride the wheel — without them an # installed-from-PyPI ``ccs-diagnose``/``ccs-compare`` raises FileNotFoundError # the moment it renders. Discovery alone does not carry them: ``templates/`` is # found as a namespace package, but a package with no matching data pattern # ships nothing. Nor would a bare ``"*"`` under the catch-all key reach the # files — setuptools globs every pattern against the owning package's directory # with ``glob(recursive=True)``, where ``*`` stops at a path separator, so the # subdirectory has to be named. tests/test_packaging_data_files.py fails if any # future non-.py asset under src/ccs/ is left undeclared here. [tool.setuptools.package-data] "ccs.diagnose" = ["templates/*.html"] "ccs.output" = ["templates/*.html"] [project.urls] Homepage = "https://github.com/Cohexa-ai/agent-coherence" Repository = "https://github.com/Cohexa-ai/agent-coherence" Issues = "https://github.com/Cohexa-ai/agent-coherence/issues" Documentation = "https://github.com/Cohexa-ai/agent-coherence/blob/main/docs/guide.md" Changelog = "https://github.com/Cohexa-ai/agent-coherence/blob/main/CHANGELOG.md" Paper = "https://arxiv.org/abs/2603.15183" [tool.ruff] line-length = 120 [tool.ruff.lint] select = ["E", "F", "I"] [tool.ruff.lint.per-file-ignores] "tests/**/*.py" = ["E402", "E501", "E701", "E702", "E741", "F841"] # Every runnable example bootstraps ``sys.path``/``PYTHONPATH`` to the repo's # ``src`` BEFORE importing ``ccs``, so the demos run from a bare checkout — the # imports cannot move to the top of the file. "examples/**/*.py" = ["E402"] # ``examples/shared_codebase/fixture_repo`` is sample application code the demo # READS as data (a 4-agent review pipeline reviews it, and its bytes feed the # demo's token-savings arithmetic). Its unused imports and unsorted blocks are # representative of the real-world code under review, not lint debt to clean. "examples/shared_codebase/fixture_repo/**/*.py" = ["E401", "E402", "F401", "F841", "I001"]