Files
plyngent/CLAUDE.md
T
NCBM 25ff849efe core/cli: level-1 cancel of in-flight turns via Ctrl+C
Run turns as asyncio tasks; SIGINT cancels the task; agent rolls back
and keeps pending_retry_text for /retry.
2026-07-14 21:02:54 +08:00

5.2 KiB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

Project overview

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.

Commands

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
pdm run pytest           # tests (pytest-asyncio auto mode)

Architecture

Data modeling: msgspec.Struct

All protocol models use msgspec.Struct — not dataclasses, not Pydantic. Optional fields use msgspec.field(default=UNSET) / default=UNSET with type T | Unset.

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].

Protocol layering (lmproto/)

lmproto/openai_compatible/     ← Base: model, config, client
lmproto/deepseek/openai_compat/ ← Extends base via inheritance + extra fields
  • openai_compatible/model.py — tagged chat messages (SystemChatMessage, UserChatMessage, …), tools, request/response, streaming chunks.
  • openai_compatible/client.pyBaseOpenAIClient / OpenAIClient via niquests async + SSE.
  • openai_compatible/config.pyOpenAIConfig (token + base URL).
  • DeepSeekDeepseekOpenAIClient; 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.

Agent (agent/)

  • ChatClient Protocol for chat_completions.
  • @tool / ToolRegistry: decorator infers JSON Schema from type hints; execute tools by name.
  • run_chat_loop: multi-round tool loop, yields AgentEvent stream; optional on_limit to continue past max rounds.
  • ChatAgent: wrapper with optional MemoryStore bind (load/append messages on success only); pending_retry_text + retry() after failed turns.

Tools (tools/)

Module-level @tool handlers. Call set_workspace_root() before use.

  • workspace: path resolve under root; path substring denylist; command basename denylist.
  • file: read_file, write_file, listdir, tree (depth/entry caps, skip VCS + optional hidden dirs), edit_replace (first occurrence).
  • 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.
  • CLI limit hooks: interactive confirm to raise tool-loop rounds, PTY session cap, or PTY output budget.
  • DEFAULT_TOOLS: file + process tool list for a ToolRegistry.

CLI (cli/)

Click app + readline REPL. Entry: plyngent / python -m plyngent.

  • 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).
  • Slash: /history, /sessions, /resume, /rounds, /retry, …
  • 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.
  • plyngent providers: list config providers.
  • 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.
  • 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).

Composition utility: Forward descriptor

utils/components.pyForward[T] / forward() for attribute forwarding on composed objects.

Type annotations are mandatory

Basedpyright recommended. Ruff includes ANN (private return types ANN202 ignored). Prefer PEP 695 aliases except where msgspec requires plain assignment (Unset).

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.