Python today: the CLI from Python
There is no Python SDK before 0.7. What Python has today is the PyPI package oortmem, which runs the oort command line, and that command's --json output: one oort.v1 envelope per call. That is enough to recall and save memories from Python now.
On this page
Install
pip install oortmem
oort setup- Node.js 22.15 or newer. Oort is written in TypeScript. The Python package is a launcher: it runs an
oortalready on your PATH, or else the matching npm release throughnpx. - Install the CLI once with npm (
npm i -g oortmem), so no call waits fornpx. - Run setup once, as for any agent: it chooses local or cloud and the keys. Every call below uses what it chose.
A helper: oort_cli.py
One function: run a command with --json, return data, and raise on a failure. The other Python samples on these pages import it.
"""oort_cli.py: run the oort CLI from Python and read its oort.v1 JSON."""
import json
import subprocess
import sys
# The PyPI launcher (pip install oortmem). Or ["oort"] with the CLI on PATH (npm i -g oortmem).
OORT = [sys.executable, "-m", "oortmem"]
class OortCliError(Exception):
"""A command that failed: the envelope's error.code, error.fix and error.exitCode."""
def __init__(self, error):
super().__init__(error["message"])
self.code = error["code"]
self.fix = error.get("fix")
self.exit_code = error.get("exitCode")
def oort(*args, stdin="", cwd=None, timeout=120):
"""Runs the CLI with these arguments and --json, and returns the envelope's data."""
proc = subprocess.run(
[*OORT, *args, "--json"],
input=stdin,
capture_output=True,
text=True,
cwd=cwd, # the project comes from this folder, as in the CLI
timeout=timeout,
check=False,
)
if not proc.stdout.strip():
# No envelope: the CLI did not start (no Node.js 22.15, say). The reason is on stderr.
raise RuntimeError(proc.stderr.strip() or f"the oort CLI exited with {proc.returncode}")
envelope = json.loads(proc.stdout)
if not envelope["ok"]:
raise OortCliError(envelope["error"])
for w in envelope["warnings"]:
print(f"oort: {w['code']}: {w['message']}", file=sys.stderr)
return envelope["data"]python -m oortmemruns the same launcher as theoortcommand, so the code works without the virtualenv on your PATH.- stdout holds exactly one envelope. Lines meant for people go to stderr.
- The CLI reads the project of the folder it runs in, plus global memories. Pass
cwdto choose the project. - Each call starts the CLI in a new process. That suits scripts, jobs and one recall and one observe per turn. For many calls a second, use the TypeScript SDK.
The oort.v1 envelope
On success:
{
"schema": "oort.v1",
"command": "recall",
"ok": true,
"data": {
"memories": [{ "id": "1b2c3d4e-…", "text": "Auth runs on port 8443.", "type": "semantic", "tier": "hot" }],
"temporal": { "mode": "current", "inferred": false }
},
"warnings": [{ "code": "lexical-only", "message": "lexical-only recall. No embed model or key. …" }]
}On failure, with the one-line message also on stderr:
{
"schema": "oort.v1",
"command": "forget",
"ok": false,
"error": {
"code": "needs-confirmation",
"message": "This needs a confirmation, but --json never asks; re-run with --yes to forget it.",
"fix": "oort forget 1b2c3d4e-… --json --yes",
"exitCode": 2
}
}datais always an object. A list is wrapped:data.memories,data.judgments. Field names are camelCase, ids are full ids, and times are ISO 8601.warningslists what the CLI also prints on stderr, each with acode:lexical-onlyandrecall-degradedfor a degraded recall, for example. A degraded recall is not a failure.error.fixis a command you can run as it is, ornull.error.exitCodematches the process's exit code.
Recall
from oort_cli import oort
data = oort("recall", "which port does auth use?")
for m in data["memories"]:
print(m["id"], m["type"], m["text"])
# What was true then, every version, or what Oort believed then:
oort("recall", "auth port", "--as-of", "2026-04-02")
oort("recall", "auth port", "--history")
oort("recall", "auth port", "--known-at", "2026-04-02T12:00:00Z")
# Only your Planet:
oort("recall", "release checklist", "--include", "planet")Each memory has id, text, type, tier and an explanation of its ranks. --as-of, --known-at and --history choose the time view, one at a time. Without them the view comes from the query ("in March"), and data.temporal says which one was used. See recall modes in the CLI reference.
Put memory in a prompt
The JSON has no prompt block. Plain oort recall prints the <oort-memory> block the hooks inject, with every memory escaped, after one header line. Take the block from there rather than building one from memories[].text:
import subprocess
import sys
from oort_cli import oort
def memory_block(question):
"""The escaped <oort-memory> block plain `oort recall` prints, or "" when nothing matched."""
out = subprocess.run(
[sys.executable, "-m", "oortmem", "recall", question],
input="",
capture_output=True,
text=True,
check=True,
).stdout
start = out.find("<oort-memory>")
return out[start:].strip() if start >= 0 else ""
memory = memory_block(event["question"])
answer = answer_with(memory, event["question"])
# After you answer: run the finished turn through the write path.
oort("observe", "--user", event["question"], "--assistant", answer)Save, pin and forget
from oort_cli import oort
# One finished turn through the write path: scrub, gate, reconcile and the writer.
r = oort("observe", "--user", "We moved staging to port 6543.", "--assistant", "Noted.")
print(r.get("outcome"))
# What the scrubber would redact or drop ({ dryRun, dropped, redactions }). Saves nothing.
print(oort("observe", "--dry-run", "--user", "Staging runs on port 6543.", "--assistant", "Noted."))
# A memory's id comes from recall or inspect. Forget asks first; --yes answers it.
memory_id = oort("recall", "staging port")["memories"][0]["id"]
oort("pin", memory_id)
oort("forget", memory_id, "--yes")- The CLI has no remember command. To save one fact, observe the turn the MCP
remembertool builds: the user says "Remember this:" and the fact, the assistant "Noted:" and the fact. forgetasks first, and with--jsonit never asks, so pass--yes.--everywhereis for Oort Cloud only.- Saving needs keys or Oort Pro. Recall works without them, keyword-only (
lexical-only).
oort observe also reads one turn as JSON on stdin:
import json
from oort_cli import oort
turn = {"userText": "We deploy from main with wrangler.", "assistantText": "Noted."}
oort("observe", stdin=json.dumps(turn))Agents
Like any agent, your code can act as one. It reads your Planet and its own Star (see Concepts). Set OORT_AGENT in the environment, or pass --agent before the command:
from oort_cli import oort
# --agent goes before the command. The agent reads your Planet and its own Star.
oort("--agent", "support-bot", "recall", "release checklist")
print(oort("--agent", "support-bot", "whoami"))Errors and exit codes
error.code is one of the same ten codes the SDK uses. The envelope has no reason, retryable or retryAfter: retry network, and show message and fix for the rest. The SDK errors page has a Python tab with a retry loop.
usage- WhenA bad flag or argument, or an input Oort refuses to parse.
- Typical fix
oort --help not-found- WhenNo such memory, judgment or model.
- Typical fixNone
ambiguous-id- WhenAn id prefix that matches more than one memory.
- Typical fixNone
needs-key- WhenNo model or key for a role the command needs.
- Typical fix
oort models pull,oort keys set typesafe needs-login- WhenNo Oort key, or Oort Cloud rejected it.
- Typical fix
oort login needs-confirmation- WhenA command that asks first, run with
--json. - Typical fixThe same command with
--yes cloud-only- WhenA command or flag that needs Oort Cloud, on a machine using the local store.
- Typical fix
oort cloud use network- WhenOort Cloud, the relay or a model download could not be reached.
- Typical fixRetry
refused- WhenA rule said no: a plan or role limit, or cloud mode for a local-only command.
- Typical fixNone
internal- WhenAnything else: a bug or an unexpected failure.
- Typical fixNone
Exit codes: 0 success, 1 an unexpected error, 2 a refusal or usage problem, 3 partial success (an import).
From the SDK to the CLI
What each TypeScript call is on the command line:
recall(query)- CLI from Python
oort recall <text>with--as-of,--known-at,--historyor--include observe(turn)- CLI from Python
oort observewith--userand--assistant, or the turn on stdin remember(facts)- CLI from PythonNo command:
oort observewith the turn the MCPremembertool builds forget(id)- CLI from Python
oort forget <id>with--yes pin(id),unpin(id)- CLI from Python
oort pin <id>,oort unpin <id> explain(id)- CLI from Python
oort inspect <id>: the memory, its decisions and its supersede chain recent()- CLI from PythonNo exact match.
oort inspectlists current memories whoami()- CLI from Python
oort whoami consolidate()- CLI from Python
oort consolidate(local only) judge.record(input)- CLI from Python
oort judge recordwith--subject,--verdict,--reason,--domain,--by judge.apply(item)- CLI from Python
oort judge apply <item> judge.list(),judge.confirm(id)- CLI from Python
oort judge list,oort judge confirm <id>with--yes formatForPrompt(result)- CLI from PythonPlain
oort recall <text>, without--json scoped(options)- CLI from PythonNone yet
Not in the CLI yet
These are TypeScript only until the native client. Each page says so in its Python tab.
- End users:
scoped(),userandorg. The CLI always acts as the person who ran setup (Multi-user apps). - The Vercel AI SDK adapter (
oortmem/ai-sdk). Python agent frameworks startoort mcpinstead (Agent frameworks). - Recall's
limit,speedandbudgetChars, and an abortsignal. Use subprocess'stimeoutto stop waiting. - The judge agent role. The CLI records as a person. An agent records through the MCP
record_judgmenttool, and a person confirms it (Judgments).