Metadata-Version: 2.5
Name: syntarus
Version: 0.4.0
Summary: Project-scoped memory client for Syntarus
Project-URL: Documentation, https://www.syntarus.com/pages/developers
Project-URL: Homepage, https://www.syntarus.com
Project-URL: API Reference, https://www.syntarus.com/pages/api-reference
Project-URL: Security, https://www.syntarus.com/pages/security
Project-URL: Source, https://github.com/sujalkherawat25-stack/memoryos/tree/main/sdk
Project-URL: Issues, https://github.com/sujalkherawat25-stack/memoryos/issues
Author: Syntarus
License: MIT License
        
        Copyright (c) 2026 Syntarus
        
        Permission is hereby granted, free of charge, to any person obtaining a copy
        of this software and associated documentation files (the "Software"), to deal
        in the Software without restriction, including without limitation the rights
        to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
        copies of the Software, and to permit persons to whom the Software is
        furnished to do so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
        SOFTWARE.
License-File: LICENSE
Keywords: agents,ai,llm,memory
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Provides-Extra: langchain
Requires-Dist: langchain-core<2,>=0.3; extra == 'langchain'
Provides-Extra: test
Requires-Dist: build<2,>=1.2; extra == 'test'
Requires-Dist: pytest-asyncio<1,>=0.24; extra == 'test'
Requires-Dist: pytest<9,>=8; extra == 'test'
Requires-Dist: twine<8,>=7; extra == 'test'
Description-Content-Type: text/markdown

# Syntarus Python SDK

The official Python client for the project-scoped Syntarus memory API.

## Install

```bash
pip install syntarus
```

## The smallest useful integration

The common path is three calls: remember a turn, search it, and inspect the
evidence behind the result. Ingestion is asynchronous by default; use
`wait=True` when the next read must see the new turn.

```python
from syntarus import MemoryClient

with MemoryClient("sk_mem_...") as memory:
    memory.remember("The customer prefers email.", user_id="customer_123", wait=True)
    context = memory.search("preferred contact channel", user_id="customer_123")
    evidence = memory.inspect_provenance("preferred contact channel", user_id="customer_123")
```

For LangChain/LangGraph applications:

```bash
pip install "syntarus[langchain]"
```

```python
from syntarus.adapters.langchain import SyntarusMemory

memory = SyntarusMemory(user_id="customer_123", api_key="sk_mem_...")
context = memory.load_memory_variables({"input": "What channel should we use?"})
memory.save_context({"input": "Use email."}, {"output": "Understood."})
memory.close()
```

LangGraph can use `make_recall_node(memory)` and `make_remember_node(memory)`
from `syntarus.adapters.langgraph`; these helpers return ordinary async graph
nodes and do not require LangChain or LangGraph at runtime.

## Continuum CLI

The package also installs the `syntarus` command for local development,
automation, and coding agents. It reads a project key only from
`SYNTARUS_API_KEY` (or a one-off `--api-key` flag); it never saves secrets to
the local profile.

```bash
export SYNTARUS_API_KEY="sk_mem_..."

syntarus doctor
syntarus memory add "The customer prefers vegetarian food." --user customer_123 --wait
syntarus memory search "food preference" --user customer_123
syntarus graph show --user customer_123 --json
syntarus event get evt_... --json
```

Use `syntarus config set-endpoint https://...` only for a self-hosted or test
endpoint. `syntarus --help` lists every command. Add `--json` to receive a
stable `{ "ok", "data" }` response suitable for agents and CI.

Python 3.10 or newer is required. Keep project API keys in a server-side
secret manager; never ship them in browser or mobile applications.

## Add and search memory

```python
from syntarus import MemoryClient

with MemoryClient(api_key="sk_mem_...") as memory:
    accepted = memory.add(
        user_id="customer_123",
        agent_id="support_agent",
        run_id="ticket_456",
        # Anchor relative dates such as "next Friday" to the source event.
        event_time="2023-05-07T12:00:00Z",
        messages=[
            {"role": "user", "content": "I prefer vegetarian food."},
            {"role": "assistant", "content": "I will remember that."},
        ],
        metadata={"channel": "voice"},
        idempotency_key="call-456-final",
    )
    memory.wait_for_event(accepted["event_id"])
    result = memory.search(
        "What food does the customer prefer?",
        user_id="customer_123",
        agent_id="support_agent",
    )
    print(result["context"])
```

