cilow 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.
cilow/__init__.py ADDED
@@ -0,0 +1,116 @@
1
+ """cilow — Python client for Cilow, the verifiable context layer for AI agents.
2
+
3
+ Point it at a running ``cilow-serve`` and call the FOUR core verbs (the MCP core profile):
4
+ ``ingest`` (the one write door: facts | text | bytes | url), ``context_pack`` (the product read:
5
+ injectible pack + citations with ``is_current`` + ``abstained`` / ``grounding`` / ``trace_id``),
6
+ ``forget`` (withdraw), and ``explain`` (why a read surfaced what it surfaced, via ``trace_id``).
7
+ ``remember`` / ``recall`` / ``answer`` / ``timeline`` / ``track_entity`` remain as full-profile /
8
+ legacy aliases. The headline feature is CALIBRATED ABSTENTION: reads return ``abstained=True``
9
+ when the engine does not know, instead of fabricating. Gate your agent on that flag.
10
+
11
+ from cilow import CilowClient
12
+
13
+ cilow = CilowClient(base_url="http://localhost:8080", token="demo")
14
+ cilow.ingest(facts=[{"subject": "Maya", "predicate": "employer", "object": "Acme"}])
15
+ r = cilow.context_pack("where does Maya work", anchor="Maya", attribute="employer")
16
+ print(r.reason if r.abstained else r.pack)
17
+ why = cilow.explain(r.trace_id) # cited claims + grounding vs the tenant threshold
18
+ """
19
+
20
+ from .client import CilowClient, ingest_source_id, now_micros
21
+ from .errors import CilowError
22
+ from .types import (
23
+ AnswerResponse,
24
+ Branch,
25
+ Citation,
26
+ CitationPart,
27
+ Claim,
28
+ ConsolidateResponse,
29
+ Fact,
30
+ FeedbackResponse,
31
+ ContextPackResponse,
32
+ ExplainCited,
33
+ ExplainReport,
34
+ ExplainResponse,
35
+ ForgetResponse,
36
+ ForkResponse,
37
+ IngestAnyResponse,
38
+ IngestResponse,
39
+ IngestAudioResponse,
40
+ IngestImageResponse,
41
+ IngestPdfResponse,
42
+ IngestStructuredResponse,
43
+ Outcome,
44
+ ReaderUsage,
45
+ ReasonCode,
46
+ TerminalLayer,
47
+ TERMINAL_LAYERS,
48
+ Evidence,
49
+ EvidenceStructure,
50
+ EvidenceProvenance,
51
+ Receipt,
52
+ RecallResponse,
53
+ RememberResponse,
54
+ RememberTextResponse,
55
+ Scope,
56
+ Source,
57
+ TimelineEntry,
58
+ TrackEntityResponse,
59
+ Workspace,
60
+ EvidenceRevision,
61
+ ContextProgram,
62
+ ContextProgramV2,
63
+ ContextTrace,
64
+ MemoryProposal,
65
+ )
66
+
67
+ __version__ = "0.1.0"
68
+
69
+ __all__ = [
70
+ "CilowClient",
71
+ "CilowError",
72
+ "now_micros",
73
+ "ingest_source_id",
74
+ "Scope",
75
+ "Fact",
76
+ "Source",
77
+ "RememberResponse",
78
+ "Claim",
79
+ "Receipt",
80
+ "RecallResponse",
81
+ "Citation",
82
+ "CitationPart",
83
+ "AnswerResponse",
84
+ "ReaderUsage",
85
+ "ReasonCode",
86
+ "TerminalLayer",
87
+ "TERMINAL_LAYERS",
88
+ "Evidence",
89
+ "EvidenceStructure",
90
+ "EvidenceProvenance",
91
+ "TimelineEntry",
92
+ "TrackEntityResponse",
93
+ "Outcome",
94
+ "FeedbackResponse",
95
+ "ConsolidateResponse",
96
+ "RememberTextResponse",
97
+ "IngestImageResponse",
98
+ "IngestAudioResponse",
99
+ "IngestPdfResponse",
100
+ "IngestStructuredResponse",
101
+ "IngestAnyResponse",
102
+ "ForgetResponse",
103
+ "ContextPackResponse",
104
+ "IngestResponse",
105
+ "ExplainResponse",
106
+ "ExplainReport",
107
+ "ExplainCited",
108
+ "Branch",
109
+ "ForkResponse",
110
+ "Workspace",
111
+ "EvidenceRevision",
112
+ "ContextProgram",
113
+ "ContextProgramV2",
114
+ "ContextTrace",
115
+ "MemoryProposal",
116
+ ]
@@ -0,0 +1,37 @@
1
+ """``cilow.adapters`` — framework bindings over the ONE :class:`CilowMemory` contract.
2
+
3
+ Every binding is a thin shim that maps a framework's memory/retriever/tool interface onto the five
4
+ contract verbs (``ingest`` / ``recall`` / ``answer`` / ``reset`` / ``health``) and re-exposes the
5
+ differentiator unchanged: ``abstained`` + ``reason_code`` + the ``grounded`` signal.
6
+
7
+ from cilow.adapters import CilowMemoryClient
8
+ from cilow.adapters import openai, anthropic, langchain
9
+
10
+ Branch on an adapter result's ``.is_calibrated_abstain`` / ``.is_infrastructure_abstain`` properties.
11
+ """
12
+
13
+ from . import anthropic, langchain, openai
14
+ from .memory import (
15
+ AnswerResult,
16
+ CilowMemory,
17
+ CilowMemoryClient,
18
+ IngestResult,
19
+ MemoryHit,
20
+ RecallResult,
21
+ )
22
+ from .remaining import REMAINING_ADAPTERS, make_autogen_memory, make_llamaindex_retriever
23
+
24
+ __all__ = [
25
+ "CilowMemory",
26
+ "CilowMemoryClient",
27
+ "MemoryHit",
28
+ "RecallResult",
29
+ "AnswerResult",
30
+ "IngestResult",
31
+ "openai",
32
+ "anthropic",
33
+ "langchain",
34
+ "REMAINING_ADAPTERS",
35
+ "make_llamaindex_retriever",
36
+ "make_autogen_memory",
37
+ ]
@@ -0,0 +1,64 @@
1
+ """Anthropic SDK binding over :class:`CilowMemory`, no ``anthropic`` dependency required.
2
+
3
+ Anthropic has no OpenAI-compatible endpoint, so the base_url swap does NOT apply (design doc R2.2).
4
+ This binding uses the explicit Messages tool-use path: register :data:`CILOW_RECALL_TOOL`, execute
5
+ calls with :func:`run_recall_tool` into a ``tool_result`` block, and/or pre-inject grounded context
6
+ with :func:`inject_memory`. Both surface the abstained/grounded branch.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any, Dict, Optional, Tuple
12
+
13
+ from .memory import CilowMemory, RecallResult
14
+
15
+ CILOW_RECALL_TOOL: Dict[str, Any] = {
16
+ "name": "cilow_recall",
17
+ "description": (
18
+ "Recall grounded facts from verifiable long-term memory. Returns memory-grounded results, "
19
+ "or a calibrated abstain when memory genuinely does not know — then DO NOT fabricate."
20
+ ),
21
+ "input_schema": {
22
+ "type": "object",
23
+ "properties": {
24
+ "query": {"type": "string", "description": "What to look up in memory."},
25
+ "anchor": {"type": "string", "description": "Optional exact entity hint."},
26
+ "attribute": {"type": "string", "description": "Optional exact predicate hint."},
27
+ },
28
+ "required": ["query"],
29
+ },
30
+ }
31
+
32
+
33
+ def run_recall_tool(memory: CilowMemory, tool_use_id: str, tool_input: Dict[str, Any]) -> Tuple[Dict[str, Any], RecallResult]:
34
+ """Execute one ``cilow_recall`` tool_use. Returns ``(tool_result_block, result)``. An INFRASTRUCTURE
35
+ abstain is marked ``is_error=True`` (retryable); a calibrated abstain is a normal result whose
36
+ content instructs the model not to fabricate."""
37
+ result = memory.recall(tool_input["query"], anchor=tool_input.get("anchor"), attribute=tool_input.get("attribute"))
38
+ block: Dict[str, Any] = {"type": "tool_result", "tool_use_id": tool_use_id}
39
+ if result.reason_code == "provider_unavailable":
40
+ block["is_error"] = True
41
+ block["content"] = f"MEMORY TEMPORARILY UNAVAILABLE: {result.reason or 'provider error'} — retry."
42
+ return block, result
43
+ if result.abstained or not result.grounded:
44
+ block["content"] = (
45
+ f"MEMORY ABSTAINED: {result.reason or 'memory does not know'}. "
46
+ "Do not fabricate; ask the user or say you don't know."
47
+ )
48
+ return block, result
49
+ lines = "\n".join(f"- {h.text}" for h in result.results)
50
+ block["content"] = f"MEMORY-GROUNDED FACTS:\n{lines}"
51
+ return block, result
52
+
53
+
54
+ def inject_memory(memory: CilowMemory, query: str, *, anchor: Optional[str] = None, attribute: Optional[str] = None) -> Tuple[str, bool, RecallResult]:
55
+ """Pre-inject grounded memory ahead of ``messages.create``: recall, and if grounded return a
56
+ ``system`` string block to prepend plus the branch signal. On an abstain the system block is empty
57
+ and ``grounded`` is False — the caller decides whether to proceed or stop. Returns
58
+ ``(system, grounded, result)``."""
59
+ result = memory.recall(query, anchor=anchor, attribute=attribute)
60
+ if not result.grounded:
61
+ return "", False, result
62
+ lines = "\n".join(f"- {h.text}" for h in result.results)
63
+ system = f"Answer using ONLY these memory-grounded facts. If they are insufficient, say so.\n{lines}"
64
+ return system, True, result
@@ -0,0 +1,96 @@
1
+ """LangChain binding over :class:`CilowMemory`, no ``langchain`` dependency required.
2
+
3
+ Two duck-typed classes that satisfy LangChain's structural contracts so they drop into any chain:
4
+ - :class:`CilowRetriever` mirrors ``BaseRetriever`` (``get_relevant_documents(query) -> list[Document]``).
5
+ - :class:`CilowChatMemory` mirrors ``BaseMemory`` (``memory_variables`` / ``load_memory_variables`` /
6
+ ``save_context`` / ``clear``).
7
+ The abstention contract is preserved: an abstain yields ZERO documents (never a fabricated one), and
8
+ the grounded/abstain branch is readable via :meth:`CilowRetriever.recall_with_signal`.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass, field
14
+ from typing import Any, Dict, List, Optional
15
+
16
+ from .memory import CilowMemory, RecallResult
17
+
18
+
19
+ @dataclass
20
+ class Document:
21
+ """Structural ``Document`` (LangChain ``langchain_core.documents``)."""
22
+
23
+ page_content: str
24
+ metadata: Dict[str, Any] = field(default_factory=dict)
25
+
26
+
27
+ class CilowRetriever:
28
+ """A LangChain-compatible retriever backed by Cilow recall."""
29
+
30
+ def __init__(self, memory: CilowMemory, *, k: Optional[int] = None, anchor: Optional[str] = None,
31
+ attribute: Optional[str] = None) -> None:
32
+ self._m = memory
33
+ self._k = k
34
+ self._anchor = anchor
35
+ self._attribute = attribute
36
+
37
+ def get_relevant_documents(self, query: str) -> List[Document]:
38
+ """BaseRetriever entry point. Returns memory-grounded docs, or ``[]`` on an abstain."""
39
+ r = self._m.recall(query, k=self._k, anchor=self._anchor, attribute=self._attribute)
40
+ if not r.grounded:
41
+ return []
42
+ return [
43
+ Document(page_content=h.text, metadata={"id": h.id, "entity": h.entity, "score": h.score, "grounded": True})
44
+ for h in r.results
45
+ ]
46
+
47
+ # LangChain 0.2+ internal hook.
48
+ def _get_relevant_documents(self, query: str) -> List[Document]:
49
+ return self.get_relevant_documents(query)
50
+
51
+ def recall_with_signal(self, query: str) -> RecallResult:
52
+ """Escape hatch: the full :class:`RecallResult` to branch on ``abstained`` / ``reason_code``."""
53
+ return self._m.recall(query, k=self._k, anchor=self._anchor, attribute=self._attribute)
54
+
55
+
56
+ class CilowChatMemory:
57
+ """A LangChain-compatible ``BaseMemory``: recall on load, persist turns on save."""
58
+
59
+ def __init__(self, memory: CilowMemory, *, memory_key: str = "history", input_key: Optional[str] = None,
60
+ output_key: Optional[str] = None, subject_hint: Optional[str] = None,
61
+ anchor: Optional[str] = None, attribute: Optional[str] = None) -> None:
62
+ self._m = memory
63
+ self._memory_key = memory_key
64
+ self._input_key = input_key
65
+ self._output_key = output_key
66
+ self._subject_hint = subject_hint
67
+ self._anchor = anchor
68
+ self._attribute = attribute
69
+
70
+ @property
71
+ def memory_variables(self) -> List[str]:
72
+ return [self._memory_key]
73
+
74
+ def load_memory_variables(self, inputs: Dict[str, Any]) -> Dict[str, Any]:
75
+ """Load grounded history. On an abstain the variable is an empty string (honest, no fabrication)."""
76
+ query = self._pick(inputs, self._input_key)
77
+ if not query:
78
+ return {self._memory_key: ""}
79
+ r = self._m.recall(query, anchor=self._anchor, attribute=self._attribute)
80
+ return {self._memory_key: "\n".join(h.text for h in r.results) if r.grounded else ""}
81
+
82
+ def save_context(self, inputs: Dict[str, Any], outputs: Dict[str, Any]) -> None:
83
+ """Persist the turn into memory so future recalls can ground on it."""
84
+ text = "\n".join(t for t in (self._pick(inputs, self._input_key), self._pick(outputs, self._output_key)) if t)
85
+ if text:
86
+ self._m.ingest(text, subject_hint=self._subject_hint)
87
+
88
+ def clear(self) -> None:
89
+ self._m.reset()
90
+
91
+ @staticmethod
92
+ def _pick(bag: Dict[str, Any], key: Optional[str]) -> str:
93
+ if key:
94
+ return str(bag.get(key, ""))
95
+ vals = list(bag.values())
96
+ return str(vals[0]) if vals else ""
@@ -0,0 +1,157 @@
1
+ """``CilowMemory`` — the ONE adapter contract every framework binding maps onto (Python mirror).
2
+
3
+ A tiny, framework-neutral surface over the shipped REST endpoints (``/v1/memories/*`` + the
4
+ ``/v1/chat/completions`` proxy). Each framework adapter (OpenAI, Anthropic, LangChain, …) is a thin
5
+ shim onto these five verbs that re-exposes the differentiator unchanged: ``abstained`` +
6
+ ``reason_code`` + the ``grounded`` signal. Transport is the existing :class:`~cilow.CilowClient` — the
7
+ bindings call the SDK, never raw HTTP.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from dataclasses import dataclass, field
13
+ from typing import List, Optional, Protocol, Union
14
+
15
+ from ..client import CilowClient
16
+ from ..types import ReasonCode
17
+
18
+ _CALIBRATED = ("below_threshold", "no_candidates")
19
+
20
+
21
+ @dataclass
22
+ class MemoryHit:
23
+ """One recalled memory line — ``text`` is ready to inject; structured fields kept alongside."""
24
+
25
+ id: str
26
+ text: str
27
+ entity: str
28
+ value: str
29
+ score: float
30
+
31
+
32
+ @dataclass
33
+ class _Abstainable:
34
+ abstained: bool = False
35
+ reason: Optional[str] = None
36
+ reason_code: Optional[ReasonCode] = None
37
+
38
+ @property
39
+ def is_infrastructure_abstain(self) -> bool:
40
+ """Retryable INFRA abstain (the embedding provider failed) — memory may hold the answer."""
41
+ return self.abstained and self.reason_code == "provider_unavailable"
42
+
43
+ @property
44
+ def is_calibrated_abstain(self) -> bool:
45
+ """Memory genuinely lacks grounded context. Do NOT fabricate past it."""
46
+ return self.abstained and self.reason_code in _CALIBRATED
47
+
48
+ @property
49
+ def should_retry(self) -> bool:
50
+ return self.is_infrastructure_abstain
51
+
52
+
53
+ @dataclass
54
+ class RecallResult(_Abstainable):
55
+ """Result of :meth:`CilowMemory.recall`. ``grounded`` is the universal drop-in signal."""
56
+
57
+ results: List[MemoryHit] = field(default_factory=list)
58
+ grounded: bool = False
59
+ margin: Optional[float] = None
60
+ packed_tokens: Optional[int] = None
61
+
62
+
63
+ @dataclass
64
+ class AnswerResult(_Abstainable):
65
+ """Result of :meth:`CilowMemory.answer` — a synthesized answer + the grounded/abstain signal."""
66
+
67
+ text: str = ""
68
+ grounded: bool = False
69
+
70
+
71
+ @dataclass
72
+ class IngestResult:
73
+ count: int
74
+ ids: List[str]
75
+
76
+
77
+ class CilowMemory(Protocol):
78
+ """The single contract. Five verbs; every framework binding targets exactly these.
79
+
80
+ ``ingest`` → ``/v1/memories/add`` · ``recall`` → ``/search`` · ``answer`` → ``/search``
81
+ (mode ``"answer"``) · ``reset`` → ``/list`` + ``/delete`` · ``health`` → ``GET /health``.
82
+ ``proxy_base_url()`` is the drop-in OpenAI-compatible base URL that emits the grounded header.
83
+ """
84
+
85
+ def ingest(self, items: Union[str, List[str]], *, subject_hint: Optional[str] = None) -> IngestResult: ...
86
+ def recall(self, query: str, *, k: Optional[int] = None, anchor: Optional[str] = None, attribute: Optional[str] = None) -> RecallResult: ...
87
+ def answer(self, query: str, *, anchor: Optional[str] = None, attribute: Optional[str] = None) -> AnswerResult: ...
88
+ def reset(self) -> int: ...
89
+ def health(self) -> bool: ...
90
+ def proxy_base_url(self) -> str: ...
91
+
92
+
93
+ class CilowMemoryClient:
94
+ """The concrete :class:`CilowMemory` over a :class:`~cilow.CilowClient`."""
95
+
96
+ def __init__(self, client: CilowClient) -> None:
97
+ self._c = client
98
+
99
+ @classmethod
100
+ def create(cls, base_url: str, token: str) -> "CilowMemoryClient":
101
+ return cls(CilowClient(base_url=base_url, token=token))
102
+
103
+ def ingest(self, items: Union[str, List[str]], *, subject_hint: Optional[str] = None) -> IngestResult:
104
+ the_list = [items] if isinstance(items, str) else list(items)
105
+ if len(the_list) == 1:
106
+ r = self._c.memories.add(the_list[0], subject_hint=subject_hint)
107
+ ids = [m["id"] for m in r.get("memories", [])]
108
+ return IngestResult(count=r.get("count", len(ids)), ids=ids)
109
+ r = self._c.memories.batch_add([{"content": c, "subject_hint": subject_hint} for c in the_list])
110
+ ids = [m["id"] for res in r.get("results", []) for m in res.get("memories", [])]
111
+ return IngestResult(count=len(ids), ids=ids)
112
+
113
+ def recall(self, query: str, *, k: Optional[int] = None, anchor: Optional[str] = None, attribute: Optional[str] = None) -> RecallResult:
114
+ r = self._c.memories.search(query, anchor=anchor, attribute=attribute)
115
+ raw = r.get("results", []) or []
116
+ if k is not None:
117
+ raw = raw[:k]
118
+ hits = [
119
+ MemoryHit(id=h.get("id", ""), text=h.get("text", ""), entity=h.get("entity", ""),
120
+ value=h.get("value", h.get("text", "")), score=h.get("score", 0.0))
121
+ for h in raw
122
+ ]
123
+ abstained = bool(r.get("abstained", False))
124
+ return RecallResult(
125
+ abstained=abstained, reason=r.get("reason"), reason_code=r.get("reason_code"),
126
+ results=hits, grounded=(not abstained and len(hits) > 0),
127
+ margin=r.get("margin"), packed_tokens=r.get("packed_tokens"),
128
+ )
129
+
130
+ def answer(self, query: str, *, anchor: Optional[str] = None, attribute: Optional[str] = None) -> AnswerResult:
131
+ r = self._c.memories.search(query, anchor=anchor, attribute=attribute, mode="answer")
132
+ abstained = bool(r.get("abstained", False))
133
+ text = r.get("context_text", "") or ""
134
+ return AnswerResult(
135
+ abstained=abstained, reason=r.get("reason"), reason_code=r.get("reason_code"),
136
+ text=text, grounded=(not abstained and bool(text)),
137
+ )
138
+
139
+ def reset(self) -> int:
140
+ deleted = 0
141
+ while True:
142
+ page = self._c.memories.list(active_only=True, limit=200)
143
+ mems = page.get("memories", [])
144
+ if not mems:
145
+ break
146
+ for m in mems:
147
+ res = self._c.memories.delete(m["id"])
148
+ deleted += res.get("deleted", 0)
149
+ if len(mems) < 200:
150
+ break
151
+ return deleted
152
+
153
+ def health(self) -> bool:
154
+ return self._c.health()
155
+
156
+ def proxy_base_url(self) -> str:
157
+ return f"{self._c.base_url}/v1"
@@ -0,0 +1,74 @@
1
+ """OpenAI SDK binding over :class:`CilowMemory`, no ``openai`` dependency required.
2
+
3
+ Two thin surfaces:
4
+ 1. ``base_url`` swap — :func:`cilow_proxy_config` returns kwargs for ``OpenAI(...)`` pointing at the
5
+ Cilow proxy; recall→inject→forward is automatic and every response carries
6
+ ``x-cilow-memory-grounded`` (read it with :func:`grounded_from_headers`).
7
+ 2. memory tool-use — hand :data:`CILOW_RECALL_TOOL` to ``tools=[...]`` and execute calls with
8
+ :func:`run_recall_tool`; the tool result carries the abstained/grounded branch so the model never
9
+ fabricates past a calibrated abstain.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import json
15
+ from typing import Any, Dict, Optional, Tuple, Union
16
+
17
+ from .memory import CilowMemory, RecallResult
18
+
19
+ GROUNDED_HEADER = "x-cilow-memory-grounded"
20
+
21
+ CILOW_RECALL_TOOL: Dict[str, Any] = {
22
+ "type": "function",
23
+ "function": {
24
+ "name": "cilow_recall",
25
+ "description": (
26
+ "Recall grounded facts from verifiable long-term memory. Returns memory-grounded results, "
27
+ "or a calibrated abstain when memory genuinely does not know — then DO NOT fabricate."
28
+ ),
29
+ "parameters": {
30
+ "type": "object",
31
+ "properties": {
32
+ "query": {"type": "string", "description": "What to look up in memory."},
33
+ "anchor": {"type": "string", "description": "Optional exact entity hint."},
34
+ "attribute": {"type": "string", "description": "Optional exact predicate hint."},
35
+ },
36
+ "required": ["query"],
37
+ },
38
+ },
39
+ }
40
+
41
+
42
+ def cilow_proxy_config(memory: CilowMemory, token: str) -> Dict[str, str]:
43
+ """Kwargs to spread into ``OpenAI(**cilow_proxy_config(memory, token))``."""
44
+ return {"base_url": memory.proxy_base_url(), "api_key": token}
45
+
46
+
47
+ def grounded_from_headers(headers: Any) -> Optional[bool]:
48
+ """Read the grounded signal off an OpenAI raw response's headers (``.with_raw_response``), a mapping,
49
+ or anything with ``.get``. Returns ``None`` when the header is absent (non-proxy)."""
50
+ raw = None
51
+ if hasattr(headers, "get"):
52
+ raw = headers.get(GROUNDED_HEADER)
53
+ if raw is None and isinstance(headers, dict):
54
+ raw = headers.get(GROUNDED_HEADER.upper())
55
+ if raw is None:
56
+ return None
57
+ return str(raw).strip().lower() == "true"
58
+
59
+
60
+ def run_recall_tool(memory: CilowMemory, raw_args: Union[str, Dict[str, Any]]) -> Tuple[str, RecallResult]:
61
+ """Execute one ``cilow_recall`` tool call. Returns ``(content, result)``: the ``content`` string to
62
+ feed back as the tool result, plus the raw :class:`RecallResult` so the caller can branch. On an
63
+ abstain the content is an explicit "memory does not know" so the model does not fabricate."""
64
+ args = json.loads(raw_args) if isinstance(raw_args, str) else raw_args
65
+ result = memory.recall(args["query"], anchor=args.get("anchor"), attribute=args.get("attribute"))
66
+ if result.abstained or not result.grounded:
67
+ kind = "temporarily unavailable" if result.reason_code == "provider_unavailable" else "no grounded facts"
68
+ content = (
69
+ f"MEMORY ABSTAINED ({kind}): {result.reason or 'memory does not know'}. "
70
+ "Do not fabricate; ask the user or say you don't know."
71
+ )
72
+ return content, result
73
+ lines = "\n".join(f"- {h.text}" for h in result.results)
74
+ return f"MEMORY-GROUNDED FACTS:\n{lines}", result
@@ -0,0 +1,65 @@
1
+ """The remaining six framework bindings — same :class:`CilowMemory` contract, same thin-shim pattern as
2
+ ``openai.py`` / ``anthropic.py`` / ``langchain.py``. Each row documents the framework's integration
3
+ point and the exact mapping onto the five verbs. Where the framework interface is a plain
4
+ function/protocol we ship a working stub; where it requires extending a framework base class we leave a
5
+ typed TODO so the shim lands the moment the dep is added.
6
+
7
+ THE INVARIANT (all nine): map the framework's memory/retriever/tool onto
8
+ ``ingest`` / ``recall`` / ``answer`` / ``reset`` / ``health`` (+ ``proxy_base_url()`` for base_url swaps)
9
+ and re-expose ``abstained`` + ``reason_code`` + ``grounded`` unchanged. An abstain is always surfaced
10
+ so the caller can apply its fallback policy.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Any, Dict, List, Optional
16
+
17
+ from .memory import CilowMemory
18
+
19
+ # framework -> (integration point, mapping, status)
20
+ REMAINING_ADAPTERS: List[Dict[str, str]] = [
21
+ {"framework": "LlamaIndex", "integration_point": "BaseRetriever._retrieve(QueryBundle) -> list[NodeWithScore]",
22
+ "mapping": "recall -> NodeWithScore list; abstain => []. Near-identical to CilowRetriever.", "status": "stub"},
23
+ {"framework": "Vercel AI SDK", "integration_point": "wrapLanguageModel middleware",
24
+ "mapping": "proxy_base_url() as the provider base URL; surface grounded in stream metadata (R2.1 streaming).", "status": "todo"},
25
+ {"framework": "CrewAI", "integration_point": "Storage / memory backend",
26
+ "mapping": "save->ingest, search->recall; branch on abstained before use.", "status": "todo"},
27
+ {"framework": "AutoGen", "integration_point": "Memory protocol (add / query)",
28
+ "mapping": "add->ingest, query->recall; abstain => empty MemoryQueryResult.", "status": "stub"},
29
+ {"framework": "Composio", "integration_point": "connector / tool bundle",
30
+ "mapping": "the five CilowMemory verbs as Composio actions.", "status": "todo"},
31
+ {"framework": "MCP", "integration_point": "native /mcp recall/answer/remember (already shipped)",
32
+ "mapping": "no shim needed — add cilow-mcp to .mcp.json.", "status": "stub"},
33
+ ]
34
+
35
+
36
+ def make_llamaindex_retriever(memory: CilowMemory, *, k: Optional[int] = None, anchor: Optional[str] = None):
37
+ """LlamaIndex stub — an object whose ``retrieve`` returns NodeWithScore-shaped dicts (abstain => []).
38
+ When ``llama-index`` is added, extend ``BaseRetriever`` and delegate to this."""
39
+
40
+ class _Retriever:
41
+ def retrieve(self, query: str) -> List[Dict[str, Any]]:
42
+ r = memory.recall(query, k=k, anchor=anchor)
43
+ if not r.grounded:
44
+ return []
45
+ return [
46
+ {"node": {"text": h.text, "metadata": {"id": h.id, "entity": h.entity, "grounded": True}}, "score": h.score}
47
+ for h in r.results
48
+ ]
49
+
50
+ return _Retriever()
51
+
52
+
53
+ def make_autogen_memory(memory: CilowMemory, *, subject_hint: Optional[str] = None):
54
+ """AutoGen stub — the ``Memory`` protocol (``add`` / ``query``). ``query`` returns grounded content
55
+ strings; an abstain yields an empty list (never a fabricated memory)."""
56
+
57
+ class _Memory:
58
+ def add(self, content: str) -> None:
59
+ memory.ingest(content, subject_hint=subject_hint)
60
+
61
+ def query(self, query: str) -> Dict[str, Any]:
62
+ r = memory.recall(query)
63
+ return {"results": [h.text for h in r.results] if r.grounded else [], "grounded": r.grounded}
64
+
65
+ return _Memory()