Metadata-Version: 2.4
Name: marrowid
Version: 1.0.8
Summary: Marrow agent-memory client for ingest, jobs, query, and the native /v1 memory API.
Project-URL: Homepage, https://marrow.id
Project-URL: Documentation, https://docs.marrow.id
Author: Marrow Technologies Ltd
License: UNLICENSED
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: Other/Proprietary License
Classifier: Operating System :: MacOS
Classifier: Operating System :: Microsoft :: Windows
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Typing :: Typed
Requires-Python: >=3.9
Requires-Dist: httpx>=0.27
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == 'dev'
Description-Content-Type: text/markdown

# marrowid - the Marrow Python client

Distribution `marrowid`, import package `marrow`, primary class `Marrow`.

The public client identities are `marrowid==1.0.8`, imported as `marrow`,
`@marrowid/sdk@1.0.8`, and `@marrowid/cli@1.0.8`. The HTTP contract is their
semantic authority. Direct HTTP, TypeScript, Python, and CLI examples run inside
a trusted application process; using one of these clients does not provide
prompt-injection isolation for untrusted content.

This proprietary typed client maps directly to Marrow's classic source/job/query
routes and native `/v1` memory routes. It uses one customer API key, preserves
API error classes, and keeps the product contract explicit: `peer.ask()`
returns cited evidence or an honest `insufficient_evidence` state. There is no `chat` method
and no compatibility import alias.

## Install

Install the exact stable version from PyPI:

```bash
python -m pip install marrowid==1.0.8
python -c "from marrow import Marrow, __version__; print(Marrow.__name__, __version__)"
```

The package metadata declares CPython 3.9 through 3.14 on macOS, Linux, and
Windows. The wheel is pure Python (`py3-none-any`).

