# neosian

> Async-only Python state layer for LLM agents: durable conversations,
> agent-curated file-shaped memory, and context lifecycle, on storage the
> product owns. Keyless to boot; Python >= 3.12.

The state layer under the agent you already use, and around the one you
build. Claude Code, Codex, OpenCode, Muse Code, Cursor and Pi get memory and
skills over MCP and every session recorded through their own hooks
(`neosian setup --write`, once per machine); a stateless `Agent` core
(tools, orchestration, streaming, fallback, guardrails, structured
output) takes the same `Conversation` and memory layers opt-in. One
memory dispatcher serves five transports: the function tool,
Anthropic's native declaration, an MCP stdio server, the shell, and the
state process's HTTP wire. Agents share context by config: a `board`
mount on a task scope, and read-only `ConversationView`s of another
conversation with cross-conversation `search_history` and `recall_turn`;
a Conversation with a `/project` mount lists itself under `sessions/`, so
one search covers a project's sessions, foreign and neosian alike. Long URLs, paths and
ids in aged turns become `[link N]` handles, expanded at the tool boundary.
Skills are documents under `skills/` in a mount: versioned, curated by
the mount flag, loaded by `list_skills`/`load_skill`, served as MCP prompts.
`McpServer` consumes any MCP server as agent tools, over stdio or HTTP.

## Learn from the shell

- `neosian docs`: list the shipped topics (they travel in the wheel, so
  they always describe the installed version).
- `neosian docs quickstart | memory | skills | messaging | stores | agents | cli | mcp | interop | agent | tools | local | topology | wire | baselines`
  prints one page, markdown on stdout, pipe-safe.
- `neosian docs topology`: who runs neosian code x where the bytes live;
  cooperating local FileStore writers share an interprocess root lock.
- The same pages online, rendered from the wheel at the current release:
  https://neosian.com/docs (this file at https://neosian.com/llms.txt).

## Set up from the shell (the human path)

- `neosian status [--at CLIENT=DIR]… [--json]`: is this machine set up? The home, which
  providers have a key (never values), this directory's two scopes, per
  client installed / MCP registered / hooks present / level (user,
  project, both) / interpreter resolving, the last recorded session, the
  install shape. Exit 0 always; findings are data.
- `neosian setup [--write] [--yes] [--client C]… [--at C=DIR]…`:
  wire every installed client (Claude Code, Codex, OpenCode, Muse Code, Cursor, Pi) to the home,
  once per machine: MCP + the record hooks in the client's own config, no
  file per project; print first. Terminal writes ask which clients to connect,
  all selected initially. Unattended writes (including JSON) require `--yes`
  or explicit `--client`; print/JSON never prompt. Setup and status report
  searched directories and accept invocation-only `--at CLIENT=DIR` overrides;
  missing client homes are never created. Selections are not saved.
  `--level user|project` and `--root DIR | --url URL` reach both installers. `--write` runs the client's own CLI for
  a file it owns (`claude mcp add-json`, `codex mcp add`) when on PATH,
  else prints the line and exits 1. `--url URL` moves every client to the
  state process in one run.
- `neosian configure --list | --provider NAME --key - | --env NAME --key - |
  --delete`: keys under `<home>/config.toml`, read from stdin, never argv;
  `--env` names a door by its key's env var (one an agent file registers).
- `neosian chat [PROMPT] [--model M] [--agent FILE] [--resume ID] [--json]`:
  the resident agent that knows neosian (a `docs` tool over these pages)
  on the same memory, and runs these verbs itself through one `neosian`
  tool: a form that only reads runs at once, one that changes state
  waits for the user's `y` in the session and is declined in a one-shot
  turn, a key never passes through it. A PROMPT or piped stdin is one
  turn, `--model fake` keyless. Its model: `--model`, else `[chat]
  model`, else a model a local server runs (a llama-server on :8080, an
  Ollama model loaded on :11434, the base URLs in `[chat] local`; no key
  needed), else latest Sonnet. `[[chat.mcp]]` tables in
  `<home>/config.toml` (`name`, then `command`/`args`/`env` or
  `url`/`headers`, `prefix`) are MCP servers it opens for the session,
  their tools added. Bare `neosian` on a terminal opens it: a full-screen
  session where a tool call is one line until expanded (ctrl+o for all,
  tab/shift+tab to focus one, enter/space to toggle it). Esc returns to
  the prompt and interrupts a running turn. Seven commands cover what
  the agent cannot do
  (`/help`, `/configure` for a key typed masked, `/model`, `/resume`,
  `/new`, `/compact`, `/exit`); `/model` changes only this session.
- `neosian update [--mode off|notify|auto]`: a PyPI check on the human
  verbs only, never on an agent verb; `auto` applies a uv tool install
  within the major.

