hebbrix 2.3.0__tar.gz → 2.3.2__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.
- hebbrix-2.3.2/CHANGELOG.md +33 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/MANIFEST.in +1 -0
- {hebbrix-2.3.0/hebbrix.egg-info → hebbrix-2.3.2}/PKG-INFO +39 -2
- {hebbrix-2.3.0 → hebbrix-2.3.2}/README.md +16 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/__init__.py +1 -1
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/client.py +1 -1
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/resources.py +240 -22
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/sync_client.py +282 -3
- {hebbrix-2.3.0 → hebbrix-2.3.2/hebbrix.egg-info}/PKG-INFO +39 -2
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix.egg-info/SOURCES.txt +2 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/pyproject.toml +3 -4
- {hebbrix-2.3.0 → hebbrix-2.3.2}/tests/test_memories_resource.py +171 -2
- {hebbrix-2.3.0 → hebbrix-2.3.2}/tests/test_openapi_parity.py +29 -41
- hebbrix-2.3.2/tests/test_procedural_resource.py +121 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/tests/test_sync_client.py +5 -1
- {hebbrix-2.3.0 → hebbrix-2.3.2}/LICENSE +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/chat.py +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/exceptions.py +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix/models.py +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix.egg-info/dependency_links.txt +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix.egg-info/requires.txt +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/hebbrix.egg-info/top_level.txt +0 -0
- {hebbrix-2.3.0 → hebbrix-2.3.2}/setup.cfg +0 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 2.3.2 — 2026-08-27
|
|
4
|
+
|
|
5
|
+
- Align procedure create/list/get/update/execute/delete with the canonical
|
|
6
|
+
`/v1/procedures` REST and OpenAPI contract, including idempotent `204`
|
|
7
|
+
deletion.
|
|
8
|
+
- Add explicit synchronous/asynchronous batch readiness receipts to the async
|
|
9
|
+
and synchronous clients.
|
|
10
|
+
- Add batch wait helpers that poll every accepted memory, propagate terminal
|
|
11
|
+
failures, respect deadlines, and support asynchronous cancellation.
|
|
12
|
+
|
|
13
|
+
## 2.3.1 — 2026-08-26
|
|
14
|
+
|
|
15
|
+
- Make `wait_for_index=True` poll the authoritative memory or job readiness
|
|
16
|
+
endpoint instead of returning a non-searchable receipt.
|
|
17
|
+
- Add `memories.wait_until_searchable(...)` to the async and synchronous
|
|
18
|
+
clients, with explicit timeout and terminal-failure handling.
|
|
19
|
+
- Preserve valid evidence-bound search rows during a degraded or abstaining
|
|
20
|
+
server response while retaining the safety metadata; malformed and explicit
|
|
21
|
+
no-match envelopes still fail closed.
|
|
22
|
+
- Keep the synchronous and asynchronous memory create, scope, idempotency, and
|
|
23
|
+
search-safety behavior aligned.
|
|
24
|
+
- Add `temporal.delete_fact(fact_id)` for tenant-scoped, idempotent cleanup of
|
|
25
|
+
facts created through the temporal API.
|
|
26
|
+
|
|
27
|
+
The supported server/SDK release pair is published by the server OpenAPI
|
|
28
|
+
document in `info.x-hebbrix-sdk-compatibility`. Patch releases preserve the
|
|
29
|
+
public API within the same major version.
|
|
30
|
+
|
|
31
|
+
## 2.3.0
|
|
32
|
+
|
|
33
|
+
- Added the GA scoped memory, corrections, search proof, and ProofLoop surface.
|
|
@@ -1,10 +1,31 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: hebbrix
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.2
|
|
4
4
|
Summary: Advanced Memory API for AI Agents with Reinforcement Learning - 3-line integration
|
|
5
5
|
Author-email: Hebbrix Team <support@hebbrix.com>
|
|
6
6
|
Maintainer-email: Hebbrix Team <support@hebbrix.com>
|
|
7
|
-
License
|
|
7
|
+
License: MIT License
|
|
8
|
+
|
|
9
|
+
Copyright (c) 2025 Hebbrix
|
|
10
|
+
|
|
11
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
12
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
13
|
+
in the Software without restriction, including without limitation the rights
|
|
14
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
15
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
16
|
+
furnished to do so, subject to the following conditions:
|
|
17
|
+
|
|
18
|
+
The above copyright notice and this permission notice shall be included in all
|
|
19
|
+
copies or substantial portions of the Software.
|
|
20
|
+
|
|
21
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
22
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
23
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
24
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
25
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
26
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
27
|
+
SOFTWARE.
|
|
28
|
+
|
|
8
29
|
Project-URL: Homepage, https://hebbrix.com
|
|
9
30
|
Project-URL: Documentation, https://docs.hebbrix.com
|
|
10
31
|
Project-URL: Repository, https://github.com/hebbrix/hebbrix-python
|
|
@@ -92,6 +113,22 @@ async def main():
|
|
|
92
113
|
wait_for_index=True,
|
|
93
114
|
)
|
|
94
115
|
|
|
116
|
+
# wait_for_index=True polls the returned memory status through the SDK.
|
|
117
|
+
# It returns only when searchable=true, raises on terminal failure, and
|
|
118
|
+
# raises TimeoutError if the caller's readiness deadline expires.
|
|
119
|
+
|
|
120
|
+
# Batch contract: a successful synchronous return means every accepted item
|
|
121
|
+
# is searchable. A bounded server timeout raises explicitly and is safe to
|
|
122
|
+
# retry with the same idempotency key; it is never a successful 202.
|
|
123
|
+
batch = await client.memories.create_batch(
|
|
124
|
+
[{"content": "First fact"}, {"content": "Second fact"}],
|
|
125
|
+
collection_id=collection["id"],
|
|
126
|
+
wait_for_index=True,
|
|
127
|
+
idempotency_key="import-42",
|
|
128
|
+
)
|
|
129
|
+
# For wait_for_index=False receipts:
|
|
130
|
+
# batch = await client.memories.wait_batch_until_searchable(batch)
|
|
131
|
+
|
|
95
132
|
# Search memories
|
|
96
133
|
results = await client.search(
|
|
97
134
|
query="What programming language does user like?",
|
|
@@ -51,6 +51,22 @@ async def main():
|
|
|
51
51
|
wait_for_index=True,
|
|
52
52
|
)
|
|
53
53
|
|
|
54
|
+
# wait_for_index=True polls the returned memory status through the SDK.
|
|
55
|
+
# It returns only when searchable=true, raises on terminal failure, and
|
|
56
|
+
# raises TimeoutError if the caller's readiness deadline expires.
|
|
57
|
+
|
|
58
|
+
# Batch contract: a successful synchronous return means every accepted item
|
|
59
|
+
# is searchable. A bounded server timeout raises explicitly and is safe to
|
|
60
|
+
# retry with the same idempotency key; it is never a successful 202.
|
|
61
|
+
batch = await client.memories.create_batch(
|
|
62
|
+
[{"content": "First fact"}, {"content": "Second fact"}],
|
|
63
|
+
collection_id=collection["id"],
|
|
64
|
+
wait_for_index=True,
|
|
65
|
+
idempotency_key="import-42",
|
|
66
|
+
)
|
|
67
|
+
# For wait_for_index=False receipts:
|
|
68
|
+
# batch = await client.memories.wait_batch_until_searchable(batch)
|
|
69
|
+
|
|
54
70
|
# Search memories
|
|
55
71
|
results = await client.search(
|
|
56
72
|
query="What programming language does user like?",
|
|
@@ -127,12 +127,12 @@ def _canonical_search_envelope(
|
|
|
127
127
|
elif data.get("no_match") and (row_ids or evidence_ids):
|
|
128
128
|
reason = "no_match_contains_evidence"
|
|
129
129
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
):
|
|
130
|
+
# Degradation and abstention are confidence signals, not proof that the
|
|
131
|
+
# API returned no evidence. Erasing valid, evidence-bound rows here made
|
|
132
|
+
# the SDK disagree with the REST response and converted a safe fallback
|
|
133
|
+
# into a false negative. Fail closed only when the envelope is malformed
|
|
134
|
+
# or the server explicitly reports no match.
|
|
135
|
+
if reason or data.get("no_match") is True:
|
|
136
136
|
data[rows_key] = []
|
|
137
137
|
if rows_key == "results":
|
|
138
138
|
data["total"] = 0
|
|
@@ -147,6 +147,8 @@ def _canonical_search_envelope(
|
|
|
147
147
|
"status": "no_grounded_match",
|
|
148
148
|
"reason": reason,
|
|
149
149
|
}
|
|
150
|
+
elif data.get("degraded") is True or data.get("abstain_recommended") is True:
|
|
151
|
+
data["sdk_safety_reason"] = "degraded_evidence_preserved"
|
|
150
152
|
return data
|
|
151
153
|
|
|
152
154
|
|
|
@@ -347,6 +349,8 @@ class MemoriesResource(BaseResource):
|
|
|
347
349
|
tags: Optional[List[str]] = None,
|
|
348
350
|
source: Optional[str] = None,
|
|
349
351
|
idempotency_key: Optional[str] = None,
|
|
352
|
+
index_timeout: float = 60.0,
|
|
353
|
+
index_poll_interval: float = 0.5,
|
|
350
354
|
) -> Dict[str, Any]:
|
|
351
355
|
"""
|
|
352
356
|
Create a memory.
|
|
@@ -422,7 +426,199 @@ class MemoriesResource(BaseResource):
|
|
|
422
426
|
request_kwargs: Dict[str, Any] = {"json": payload}
|
|
423
427
|
if idempotency_key:
|
|
424
428
|
request_kwargs["headers"] = {"Idempotency-Key": idempotency_key}
|
|
425
|
-
|
|
429
|
+
receipt = await self.client.post("/v1/memories", **request_kwargs)
|
|
430
|
+
if wait_for_index:
|
|
431
|
+
receipt = await self._ensure_searchable_receipt(
|
|
432
|
+
receipt,
|
|
433
|
+
timeout=index_timeout,
|
|
434
|
+
poll_interval=index_poll_interval,
|
|
435
|
+
)
|
|
436
|
+
return receipt
|
|
437
|
+
|
|
438
|
+
async def create_batch(
|
|
439
|
+
self,
|
|
440
|
+
memories: List[Dict[str, Any]],
|
|
441
|
+
*,
|
|
442
|
+
collection_id: Optional[str] = None,
|
|
443
|
+
user_id: Optional[str] = None,
|
|
444
|
+
agent_id: Optional[str] = None,
|
|
445
|
+
run_id: Optional[str] = None,
|
|
446
|
+
app_id: Optional[str] = None,
|
|
447
|
+
namespace: Optional[str] = None,
|
|
448
|
+
wait_for_index: bool = False,
|
|
449
|
+
idempotency_key: Optional[str] = None,
|
|
450
|
+
) -> Dict[str, Any]:
|
|
451
|
+
"""Create a durable batch with explicit synchronous/async readiness.
|
|
452
|
+
|
|
453
|
+
When ``wait_for_index`` is true, a successful return is guaranteed to
|
|
454
|
+
have ``processing_status=completed`` and ``searchable=true``. A bounded
|
|
455
|
+
server timeout raises an HTTP error and may be retried with the same
|
|
456
|
+
idempotency key; it is never returned as a successful processing state.
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
if not 1 <= len(memories) <= 100:
|
|
460
|
+
raise ValueError("memories must contain between 1 and 100 items")
|
|
461
|
+
if any(not str(item.get("content") or "").strip() for item in memories):
|
|
462
|
+
raise ValueError("every batch memory must contain non-empty content")
|
|
463
|
+
payload = {
|
|
464
|
+
"memories": memories,
|
|
465
|
+
"wait_for_index": wait_for_index,
|
|
466
|
+
}
|
|
467
|
+
optional = {
|
|
468
|
+
"collection_id": collection_id,
|
|
469
|
+
"user_id": user_id,
|
|
470
|
+
"agent_id": agent_id,
|
|
471
|
+
"run_id": run_id,
|
|
472
|
+
"app_id": app_id,
|
|
473
|
+
"namespace": namespace,
|
|
474
|
+
}
|
|
475
|
+
payload.update(
|
|
476
|
+
{key: value for key, value in optional.items() if value is not None}
|
|
477
|
+
)
|
|
478
|
+
kwargs: Dict[str, Any] = {"json": payload}
|
|
479
|
+
if idempotency_key:
|
|
480
|
+
kwargs["headers"] = {"Idempotency-Key": idempotency_key}
|
|
481
|
+
receipt = await self.client.post("/v1/memories/batch", **kwargs)
|
|
482
|
+
if wait_for_index and not (
|
|
483
|
+
receipt.get("searchable") is True
|
|
484
|
+
and str(receipt.get("processing_status") or "").casefold() == "completed"
|
|
485
|
+
):
|
|
486
|
+
raise RuntimeError(
|
|
487
|
+
"wait_for_index batch response was not fully searchable; retry "
|
|
488
|
+
"with the same Idempotency-Key"
|
|
489
|
+
)
|
|
490
|
+
return receipt
|
|
491
|
+
|
|
492
|
+
async def wait_batch_until_searchable(
|
|
493
|
+
self,
|
|
494
|
+
receipt: Dict[str, Any],
|
|
495
|
+
*,
|
|
496
|
+
timeout: float = 60.0,
|
|
497
|
+
poll_interval: float = 0.5,
|
|
498
|
+
) -> Dict[str, Any]:
|
|
499
|
+
"""Poll every item in an asynchronous batch receipt to one terminal state.
|
|
500
|
+
|
|
501
|
+
Cancellation uses normal asyncio task cancellation. A failed/cancelled
|
|
502
|
+
item or a local deadline raises explicitly; a successful return means
|
|
503
|
+
every batch item reported ``searchable=true``.
|
|
504
|
+
"""
|
|
505
|
+
|
|
506
|
+
memory_ids = list(dict.fromkeys(map(str, receipt.get("memory_ids") or [])))
|
|
507
|
+
if not memory_ids:
|
|
508
|
+
raise ValueError("batch receipt does not contain memory_ids")
|
|
509
|
+
deadline = time.monotonic() + max(0.0, timeout)
|
|
510
|
+
while True:
|
|
511
|
+
rows = await asyncio.gather(
|
|
512
|
+
*(self.get(memory_id) for memory_id in memory_ids)
|
|
513
|
+
)
|
|
514
|
+
for row in rows:
|
|
515
|
+
state = str(row.get("processing_status") or "").casefold()
|
|
516
|
+
if state in {"failed", "cancelled", "canceled"}:
|
|
517
|
+
raise RuntimeError(
|
|
518
|
+
f"memory {row.get('id')} indexing reached terminal state {state}"
|
|
519
|
+
)
|
|
520
|
+
if all(row.get("searchable") is True for row in rows):
|
|
521
|
+
return {
|
|
522
|
+
**receipt,
|
|
523
|
+
"processing_status": "completed",
|
|
524
|
+
"searchable": True,
|
|
525
|
+
"results": [
|
|
526
|
+
{
|
|
527
|
+
"id": memory_id,
|
|
528
|
+
"memory_id": memory_id,
|
|
529
|
+
"processing_status": "completed",
|
|
530
|
+
}
|
|
531
|
+
for memory_id in memory_ids
|
|
532
|
+
],
|
|
533
|
+
}
|
|
534
|
+
if time.monotonic() >= deadline:
|
|
535
|
+
raise TimeoutError(f"batch was not searchable within {timeout}s")
|
|
536
|
+
await asyncio.sleep(max(0.05, poll_interval))
|
|
537
|
+
|
|
538
|
+
async def wait_until_searchable(
|
|
539
|
+
self,
|
|
540
|
+
memory_id: str,
|
|
541
|
+
*,
|
|
542
|
+
timeout: float = 60.0,
|
|
543
|
+
poll_interval: float = 0.5,
|
|
544
|
+
) -> Dict[str, Any]:
|
|
545
|
+
"""Poll the authoritative memory status until indexing is terminal."""
|
|
546
|
+
|
|
547
|
+
deadline = time.monotonic() + max(0.0, timeout)
|
|
548
|
+
while True:
|
|
549
|
+
memory = await self.get(memory_id)
|
|
550
|
+
state = str(memory.get("processing_status") or "").casefold()
|
|
551
|
+
if memory.get("searchable") is True:
|
|
552
|
+
return memory
|
|
553
|
+
if state == "completed":
|
|
554
|
+
raise RuntimeError(
|
|
555
|
+
f"memory {memory_id} reported completed without searchable=true"
|
|
556
|
+
)
|
|
557
|
+
if state in {"failed", "cancelled", "canceled"}:
|
|
558
|
+
raise RuntimeError(
|
|
559
|
+
f"memory {memory_id} indexing reached terminal state {state}"
|
|
560
|
+
)
|
|
561
|
+
if time.monotonic() >= deadline:
|
|
562
|
+
raise TimeoutError(
|
|
563
|
+
f"memory {memory_id} was not searchable within {timeout}s"
|
|
564
|
+
)
|
|
565
|
+
await asyncio.sleep(max(0.05, poll_interval))
|
|
566
|
+
|
|
567
|
+
async def _ensure_searchable_receipt(
|
|
568
|
+
self,
|
|
569
|
+
receipt: Dict[str, Any],
|
|
570
|
+
*,
|
|
571
|
+
timeout: float,
|
|
572
|
+
poll_interval: float,
|
|
573
|
+
) -> Dict[str, Any]:
|
|
574
|
+
if (
|
|
575
|
+
receipt.get("searchable") is True
|
|
576
|
+
and str(receipt.get("processing_status") or "").casefold() == "completed"
|
|
577
|
+
):
|
|
578
|
+
return receipt
|
|
579
|
+
job_id = str(receipt.get("job_id") or "")
|
|
580
|
+
if job_id:
|
|
581
|
+
deadline = time.monotonic() + max(0.0, timeout)
|
|
582
|
+
while True:
|
|
583
|
+
job = await self.client.get(f"/v1/memory-jobs/{job_id}")
|
|
584
|
+
state = str(job.get("status") or "").casefold()
|
|
585
|
+
if state == "completed":
|
|
586
|
+
receipt.update(job)
|
|
587
|
+
receipt["searchable"] = True
|
|
588
|
+
receipt["processing_status"] = "completed"
|
|
589
|
+
return receipt
|
|
590
|
+
if state in {"failed", "cancelled", "canceled"}:
|
|
591
|
+
raise RuntimeError(
|
|
592
|
+
f"memory job {job_id} reached terminal state {state}"
|
|
593
|
+
)
|
|
594
|
+
if time.monotonic() >= deadline:
|
|
595
|
+
raise TimeoutError(
|
|
596
|
+
f"memory job {job_id} did not become searchable within {timeout}s"
|
|
597
|
+
)
|
|
598
|
+
await asyncio.sleep(max(0.05, poll_interval))
|
|
599
|
+
|
|
600
|
+
candidates = [
|
|
601
|
+
receipt.get("id"),
|
|
602
|
+
*(
|
|
603
|
+
item.get("id") or item.get("memory_id")
|
|
604
|
+
for item in (receipt.get("results") or [])
|
|
605
|
+
if isinstance(item, dict)
|
|
606
|
+
),
|
|
607
|
+
]
|
|
608
|
+
memory_id = next((str(value) for value in candidates if value), "")
|
|
609
|
+
if not memory_id:
|
|
610
|
+
raise RuntimeError(
|
|
611
|
+
"wait_for_index response contained neither a memory id nor a job id"
|
|
612
|
+
)
|
|
613
|
+
ready = await self.wait_until_searchable(
|
|
614
|
+
memory_id,
|
|
615
|
+
timeout=timeout,
|
|
616
|
+
poll_interval=poll_interval,
|
|
617
|
+
)
|
|
618
|
+
receipt["searchable"] = True
|
|
619
|
+
receipt["processing_status"] = "completed"
|
|
620
|
+
receipt["status_url"] = ready.get("status_url") or f"/v1/memories/{memory_id}"
|
|
621
|
+
return receipt
|
|
426
622
|
|
|
427
623
|
async def list_page(
|
|
428
624
|
self,
|
|
@@ -1091,6 +1287,15 @@ class RLResource(BaseResource):
|
|
|
1091
1287
|
class ProceduralResource(BaseResource):
|
|
1092
1288
|
"""Procedural memory endpoints."""
|
|
1093
1289
|
|
|
1290
|
+
@staticmethod
|
|
1291
|
+
def _unwrap_procedure(response: Dict[str, Any]) -> Dict[str, Any]:
|
|
1292
|
+
procedure = response.get("procedure")
|
|
1293
|
+
if isinstance(procedure, dict):
|
|
1294
|
+
return procedure
|
|
1295
|
+
if response.get("procedure_id") and not response.get("id"):
|
|
1296
|
+
return {**response, "id": response["procedure_id"]}
|
|
1297
|
+
return response
|
|
1298
|
+
|
|
1094
1299
|
async def create(
|
|
1095
1300
|
self,
|
|
1096
1301
|
name: str,
|
|
@@ -1116,18 +1321,19 @@ class ProceduralResource(BaseResource):
|
|
|
1116
1321
|
Returns:
|
|
1117
1322
|
Created procedure
|
|
1118
1323
|
"""
|
|
1119
|
-
|
|
1120
|
-
"/
|
|
1324
|
+
response = await self.client.post(
|
|
1325
|
+
"/v1/procedures",
|
|
1121
1326
|
json={
|
|
1122
1327
|
"name": name,
|
|
1123
1328
|
"description": description,
|
|
1124
|
-
"
|
|
1125
|
-
"
|
|
1329
|
+
"condition": {"expression": trigger_condition},
|
|
1330
|
+
"action": {"steps": action_sequence},
|
|
1126
1331
|
"collection_id": collection_id,
|
|
1127
1332
|
"category": category,
|
|
1128
|
-
"
|
|
1333
|
+
"parameters": metadata or {},
|
|
1129
1334
|
},
|
|
1130
1335
|
)
|
|
1336
|
+
return self._unwrap_procedure(response)
|
|
1131
1337
|
|
|
1132
1338
|
async def list(
|
|
1133
1339
|
self,
|
|
@@ -1154,7 +1360,10 @@ class ProceduralResource(BaseResource):
|
|
|
1154
1360
|
if category:
|
|
1155
1361
|
params["category"] = category
|
|
1156
1362
|
|
|
1157
|
-
|
|
1363
|
+
response = await self.client.get("/v1/procedures", params=params)
|
|
1364
|
+
return (
|
|
1365
|
+
response if isinstance(response, list) else response.get("procedures", [])
|
|
1366
|
+
)
|
|
1158
1367
|
|
|
1159
1368
|
async def get(self, procedure_id: str) -> Dict[str, Any]:
|
|
1160
1369
|
"""
|
|
@@ -1166,7 +1375,8 @@ class ProceduralResource(BaseResource):
|
|
|
1166
1375
|
Returns:
|
|
1167
1376
|
Procedure data
|
|
1168
1377
|
"""
|
|
1169
|
-
|
|
1378
|
+
response = await self.client.get(f"/v1/procedures/{procedure_id}")
|
|
1379
|
+
return self._unwrap_procedure(response)
|
|
1170
1380
|
|
|
1171
1381
|
async def execute(
|
|
1172
1382
|
self,
|
|
@@ -1183,10 +1393,12 @@ class ProceduralResource(BaseResource):
|
|
|
1183
1393
|
Returns:
|
|
1184
1394
|
Execution result
|
|
1185
1395
|
"""
|
|
1186
|
-
|
|
1187
|
-
f"/
|
|
1188
|
-
json={"
|
|
1396
|
+
response = await self.client.post(
|
|
1397
|
+
f"/v1/procedures/{procedure_id}/execute",
|
|
1398
|
+
json={"input_state": context or {}},
|
|
1189
1399
|
)
|
|
1400
|
+
result = response.get("execution_result")
|
|
1401
|
+
return result if isinstance(result, dict) else response
|
|
1190
1402
|
|
|
1191
1403
|
async def update(
|
|
1192
1404
|
self,
|
|
@@ -1217,13 +1429,14 @@ class ProceduralResource(BaseResource):
|
|
|
1217
1429
|
if description is not None:
|
|
1218
1430
|
data["description"] = description
|
|
1219
1431
|
if trigger_condition is not None:
|
|
1220
|
-
data["
|
|
1432
|
+
data["condition"] = {"expression": trigger_condition}
|
|
1221
1433
|
if action_sequence is not None:
|
|
1222
|
-
data["
|
|
1434
|
+
data["action"] = {"steps": action_sequence}
|
|
1223
1435
|
if metadata is not None:
|
|
1224
|
-
data["
|
|
1436
|
+
data["parameters"] = metadata
|
|
1225
1437
|
|
|
1226
|
-
|
|
1438
|
+
response = await self.client.patch(f"/v1/procedures/{procedure_id}", json=data)
|
|
1439
|
+
return self._unwrap_procedure(response)
|
|
1227
1440
|
|
|
1228
1441
|
async def delete(self, procedure_id: str) -> None:
|
|
1229
1442
|
"""
|
|
@@ -1232,7 +1445,7 @@ class ProceduralResource(BaseResource):
|
|
|
1232
1445
|
Args:
|
|
1233
1446
|
procedure_id: Procedure ID
|
|
1234
1447
|
"""
|
|
1235
|
-
await self.client.delete(f"/
|
|
1448
|
+
await self.client.delete(f"/v1/procedures/{procedure_id}")
|
|
1236
1449
|
|
|
1237
1450
|
|
|
1238
1451
|
class TemporalResource(BaseResource):
|
|
@@ -1333,6 +1546,11 @@ class TemporalResource(BaseResource):
|
|
|
1333
1546
|
},
|
|
1334
1547
|
)
|
|
1335
1548
|
|
|
1549
|
+
async def delete_fact(self, fact_id: str) -> Dict[str, Any]:
|
|
1550
|
+
"""Permanently delete a tenant-scoped temporal fact by stable ID."""
|
|
1551
|
+
|
|
1552
|
+
return await self.client.delete(f"/temporal/facts/{fact_id}")
|
|
1553
|
+
|
|
1336
1554
|
|
|
1337
1555
|
class WorkingMemoryResource(BaseResource):
|
|
1338
1556
|
"""Working memory buffer endpoints."""
|