agent-coherence 0.1.0__py3-none-any.whl
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.
- agent_coherence-0.1.0.dist-info/METADATA +60 -0
- agent_coherence-0.1.0.dist-info/RECORD +52 -0
- agent_coherence-0.1.0.dist-info/WHEEL +5 -0
- agent_coherence-0.1.0.dist-info/entry_points.txt +4 -0
- agent_coherence-0.1.0.dist-info/top_level.txt +1 -0
- ccs/__init__.py +6 -0
- ccs/adapters/__init__.py +16 -0
- ccs/adapters/autogen.py +60 -0
- ccs/adapters/base.py +136 -0
- ccs/adapters/crewai.py +58 -0
- ccs/adapters/langgraph.py +65 -0
- ccs/agent/__init__.py +9 -0
- ccs/agent/cache.py +63 -0
- ccs/agent/runtime.py +151 -0
- ccs/artifacts/__init__.py +24 -0
- ccs/artifacts/diff_engine.py +82 -0
- ccs/bus/__init__.py +8 -0
- ccs/bus/event_bus.py +74 -0
- ccs/cli/__init__.py +4 -0
- ccs/cli/compare.py +57 -0
- ccs/cli/simulate.py +70 -0
- ccs/coordinator/__init__.py +5 -0
- ccs/coordinator/registry.py +116 -0
- ccs/coordinator/service.py +261 -0
- ccs/core/__init__.py +20 -0
- ccs/core/clock.py +31 -0
- ccs/core/exceptions.py +32 -0
- ccs/core/granularity.py +56 -0
- ccs/core/invariants.py +40 -0
- ccs/core/states.py +85 -0
- ccs/core/types.py +68 -0
- ccs/hardening/__init__.py +8 -0
- ccs/hardening/architecture.py +236 -0
- ccs/output/__init__.py +13 -0
- ccs/output/report.py +107 -0
- ccs/simulation/__init__.py +19 -0
- ccs/simulation/aggregation.py +104 -0
- ccs/simulation/bounds.py +22 -0
- ccs/simulation/consistency.py +65 -0
- ccs/simulation/engine.py +461 -0
- ccs/simulation/metrics.py +105 -0
- ccs/simulation/scenarios.py +328 -0
- ccs/strategies/__init__.py +23 -0
- ccs/strategies/access_count.py +47 -0
- ccs/strategies/base.py +77 -0
- ccs/strategies/broadcast.py +49 -0
- ccs/strategies/eager.py +46 -0
- ccs/strategies/lazy.py +37 -0
- ccs/strategies/lease.py +57 -0
- ccs/strategies/selector.py +48 -0
- ccs/transport/__init__.py +5 -0
- ccs/transport/network_sim.py +96 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: agent-coherence
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MESI-style artifact coherence for multi-agent AI systems
|
|
5
|
+
Author: Arbiter contributors
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/hipvlady/agent-coherence
|
|
8
|
+
Project-URL: Repository, https://github.com/hipvlady/agent-coherence
|
|
9
|
+
Project-URL: Issues, https://github.com/hipvlady/agent-coherence/issues
|
|
10
|
+
Keywords: multi-agent,llm,cache-coherence,mesi,token-efficiency,agents
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Requires-Python: >=3.11
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
Requires-Dist: pyyaml>=6.0
|
|
20
|
+
Provides-Extra: dev
|
|
21
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# agent-coherence — The Coherence Protocol for AI Agents
|
|
24
|
+
|
|
25
|
+
[](https://github.com/hipvlady/agent-coherence/actions/workflows/ci.yml)
|
|
26
|
+
|
|
27
|
+
`agent-coherence` implements MESI-style cache coherence for shared artifacts in multi-agent LLM systems, reducing synchronization token overhead and preventing stale-context coordination failures.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install agent-coherence
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Quick start
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from ccs.simulation.engine import run_strategy_comparison
|
|
39
|
+
from ccs.simulation.scenarios import load_scenario
|
|
40
|
+
|
|
41
|
+
scenario = load_scenario("benchmarks/scenarios/planning_canonical.yaml")
|
|
42
|
+
report = run_strategy_comparison(scenario, strategies=["eager", "lazy"], runs=5, seed_start=20260305)
|
|
43
|
+
print(report.to_dict()["aggregated"])
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Reproduce benchmark artifacts
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
bash reproduce.sh
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
See [REPRODUCE.md](REPRODUCE.md) for full output mapping and baseline verification details.
|
|
53
|
+
|
|
54
|
+
## Paper
|
|
55
|
+
|
|
56
|
+
Token Coherence: MESI-Style Cache Coherence for Shared Artifacts in Multi-Agent LLM Systems
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
|
|
60
|
+
Apache-2.0 (`LICENSE`)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
ccs/__init__.py,sha256=2S8ad6p_CbjpSl-X3mDhJj9oXjr1AISYM1hFY3PXkxI,142
|
|
2
|
+
ccs/adapters/__init__.py,sha256=gyT71N9t3Fb8itew4jEdjo28T2g3hR4Lxs6viBmwyLE,404
|
|
3
|
+
ccs/adapters/autogen.py,sha256=Prsk1VVsgYv3ebTPaGnN_zRj8Ddj_TYuBEbinD-rxCE,2025
|
|
4
|
+
ccs/adapters/base.py,sha256=eXN315RUBQ6WglPQGb9Hxcj4MHFY81C5crzQdtGw17U,5027
|
|
5
|
+
ccs/adapters/crewai.py,sha256=WUHOn-Czs5B97RANKWiU8iDjHKD1xANnNu5oWZYTr0U,1967
|
|
6
|
+
ccs/adapters/langgraph.py,sha256=NvRzg25FPBjSzIBtLSJqw9E_IWvkoEZrv8MnISYsj6Q,2268
|
|
7
|
+
ccs/agent/__init__.py,sha256=XJfn-7psLp0Jh5RNsWEWF7ZGW57jdmtYjFWLl9HZbJk,243
|
|
8
|
+
ccs/agent/cache.py,sha256=xVCdW-Y2WvNyjhQlEv87txrgRwL6mOPdl3fSaDRa-eI,2142
|
|
9
|
+
ccs/agent/runtime.py,sha256=IxfrFUuQMufpz3kTyKfAaUQDBmQs5cOwbcP13ry3yAE,5317
|
|
10
|
+
ccs/artifacts/__init__.py,sha256=22WxwBkzE7duFv3QOATR31M8oUZX47U_3gz9knZJpGo,525
|
|
11
|
+
ccs/artifacts/diff_engine.py,sha256=THqQBvNt2jecgYSHrSrU00gnswL4PzPre_g-TQMNn8E,2505
|
|
12
|
+
ccs/bus/__init__.py,sha256=5lC8Ays3kjBYrCNi04V5Xan8yjppT1zielN4aDMVA1w,270
|
|
13
|
+
ccs/bus/event_bus.py,sha256=QId5bn_fcFqSgk1M04ChMdSKJIfx-R5563zUuEIRt6w,2511
|
|
14
|
+
ccs/cli/__init__.py,sha256=e1CJhIy_tAMvtQgBQg-UmJejQN9FlRIA4hIVHaI9IsI,154
|
|
15
|
+
ccs/cli/compare.py,sha256=do-UMWIlHjkF9fM2-Y56LMpxubaRrsVVGl7M3lMiRoc,1947
|
|
16
|
+
ccs/cli/simulate.py,sha256=XRlY-p-M5kuNZQllCs7twZCKcZwr158_SUfCzL5zp-k,2526
|
|
17
|
+
ccs/coordinator/__init__.py,sha256=PrFEsZBKkjjiLAZB73HGlwiN_VeyQpRt4tu9DUYEwuU,142
|
|
18
|
+
ccs/coordinator/registry.py,sha256=qbV-__pYudg0qT9oD_rh_m81fHaJiy9fPqPWjc24Rak,4733
|
|
19
|
+
ccs/coordinator/service.py,sha256=dk3lnKikGorZIYR8aFzxA0ClecpsEPA9AEYjiIghv0I,10131
|
|
20
|
+
ccs/core/__init__.py,sha256=zfMsBhGqaCoRnTVLyY-JC5ek4spreU_vg-8oMuCT5SM,426
|
|
21
|
+
ccs/core/clock.py,sha256=zx9pUq52YzvVF1oE4XxM6-cQ4jL26Szf-0CCFeIYC7Q,869
|
|
22
|
+
ccs/core/exceptions.py,sha256=o5s4AxZrt1QzMuLei9p_31mxF5sqw1OrDfc_p5WgC5I,1033
|
|
23
|
+
ccs/core/granularity.py,sha256=Oa_UPOO-gk6vr5qfhtHDBpx_lfz2HqojRHNFxdu3cl4,1555
|
|
24
|
+
ccs/core/invariants.py,sha256=L4fL7Fbt-aYr1Idr1I9UiE9DqmuKPc2jCQc3xsIuT-c,1342
|
|
25
|
+
ccs/core/states.py,sha256=ip5IPDHs0dtteLwVNunnIMSO91XkkpjVee5f2us0574,2712
|
|
26
|
+
ccs/core/types.py,sha256=2dRw1IV0SulwNxP_Dm2vIKFdxwSzaC8URmVDnLwALOw,1595
|
|
27
|
+
ccs/hardening/__init__.py,sha256=IfdPmq1rAcCvJK9Dmo72SDM7GAEeg4gWT8vqhKGkIGM,264
|
|
28
|
+
ccs/hardening/architecture.py,sha256=WW0DFWkiyc2JpzSefgceQ420lqD4R3f3YruANgCxPlw,7619
|
|
29
|
+
ccs/output/__init__.py,sha256=tmlFl0DjV0tjvKKmTPS39Y8OBxQEgKWxrKUs3A2Y2LU,354
|
|
30
|
+
ccs/output/report.py,sha256=WftfAEWpeWW_CoeMgQA8qmdkbm-YBGfNdG13zMEQBLI,3967
|
|
31
|
+
ccs/simulation/__init__.py,sha256=RYWDi74I39j2oOsB9mrKHTaSB77vcopJGHyg6zaE428,611
|
|
32
|
+
ccs/simulation/aggregation.py,sha256=_5uR93OUKMtFEJsUPZbuyFUZlsyKE8VOYW8PvPMjI50,4245
|
|
33
|
+
ccs/simulation/bounds.py,sha256=mn6AcflF0tJbb3rjaXPeHS-mWAA_Q3rG3qBIT26NoIw,766
|
|
34
|
+
ccs/simulation/consistency.py,sha256=0o6nEzEuoCBWIksQEspeb8jAaaUZCa6Xa7m0asSqzAk,2413
|
|
35
|
+
ccs/simulation/engine.py,sha256=fANyYoTmiIKYM3i22UYZBJulNZRgEBhe75lywfl4sBA,18617
|
|
36
|
+
ccs/simulation/metrics.py,sha256=3Tz3_J6-diTb-6SSjkt8W8GVmUDxdZxyv2hyZ-Vx2RU,3345
|
|
37
|
+
ccs/simulation/scenarios.py,sha256=F3xgrKPFuasNN_VyHV1NqvfFwPjg8sEsVUi-5YboHwc,12294
|
|
38
|
+
ccs/strategies/__init__.py,sha256=E_Y3BAFAEAnLPzAN7PR77dtrC-vccXNHMjftPDegELk,642
|
|
39
|
+
ccs/strategies/access_count.py,sha256=IZ4dWKMlPzENZzM2xlVSrOIlsFZtt9LTQoz8niA0Elw,1375
|
|
40
|
+
ccs/strategies/base.py,sha256=74z_TynciGT-gGMaZkGr95x1MzrHN3KiOI-PYMEkqfs,2388
|
|
41
|
+
ccs/strategies/broadcast.py,sha256=ACYq6sf5SFowwmZJYML5jz_MT5-xljRZv3Whe0-RfOA,1326
|
|
42
|
+
ccs/strategies/eager.py,sha256=CnpyK_F6OdHg2JC2B9Xg2LpJ3uPByJ2RHODiUVbATqU,1237
|
|
43
|
+
ccs/strategies/lazy.py,sha256=dk36rrhmvaUgREHeGpAElx3JdcHiVta1dtt600jyLtk,1025
|
|
44
|
+
ccs/strategies/lease.py,sha256=msTDi4RXC7BrlFV71c1Xm8srngkJs82GAYun14qsUVQ,1674
|
|
45
|
+
ccs/strategies/selector.py,sha256=g34sxyQHAcAWyAPVw-YqY_g0OrItO7cFiOpGtrQXUbg,1489
|
|
46
|
+
ccs/transport/__init__.py,sha256=yOEOoqbjv4BOeOgHKpiwBRWd-gtkUcSHAvpc9Kvfg9c,125
|
|
47
|
+
ccs/transport/network_sim.py,sha256=4JwcHu7Vfturctc4NBPjUX_TgJP5hsySbL5uIGB8CUw,2867
|
|
48
|
+
agent_coherence-0.1.0.dist-info/METADATA,sha256=BgW8EXLjkvhsEq-axrcrrq2xeD-8vMkaMrtiC9Cbe8Y,2036
|
|
49
|
+
agent_coherence-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
50
|
+
agent_coherence-0.1.0.dist-info/entry_points.txt,sha256=H1ASIW41_8EPpNxEt6--wZhg_xCJV9-fpQWp2aiyWuk,147
|
|
51
|
+
agent_coherence-0.1.0.dist-info/top_level.txt,sha256=B_84q_sceurFYPD4eVb3n5al34HIbMKe3LgAXojdC_g,4
|
|
52
|
+
agent_coherence-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ccs
|
ccs/__init__.py
ADDED
ccs/adapters/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""Framework-facing integration adapters for CCS runtime."""
|
|
5
|
+
|
|
6
|
+
from .autogen import AutoGenAdapter
|
|
7
|
+
from .base import CoherenceAdapterCore
|
|
8
|
+
from .crewai import CrewAIAdapter
|
|
9
|
+
from .langgraph import LangGraphAdapter
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"CoherenceAdapterCore",
|
|
13
|
+
"LangGraphAdapter",
|
|
14
|
+
"CrewAIAdapter",
|
|
15
|
+
"AutoGenAdapter",
|
|
16
|
+
]
|
ccs/adapters/autogen.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""AutoGen-oriented coherence adapter utilities."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Mapping
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from ccs.core.types import Artifact
|
|
12
|
+
|
|
13
|
+
from .base import CoherenceAdapterCore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AutoGenAdapter:
|
|
17
|
+
"""Adapter exposing per-turn hooks for AutoGen-like conversations."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, *, strategy_name: str = "lazy", core: CoherenceAdapterCore | None = None) -> None:
|
|
20
|
+
self.core = core if core is not None else CoherenceAdapterCore(strategy_name=strategy_name)
|
|
21
|
+
|
|
22
|
+
def register_agent(self, name: str) -> UUID:
|
|
23
|
+
"""Register one conversational agent identity."""
|
|
24
|
+
return self.core.register_agent(name)
|
|
25
|
+
|
|
26
|
+
def register_artifact(self, *, name: str, content: str, size_tokens: int | None = None) -> Artifact:
|
|
27
|
+
"""Register shared artifact for conversation context."""
|
|
28
|
+
return self.core.register_artifact(name=name, content=content, size_tokens=size_tokens)
|
|
29
|
+
|
|
30
|
+
def pre_turn_context(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
agent_name: str,
|
|
34
|
+
artifact_ids: list[UUID],
|
|
35
|
+
now_tick: int,
|
|
36
|
+
) -> dict[UUID, str]:
|
|
37
|
+
"""Fetch current context before an agent turn."""
|
|
38
|
+
return {
|
|
39
|
+
artifact_id: self.core.read(agent_name=agent_name, artifact_id=artifact_id, now_tick=now_tick).content
|
|
40
|
+
for artifact_id in artifact_ids
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
def post_turn_commit(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
agent_name: str,
|
|
47
|
+
updates: Mapping[UUID, str],
|
|
48
|
+
now_tick: int,
|
|
49
|
+
) -> dict[UUID, int]:
|
|
50
|
+
"""Commit turn updates and return updated versions."""
|
|
51
|
+
versions: dict[UUID, int] = {}
|
|
52
|
+
for artifact_id, content in updates.items():
|
|
53
|
+
artifact = self.core.write(
|
|
54
|
+
agent_name=agent_name,
|
|
55
|
+
artifact_id=artifact_id,
|
|
56
|
+
content=content,
|
|
57
|
+
now_tick=now_tick,
|
|
58
|
+
)
|
|
59
|
+
versions[artifact_id] = artifact.version
|
|
60
|
+
return versions
|
ccs/adapters/base.py
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""Common adapter runtime that wires coordinator, agents, and event bus."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from uuid import NAMESPACE_URL, UUID, uuid5
|
|
10
|
+
|
|
11
|
+
from ccs.agent.runtime import AgentRuntime
|
|
12
|
+
from ccs.bus.event_bus import ArtifactUpdateEvent, InMemoryEventBus
|
|
13
|
+
from ccs.coordinator.registry import ArtifactRegistry
|
|
14
|
+
from ccs.coordinator.service import CoordinatorService
|
|
15
|
+
from ccs.core.types import Artifact, FetchResponse
|
|
16
|
+
from ccs.strategies.base import SyncStrategy
|
|
17
|
+
from ccs.strategies.selector import build_strategy
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class AgentBinding:
|
|
22
|
+
"""Resolved identity/runtime tuple for an adapter-managed agent."""
|
|
23
|
+
|
|
24
|
+
name: str
|
|
25
|
+
agent_id: UUID
|
|
26
|
+
runtime: AgentRuntime
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CoherenceAdapterCore:
|
|
30
|
+
"""Reusable cluster abstraction for framework adapters."""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
*,
|
|
35
|
+
strategy_name: str = "lazy",
|
|
36
|
+
lease_ttl_ticks: int = 300,
|
|
37
|
+
access_count_max_accesses: int = 100,
|
|
38
|
+
event_bus: InMemoryEventBus | None = None,
|
|
39
|
+
) -> None:
|
|
40
|
+
self.registry = ArtifactRegistry()
|
|
41
|
+
self.coordinator = CoordinatorService(self.registry)
|
|
42
|
+
self.strategy: SyncStrategy = build_strategy(
|
|
43
|
+
strategy_name,
|
|
44
|
+
lease_ttl_ticks=lease_ttl_ticks,
|
|
45
|
+
access_count_max_accesses=access_count_max_accesses,
|
|
46
|
+
)
|
|
47
|
+
self.event_bus = event_bus if event_bus is not None else InMemoryEventBus()
|
|
48
|
+
self._agents_by_name: dict[str, AgentBinding] = {}
|
|
49
|
+
|
|
50
|
+
def register_agent(self, name: str) -> UUID:
|
|
51
|
+
"""Register one agent runtime and subscribe it to bus events."""
|
|
52
|
+
existing = self._agents_by_name.get(name)
|
|
53
|
+
if existing is not None:
|
|
54
|
+
return existing.agent_id
|
|
55
|
+
|
|
56
|
+
agent_id = uuid5(NAMESPACE_URL, f"ccs-agent:{name}")
|
|
57
|
+
runtime = AgentRuntime(agent_id=agent_id, coordinator=self.coordinator, strategy=self.strategy)
|
|
58
|
+
self.event_bus.subscribe(
|
|
59
|
+
agent_id=agent_id,
|
|
60
|
+
on_invalidation=runtime.handle_invalidation,
|
|
61
|
+
on_update=lambda event, runtime=runtime: runtime.handle_update(
|
|
62
|
+
artifact_id=event.artifact_id,
|
|
63
|
+
version=event.version,
|
|
64
|
+
content=event.content,
|
|
65
|
+
now_tick=event.issued_at_tick,
|
|
66
|
+
writer_agent_id=event.issuer_agent_id,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
self._agents_by_name[name] = AgentBinding(name=name, agent_id=agent_id, runtime=runtime)
|
|
70
|
+
return agent_id
|
|
71
|
+
|
|
72
|
+
def register_artifact(
|
|
73
|
+
self,
|
|
74
|
+
*,
|
|
75
|
+
name: str,
|
|
76
|
+
content: str,
|
|
77
|
+
size_tokens: int | None = None,
|
|
78
|
+
) -> Artifact:
|
|
79
|
+
"""Register a shared artifact in the coordinator directory."""
|
|
80
|
+
return self.coordinator.register_artifact(name=name, content=content, size_tokens=size_tokens)
|
|
81
|
+
|
|
82
|
+
def read(self, *, agent_name: str, artifact_id: UUID, now_tick: int) -> FetchResponse:
|
|
83
|
+
"""Read artifact through one registered runtime."""
|
|
84
|
+
return self._binding(agent_name).runtime.read(artifact_id, now_tick=now_tick)
|
|
85
|
+
|
|
86
|
+
def write(
|
|
87
|
+
self,
|
|
88
|
+
*,
|
|
89
|
+
agent_name: str,
|
|
90
|
+
artifact_id: UUID,
|
|
91
|
+
content: str,
|
|
92
|
+
now_tick: int,
|
|
93
|
+
) -> Artifact:
|
|
94
|
+
"""Write artifact through one runtime and dispatch peer events."""
|
|
95
|
+
writer = self._binding(agent_name)
|
|
96
|
+
updated, invalidation_signals = writer.runtime.write(
|
|
97
|
+
artifact_id=artifact_id,
|
|
98
|
+
content=content,
|
|
99
|
+
now_tick=now_tick,
|
|
100
|
+
)
|
|
101
|
+
peers = [binding.agent_id for binding in self._agents_by_name.values() if binding.agent_id != writer.agent_id]
|
|
102
|
+
|
|
103
|
+
for signal in invalidation_signals:
|
|
104
|
+
self.event_bus.publish_invalidation(signal, recipients=peers)
|
|
105
|
+
|
|
106
|
+
if self.strategy.broadcasts_content_on_commit():
|
|
107
|
+
self.event_bus.publish_update(
|
|
108
|
+
ArtifactUpdateEvent(
|
|
109
|
+
artifact_id=artifact_id,
|
|
110
|
+
version=updated.version,
|
|
111
|
+
content=content,
|
|
112
|
+
issued_at_tick=now_tick,
|
|
113
|
+
issuer_agent_id=writer.agent_id,
|
|
114
|
+
),
|
|
115
|
+
recipients=peers,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return updated
|
|
119
|
+
|
|
120
|
+
def content(self, *, agent_name: str, artifact_id: UUID) -> str | None:
|
|
121
|
+
"""Return local content cached by one agent runtime."""
|
|
122
|
+
return self._binding(agent_name).runtime.content(artifact_id)
|
|
123
|
+
|
|
124
|
+
def runtime(self, agent_name: str) -> AgentRuntime:
|
|
125
|
+
"""Return concrete runtime for adapter extensions/testing."""
|
|
126
|
+
return self._binding(agent_name).runtime
|
|
127
|
+
|
|
128
|
+
def agent_names(self) -> list[str]:
|
|
129
|
+
"""Return registered adapter agent names."""
|
|
130
|
+
return sorted(self._agents_by_name.keys())
|
|
131
|
+
|
|
132
|
+
def _binding(self, agent_name: str) -> AgentBinding:
|
|
133
|
+
binding = self._agents_by_name.get(agent_name)
|
|
134
|
+
if binding is None:
|
|
135
|
+
raise KeyError(f"unknown_agent '{agent_name}'")
|
|
136
|
+
return binding
|
ccs/adapters/crewai.py
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""CrewAI-oriented coherence adapter utilities."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from uuid import UUID
|
|
9
|
+
|
|
10
|
+
from ccs.core.types import Artifact
|
|
11
|
+
|
|
12
|
+
from .base import CoherenceAdapterCore
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CrewAIAdapter:
|
|
16
|
+
"""Adapter exposing task lifecycle helpers for CrewAI-style flows."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, *, strategy_name: str = "lazy", core: CoherenceAdapterCore | None = None) -> None:
|
|
19
|
+
self.core = core if core is not None else CoherenceAdapterCore(strategy_name=strategy_name)
|
|
20
|
+
|
|
21
|
+
def register_agent(self, name: str) -> UUID:
|
|
22
|
+
"""Register one crew member identity."""
|
|
23
|
+
return self.core.register_agent(name)
|
|
24
|
+
|
|
25
|
+
def register_artifact(self, *, name: str, content: str, size_tokens: int | None = None) -> Artifact:
|
|
26
|
+
"""Register shared artifact accessible to the crew."""
|
|
27
|
+
return self.core.register_artifact(name=name, content=content, size_tokens=size_tokens)
|
|
28
|
+
|
|
29
|
+
def prepare_task_context(
|
|
30
|
+
self,
|
|
31
|
+
*,
|
|
32
|
+
agent_name: str,
|
|
33
|
+
artifact_ids: list[UUID],
|
|
34
|
+
now_tick: int,
|
|
35
|
+
) -> dict[UUID, str]:
|
|
36
|
+
"""Materialize artifact content for one task execution."""
|
|
37
|
+
content_by_artifact: dict[UUID, str] = {}
|
|
38
|
+
for artifact_id in artifact_ids:
|
|
39
|
+
response = self.core.read(agent_name=agent_name, artifact_id=artifact_id, now_tick=now_tick)
|
|
40
|
+
content_by_artifact[artifact_id] = response.content
|
|
41
|
+
return content_by_artifact
|
|
42
|
+
|
|
43
|
+
def commit_task_artifact(
|
|
44
|
+
self,
|
|
45
|
+
*,
|
|
46
|
+
agent_name: str,
|
|
47
|
+
artifact_id: UUID,
|
|
48
|
+
content: str,
|
|
49
|
+
now_tick: int,
|
|
50
|
+
) -> int:
|
|
51
|
+
"""Commit task output for one artifact and return resulting version."""
|
|
52
|
+
artifact = self.core.write(
|
|
53
|
+
agent_name=agent_name,
|
|
54
|
+
artifact_id=artifact_id,
|
|
55
|
+
content=content,
|
|
56
|
+
now_tick=now_tick,
|
|
57
|
+
)
|
|
58
|
+
return artifact.version
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""LangGraph-oriented coherence adapter utilities."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from typing import Mapping
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from ccs.core.types import Artifact
|
|
12
|
+
|
|
13
|
+
from .base import CoherenceAdapterCore
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LangGraphAdapter:
|
|
17
|
+
"""Adapter exposing pre/post node hooks backed by CCS runtime."""
|
|
18
|
+
|
|
19
|
+
def __init__(self, *, strategy_name: str = "lazy", core: CoherenceAdapterCore | None = None) -> None:
|
|
20
|
+
self.core = core if core is not None else CoherenceAdapterCore(strategy_name=strategy_name)
|
|
21
|
+
|
|
22
|
+
def register_agent(self, name: str) -> UUID:
|
|
23
|
+
"""Register node runtime identity."""
|
|
24
|
+
return self.core.register_agent(name)
|
|
25
|
+
|
|
26
|
+
def register_artifact(self, *, name: str, content: str, size_tokens: int | None = None) -> Artifact:
|
|
27
|
+
"""Register shared artifact used by LangGraph nodes."""
|
|
28
|
+
return self.core.register_artifact(name=name, content=content, size_tokens=size_tokens)
|
|
29
|
+
|
|
30
|
+
def before_node(
|
|
31
|
+
self,
|
|
32
|
+
*,
|
|
33
|
+
agent_name: str,
|
|
34
|
+
artifact_ids: list[UUID],
|
|
35
|
+
now_tick: int,
|
|
36
|
+
) -> dict[UUID, dict[str, object]]:
|
|
37
|
+
"""Read artifacts before node execution and return context payload."""
|
|
38
|
+
context: dict[UUID, dict[str, object]] = {}
|
|
39
|
+
for artifact_id in artifact_ids:
|
|
40
|
+
response = self.core.read(agent_name=agent_name, artifact_id=artifact_id, now_tick=now_tick)
|
|
41
|
+
context[artifact_id] = {
|
|
42
|
+
"version": response.version,
|
|
43
|
+
"content": response.content,
|
|
44
|
+
"state": response.state_grant.value,
|
|
45
|
+
}
|
|
46
|
+
return context
|
|
47
|
+
|
|
48
|
+
def commit_outputs(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
agent_name: str,
|
|
52
|
+
writes: Mapping[UUID, str],
|
|
53
|
+
now_tick: int,
|
|
54
|
+
) -> dict[UUID, int]:
|
|
55
|
+
"""Commit node outputs and return artifact versions."""
|
|
56
|
+
versions: dict[UUID, int] = {}
|
|
57
|
+
for artifact_id, content in writes.items():
|
|
58
|
+
artifact = self.core.write(
|
|
59
|
+
agent_name=agent_name,
|
|
60
|
+
artifact_id=artifact_id,
|
|
61
|
+
content=content,
|
|
62
|
+
now_tick=now_tick,
|
|
63
|
+
)
|
|
64
|
+
versions[artifact_id] = artifact.version
|
|
65
|
+
return versions
|
ccs/agent/__init__.py
ADDED
ccs/agent/cache.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Copyright (c) 2026 Arbiter contributors.
|
|
2
|
+
# The Coherence Protocol for AI Agents
|
|
3
|
+
|
|
4
|
+
"""Local artifact cache used by agent runtime."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from dataclasses import replace
|
|
9
|
+
from uuid import UUID
|
|
10
|
+
|
|
11
|
+
from ccs.core.states import MESIState
|
|
12
|
+
from ccs.core.types import ArtifactCacheEntry
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ArtifactCache:
|
|
16
|
+
"""In-memory per-agent cache for artifact entries."""
|
|
17
|
+
|
|
18
|
+
def __init__(self) -> None:
|
|
19
|
+
self._entries: dict[UUID, ArtifactCacheEntry] = {}
|
|
20
|
+
|
|
21
|
+
def get(self, artifact_id: UUID) -> ArtifactCacheEntry | None:
|
|
22
|
+
"""Return cache entry for artifact if present."""
|
|
23
|
+
return self._entries.get(artifact_id)
|
|
24
|
+
|
|
25
|
+
def put(self, artifact_id: UUID, entry: ArtifactCacheEntry) -> None:
|
|
26
|
+
"""Insert or replace cache entry for artifact."""
|
|
27
|
+
self._entries[artifact_id] = entry
|
|
28
|
+
|
|
29
|
+
def invalidate(
|
|
30
|
+
self,
|
|
31
|
+
artifact_id: UUID,
|
|
32
|
+
*,
|
|
33
|
+
invalidated_version: int | None = None,
|
|
34
|
+
issued_at_tick: int = 0,
|
|
35
|
+
) -> None:
|
|
36
|
+
"""Set entry state to INVALID, creating placeholder if missing."""
|
|
37
|
+
entry = self._entries.get(artifact_id)
|
|
38
|
+
if entry is None:
|
|
39
|
+
self._entries[artifact_id] = ArtifactCacheEntry(
|
|
40
|
+
artifact_id=artifact_id,
|
|
41
|
+
state=MESIState.INVALID,
|
|
42
|
+
local_version=max(invalidated_version or 0, 0),
|
|
43
|
+
acquired_at_tick=issued_at_tick,
|
|
44
|
+
)
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
next_version = entry.local_version
|
|
48
|
+
if invalidated_version is not None:
|
|
49
|
+
next_version = min(entry.local_version, invalidated_version)
|
|
50
|
+
self._entries[artifact_id] = replace(
|
|
51
|
+
entry,
|
|
52
|
+
state=MESIState.INVALID,
|
|
53
|
+
local_version=max(next_version, 0),
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
def has_valid(self, artifact_id: UUID) -> bool:
|
|
57
|
+
"""Return whether artifact is cached in non-invalid state."""
|
|
58
|
+
entry = self._entries.get(artifact_id)
|
|
59
|
+
return entry is not None and entry.state != MESIState.INVALID
|
|
60
|
+
|
|
61
|
+
def entries(self) -> dict[UUID, ArtifactCacheEntry]:
|
|
62
|
+
"""Return shallow copy of cached entries."""
|
|
63
|
+
return dict(self._entries)
|