## Operate memory from the shell

- `neosian memory view /`: the first command to try; renders the
  memory index. No flags inside a project means this directory's
  layout (`/user`, `/project`); `--scope S` or `NEOSIAN_SCOPE` names a
  scope. The store is the home, `~/.neosian` or `$NEOSIAN_HOME`, unless
  `--root DIR`, `--url` or the DSN names one.
- Six commands: view, create, str_replace, insert, delete, rename.
  `--json` prints the memory tool's result envelope verbatim.
- `neosian memory maintain`, the gardener: keyless dedup + empty-prune;
  `--model MODEL` adds the semantic pass (merge, prune stale, promote).
- A skill is `create /project/skills/<name>` with a frontmatter
  `description` and the instructions as the body (`neosian docs skills`);
  `versions` and `revert` work on it like any document.
- Operator verbs, keyless: `versions PATH` (the audit trail; `--json`
  carries full historical content), `redact PATH [--all]` (the one
  eraser; audit skeleton preserved), `revert PATH --version N` (undo).
- `neosian audit --scope S [--conversation C] [--actor A] [--since T]
  [--json]`, the ledger: what was done, by whom, when, newest first,
  identical on a root, Postgres, or the state process (`--url`).
- `neosian search TERMS... [--conversation ID]... [--limit N] [--json]`:
  the turns holding every term, newest first, across every conversation
  the store holds; identical on every substrate.
- `neosian export DIR` / `neosian import DIR`: a store moves whole,
  history included, any substrate to any other; DIR is a FileStore root.
  An import needs every scope and conversation empty in the target.
- Exit tiers: 0 success, 1 ran-and-failed, 2 bad invocation, 130
  interrupt. stdout carries the artifact; stderr carries guidance; with
  `--json` in argv a tier-2 error is also one `{"error": "usage"}` object.
- Postgres arrives only via the NEOSIAN_POSTGRES_DSN environment
  variable (never an argv flag). `python -m neosian.memory` is the
  PATH-free twin.

## Upgrade to MCP

- `neosian mcp install --client claude-code|claude-desktop|cursor|codex|opencode|muse-code|pi`
  prints the exact registration; `--write` applies it (refused when the
  client is not installed). Once per machine by default: the entry names
  the store and no mount, the server derives each session's layout from
  the directory the client spawns it in (/user alone where that has no
  name); `--level project` writes this directory's file with its layout in
  the line (not for claude-desktop, cursor, codex: one file each). Claude
  Code's user scope and Codex are print-only, their own CLIs write those
  files: apply with the printed `claude mcp add-json --scope user` /
  `codex mcp add` line, or let `neosian setup --write` run it.
- `python -m neosian.mcp --root DIR --scope user:me` serves the same
  store over stdio: the `memory` tool, `list_skills` and
  `load_skill` over the mounts' skills (each skill also an MCP prompt, a
  slash command in Claude Code), `recall_turn(turn, conversation)` for
  any recorded turn of any agent's session, verbatim, and
  `search_history(query, conversation=)` for the turns holding every term,
  newest first.
- The other direction: `async with McpServer.stdio(cmd, args) as s:`
  (or `.http(url, headers=)`, `.in_process(server)`) from `neosian.mcp`
  gives the server's tools as `AgentConfig(tools=[*s.tools])`, schema
  verbatim, `is_error` in-band, `prefix=` for two servers that clash;
  the gate and hooks apply unchanged (`neosian docs mcp`).
- Under your own agent: `tool_definition(memory_tool)` is the definition
  any framework can carry (name, description, JSON Schema) and the tool
  itself the executor; `examples/interop_pydantic_ai.py` and
  `examples/interop_openai_agents.py` run it under pydantic-ai and the
  OpenAI Agents SDK, keyless in the unit tier (`neosian docs interop`).

## Record a foreign agent

- `neosian record install --client claude-code|codex|opencode|muse-code|cursor|pi` prints
  the client hooks (UserPromptSubmit, PostToolUse, Stop, SessionStart;
  Cursor has five native events below; OpenCode has a plugin file). Once per machine by default: the line names
  the home and no mount, and each session gets `user:<login>` at /user
  and `user:<login>/proj:<slug>` at /project, derived from the client's
  project directory (Claude Code: `--project "$CLAUDE_PROJECT_DIR"`);
  `--write` merges them into ~/.claude/settings.json or
  ~/.codex/hooks.json, every other hook preserved, or writes
  plugins/neosian-record.js in OpenCode's config directory (Codex reviews
  a new hook once in /hooks). `--level project` writes this directory's
  file with its layout in the line; `--root DIR --scope S` override at
  either level. One level per client: clients merge hook sources, so a
  user-level `--write` removes this directory's old entry and a
  project-level install beside user-level hooks is refused.