[Marrow](https://marrow.id) · [Documentation](https://docs.marrow.id)

## First use: connect context and retrieve it

Create a customer API key with `ingest` and `query` scopes, then initialize the
client. Keep `MARROW_API_KEY` in server secret storage or the invoking process
environment. Do not put it in source, arguments, notebooks, logs, screenshots,
or generated documentation.

```python
import os
from marrow import Marrow

client = Marrow(
    api_key=os.environ["MARROW_API_KEY"],
    host=os.environ.get("MARROW_API_BASE_URL", "https://api.marrow.id"),
)
```

Dry-runs may omit an idempotency key. Live URL and file ingest require a
caller-owned `idempotency_key` between 8 and 160 characters so a retry can reuse
the same submission identity without creating another job.

```python
preview = client.ingest.url(
    "https://example.com/onboarding-note",
    dry_run=True,
    dated_at="2026-07-12",
)

queued = client.ingest.url(
    "https://example.com/onboarding-note",
    dry_run=False,
    dated_at="2026-07-12",
    idempotency_key="onboarding-note-2026-07-12",
)

job = client.ingest.jobs.wait(
    queued.job.id,
    max_attempts=60,
    poll_interval=1.0,
)
if job.job.status == "succeeded":
    result = client.query(
        "What should this workflow remember about onboarding?",
        limit=5,
        evidence_limit=3,
    )
```

Use `client.ingest.file(...)` for a local path; pass `content_type` when the
media type is known. `client.ingest.jobs.list()` and `.get(job_id)` provide
single reads when the application owns its own polling loop. Classic query
returns only the current launch statuses `partial` or `insufficient_evidence`;
the reserved `answered` status fails closed instead of being treated as a
verified answer.

## Native memory usage

The native memory methods are available and use `memory.read` and
`memory.write` scopes. Use a dedicated customer API key with only the scopes
the app or agent needs.

```python
import time

peer = client.peer("riley")
session = client.session("project-update")

receipt = session.add_messages(
    [
        {
            "role": "user",
            "content": "Project update should cite retention notes and keep the pricing caveat visible.",
        }
    ],
    peer_id="riley",
    infer=True,
    source_ids=["550e8400-e29b-41d4-a716-446655440000"],
)

for attempt in range(60):
    event = client.events.get(receipt.event_id)
    if event.status == "succeeded":
        break
    if event.status in {"failed", "quarantined"}:
        raise RuntimeError(
            f"Memory event {receipt.event_id} ended with {event.status}"
        )
    if attempt == 59:
        raise TimeoutError(f"Memory event {receipt.event_id} is still processing")
    time.sleep(1)

context = peer.ask(
    "What context should the project-update assistant use?",
    session="project-update",
)

if context.status == "insufficient_evidence":
    raise RuntimeError("Add source material before using Marrow context")
```

`infer` and `source_ids` are optional write controls from the native
`MessagesCreateRequest` schema. Omit `infer` to use the service default. Pass
`source_ids` only when the application already has up to 100 succeeded ingest
job IDs from the same account. The API rejects missing, foreign-account, and
nonterminal jobs before creating a memory event. Use the same event-success gate
before `peer.context()` or `session.context()` reads derived from the write.

Claim mutations use optimistic concurrency. Read the claim first and pass its
`head_revision` back to the change:

```python
claim = client.claims.get("mem_0123456789abcdef01234567")
corrected = client.claims.update(
    claim.id,
    "Project updates should lead with the decision.",
    head_revision=claim.head_revision,
    reason="User clarified the preference.",
)
client.claims.delete(
    claim.id,
    head_revision=corrected.head_revision,
    reason="User withdrew the preference.",
)
```

Corrections return `eligibility: "eligible"`; a stale revision fails with
`409 revision_conflict` instead of overwriting a newer head.

Every API method returns a named response object. Attributes are the primary API;
mapping access remains available for existing code. `Context.blocks` contains
typed `ContextBlock` objects, and `repr()` identifies each response type.

Delete sources, peers, and sessions through the same client, then poll the
returned job or event receipt:

```python
source_deletion = client.sources.delete(source_id)
client.ingest.jobs.wait(source_deletion.job.id)

peer_deletion = client.peer("riley").delete()
client.events.get(peer_deletion.event_id)
```

## Errors

Import errors directly from `marrow`. `MarrowConfigError` is raised locally for
invalid client configuration. Hosted failures raise `MarrowApiError` or the
more specific `ValidationError`, `WrongCredentialClassError`,
`ForbiddenScopeError`, `NotFoundError`, or `ConflictError`. API errors expose
`status_code`, `code`, `request_id`, and validation `issues`.

## Public surface

- `Marrow(api_key=..., workspace="default")`
- `client.access()`
- `client.ingest.url()` / `client.ingest.file()`
- `client.ingest.jobs.list()` / `.get()` / `.wait()`
- `client.sources.delete(source_id)`
- `client.query()`
- `client.workspaces.upsert()` / `client.workspaces.list()`
- `client.peer(id).create()` / `.get()` / `.ask()` / `.context()` /
  `.representation()` / `.delete()`
- `client.session(id).create()` / `.get()` / `.add_messages()` /
  `.messages()` / `.context(peer=...)` / `.delete()`
- `client.claims.query()` / `.list()` / `.get()` / `.history()` / `.update()` /
  `.delete()`
- `client.events.get(event_id)`
- `client.queue.status()`

## Source verification

For repository development:

```bash
cd clients/python
python -m pip install -e ".[dev]"
python -m pytest
```

The offline parity suite drives every native route and the classic
ingest/job/query path through a recording transport and verifies that each
request carries the customer-key bearer. The release gate builds the wheel and
sdist twice, compares their bytes and exact inventories, runs metadata and Twine
checks, verifies the registry version is unused, and clean-installs both artifacts
before executing the classic path, native route, strict-response, and
structured-error probes.

Proprietary. `UNLICENSED` means the distribution is a delivery channel, not an
open-source grant.
