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
Python with the Oort CLIYour Python code runs the oort command line with subprocess and the --json flag. The CLI reads and writes the same memory as the hooks, the MCP server and the TypeScript SDK, and prints one oort.v1 JSON envelope on stdout.runsYour Python codesubprocessoort --jsonone envelope per callMemory~/.oort or Oort Cloud

Install

Terminal
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 oort already on your PATH, or else the matching npm release through npx.
  • Install the CLI once with npm (npm i -g oortmem), so no call waits for npx.
  • 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
"""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 oortmem runs the same launcher as the oort command, 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 cwd to 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:

JSON, trimmed
{
  "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:

JSON
{
  "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
  }
}
  • data is always an object. A list is wrapped: data.memories, data.judgments. Field names are camelCase, ids are full ids, and times are ISO 8601.
  • warnings lists what the CLI also prints on stderr, each with a code: lexical-only and recall-degraded for a degraded recall, for example. A degraded recall is not a failure.
  • error.fix is a command you can run as it is, or null. error.exitCode matches the process's exit code.

Recall

Python
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:

Python
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

Python
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 remember tool builds: the user says "Remember this:" and the fact, the assistant "Noted:" and the fact.
  • forget asks first, and with --json it never asks, so pass --yes. --everywhere is 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:

Python
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:

Python
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 fixoort --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 fixoort models pull, oort keys set typesafe
needs-login
WhenNo Oort key, or Oort Cloud rejected it.
Typical fixoort 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 fixoort 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 Pythonoort recall <text> with --as-of, --known-at, --history or --include
observe(turn)
CLI from Pythonoort observe with --user and --assistant, or the turn on stdin
remember(facts)
CLI from PythonNo command: oort observe with the turn the MCP remember tool builds
forget(id)
CLI from Pythonoort forget <id> with --yes
pin(id), unpin(id)
CLI from Pythonoort pin <id>, oort unpin <id>
explain(id)
CLI from Pythonoort inspect <id>: the memory, its decisions and its supersede chain
recent()
CLI from PythonNo exact match. oort inspect lists current memories
whoami()
CLI from Pythonoort whoami
consolidate()
CLI from Pythonoort consolidate (local only)
judge.record(input)
CLI from Pythonoort judge record with --subject, --verdict, --reason, --domain, --by
judge.apply(item)
CLI from Pythonoort judge apply <item>
judge.list(), judge.confirm(id)
CLI from Pythonoort 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(), user and org. The CLI always acts as the person who ran setup (Multi-user apps).
  • The Vercel AI SDK adapter (oortmem/ai-sdk). Python agent frameworks start oort mcp instead (Agent frameworks).
  • Recall's limit, speed and budgetChars, and an abort signal. Use subprocess's timeout to stop waiting.
  • The judge agent role. The CLI records as a person. An agent records through the MCP record_judgment tool, and a person confirms it (Judgments).