- The hooks call `python -m neosian.record` with the payload on stdin; a
  prompt-to-stop span lands as one turn by `claude-code:<session_id>` in
  the conversation the session id names, plus a sessions document at
  sessions/<session_id> in the mount at /project. Read it back: `neosian audit --scope S
  --conversation <session_id>`. On SessionStart the verb prints the memory
  index, the pending handoff note and "where we left off" (the recent
  sessions, log-projected) under the client's own ceiling: the client
  adds a hook's stdout to the model's context; a client with no start
  hook (OpenCode) reads the same blocks from the MCP server's
  instructions. A handoff is declared, never assumed: `handoff(note)`
  on the server writes the note on departure (one per scope,
  `/project/handoff`, pending until picked up, never deleted), and
  `continue_session(conversation=None)` delivers a recorded
  conversation whole in one call and picks the note up; the sessions
  document then says `continues: <id>`, and `neosian audit` shows it.
  `neosian continue` prints the same text from the shell, as a read.
- Hooks beside an MCP server are two writers: use `--url` (the state
  process; `neosian setup --url URL --write --yes` moves the whole machine) or
  Postgres. `neosian docs agents` carries the client table.

## Reach it over the network

- `NEOSIAN_SERVE_TOKEN=... neosian serve` runs the state process
  on the home (`--root DIR` another root): memory and conversations on a port, MCP
  over streamable HTTP at /mcp when started with mounts. Token is
  env-only (one token, or a per-client table `actor=token,...`, so the
  process records who wrote); unset refuses to start; /health is
  unauthenticated. Shell clients: `--url URL` + NEOSIAN_CLIENT_TOKEN.
- The shipped Dockerfile is the appliance: `docker run -e
  NEOSIAN_SERVE_TOKEN=... -p 6367:6367 -v state:/data neosian`.
- Cursor: `setup --client cursor --write` adds native version-1 hooks at
  ~/.cursor/hooks.json beside user MCP. Interactive CLI 2026.09.10-fd3934a
  records as cursor:<conversation_id>; --print lacks the full event stream.
  Completed turns wait for stop and afterAgentResponse in either order.
  sessionStart returns JSON additional_context. One workspace root selects
  /project; ambiguous roots use /user unless a project or scope is named.
  Imported Claude recorder calls carrying cursor_version are ignored.
  MCP credentials use ${env:NAME} references; hooks inherit the environment.
- Muse Code: `--client muse-code`; user MCP and hooks share
  $XDG_CONFIG_HOME/muse/settings.json (default ~/.config/muse), schema_version
  1; project MCP uses shared .mcp.json, hooks .muse/hooks.json (workspace
  trust required). Muse clears child environments: --url/Postgres hooks
  require user-level managed neosian-hooks.json and pass credential names
  through managed_hooks_env_vars; MCP uses ${NAME} env references. Another
  managed pointer is refused, never replaced. Shared project MCP survives
  user install; mcp_shadowed_by reports its override. Hook print mode is a
  files change map, with no unrelated settings. Actor muse-code:<session>.
- Pi (1.0.1+): `setup --client pi --write` installs user MCP at
  ~/.pi/agent/mcp.json and extensions/neosian-record.ts; PI_CODING_AGENT_DIR
  relocates both. Project level uses .pi/ (trust required). Native MCP
  tools have direct exposure; interactive and print modes record as
  pi:<session>. Startup and compaction refresh a bounded neosian prompt
  section with memory instructions and the index, note and recent sessions.
  Tool results, including nested MCP calls, are serialized;
  agent_settled closes the span after retries. Credentials stay env-only.
- Python clients: `await RemoteStore.connect(url, token=...)` carries both
  storage ABCs over the wire, core install, drops in where FileStore
  does. Listings cross in pages of at most 500 rows, followed for you;
  `Pageable` pages them yourself. `neosian docs topology` carries the
  full shape.
- `neosian docs wire` is the HTTP contract a client in any language
  implements: the nineteen /v1/ routes, the envelope, paging, the JSON
  shapes, WIRE_VERSION.

## Bring an OpenAI-compatible model

- `register_model("acme-large", provider=OpenAICompatible(name="acme",
  api_key_env="ACME_API_KEY", base_url="https://llm.acme.example/v1"),
  ...)` is the day-one door for any model neosian has not shipped: once
  at import; the model prices in µ$ and passes every gate like a shipped
  one. `wire="chat"` (Chat Completions, the default) or `"responses"`
  (the Responses API, stateless: encrypted reasoning carried on the
  message between tool calls, nothing stored at the provider); the chat
  dialect knobs and `thinking_switch` beside it. `neosian docs
  quickstart` carries the snippet.