`agent_id` creates an isolated memory namespace. Omit it for a user profile
shared by every agent in the project. `run_id` groups the conversation history
used during ingestion. `metadata` is event metadata and is returned by event
status and webhook payloads; it is not currently a memory-search filter.
`event_time` is optional, but use it for replayed or delayed conversations so
relative dates are resolved against when the interaction occurred rather than
when Syntarus processes it. It accepts an ISO-8601 string or a Python
`datetime`.

## Bounded bulk import

Bulk imports are source-identified and run on a lower-priority queue. Estimate
first, then explicitly start the manifest; the same `(source_id,
source_version)` cannot be accepted twice in a project.

```python
records = [{
    "source_id": "ticket-123",
    "source_version": "2026-09-09",
    "user_id": "customer_123",
    "messages": [{"role": "user", "content": "I prefer email."}],
}]
estimate = memory.bulk_estimate(records)
manifest = memory.bulk_start(records)
memory.bulk_pause(manifest["manifest"]["id"])
memory.bulk_resume(manifest["manifest"]["id"])
status = memory.bulk_status(manifest["manifest"]["id"])
errors = memory.bulk_errors(manifest["manifest"]["id"])
```

Each record keeps its original turn order and event timestamp. Failed records
are exported separately; cancelling a manifest does not erase its audit-safe
status.

## Async client

```python
from syntarus import AsyncMemoryClient

async with AsyncMemoryClient(api_key="sk_mem_...") as memory:
    accepted = await memory.add(
        user_id="customer_123",
        messages=[{"role": "user", "content": "Call me after 5 PM."}],
    )
    await memory.wait_for_event(accepted["event_id"])
```

## Production transport behavior

Clients reuse an HTTP connection pool, send a correlation header on every
request, and retry up to two times by default for temporary network errors and
retryable HTTP responses. Reads are safe to replay. Memory writes and deletion
jobs are also safe to retry because the SDK always sends an idempotency key.
Other state-changing calls are deliberately not retried automatically.

Tune this per client, or make a short-lived override for a special operation:

```python
from syntarus import MemoryClient, APIStatusError, RateLimitError

with MemoryClient("sk_mem_...", timeout=30.0, max_retries=3) as memory:
    try:
        result = memory.search("current preference", user_id="customer_123")
    except RateLimitError as error:
        # Respect an API-supplied delay when present.
        print(error.retry_after, error.request_id)
    except APIStatusError as error:
        print(error.status_code, error.request_id)
    # The new client must be closed if it is used.
    with memory.with_options(timeout=5.0, max_retries=0) as fast_fail:
        fast_fail.search("health probe", user_id="customer_123")
```

`APIStatusError` exposes `status_code`, `request_id`, and a parsed response
body. It never contains your API key. The SDK does not retry non-idempotent
state transitions such as webhook creation or bulk pause/resume/cancel.

For a large import, wait on the manifest rather than writing a polling loop:

```python
manifest = memory.bulk_start(records)
result = memory.wait_for_manifest(manifest["manifest"]["id"], timeout=3600)
```

## Lifecycle operations

```python
memory.export(user_id="customer_123", agent_id="support_agent")
memory.delete("memory-uuid")
memory.delete_user(user_id="customer_123", agent_id="support_agent")
```

Deleting a user without `agent_id` deletes only the shared user namespace.
Delete each agent namespace separately when an application uses agent-scoped
memory.

## Enterprise isolation and governance

Use a project key with `tokens:write` to mint a short-lived credential for one
end user and, optionally, one agent. Subject tokens can only read or write
their bound namespace; they cannot export, delete, manage keys, or read audit
logs.

