2026-07-14 17:19:31 +08:00
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project overview
2026-07-14 17:25:22 +08:00
Plyngent is an LLM chat and agent toolkit (Python 3.14+, PDM-managed). Early development: protocol clients, config, async memory, and a runtime client factory exist. Agent, CLI, web, tools, and router are still planned/stubbed.
2026-07-14 17:19:31 +08:00
## Commands
``` bash
pdm install # first-time dependency setup
pdm sync # sync after pulling changes
pdm run basedpyright . # type checking (basedpyright, "recommended" strictness)
pdm run ruff check . # linting
pdm run ruff format . # formatting
2026-07-14 17:25:22 +08:00
pdm run pytest # tests (pytest-asyncio auto mode)
2026-07-14 17:19:31 +08:00
```
## Architecture
### Data modeling: `msgspec.Struct`
2026-07-14 17:25:22 +08:00
All protocol models use `msgspec.Struct` — not dataclasses, not Pydantic. Optional fields use `msgspec.field(default=UNSET)` / `default=UNSET` with type `T | Unset` .
2026-07-14 17:19:31 +08:00
2026-07-14 17:25:22 +08:00
`typedef.py` : `Unset = UnsetType` (plain assignment so msgspec recognizes it; do **not ** use PEP 695 `type Unset = ...` ). Multi-struct unions must be **tagged ** via `tag_field` / `tag` (e.g. `role` , `type` ) for decode. `JSONSchema` is `dict[str, Any]` .
2026-07-14 17:19:31 +08:00
### Protocol layering (`lmproto/`)
```
lmproto/openai_compatible/ ← Base: model, config, client
lmproto/deepseek/openai_compat/ ← Extends base via inheritance + extra fields
```
2026-07-14 17:25:22 +08:00
- **`openai_compatible/model.py` ** — tagged chat messages (`SystemChatMessage` , `UserChatMessage` , …), tools, request/response, streaming chunks.
- **`openai_compatible/client.py` ** — `BaseOpenAIClient` / `OpenAIClient` via `niquests` async + SSE.
- **`openai_compatible/config.py` ** — `OpenAIConfig` (token + base URL).
- **DeepSeek** — `DeepseekOpenAIClient` ; models add `reasoning_content` , `prefix` , `ThinkingOptions` .
### Config (`config/`)
TOML load/store (`ConfigStore` ): `[providers]` tagged union presets, `[database]` section. Default path via platformdirs.
### Runtime (`runtime/`)
`create_client(provider)` maps config `Provider` → protocol client. OpenAI / openai-compatible / deepseek(openai convention) supported; anthropic and deepseek anthropic convention raise `ProviderNotSupportedError` .
### Memory (`memory/`)
Async SQLAlchemy + aiosqlite. `MemoryStore` : schema init, default local user, sessions, messages stored as msgspec chat message JSON.
2026-07-14 17:19:31 +08:00
2026-07-14 17:45:47 +08:00
### Agent (`agent/`)
- **`ChatClient` ** Protocol for `chat_completions` .
- **`@tool` / `ToolRegistry` **: decorator infers JSON Schema from type hints; execute tools by name.
2026-07-14 22:22:14 +08:00
- **`run_chat_loop` **: multi-round tool loop; default **streaming ** text deltas + raw-SSE tool-call merge; optional `on_limit` .
- **`ChatAgent` **: optional `MemoryStore` (persist on success only); `stream` flag; `pending_retry_text` + `retry()` .
- Events: text_delta, assistant_message, tool_call/result, max_rounds, **error ** , **cancelled ** .
2026-07-14 17:45:47 +08:00
2026-07-14 18:20:32 +08:00
### Tools (`tools/`)
Module-level `@tool` handlers. Call `set_workspace_root()` before use.
- **`workspace` **: path resolve under root; path substring denylist; command basename denylist.
2026-07-14 21:47:57 +08:00
- **`file` **: `read_file` , `write_file` , `listdir` , `tree` (depth/entry caps, skip VCS + optional hidden dirs), `edit_replace` (first occurrence), `copy_path` / `move_path` / `delete_path` (shutil; dirs supported).
2026-07-14 19:47:32 +08:00
- **`process` **: `run_command` (argv, no shell, timeout, optional stdin/env); PTY `open_pty` / `read_pty` / `write_pty` / `close_pty` (**Unix only**: `pty` +`fork` ).
- PTY: structured status (`alive` /`exit_code` /`data` ); `read_pty(..., until=)` ; session limit/idle TTL/output budget; close SIGTERM→SIGKILL.
2026-07-14 19:53:34 +08:00
- CLI limit hooks: interactive confirm to raise tool-loop rounds, PTY session cap, or PTY output budget.
2026-07-14 18:20:32 +08:00
- **`DEFAULT_TOOLS` **: file + process tool list for a `ToolRegistry` .
2026-07-14 18:29:46 +08:00
### CLI (`cli/`)
Click app + readline REPL. Entry: `plyngent` / `python -m plyngent` .
2026-07-14 19:15:12 +08:00
- **`plyngent chat` **: provider/model selection (flags or interactive), SQLite sessions via config `[database]` (file DB under user data if unset/`:memory:` ), resumes latest session by default (`--new` / `--session` ).
2026-07-14 20:56:08 +08:00
- Slash: `/history` , `/sessions` , `/resume` , `/rounds` , `/retry` , …
2026-07-14 21:02:54 +08:00
- Failed/cancelled turns: not written to DB; Ctrl+C cancels the in-flight turn task; auto-retry 10s/20s/30s (Ctrl+C cancels wait); `/retry` manual.
2026-07-14 18:29:46 +08:00
- **`plyngent providers` **: list config providers.
2026-07-14 18:44:32 +08:00
- **`plyngent config path|edit` **: print or open config in `$EDITOR` (`shlex` -split, e.g. `codium --wait` ).
- If no providers and `$EDITOR` is set, chat/providers prompt to edit config then reload.
2026-07-14 19:15:12 +08:00
- Tools default on (`--tools` / `--no-tools` ); workspace defaults to cwd; `--max-rounds` default 32.
- Readline/editline: Tab completion; input history file under platformdirs user data (`repl_history` ).
2026-07-14 18:29:46 +08:00
2026-07-14 17:19:31 +08:00
### Composition utility: `Forward` descriptor
2026-07-14 17:25:22 +08:00
`utils/components.py` — `Forward[T]` / `forward()` for attribute forwarding on composed objects.
2026-07-14 17:19:31 +08:00
### Type annotations are mandatory
2026-07-14 17:25:22 +08:00
Basedpyright `recommended` . Ruff includes `ANN` (private return types `ANN202` ignored). Prefer PEP 695 aliases except where msgspec requires plain assignment (`Unset` ).
2026-07-14 17:19:31 +08:00
## Commit messages
Scoped commit messages, not conventional commits:
```
<scope>: <brief description>
```
Examples: `deps: add fastapi` , `core/mq: fix incorrect message sending` , `router: add routing service for xxx` , `test/webserver: change test client` , `ci/lint: run ruff style check` .
Check `git log` for the full convention.