pydantic-clai¶
The catalog agent pydantic-clai covers the Pydantic AI CLIs: clai v1, a chat CLI, and
CLAI 2.0 (pydantic-clai2), a terminal coding agent; both come from the Pydantic AI
repository. It came into the catalog from the agent framework sweep for
issue #64; the Pydantic AI library
itself and Logfire (Pydantic's hosted tracing service), which got no catalog line, are covered
in research/frameworks.md. Its parser is in the backlog
(issue 66).
Source and evidence¶
pydantic/pydantic-ai, MIT, open source, commit
0ba01f92be3fc361f89970502cd6e7dd3611aec3.
The repository ships pydantic-ai(-slim), pydantic-graph,
pydantic-evals, the clai CLI, and under src/ two newer packages:
pydantic-ai-harness and CLAI 2.0 (pydantic-clai2, commands clai2 and
pydantic-clai2,
pyproject.toml:62-65;
pydantic_clai2-0.54.0 is on PyPI and its wheel was fetched and read, not
installed). CLAI 2.0 is a terminal coding agent with a coder plugin on
by default
(README.md:3-5).
All claims are from source.
CLAI 2.0 runs commands on the host: LocalWorkspaceBackend "runs commands
as plain host subprocesses — it isolates nothing"
(workspaces/local.py:1-5),
and the harness ships a shell toolset
(shell/_toolset.py:50,
365).
clai v1 runs only provider-native tools
(_cli/__init__.py:60-62).
Observed¶
- No real install: the
pydantic_clai2-0.54.0wheel was fetched and read, not installed.
Source reviews¶
- 2026-10-21: pydantic/pydantic-ai at
62b7bf0, from source only: the state directory is now collected, with its workspace clones excluded and the workspace tokens flagged (State directory, Credentials, Exclusions); the GitHub plugin's token sources are added to Credentials. - 2026-10-19:
62b7bf0against0ba01f9. Added CLAI 2.0's XDG state directory, which the catalog does not name, the harness step store andmetadatakeys ofsessions.db, the saved model defaults inconfig.db, the workspaces plugin's scratch checkouts, and the absence of per-call approval and of session sync (Per-user storage, Credentials, Exclusions, Where the project path is recorded, Parsing).
Collection¶
Per-user storage¶
The library has no default path under the home; message history is
returned to the caller (see research/frameworks.md). The CLIs
write:
| Path (home-relative, all OSes) | Holds | Default or opt-in |
|---|---|---|
.pydantic-ai/prompt-history.txt |
clai v1 prompt_toolkit input history (_cli/__init__.py:52-58, 460-463) |
default for clai |
.config/pydantic-clai2/ ($XDG_CONFIG_HOME honoured; ~/.config on macOS and Windows too) |
the CLAI 2.0 folder (settings_store.py:60-70) | default |
.config/pydantic-clai2/config.db |
settings (settings table), "never credentials or conversation messages" (same citation; __main__.py:29); at 62b7bf0 also the enabled plugins, plugins(id, declaration), and the saved model defaults, tables models, model_settings and model_chains (config/settings_store.py:83-102) |
default |
.config/pydantic-clai2/sessions.db |
the sessions, table conversations, and the harness step store (schema in Session store) (_app.py:540, conversations.py:37-58, 154-156) |
default |
.config/pydantic-clai2/input-history |
prompt history (_app.py:789) | default |
.config/pydantic-clai2/credentials-<account>.enc (the keyring holds the Fernet key, service pydantic-clai2) or credentials-<account>.json (plaintext, no keyring), with .lock sidecars |
provider logins, api-keys, Copilot (credential_store.py:1-5, 23-42, 149-152) |
on login |
.config/pydantic-clai2/mcp.json, mcp_logs/ |
user MCP servers (headers "may be sensitive"), server logs (mcp/_store.py:70-88) | on /mcp install |
.config/pydantic-clai2/logfire/logfire_credentials.json |
Logfire write token (builtin_plugins/logfire.py:221-227) | opt-in plugin |
Scratch directories¶
The workspaces plugin's clones under the state
directory and the worktrees it makes at <repository>/.worktrees/<id>
(or under a kind's root) are the agent's own checkouts; the clones are
under the home in the state directory, where the catalog excludes them
(Exclusions), and the worktrees sit wherever the user's
repository is. Both are recorded in
workspace.json (path, root) and in a session's metadata.workspace
and checkout.root.
State directory¶
At 62b7bf0 CLAI 2.0 keeps state
that is not a preference in $XDG_STATE_HOME/pydantic-clai2 when that
variable is absolute, else .local/state/pydantic-clai2, on every OS
(config/settings_store.py:60-69).
The catalog line pydantic-clai|.local/state/pydantic-clai2 collects it. Its contents:
Path under .local/state/pydantic-clai2/ |
Holds | Default or opt-in |
|---|---|---|
orchestrator.json |
the web UI orchestrator's own conversation, the agent that creates workspaces and gives their agents tasks; never saved in sessions.db (builtin_plugins/web/orchestrator.py:1-7, :49, :137-138) |
web UI with the workspaces plugin |
workspaces/registry/<id>/workspace.json |
a workspace record: id, kind, repo, ref, branch, checkout path and root, status, created, teardown, model, and while it runs its CLAI's pid, port, started (builtin_plugins/workspaces/registry.py:40-79) |
workspaces plugin |
workspaces/registry/<id>/token, session.log |
the access token of the workspace's clai2 --web process and that process's output (builtin_plugins/workspaces/sessions.py:118-127) |
while the workspace runs |
workspaces/clones/<repo slug>-<10 hex>/ |
git clones the plugin makes of remote repositories, with the workspaces' worktrees in .worktrees/<id> inside unless the workspace kind names a root (builtin_plugins/workspaces/manager.py:49-52, backend.py:79-98) |
workspaces plugin, remote repository |
aoe/ |
which CLAI conversation an Agent of Empires (a tmux session manager) session holds (builtin_plugins/aoe.py:1-7, :67-69) | opt-in plugin |
Destroying a workspace stops its CLAI, removes the checkout when its
teardown says so, and deletes its registry folder with the token and the
log
(builtin_plugins/workspaces/manager.py:139-149,
registry.py:117-119);
the session the workspace's CLAI saved stays in sessions.db.
Session store¶
sessions.db and input-history are beside config.db, opened at 62b7bf0 from
runtime/process.py:48
and
runtime/session_runtime.py:560.
sessions.db has conversations(id, revision, metadata, messages, search_text, updated_at), the
same columns at both commits
(step_persistence/conversations.py:174-177);
metadata gained checkout (repository, name, root, branch of the
git checkout, kept so a session can be grouped after its folder is gone)
and archived_at
(:38-48,
:52-75).
The same file now holds the harness step store, which CLAI opens for
every run with up to eight message snapshots per run
(runtime/_session.py:74-76):
runs, events(kind, step_index, timestamp, tool_call_id, tool_name,
error, metadata), snapshots(messages) with
snapshot_messages, and tool_effects(tool_call_id, tool_name, status,
started_at, ended_at, effect_summary)
(step_persistence/_store.py:951-1040).
A sessions.repl/ directory beside sessions.db keeps each session's
run_code REPL state
(runtime/repl_state.py:1-10).
Deleting a session removes its conversations row and, in the same
transaction, its runs, events, snapshots and tool effects
(conversations.py:357-377),
then its REPL file
(runtime/sessions.py:345-350);
the prompt history files keep its prompts. Archiving deletes nothing.
Other agents' sessions¶
CLAI 2.0's /resume reads Claude Code
sessions (~/.claude/projects/*/*.jsonl) and Codex rollouts
(~/.codex/sessions/**) and copies them into sessions.db
(imported_sessions.py:1-5,
claude_code_sessions.py:41-43,
codex_sessions.py:35-37).
A Claude Code or Codex session can therefore appear in CLAI's store as
well as in its own; see research/claude-code.md and
research/codex-cli.md. Both are already in the catalog.
Docker¶
None.
Credentials¶
.config/pydantic-clai2/credentials-*(the.encand.jsonforms and their.locksidecars): flagged. Without a keyring the.jsonform is plaintext; with one, the.encform needs the keyring's Fernet key..config/pydantic-clai2/logfire/logfire_credentials.json: flagged..config/pydantic-clai2/mcp.jsonand a project's.clai/mcp_servers.json(the same server model, Project-local files) give stdio serversenvand HTTP serversheaders, which can embed tokens; both are flagged..local/state/pydantic-clai2/workspaces/registry/<id>/tokenis the access token of a running workspace's localclai2 --webserver, plaintext, owner-only (State directory): flagged, by the glob.local/state/pydantic-clai2/workspaces/registry/*/token. Slack sign-in tokens go to the credential store under the accountslack-oauth(slack_app.py:15), so acredentials-slack-oauth.*file is already flagged.- The GitHub token of the built-in
githubplugin, which gives the agent GitHub's hosted MCP tools, comes from the GitHub CLI's sign-in or from a key saved with/keys, by default under the nameGITHUB_TOKEN(builtin_plugins/github.py:1-9, :34). The CLI's token is read withgh auth tokenon every run and stays ingh's own store, the OS keyring, so CLAI keeps no copy of it (gh_cli.py:1-5, :58-74). Saved keys are one bundle in the credential store under the accountapi-keys(config/api_keys.py:114-121), so acredentials-api-keys.*file is already flagged. The plugin's settings hold onlylogin(ghorkey), the key's name and the MCP endpointurl(builtin_plugins/github.py:48-54).
Exclusions¶
.local/state/pydantic-clai2/workspaces/clones, the full git clones of remote repositories
the workspaces plugin makes, with the workspaces' worktrees inside them
(State directory): excluded, since they are copies of repositories that
exist elsewhere; what a workspace's agent did is recorded in its session.
CLAI 2.0 puts git worktrees at
<repo>/.worktrees/<name>
(worktrees.py:46);
.worktrees is not a project catalog entry, and it is too generic a name
to exclude.
Project-local files¶
.clai/settings.json and .clai/mcp_servers.json
(project_settings.py:10,
mcp/_store.py:25),
collected through the pydantic-clai|.clai project catalog entry.
Where the project path is recorded¶
sessions.db, conversations.metadata, JSON key workspace. It is
recorded only inside SQLite, so discover_projects and Find-Projects
cannot read it; a project's .clai directory is collected when another
agent's state or a -p option names the project. The analyzer is the
place to read it.
metadata.checkout.root holds the git
checkout's top-level folder beside workspace, also inside SQLite. A
workspace's workspace.json (plain JSON, keys path and root, and
repo when that is a local path) names the agent's own checkouts, which
are scratch directories rather than projects
(Scratch directories).
Parsing¶
pydantic-clai is backlog: its parser is
issue 66, and nothing it writes is parsed
yet. A parser would read CLAI 2.0's .config/pydantic-clai2/sessions.db: the conversations
table (metadata with workspace, model, title, checkout and archived_at; messages)
and the harness step store in the same file (runs, events, snapshots, tool_effects)
(Session store), with the prompt histories input-history and
clai v1's .pydantic-ai/prompt-history.txt, which keep a deleted session's prompts, and the
saved model defaults in config.db. The orchestrator's orchestrator.json and the workspace
records sit in the state directory, which is collected
(State directory); a session imported from Claude Code or Codex repeats
that agent's own store (Other agents' sessions). input-history and
prompt-history.txt are line files and orchestrator.json a whole-file JSON document.
CLAI 2.0 asks for no per-call tool approval: "CLAI runs tools without approval"
(builtin_plugins/google_workspace.py:45),
and ApprovalRequired and DeferredTool occur nowhere in src/pydantic_clai2 at
62b7bf0 (the approv matches are plugin approvals, OAuth sign-in prompts and a prompt
instruction). A project's plugins stay disabled until approved with /plugins enable
(config/project_settings.py:27),
and enabled plugins are kept in config.db (Per-user storage). Destroying a
workspace needs the user's Destroy <id> answer to an ask_user_question call, which stays in
the orchestrator's messages
(builtin_plugins/web/workspace_tools.py:80-102).
The model is recorded per conversation in conversations.metadata.model, per response in the
pydantic-ai messages, and per workspace in its record's model.
No session is synced. The observability plugin, on by default, sends run traces to Pydantic
Logfire when a token is present (send_to_logfire if-token-present), without prompt and
response content unless include_content is turned on
(builtin_plugins/logfire.py:1-14,
:70-73).
The Slack integration is an MCP sign-in, not a relay
(slack_app.py:1-15).
Confidence¶
- High: everything stated from source at the cited commits.
- Not determined: whether the
credentials-*.lockfiles hold anything (the docstring says the SQLite lock file holds no secrets, credential_store.py:80); the fullmessagesencoding insessions.db(media externalised to aSqliteMediaStore), which needs a record-schema study under Parsing before a parser is written.