- A local server is the same door declared keyless:
  `OpenAICompatible(name="local", api_key_env=None,
  base_url="http://127.0.0.1:8080/v1")` reads no variable and sends the
  SDK a placeholder, never a key of yours; price it on a zero card
  (`ModelPricing(input_per_mtok=0, output_per_mtok=0)`, priced at zero
  rather than unknown). `neosian docs local` carries the llama.cpp and
  Ollama recipes (`llama-server -hf ggml-org/gemma-4-E4B-it-GGUF:Q4_0
  --jinja`, `ollama pull gemma4:e4b`) and the measured local row;
  `examples/local_agent.py` is the runnable form. `neosian chat` finds a
  running llama-server or Ollama itself, registers its models at the
  served window and lists them in `/model`.
- Every shipped row is a `Model` member, the door rows too:
  `Model.GROK_4_6` (xAI `XAI_API_KEY`), `Model.GEMINI_3_8_FLASH` (Gemini
  `GEMINI_API_KEY`), `Model.KIMI_K3` (Moonshot `MOONSHOT_API_KEY`),
  `Model.QWEN_3_8_MAX` (Alibaba Model Studio `DASHSCOPE_API_KEY`): first-party, fingerprinted, no client of their own;
  each earned by green dispatched runs of the memory baselines and
  carrying its provider's clock (`spec.retires`, `spec.card_until`).
  OpenAI's rows (`gpt-6.1-sol` latest, `gpt-6-sol` default during the
  migration window, `gpt-6-astra`, `gpt-6-luna`) and xAI's speak Responses.
- `ModelSelector` follows each Claude/GPT tier: for example
  `CLAUDE_SONNET_LATEST`, `GPT_SOL_DEFAULT`; strings such as
  `anthropic:sonnet:latest` and `openai:sol:default` work everywhere models
  are selected. Latest moves on a qualified neosian release; default moves
  and predecessors retire in the first release after a full 30-day window.
  Every successor ages independently; warnings name replacements and dates.
  Exact `Model` pins never silently redirect. Package releases, never the
  wall clock, change installed routing. The resident chat agent defaults
  to a model a local server runs, else latest Sonnet (5.5), separately
  from library defaults.
- `claude-haiku-5-5` (Haiku 5.5; `CLAUDE_HAIKU_DEFAULT` and `_LATEST` name
  the same row) is the fast Claude row, priced by prompt length: $0.10/$0.50
  per MTok up to a 100,000-token prompt, $0.50/$2.50 beyond; the card is
  picked per call from `Usage.prompt_tokens` and both cards are sealed.
- Effort includes `XHIGH` and explicit `NONE` where supported (Luna);
  Python `None` leaves the provider default. Claude 5.5 preserves summarized
  thinking and asks the provider to drop prefix-incompatible signed blocks.
  `neosian docs agent` specifies the catalog policy and effort controls.

## Install

- `uv add "neosian==1.7.0"`: one package, the library and its
  provider SDKs, the `neosian` shell, the MCP server and client, the
  state process and OpenTelemetry spans (about 70 MB; nothing loaded
  until used). Pin a release for reproducible installs.
  Keyless to start: `Model.FAKE` and `FileStore`
  need nothing. The one extra is the Postgres driver for `PostgresStore`,
  `neosian[postgres]` (`[all]` its alias).
- `curl -fsS https://neosian.com/install | bash` puts uv and neosian on a
  machine with nothing on it; the script says what it installs first.
- The appliance: `docker run -d -e NEOSIAN_SERVE_TOKEN=… -p 6367:6367
  -v neosian-state:/data ghcr.io/mausa-ai/neosian:1.7.0`, the state
  process on a volume, `/health` the one open route.

## Docs

- https://github.com/mausa-ai/neosian/blob/v1.7.0/docs/README.md: install and quickstart.
- https://github.com/mausa-ai/neosian/blob/v1.7.0/docs/SERVICES.md: every environment key and what turning it off means.
- `neosian docs baselines`: the published per-provider memory numbers.

## Messages and reminders

- `neosian docs messaging`: the durable inbox, history annotations, claims and
  resettable reminders. MCP exposes `messages`; hooks provide exact session IDs.
- `neosian messages send|list|view|ack|claim|renew|release|complete|snooze|cancel`
  shares the tool dispatcher; `--json` gives its envelope.
- Python: `neosian.messaging.Mailbox`, `MailboxConfig`, `MessageTarget`;
  `Conversation(..., mailbox=MailboxConfig())` opts in.
- Local FileStore writers coordinate through an interprocess root lock; restart
  all old writer processes when upgrading. Wake works only in attached capable
  receivers; ordinary unread delivery waits for the next supported activity.
