cortexdbai 0.4.3__tar.gz → 0.6.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.
Files changed (95) hide show
  1. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/.gitignore +3 -3
  2. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/PKG-INFO +7 -1
  3. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/README.md +6 -0
  4. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/__init__.py +71 -71
  5. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/client.py +333 -0
  6. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/pyproject.toml +186 -186
  7. cortexdbai-0.6.0/tests/test_conflicts.py +310 -0
  8. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/__init__.py +0 -0
  9. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/__init__.py +0 -0
  10. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/agent.py +0 -0
  11. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ag2/tools.py +0 -0
  12. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/__init__.py +0 -0
  13. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/memory.py +0 -0
  14. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/agno/tools.py +0 -0
  15. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/__init__.py +0 -0
  16. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/agent.py +0 -0
  17. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/autogen/tools.py +0 -0
  18. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/__init__.py +0 -0
  19. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/memory.py +0 -0
  20. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/beeai/tools.py +0 -0
  21. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/__init__.py +0 -0
  22. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/memory.py +0 -0
  23. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/camel/tools.py +0 -0
  24. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/controlflow/__init__.py +0 -0
  25. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/controlflow/memory.py +0 -0
  26. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/__init__.py +0 -0
  27. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/memory.py +0 -0
  28. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/crewai/tools.py +0 -0
  29. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/__init__.py +0 -0
  30. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/config.py +0 -0
  31. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/embeddings.py +0 -0
  32. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/deepinfra/openai_compat.py +0 -0
  33. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/__init__.py +0 -0
  34. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/manifest.yaml +0 -0
  35. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dify/tool.py +0 -0
  36. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/__init__.py +0 -0
  37. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/memory.py +0 -0
  38. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/dspy/retriever.py +0 -0
  39. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/__init__.py +0 -0
  40. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/config.py +0 -0
  41. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/embeddings.py +0 -0
  42. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/fireworks/openai_compat.py +0 -0
  43. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/flowise/__init__.py +0 -0
  44. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/flowise/node.py +0 -0
  45. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/google_adk/__init__.py +0 -0
  46. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/google_adk/tools.py +0 -0
  47. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/__init__.py +0 -0
  48. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/config.py +0 -0
  49. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/embeddings.py +0 -0
  50. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/groq/openai_compat.py +0 -0
  51. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/hermes/__init__.py +0 -0
  52. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/hermes/provider.py +0 -0
  53. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/instructor/__init__.py +0 -0
  54. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/instructor/memory.py +0 -0
  55. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/__init__.py +0 -0
  56. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/memory.py +0 -0
  57. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/retriever.py +0 -0
  58. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langchain/tools.py +0 -0
  59. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langgraph/__init__.py +0 -0
  60. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/langgraph/memory.py +0 -0
  61. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/letta/__init__.py +0 -0
  62. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/letta/storage.py +0 -0
  63. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/llamaindex/__init__.py +0 -0
  64. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/llamaindex/memory.py +0 -0
  65. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/__init__.py +0 -0
  66. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/actions.py +0 -0
  67. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/nemo_guardrails/knowledge_base.py +0 -0
  68. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/__init__.py +0 -0
  69. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/config.py +0 -0
  70. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/ollama/embeddings.py +0 -0
  71. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/openai_agents/__init__.py +0 -0
  72. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/openai_agents/tools.py +0 -0
  73. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/__init__.py +0 -0
  74. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/blocks.py +0 -0
  75. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/prefect/tasks.py +0 -0
  76. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/__init__.py +0 -0
  77. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/dependencies.py +0 -0
  78. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/pydanticai/tools.py +0 -0
  79. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/smolagents/__init__.py +0 -0
  80. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/smolagents/tools.py +0 -0
  81. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/__init__.py +0 -0
  82. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/activities.py +0 -0
  83. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/temporal/interceptor.py +0 -0
  84. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/__init__.py +0 -0
  85. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/config.py +0 -0
  86. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/embeddings.py +0 -0
  87. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/together/openai_compat.py +0 -0
  88. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/__init__.py +0 -0
  89. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/config.py +0 -0
  90. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/embeddings.py +0 -0
  91. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/integrations/vllm/openai_compat.py +0 -0
  92. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/py.typed +0 -0
  93. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/__init__.py +0 -0
  94. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/cortexdb/v1/exceptions.py +0 -0
  95. {cortexdbai-0.4.3 → cortexdbai-0.6.0}/tests/__init__.py +0 -0
