# priests

CLI tool and HTTP service for [priest](https://github.com/tjcccc/priest) AI orchestration.

## What it does

- `priests run` — interactive chat or single-prompt CLI
- `priests service start` — FastAPI HTTP service (`POST /v1/run`, `POST /v1/chat`, session management)
- Profile management, config management, provider and model setup

## Requirements

- Python 3.11+
- [uv](https://docs.astral.sh/uv/)
- At least one configured provider (Ollama local, or any supported cloud API)

## Install

### From source

```bash
git clone https://github.com/tjcccc/priests
cd priests
uv sync
```

## Setup

```bash
priests init
```

Walks you through selecting a provider, entering an API key, picking a model, and writing `~/.priests/priests.toml`.

## Local development

```bash
uv sync
uv run priests init
uv run priests service start
```

Open `http://localhost:8777/ui`.

When changing UI assets:

```bash
cd priests/ui
npm install
npm run build
cd ../..
uv run priests service start
```

## Usage

```bash
# Interactive chat
priests run

# Single prompt
priests run "your prompt"

# With options
priests run --provider bailian --model qwen-plus --profile friend --session my-session
priests run "your prompt" --think true --memories false

# Model management
priests model                    # show current default model
priests model list               # list saved models
priests model default            # interactively pick a new default
priests model default --profile coder  # set/clear the model override for a profile
priests model add                # add a new provider + model

# Provider info
priests provider                 # show current provider
priests provider list            # list all providers with configured status
priests provider <name> list     # show known models for a provider

# Profile management
priests profile                  # show current profile
priests profile list
priests profile init             # prompts for name
priests profile init my_profile

# Config
priests config show
priests config set default.model qwen-plus

# HTTP service (foreground)
priests service start
priests service status

# Help
priests --help
priests <command> --help
```

## Chat commands

Inside `priests run` interactive mode:

| Command | Description |
|---|---|
| `/help` | Show available commands |
| `/think on` | Enable thinking mode (Qwen3 / Ollama) |
| `/think off` | Disable thinking mode |
| `/new` | Start a new session |
| `/search <query>` | Run a web search; results injected into your next message (requires `priests[search]`) |
| `/remember <text>` | Save text directly to today's short memory (`auto_short.md`) |
| `/remember user <text>` | Save approved durable user memory (`user.md`) |
| `/remember pref <text>` | Save approved durable preference memory (`preferences.md`) |
| `/exit` | Exit the chat |

Ctrl+J inserts a newline. Enter submits.

## Supported providers

| Key | Provider | Type | Region |
|-----|----------|------|--------|
| `ollama` | Ollama | Local | Local |
| `llamacpp` | llama.cpp | Local | Local |
| `lmstudio` | LM Studio | Local | Local |
| `openai` | OpenAI | API | International |
| `anthropic` | Anthropic Claude | API | International |
| `gemini` | Google Gemini | API | International |
| `groq` | Groq | API | International |
| `openrouter` | OpenRouter (gateway) | API | International |
| `deepseek` | DeepSeek | API | China mainland |
| `mistral` | Mistral AI | API | International |
| `together` | Together AI | API | International |
| `perplexity` | Perplexity | API | International |
| `cohere` | Cohere | API | International |
| `minimax` | MiniMax | API | International |
| `bailian` | Alibaba Bailian | API | China mainland |
| `alibaba_cloud` | Alibaba Cloud | API | International |
| `kimi` | Kimi (Moonshot) | API | China mainland |
| `github_copilot` | GitHub Copilot | OAuth | International |
| `chatgpt` | ChatGPT (OpenAI OAuth) | OAuth | International |
| `custom` | Custom OpenAI-compatible endpoint | API | Any |

Local providers (Ollama, llama.cpp, LM Studio) require no API key and fetch available models automatically from the running server.

## Config file

Location: `~/.priests/priests.toml`

```toml
[default]
provider = "bailian"
model = "qwen-plus"
profile = "default"
think = false

[models]
options = [
    "bailian/qwen-plus",
    "gemini/gemini-2.5-flash",
]

[paths]
profiles_dir = "~/.priests/profiles"
sessions_db  = "~/.priests/sessions.db"

[service]
host = "127.0.0.1"
port = 8777

[proxy]
url = "http://127.0.0.1:7890"

[providers.ollama]
base_url = "http://localhost:11434"

[providers.bailian]
api_key = "sk-..."
base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1"
use_proxy = false

[providers.gemini]
api_key = "AIza..."
base_url = "https://generativelanguage.googleapis.com/v1beta/openai/"
use_proxy = true

[memory]
size_limit = 50000  # max characters in auto_short.md; 0 = unlimited
```

### Proxy

Set `[proxy] url` to your local proxy (e.g. Clash, Shadowsocks). Then set `use_proxy = true` on each provider that needs it. Providers with `use_proxy = false` (the default) connect directly even when `[proxy]` is configured.

Env var overrides use `PRIESTS_` prefix with `__` for nesting:

```bash
PRIESTS_DEFAULT__MODEL=qwen-plus priests run "hello"
PRIESTS_SERVICE__PORT=9000 priests service start
```

## Profiles

Profiles live in `~/.priests/profiles/<name>/` and define behavior context:

```
profiles/
  default/
    PROFILE.md      # identity and persona
    RULES.md        # behavior and constraints (plain language)
    CUSTOM.md       # user customization
    profile.toml    # per-profile settings (memories on/off)
    memories/       # persistent memory files assembled by priests
      user.md           # stable facts about the user (permanent)
      preferences.md    # approved user preferences (permanent)
      auto_short.md     # time-sensitive observations, tasks, reminders (rolling)
      pending/          # durable memory proposals awaiting approval
```

Create a new profile:

```bash
priests profile init "english_teacher"
# edit ~/.priests/profiles/english_teacher/PROFILE.md
```

Use it:

```bash
priests run --profile english_teacher
```

Rename or delete non-default profiles from the Config page → Profile Configuration section, or manage them directly in `~/.priests/profiles/`.

For tool profiles that don't need memory (formatters, dictionaries, etc.), set `memories = false` in `profile.toml`:

```toml
# ~/.priests/profiles/json_master/profile.toml
memories = false
```

Profiles can also override the global default model by setting both `provider` and `model`.
Leave them unset to keep using `[default] provider` and `[default] model`.

Set or clear the override from the CLI:

```bash
priests model default --profile coder
```

```toml
# ~/.priests/profiles/coder/profile.toml
provider = "bailian"
model = "qwen-plus"
memories = true
```

## Memory system

priests owns the chat-app memory policy and passes assembled memory to `priest` through the special request `memory` lane. The core `priest` framework remains generic; it does not decide what `user.md`, `preferences.md`, or `auto_short.md` mean.

Profile files and memory files have different authority:

| File | Use for |
|------|---------|
| `PROFILE.md` | Assistant identity/persona |
| `RULES.md` | Human-authored hard behavior rules |
| `CUSTOM.md` | Human-authored profile setup/context |
| `memories/user.md` | Approved stable facts about the user |
| `memories/preferences.md` | Approved stable user preferences, lower priority than profile docs |
| `memories/auto_short.md` | Time-sensitive observations, tasks, reminders — rolling, trimmed by `size_limit` |
| `memories/pending/*.md` | Durable memory proposals awaiting user approval |

Legacy `memories/notes.md` is still read if present, but priests treats it as read-only legacy memory and no longer writes to it automatically.

Model-generated memory is intentionally conservative:

| Policy | Behavior |
|--------|----------|
| Auto-applied | Only short-term `auto_short` entries |
| Proposed | Durable `user` / `preferences` entries, saved under `pending/` |
| Never auto-written | `PROFILE.md`, `RULES.md`, `CUSTOM.md`, `profile.toml`, legacy `notes.md` |

`auto_short.md` uses dated sections (`## YYYY-MM-DD`). Oldest sections are dropped automatically once the file exceeds `memory.size_limit` characters.

To disable memory for a single run:

```bash
priests run --memories false
```

To disable permanently for a profile, set `memories = false` in `profile.toml`.

## Web UI

Start the service with `priests service start` and open `http://localhost:8777/ui`.

- **Chat**: streaming responses, markdown rendering, image attach (drag-and-drop or click), per-turn model and timing info
- **Sessions**: sidebar with per-profile session lists; pin, rename, and delete sessions
- **Config page** (`/ui/config`): full settings UI with section nav
  - Defaults: provider + model select, profile, timeout, thinking mode
  - Profile Configuration: editor for PROFILE.md / RULES.md / CUSTOM.md per profile; rename and delete profiles
  - Model Configuration: saved model list grouped by provider type (local / API / OAuth)
  - Providers: per-provider API key, base URL, and proxy toggle
  - Memory, Web Search, Service, Proxy, Paths sections — all editable with hot-reload (no restart required except host/port)

## HTTP API

Start the service with `priests service start`, then:

```
GET  /health
POST /v1/run                        single run, no session
POST /v1/run?memories=false         single run, memory disabled
POST /v1/chat                       session-backed chat
POST /v1/chat?memories=false        session-backed chat, memory disabled
GET  /v1/sessions                   list sessions
GET  /v1/sessions/{id}              get session with full turn history
GET  /v1/config                     get full config (API keys masked)
PATCH /v1/config                    partial config update (dotted keys)
GET  /v1/providers/{name}/models    list available models for a provider
GET  /v1/profiles                   list profiles
GET  /v1/profiles/{name}            get profile files
PUT  /v1/profiles/{name}            update profile files
POST /v1/profiles                   create profile
POST /v1/profiles/{name}/rename     rename profile
DELETE /v1/profiles/{name}          delete profile
```
