Marrow agent-memory client for ingest, jobs, query, and the native /v1 memory API.
Project description
marrowid - the Marrow Python client
Distribution marrowid, import package marrow, primary class Marrow.
The public client identities are marrowid==1.0.3, imported as marrow,
@marrowid/sdk@1.0.3, and @marrowid/cli@1.0.3. 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:
python -m pip install marrowid==1.0.3
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).
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.
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.
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.
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:
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: "pending_review"; a stale revision fails with
409 revision_conflict instead of overwriting a newer head.
Public surface
Marrow(api_key=..., workspace="default")client.ingest.url()/client.ingest.file()client.ingest.jobs.list()/.get()/.wait()client.query()client.workspaces.upsert()/client.workspaces.list()client.peer(id).create()/.get()/.ask()/.context()/.representation()client.session(id).create()/.get()/.add_messages()/.messages()/.context(peer=...)client.claims.query()/.list()/.get()/.history()/.update()/.delete()client.events.get(event_id)client.queue.status()
Source verification
For repository development:
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.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file marrowid-1.0.3.tar.gz.
File metadata
- Download URL: marrowid-1.0.3.tar.gz
- Upload date:
- Size: 10.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d12a4d8313d0884e4a20ce5f23b8364e8784dbc0c862e3fd62262595e4375d9e
|
|
| MD5 |
63eaa0d3ecb983b9256d9df8921d059d
|
|
| BLAKE2b-256 |
632cdcc683281ce0cdc9922bd217813247f6b00c69e453b01af33c7195575bc8
|
File details
Details for the file marrowid-1.0.3-py3-none-any.whl.
File metadata
- Download URL: marrowid-1.0.3-py3-none-any.whl
- Upload date:
- Size: 12.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f89dd64f872f2c131897f5be013d9ad261c9b20c52b8014e5f835d0cc46f5a2b
|
|
| MD5 |
ea842a5abf71005dbb296f1fb1ba19df
|
|
| BLAKE2b-256 |
9b3ce8a12e8cfa0021ddc2027dcefe51fd57162f1f974695238391edec3bbf56
|