@@ -77,6 +77,6 @@ blog/
77
77
  sales/
78
78
  videos/
79
79
  local-instance/
80
-
81
- # doc-claims verifier scratch output
82
- tools/verifier_out*/
80
+
81
+ # doc-claims verifier scratch output
82
+ tools/verifier_out*/
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cortexdbai
3
- Version: 0.4.3
3
+ Version: 0.6.0
4
4
  Summary: The Long-Term Memory Layer for AI Systems
5
5
  Project-URL: Homepage, https://cortexdb.ai
6
6
  Project-URL: Documentation, https://docs.cortexdb.ai
@@ -173,6 +173,12 @@ print(c.recall(c.actor, query="what did I say?")["context_block"])
173
173
  c.experience("ws:demo", text="Deployed payments-service v3.1 to prod.")
174
174
  c.experience("ws:demo", text="Rolled back auth-gateway.", wait="indexed")
175
175
 
176
+ # Large content: wait="captured" returns at the durable accept point (~10ms);
177
+ # poll write_status to see indexing progress. After a client timeout,
178
+ # found=False or status="failed" both mean re-sending is safe (re-processes).
179
+ c.experience("ws:demo", text=big_transcript, idempotency_key="k1", wait="captured")
180
+ c.write_status(idempotency_key="k1") # {found, status, stages_completed, ...}
181
+
176
182
  # Recall — returns a StratifiedPack { context_block, layers, provenance }.
177
183
  pack = c.recall("ws:demo", query="recent deploys", view="holistic")
178
184
 
@@ -60,6 +60,12 @@ print(c.recall(c.actor, query="what did I say?")["context_block"])
60
60
  c.experience("ws:demo", text="Deployed payments-service v3.1 to prod.")
61
61
  c.experience("ws:demo", text="Rolled back auth-gateway.", wait="indexed")
62
62
 
63
+ # Large content: wait="captured" returns at the durable accept point (~10ms);
64
+ # poll write_status to see indexing progress. After a client timeout,
65
+ # found=False or status="failed" both mean re-sending is safe (re-processes).
66
+ c.experience("ws:demo", text=big_transcript, idempotency_key="k1", wait="captured")
67
+ c.write_status(idempotency_key="k1") # {found, status, stages_completed, ...}
68
+
63
69
  # Recall — returns a StratifiedPack { context_block, layers, provenance }.
64
70
  pack = c.recall("ws:demo", query="recent deploys", view="holistic")
65
71
 
