Claude Code loads this file via
CLAUDE.md(@AGENTS.mdimport) — the two stay in sync. Edit this file, notCLAUDE.md.
- Language/Version: Python 3.11–3.14
- Core Libraries:
agent-utilities,fastmcp,pydantic-ai - Key principles: Functional patterns, Pydantic for data validation, asynchronous tool execution.
- Architecture:
mcp_server.py: Main MCP server entry point and tool registration.agent_server.py: Pydantic AI agent definition and logic.skills/: Directory containing modular agent skills (if applicable).
graph TD
User([User/A2A]) --> Server[A2A Server / FastAPI]
Server --> Agent[Pydantic AI Agent]
Agent --> Skills[Modular Skills]
Agent --> MCP[MCP Server / FastMCP]
MCP --> Client[API Client / Wrapper]
Client --> ExternalAPI([External Service API])
sequenceDiagram
participant U as User
participant S as Server
participant A as Agent
participant T as MCP Tool
participant API as External API
U->>S: Request
S->>A: Process Query
A->>T: Invoke Tool
T->>API: API Request
API-->>T: API Response
T-->>A: Tool Result
A-->>S: Final Response
S-->>U: Output
pip install .[all]
pre-commit run --all-files
listmonk-mcp
listmonk-agent
- MCP Entry Point →
mcp_server.py - Agent Entry Point →
agent_server.py - Source Code → listmonk_api/
- Skills →
skills/(if exists)
├── .bumpversion.cfg
├── .dockerignore
├── .env
├── .gitattributes
├── .gitignore
├── .pre-commit-config.yaml
├── AGENTS.md
├── Dockerfile
├── LICENSE
├── MANIFEST.in
├── README.md
├── compose.yml
├── debug.Dockerfile
├── listmonk_api
│ ├── __init__.py
│ ├── agent_server.py
│ ├── auth.py
│ └── mcp_server.py
├── pyproject.toml
└── requirements.txt
Always:
- Use
agent-utilitiesfor common patterns (e.g.,create_mcp_server,create_agent). - Define input/output models using Pydantic.
- Include descriptive docstrings for all tools (they are used as tool descriptions for LLMs).
- Check for optional dependencies using
try/except ImportError.
Good example:
from agent_utilities import create_mcp_server
from mcp.server.fastmcp import FastMCP
mcp = create_mcp_server("my-agent")
@mcp.tool()
async def my_tool(param: str) -> str:
"""Description for LLM."""
return f"Result: {param}"Do:
- Run
pre-commitbefore pushing changes. - Use existing patterns from
agent-utilities. - Keep tools focused and idempotent where possible.
Don't:
- Use
cdcommands in scripts; use absolute paths or relative to project root. - Add new dependencies to
dependenciesinpyproject.tomlwithout checkingoptional-dependenciesfirst. - Hardcode secrets; use environment variables or
.envfiles.
Always do:
- Run lint/test via
pre-commit. - Use
agent-utilitiesbase classes.
Ask first:
- Major refactors of
mcp_server.pyoragent_server.py. - Deleting or renaming public tool functions.
Never do:
- Commit
.envfiles or secrets. - Modify
agent-utilitiesoruniversal-skillsfiles from within this package.
- Propose a plan first before making large changes.
- Check
agent-utilitiesdocumentation for existing helpers.
NEVER write any of the following to this repository:
- Temporary test scripts (
test_*.py,debug_*.pyoutside oftests/) - Scratch scripts or experimental one-off files
- Log files (
.log,.txtcommand output) - Random text files with command output or debug dumps
- Any file that is NOT production source code, tests in
tests/, or documentation
Why: These files expose private filesystem paths, credentials, and internal infrastructure details when pushed to GitHub publicly.
Where to put scratch work instead:
- Use
~/workspace/scratch/for temporary scripts and experiments - Use
~/workspace/reports/for command output and reports - Keep test scripts in the
tests/directory following proper pytest conventions
The repository ROOT must contain only canonical project files (packaging,
config, docs, lockfiles). The only hidden directories allowed at root are
.git/, .github/, and .specify/ (plus a local, git-ignored .venv/).
NEVER write any of the following — anywhere in the repo, and ESPECIALLY at the root:
- One-off / debug / migration scripts:
fix_*.py,migrate_*.py,refactor_*.py,replace_*.py,update_*.py,debug_*.py, ortest_*.pyat the root (real tests live intests/only). - Databases / data dumps:
*.db,*.db-wal,*.sqlite*,*.corrupted. - Logs / command output:
*.log, scratch*.txt,*.orig,*.rej,*.bak. - Build artifacts:
*.tsbuildinfo, compiled binaries, coverage files. - AI agent scratch directories:
.agent/,.agents/,.agent_data/,.tmp/,.hypothesis/, or any per-tool cache committed to git. - Any file that is NOT production source, a test in
tests/, documentation, or a recognized config/lockfile.
Why: scratch at the root leaks private paths/credentials, bloats the tree, and erodes a pristine codebase.
Where scratch goes instead: ~/workspace/scratch/ (experiments),
~/workspace/reports/ (command output); tests go in tests/ (pytest).
Before finishing a task, run git status and confirm no stray root files were added.
These four habits cut the most common LLM coding mistakes. For trivial tasks, use judgment; the bias here is correctness over speed.
- Think before coding. State your assumptions explicitly. If a request has more than one reasonable reading, surface the options instead of silently picking one. If a simpler approach exists, say so and push back when warranted. When something is genuinely unclear, stop and name what's confusing — ask, don't guess.
- Simplicity first. Write the minimum code that solves the stated problem — no
speculative features, no abstraction for single-use code, no configurability that
wasn't requested, no error handling for impossible states. If you wrote 200 lines and
it could be 50, rewrite it. (Name code from its purpose, never
wave0/phase2/v2.) - Stay surgical. Every changed line should trace directly to the task. Don't refactor, reformat, or "improve" working code adjacent to your change; match the existing style even where you'd do it differently. Remove only the imports/symbols your own change orphaned; if you spot unrelated dead code, mention it rather than deleting it inline. Exception — the Quality Bar below: lint/format/type errors the pre-commit gate flags get fixed regardless of who introduced them. In short: surgical on behavior, clean on lint.
- Verify against a goal. Turn the task into a checkable outcome before you start: "fix the bug" → "write a failing test that reproduces it, then make it pass"; "add validation" → "tests for the invalid inputs pass". For multi-step work, state the short plan and the check for each step, then loop until the checks pass.
After completing any code change, run the project's pre-commit suite and drive it fully green before committing:
pre-commit run --all-filesResolve every issue it reports — failures, lint errors, type errors, and
warnings — including problems that pre-date your change and were not caused by
your edits. The standing goal is a clean, working codebase with no errors and
no warnings. Do not silence checks (# noqa, # type: ignore, SKIP=,
--no-verify) to force green unless the exception is already documented in this
file as a known, unavoidable limitation. Only commit once pre-commit run --all-files passes cleanly; if a check legitimately cannot pass, stop and explain
why rather than bypassing it.
Multiple agents/sessions work the agent-packages/* repos concurrently. Do not
edit the canonical checkout (${WORKSPACE_ROOT}/agent-packages/<repo>) — a
background repository-manager sync can reset its working tree and discard
uncommitted edits. Take your own git worktree on your own branch instead:
# preferred — repository-manager MCP:
rm_worktree add <repo> <your-branch> # -> ${WORKTREE_ROOT}/<repo>/<your-branch>
# raw-git fallback:
git -C agent-packages/<repo> checkout main
git -C agent-packages/<repo> worktree add ${WORKTREE_ROOT}/<repo>/<branch> -b <branch>Work in the worktree and commit often (commits survive a working-tree reset).
Each session must use a distinct branch — git allows a branch in only one
worktree, which is what keeps concurrent sessions from colliding. Worktrees live
under ${WORKTREE_ROOT}/ (outside the workspace scan, so the sync leaves them
alone).
Finishing work in a worktree — run this sequence before calling it done:
- Pre-commit green —
pre-commit run --all-files; resolve every issue per the Quality Bar above (including pre-existing), no--no-verify. - Commit in the worktree.
- Merge to main locally —
rm_worktree merge <repo> <branch> --into main(orgit merge --no-ff). Push only when the user asks. - Clean up — remove the worktree and delete the merged branch:
rm_worktree remove <repo> <branch> --delete-branch;rm_worktree pruneclears stale entries. (Raw-git:git worktree remove <path> && git branch -d <branch>.)
Working in parallel with other sessions/worktrees? Reserve a concept id before you write its CONCEPT: marker so two sessions never collide:
agent-utilities --json concept reserve --ns EG-KG.compute.backend # or a package prefix, e.g. KEYFull protocol (ledger, merge=union, reconcile, MCP/REST): https://knuckles-team.github.io/agent-utilities/concept_coordination/
The two most common release-breakers in this fleet are version drift (the version in
pyproject.toml/.bumpversion.cfg advancing while README.md, docker/Dockerfile, and the
module __version__s lag) and a stale uv.lock (shipping known-vulnerable transitive deps).
A version mismatch makes the next bump-my-version throw VersionNotFoundException; a stale lock
is what Dependabot flags. Rules:
- Never hand-edit a version string. Change the version ONLY via
bump-my-version bump {patch|minor|major}(a.k.a.bump2version), which rewrites every file registered in.bumpversion.cfgin one atomic, tagged commit. If you edited the version inpyproject.tomlby hand, you created drift — revert and use the bumper. - Every version-bearing file must be registered in
.bumpversion.cfg— at minimumpyproject.tomlANDREADME.md, plusdocker/Dockerfileand any module__version__. Never add a file that embeds the version without a[bumpversion:file:...]entry for it. - Re-lock on every dependency change. After editing
pyproject.tomldeps/extras, runuv lockand commituv.lockin the SAME change. Theuv-lockpre-commit hook runs with--lockedand fails on drift — never bypass it. The committeduv.lockis the Dependabot/security surface. - Patch CVEs with a version floor at the source, then re-lock.
uvresolves one version graph-wide, so a lower-bound in the extra that pulls a dependency raises it for the whole lock.
Upstream currency edict — target the newest release; a pin is a hypothesis, not a fact (READ BEFORE capping, deferring, or opt-in-gating an upgrade)
This governs how we treat other people's releases, deprecations, and version caps in
this repo (fleet-wide edict, propagated from agent-utilities/AGENTS.md).
- Latest by default. Target the newest upstream release -- including a pre-release where the ecosystem has already moved onto it. Sitting on an old major because the upgrade is work is not a reason to defer it.
- A conservative upstream pin is a hypothesis, not a fact -- test it, don't inherit
it. Upstream maintainers cap defensively (an unreleased major, an untested surface)
as often as they cap for a known break. Worked example (from
agent-utilities):pydantic-ai-slim2.18.0 declaredfastmcp-slim[client]>=3.3.0with no upper bound; 2.19.0 added<4purely as a defensive guard while fastmcp 4 was still pre-release -- not because of an observed incompatibility. Blocking an upgrade on that kind of cap without testing it is the wrong default. - Forward-fix only. When an upgrade breaks something, fix the break to proceed -- do not pin backwards, vendor a fork, or route around it. If a break is genuinely unfixable inside this repo, say exactly what and why, and carry a plan to unblock it -- never an indefinite pin.
- Deprecations are fixed on sight, in code AND in tests. A
DeprecationWarningfrom an upstream library is a defect to fix now, not noise to filter. Never silence one with a warning filter,# noqa, or a pytestfilterwarningsentry in order to go green. - Adopt upstream features rather than reimplementing them. If upstream ships a capability this repo hand-rolled, migrate to theirs and delete the local one.
- Nothing built on an upgrade ships opt-in. A new capability an upgrade unlocks is default-on unless it genuinely costs compute, in which case it is policy-selected, never flag-gated. An opt-in extra or a dependency-conflict fork is an interim state that must carry a written plan to become the default, never a resting place.