```python
issued = memory.create_subject_token(
    user_id="customer_123",
    agent_id="support_agent",
    scopes=["memories:read", "memories:write"],
    ttl_seconds=900,
)

with MemoryClient(api_key=issued["token"]) as delegated:
    delegated.search("What does this customer prefer?", user_id="customer_123")
```

Verified deletion is a durable cross-store job. Poll it with the normal event
method to receive the per-store completion receipt:

```python
deletion = memory.request_user_deletion(
    user_id="customer_123",
    agent_id="support_agent",
    idempotency_key="erase-customer-123-support",
)
receipt = memory.wait_for_event(deletion["event_id"])
print(receipt["result"]["stores"])

audit = memory.audit_events(user_id="customer_123", limit=50)
```

The project owner must enable delegated tokens in the developer console first.
Audit records intentionally exclude memory text and secret values.

## Deterministic action-policy check

Before a consequential external action, ask Continuum to check direct user-stated
memory constraints immediately before execution:

```python
check = memory.check_action_policy(
    user_id="customer_123",
    action_type="food_order",
    action_description="Order Thai satay for David",
    arguments={"item": "Thai satay", "diner": "David"},
)
if check["blocked"] or not check["memory_policy_clear"]:
    raise RuntimeError(f"Stop action: {check['reason_codes']}")
# Run your own authorization, ingredient verification, and approval checks too.
execute_order()
```

The async client provides `await memory.check_action_policy(...)`. Treat request
errors, timeouts, blocked results, and inconclusive results as stop signals. A
clear memory-policy result is not application authorization: the endpoint never
executes the action and always returns `execution_authorized: false`. Apply your
own ACLs, transaction limits, and human-confirmation requirements.

For a single fail-closed integration point, pass the side effect to
`execute_guarded`. It calls `/actions/authorize` immediately before invoking
your callable and raises `ActionExecutionBlocked` for blocked, stale/review,
untrusted, inconclusive, unavailable, unauthorized, or unconfirmed actions:

```python
from syntarus import ActionExecutionBlocked

try:
    result = memory.execute_guarded(
        user_id="customer_123",
        action_type="send_email",
        action_description="Send the approved support reply",
        executor=lambda: mailer.send(...),
        application_authorized=acl.allows("send_email"),
        confirmation_received=human_confirmed,
    )
except ActionExecutionBlocked:
    # Do not retry the side effect; request a fresh policy/confirmation.
    result = {"status": "stopped"}

Current-state labels are conservative: `current` is backed by an explicitly
configured single-valued project slot; `historical` is superseded evidence;
`unversioned` means Continuum has no proof either way. Historical evidence is
kept for past-tense retrieval.

## Webhooks

```python
created = memory.create_webhook("https://example.com/syntarus/events")
signing_secret = created["signing_secret"]  # returned once
memory.list_webhooks()
memory.webhook_deliveries(webhook_id=created["webhook"]["id"])
memory.delete_webhook(created["webhook"]["id"])
```

Deliveries include `X-Syntarus-Timestamp`, `X-Syntarus-Delivery`, and
`X-Syntarus-Signature-256`. Verify the signature as HMAC-SHA256 over
`<timestamp>.<raw_request_body>` and reject timestamps outside a five-minute
window. A non-2xx response is retried durably and eventually becomes a
dead-letter delivery visible through `webhook_deliveries`.

## Errors

The SDK raises typed exceptions derived from `SyntarusError`:

- `AuthenticationError`
- `PermissionDenied`
- `RateLimitError` (`retry_after` is available when the API sends it)
- `APIConnectionError`
- `APITimeoutError`
- `EventFailedError` (`event` contains the terminal event record)

## Syntarus resources

- [Syntarus website](https://www.syntarus.com)
- [Developer portal](https://www.syntarus.com/pages/developers)
- [API reference](https://www.syntarus.com/pages/api-reference)
- [Security and reliability](https://www.syntarus.com/pages/security)
- [Source code](https://github.com/sujalkherawat25-stack/memoryos/tree/main/sdk)
- [Report an issue](https://github.com/sujalkherawat25-stack/memoryos/issues)