@@ -1,71 +1,71 @@
1
- """CortexDB Python SDK — the long-term memory layer for AI systems.
2
-
3
- The SDK is a thin HTTP client over the v1 API surface that cortexdb
4
- exposes by default on port 3141. The top-level :class:`Cortex` class
5
- (alias :class:`V1Client`) is what you should reach for.
6
-
7
- Quick start::
8
-
9
- from cortexdb import Cortex
10
-
11
- # Local docker run with no auth (CORTEX_API_KEY unset on the server):
12
- with Cortex("http://localhost:3141") as c:
13
- c.experience("ws:demo", text="Priya at Acme signed for 200 seats.")
14
- pack = c.recall("ws:demo", query="How many seats did Acme sign for?")
15
- print(pack)
16
-
17
- # Hosted / multi-tenant deployment with PASETO bearer + actor header:
18
- with Cortex(
19
- "https://api-v1.cortexdb.ai",
20
- actor="user:alice",
21
- bearer="eyJ...",
22
- ) as c:
23
- out = c.answer(
24
- "org:initech/user:alice",
25
- question="What did Alice say about coffee?",
26
- )
27
-
28
- Versions ≤ 0.2.x exposed a separate ``cortexdb.Cortex`` class targeting
29
- the legacy ``/v1/remember`` endpoints. That surface was retired in 0.3.0
30
- because the cortexdb binary's public port now only serves the v1 API;
31
- the legacy endpoints survive only as an internal bench-compat helper.
32
- If you need them, pin ``cortexdbai<0.3``.
33
- """
34
-
35
- from cortexdb.v1 import V1Client, AsyncV1Client
36
- from cortexdb.v1.exceptions import (
37
- V1APIError,
38
- V1AuthError,
39
- V1ConnectionError,
40
- V1Error,
41
- V1NotConfiguredError,
42
- V1PolicyDeniedError,
43
- V1RateLimitError,
44
- V1TimeoutError,
45
- )
46
- from cortexdb import v1 # the explicit submodule namespace
47
-
48
- # Ergonomic top-level aliases. New code can write either form.
49
- Cortex = V1Client
50
- AsyncCortex = AsyncV1Client
51
-
52
- __version__ = "0.4.3"
53
- __all__ = [
54
- # Clients (preferred top-level names)
55
- "Cortex",
56
- "AsyncCortex",
57
- # Explicit v1 names (identical classes, kept for code that pinned them)
58
- "V1Client",
59
- "AsyncV1Client",
60
- # Submodule
61
- "v1",
62
- # Exceptions
63
- "V1Error",
64
- "V1APIError",
65
- "V1AuthError",
66
- "V1ConnectionError",
67
- "V1TimeoutError",
68
- "V1RateLimitError",
69
- "V1NotConfiguredError",
70
- "V1PolicyDeniedError",
71
- ]
1
+ """CortexDB Python SDK — the long-term memory layer for AI systems.
2
+
3
+ The SDK is a thin HTTP client over the v1 API surface that cortexdb
4
+ exposes by default on port 3141. The top-level :class:`Cortex` class
5
+ (alias :class:`V1Client`) is what you should reach for.
6
+
7
+ Quick start::
8
+
9
+ from cortexdb import Cortex
10
+
11
+ # Local docker run with no auth (CORTEX_API_KEY unset on the server):
12
+ with Cortex("http://localhost:3141") as c:
13
+ c.experience("ws:demo", text="Priya at Acme signed for 200 seats.")
14
+ pack = c.recall("ws:demo", query="How many seats did Acme sign for?")
15
+ print(pack)
16
+
17
+ # Hosted / multi-tenant deployment with PASETO bearer + actor header:
18
+ with Cortex(
19
+ "https://api-v1.cortexdb.ai",
20
+ actor="user:alice",
21
+ bearer="eyJ...",
22
+ ) as c:
23
+ out = c.answer(
24
+ "org:initech/user:alice",
25
+ question="What did Alice say about coffee?",
26
+ )
27
+
28
+ Versions ≤ 0.2.x exposed a separate ``cortexdb.Cortex`` class targeting
29
+ the legacy ``/v1/remember`` endpoints. That surface was retired in 0.3.0
30
+ because the cortexdb binary's public port now only serves the v1 API;
31
+ the legacy endpoints survive only as an internal bench-compat helper.
32
+ If you need them, pin ``cortexdbai<0.3``.
33
+ """
34
+
35
+ from cortexdb.v1 import V1Client, AsyncV1Client
36
+ from cortexdb.v1.exceptions import (
37
+ V1APIError,
38
+ V1AuthError,
39
+ V1ConnectionError,
40
+ V1Error,
41
+ V1NotConfiguredError,
42
+ V1PolicyDeniedError,
43
+ V1RateLimitError,
44
+ V1TimeoutError,
45
+ )
46
+ from cortexdb import v1 # the explicit submodule namespace
47
+
48
+ # Ergonomic top-level aliases. New code can write either form.
49
+ Cortex = V1Client
50
+ AsyncCortex = AsyncV1Client
51
+
52
+ __version__ = "0.6.0"
53
+ __all__ = [
54
+ # Clients (preferred top-level names)
55
+ "Cortex",
56
+ "AsyncCortex",
57
+ # Explicit v1 names (identical classes, kept for code that pinned them)
58
+ "V1Client",
59
+ "AsyncV1Client",
60
+ # Submodule
61
+ "v1",
62
+ # Exceptions
63
+ "V1Error",
64
+ "V1APIError",
65
+ "V1AuthError",
66
+ "V1ConnectionError",
67
+ "V1TimeoutError",
68
+ "V1RateLimitError",
69
+ "V1NotConfiguredError",
70
+ "V1PolicyDeniedError",
71
+ ]
@@ -148,6 +148,99 @@ def _envelope(
148
148
  return env
149
149
 
150
150
 
151
+ # Per-action argument contract for POST /v1/conflicts/{id}/resolve. The
152
+ # server rejects unknown shapes with a 422; validating client-side gives the
153
+ # caller a `ValueError` naming the exact problem before any bytes move.
154
+ _RESOLVE_ACTIONS: dict = {
155
+ "pick": {"required": ("winner",), "optional": ("reason",)},
156
+ "split": {"required": ("boundary", "earlier", "later"), "optional": ()},
157
+ "new_information": {"required": ("record",), "optional": ()},
158
+ "dismiss": {"required": (), "optional": ("reason",)},
159
+ }
160
+
161
+
162
+ def _resolve_conflict_body(action: str, kwargs: Mapping[str, Any]) -> dict:
163
+ """Validate + build the resolve-conflict request body.
164
+
165
+ ``kwargs`` maps every action-specific parameter name to its value (or
166
+ ``None`` when the caller didn't pass it). Raises :class:`ValueError` on
167
+ an unknown action, a missing required parameter, or a parameter that
168
+ doesn't belong to the chosen action.
169
+ """
170
+ spec = _RESOLVE_ACTIONS.get(action)
171
+ if spec is None:
172
+ raise ValueError(
173
+ f"unknown resolve action {action!r}; expected one of "
174
+ f"{sorted(_RESOLVE_ACTIONS)}"
175
+ )
176
+ allowed = set(spec["required"]) | set(spec["optional"])
177
+ provided = {k for k, v in kwargs.items() if v is not None}
178
+ missing = [k for k in spec["required"] if k not in provided]
179
+ if missing:
180
+ raise ValueError(f"action {action!r} requires {missing}")
181
+ extraneous = sorted(provided - allowed)
182
+ if extraneous:
183
+ raise ValueError(
184
+ f"action {action!r} does not accept {extraneous} "
185
+ f"(allowed: {sorted(allowed)})"
186
+ )
187
+ body: dict = {"action": action}
188
+ for k in allowed:
189
+ v = kwargs.get(k)
190
+ if v is not None:
191
+ body[k] = v
192
+ if action in ("pick", "dismiss") and "reason" not in body:
193
+ body["reason"] = ""
194
+ return body
195
+
196
+
197
+ def _range_expr(value: Any) -> str:
198
+ """Normalize a bi-temporal range to the server's ``start..end`` string.
199
+
200
+ Accepts a preformatted ``"start..end"`` string or a 2-item
201
+ ``(start, end)`` sequence of date expressions.
202
+ """
203
+ if isinstance(value, str):
204
+ return value
205
+ try:
206
+ start, end = value
207
+ except (TypeError, ValueError) as e:
208
+ raise ValueError(
209
+ "range must be a 'start..end' string or a (start, end) pair"
210
+ ) from e
211
+ return f"{start}..{end}"
212
+
213
+
214
+ def _claims_params(
215
+ scope: str,
216
+ *,
217
+ subject: Optional[str],
218
+ predicate: Optional[str],
219
+ valid_as_of: Optional[str],
220
+ known_as_of: Optional[str],
221
+ valid_during: Optional[Any],
222
+ recorded_during: Optional[Any],
223
+ limit: Optional[int],
224
+ ) -> dict:
225
+ """Build the query params for ``GET /v1/claims``."""
226
+ params: dict = {"scope": scope}
227
+ if subject:
228
+ params["subject"] = subject
229
+ if predicate:
230
+ params["predicate"] = predicate
231
+ if valid_as_of:
232
+ params["valid_as_of"] = valid_as_of
233
+ if known_as_of:
234
+ params["known_as_of"] = known_as_of
235
+ if valid_during is not None:
236
+ params["valid_during"] = _range_expr(valid_during)
237
+ if recorded_during is not None:
238
+ params["recorded_during"] = _range_expr(recorded_during)
239
+ if limit is not None:
240
+ params["limit"] = limit
241
+ return params
242
+
243
+
151
244
  # ── Synchronous client ──────────────────────────────────────────────────────
152
245
 
153
246
 
@@ -291,6 +384,28 @@ class V1Client:
291
384
  path = "/v1/experience" + (f"?wait={wait}" if wait else "")
292
385
  return self._post(path, env)
293
386
 
387
+ def write_status(
388
+ self,
389
+ *,
390
+ idempotency_key: Optional[str] = None,
391
+ event_id: Optional[str] = None,
392
+ ) -> dict:
393
+ """``GET /v1/experience/status`` — did a write land, and how far has
394
+ it progressed? The safe resume path after a client-side timeout, and
395
+ the poll target after ``experience(..., wait="captured")``.
396
+
397
+ Returns ``{found, event_id, status, stages_completed, note}`` where
398
+ ``status`` is ``"captured"`` / ``"indexed"`` / ``"failed"``.
399
+ ``found: False`` (or ``status: "failed"``) means re-sending the same
400
+ write with the same ``idempotency_key`` is safe — it re-processes
401
+ rather than replaying."""
402
+ params: dict = {}
403
+ if idempotency_key is not None:
404
+ params["idempotency_key"] = idempotency_key
405
+ if event_id is not None:
406
+ params["event_id"] = event_id
407
+ return self._get("/v1/experience/status", params=params)
408
+
294
409
  def upload_blob(
295
410
  self, data: bytes, content_type: str = "application/octet-stream"
296
411
  ) -> dict:
@@ -492,6 +607,130 @@ class V1Client:
492
607
  body: dict = {"scope": scope, "topics": list(topics) if topics else []}
493
608
  return self._post("/v1/understanding/synthesize", body)
494
609
 
610
+ # ── conflicts + bi-temporal claims ────────────────────────────────
611
+ #
612
+ # Rendering note: RecordView boundaries carry `valid_from_basis` /
613
+ # `valid_to_basis` ("stated" | "observed"). An "observed" boundary is
614
+ # only a knowledge bound — present it as "by <date>", never
615
+ # "on/since <date>".
616
+
617
+ def list_conflicts(
618
+ self,
619
+ scope: str,
620
+ *,
621
+ status: str = "open",
622
+ limit: int = 50,
623
+ cursor: Optional[int] = None,
624
+ ) -> dict:
625
+ """``GET /v1/conflicts`` — the conflict queue for a scope.
626
+
627
+ ``status`` is ``"open"`` (default) or ``"all"``. Returns
628
+ ``{conflicts: [ConflictView], total, next_cursor}``; pass
629
+ ``next_cursor`` back as ``cursor`` to page.
630
+ """
631
+ params: dict = {"scope": scope, "status": status, "limit": limit}
632
+ if cursor is not None:
633
+ params["cursor"] = cursor
634
+ return self._get("/v1/conflicts", params=params)
635
+
636
+ def get_conflict(self, conflict_id: str) -> dict:
637
+ """``GET /v1/conflicts/{id}`` — one conflict with full member
638
+ records."""
639
+ return self._get(f"/v1/conflicts/{conflict_id}")
640
+
641
+ def resolve_conflict(
642
+ self,
643
+ conflict_id: str,
644
+ action: str,
645
+ *,
646
+ winner: Optional[str] = None,
647
+ reason: Optional[str] = None,
648
+ boundary: Optional[str] = None,
649
+ earlier: Optional[str] = None,
650
+ later: Optional[str] = None,
651
+ record: Optional[str] = None,
652
+ ) -> dict:
653
+ """``POST /v1/conflicts/{id}/resolve`` — manually resolve a conflict.
654
+
655
+ Actions and their arguments:
656
+
657
+ * ``"pick"`` — ``winner`` (record UUID) required, ``reason`` optional.
658
+ * ``"split"`` — ``boundary`` (absolute date expression, e.g.
659
+ ``"2024-03"``), ``earlier`` and ``later`` (record UUIDs) required.
660
+ * ``"new_information"`` — ``record`` (record UUID) required.
661
+ * ``"dismiss"`` — ``reason`` optional.
662
+
663
+ Raises :class:`ValueError` on a bad action/argument combination.
664
+ Returns the post-resolution ``ConflictView``.
665
+ """
666
+ body = _resolve_conflict_body(
667
+ action,
668
+ {
669
+ "winner": winner,
670
+ "reason": reason,
671
+ "boundary": boundary,
672
+ "earlier": earlier,
673
+ "later": later,
674
+ "record": record,
675
+ },
676
+ )
677
+ return self._post(f"/v1/conflicts/{conflict_id}/resolve", body)
678
+
679
+ def query_claims(
680
+ self,
681
+ scope: str,
682
+ *,
683
+ subject: Optional[str] = None,
684
+ predicate: Optional[str] = None,
685
+ valid_as_of: Optional[str] = None,
686
+ known_as_of: Optional[str] = None,
687
+ valid_during: Optional[Any] = None,
688
+ recorded_during: Optional[Any] = None,
689
+ limit: Optional[int] = None,
690
+ ) -> dict:
691
+ """``GET /v1/claims`` — the two-axis bi-temporal record query
692
+ ("what was true at X, as known at Y").
693
+
694
+ * ``valid_as_of`` — world-time point (``"2023"``, ``"2023-06-01"``).
695
+ Defaults server-side to now.
696
+ * ``known_as_of`` — record-time point. Defaults server-side to now.
697
+ * ``valid_during`` / ``recorded_during`` — ranges; either a
698
+ ``"start..end"`` string or a ``(start, end)`` pair.
699
+
700
+ Returns ``{records: [RecordView]}``.
701
+ """
702
+ params = _claims_params(
703
+ scope,
704
+ subject=subject,
705
+ predicate=predicate,
706
+ valid_as_of=valid_as_of,
707
+ known_as_of=known_as_of,
708
+ valid_during=valid_during,
709
+ recorded_during=recorded_during,
710
+ limit=limit,
711
+ )
712
+ return self._get("/v1/claims", params=params)
713
+
714
+ def claim_history(self, scope: str, *, subject: str, predicate: str) -> dict:
715
+ """``GET /v1/claims/history`` — the full change-log of one claim:
716
+ every valid interval and record-time correction, plus any conflicts
717
+ involving the claim. Returns ``{claim, history, conflicts}``."""
718
+ return self._get(
719
+ "/v1/claims/history",
720
+ params={"scope": scope, "subject": subject, "predicate": predicate},
721
+ )
722
+
723
+ def belief_history(self, belief_id: str) -> dict:
724
+ """``GET /v1/beliefs/history`` — current belief + every superseded
725
+ semantic version, oldest first. Returns
726
+ ``{current, versions, revision_count}``."""
727
+ return self._get("/v1/beliefs/history", params={"belief_id": belief_id})
728
+
729
+ def concept_history(self, concept_id: str) -> dict:
730
+ """``GET /v1/concepts/{id}/history`` — current concept + superseded
731
+ semantic versions. Returns ``{current, versions}``."""
732
+ return self._get(f"/v1/concepts/{concept_id}/history")
733
+
495
734
  # ── forget + erasures ─────────────────────────────────────────────
496
735
 
497
736
  def forget(
@@ -798,6 +1037,100 @@ class AsyncV1Client:
798
1037
  body["question_date"] = question_date
799
1038
  return await self._post("/v1/answer", body)
800
1039
 
1040
+ # ── conflicts + bi-temporal claims ────────────────────────────────
1041
+ # Same surface + semantics as the sync client; see V1Client for the
1042
+ # full docstrings (including the "observed → by <date>" rendering rule).
1043
+
1044
+ async def list_conflicts(
1045
+ self,
1046
+ scope: str,
1047
+ *,
1048
+ status: str = "open",
1049
+ limit: int = 50,
1050
+ cursor: Optional[int] = None,
1051
+ ) -> dict:
1052
+ """``GET /v1/conflicts``. See :meth:`V1Client.list_conflicts`."""
1053
+ params: dict = {"scope": scope, "status": status, "limit": limit}
1054
+ if cursor is not None:
1055
+ params["cursor"] = cursor
1056
+ return await self._get("/v1/conflicts", params=params)
1057
+
1058
+ async def get_conflict(self, conflict_id: str) -> dict:
1059
+ """``GET /v1/conflicts/{id}``. See :meth:`V1Client.get_conflict`."""
1060
+ return await self._get(f"/v1/conflicts/{conflict_id}")
1061
+
1062
+ async def resolve_conflict(
1063
+ self,
1064
+ conflict_id: str,
1065
+ action: str,
1066
+ *,
1067
+ winner: Optional[str] = None,
1068
+ reason: Optional[str] = None,
1069
+ boundary: Optional[str] = None,
1070
+ earlier: Optional[str] = None,
1071
+ later: Optional[str] = None,
1072
+ record: Optional[str] = None,
1073
+ ) -> dict:
1074
+ """``POST /v1/conflicts/{id}/resolve``.
1075
+ See :meth:`V1Client.resolve_conflict` for the per-action contract."""
1076
+ body = _resolve_conflict_body(
1077
+ action,
1078
+ {
1079
+ "winner": winner,
1080
+ "reason": reason,
1081
+ "boundary": boundary,
1082
+ "earlier": earlier,
1083
+ "later": later,
1084
+ "record": record,
1085
+ },
1086
+ )
1087
+ return await self._post(f"/v1/conflicts/{conflict_id}/resolve", body)
1088
+
1089
+ async def query_claims(
1090
+ self,
1091
+ scope: str,
1092
+ *,
1093
+ subject: Optional[str] = None,
1094
+ predicate: Optional[str] = None,
1095
+ valid_as_of: Optional[str] = None,
1096
+ known_as_of: Optional[str] = None,
1097
+ valid_during: Optional[Any] = None,
1098
+ recorded_during: Optional[Any] = None,
1099
+ limit: Optional[int] = None,
1100
+ ) -> dict:
1101
+ """``GET /v1/claims``. See :meth:`V1Client.query_claims`."""
1102
+ params = _claims_params(
1103
+ scope,
1104
+ subject=subject,
1105
+ predicate=predicate,
1106
+ valid_as_of=valid_as_of,
1107
+ known_as_of=known_as_of,
1108
+ valid_during=valid_during,
1109
+ recorded_during=recorded_during,
1110
+ limit=limit,
1111
+ )
1112
+ return await self._get("/v1/claims", params=params)
1113
+
1114
+ async def claim_history(
1115
+ self, scope: str, *, subject: str, predicate: str
1116
+ ) -> dict:
1117
+ """``GET /v1/claims/history``. See :meth:`V1Client.claim_history`."""
1118
+ return await self._get(
1119
+ "/v1/claims/history",
1120
+ params={"scope": scope, "subject": subject, "predicate": predicate},
1121
+ )
1122
+
1123
+ async def belief_history(self, belief_id: str) -> dict:
1124
+ """``GET /v1/beliefs/history``. See :meth:`V1Client.belief_history`."""
1125
+ return await self._get(
1126
+ "/v1/beliefs/history", params={"belief_id": belief_id}
1127
+ )
1128
+
1129
+ async def concept_history(self, concept_id: str) -> dict:
1130
+ """``GET /v1/concepts/{id}/history``.
1131
+ See :meth:`V1Client.concept_history`."""
1132
+ return await self._get(f"/v1/concepts/{concept_id}/history")
1133
+
801
1134
  async def _get(
802
1135
  self,
803
1136
  path: str,