CodeAgent
A document and coding agent. It reads and edits files, Excel workbooks and PDFs (scans included) in a project folder, searches a library of documents, browses the web, writes mail drafts and code, asks before every change, and remembers what it learned about each project. The model is Claude (Anthropic's API, or your own Azure subscription through Microsoft Foundry) or DeepSeek. Use it from the terminal, or as a library to build your own tool on top of it (a desktop app that fills Excel workbooks from invoices, a voice assistant over a library of PDFs...).
pip install seedagent
Or from a clone of this repository: uv sync installs the package and the coding-agent command in .venv (uv run coding-agent), and
uv sync --all-extras adds the browser and the Excel backend. An application built on it just runs uv sync too: its own pyproject.toml
and uv.lock say which version of the engine to fetch.
The package seedagent installs the seedagent Python package and the coding-agent command (also available as seedagent).
Python 3.10 or later; Windows, macOS and Linux.
Configure
Environment variables, or a .env file in the folder you run from (or ~/.coding-agent/.env):
| Variable | Meaning |
|---|---|
ANTHROPIC_FOUNDRY_ENDPOINT |
https://<resource>.services.ai.azure.com/anthropic |
ANTHROPIC_FOUNDRY_DEPLOYMENT |
your Claude deployment name |
ANTHROPIC_FOUNDRY_API_KEY |
an API key; leave unset to sign in with your Microsoft work account |
Without an API key, the agent signs in with the account of your Windows session, a developer's
az login, or else the Microsoft sign-in page (once; the account is remembered).
Without Azure, use Anthropic's own API: leave ANTHROPIC_FOUNDRY_ENDPOINT unset and set
| Variable | Meaning |
|---|---|
ANTHROPIC_API_KEY |
an API key from console.anthropic.com |
ANTHROPIC_MODEL |
a model ID (default claude-sonnet-5) |
Models: claude-sonnet-5 (the default, $2/$10 per million input/output tokens), claude-sonnet-5-5 (same price), claude-haiku-4-5
($1/$5, the cheapest; no effort setting, weaker with tools; good for the two background models AGENT_MEMORY_MODEL and
AGENT_COMPACT_MODEL), claude-opus-5-5 ($4/$20), claude-opus-5 ($5/$25) and claude-fable-5-1 ($10/$50, its thinking cannot be turned off).
Thinking and effort (cost and speed), for the agent's own requests:
| Variable | Meaning |
|---|---|
CODEAGENT_THINKING |
thinking before the answer: off (the default), adaptive, between_tools or disabled |
CODEAGENT_EFFORT |
how much the model thinks and writes: medium (the default), low, high, xhigh, max, or default (nothing is sent: the model's own default applies) |
off means the lowest setting the model accepts: disabled, between_tools on claude-sonnet-5-5 (it answers 400 to disabled), adaptive on
claude-opus-5-5 (it answers 400 to both), and
no thinking field at all on claude-fable-* (its thinking cannot be turned off). On Foundry the model is a deployment name, so off may guess
wrong: name between_tools or disabled yourself. between_tools does not work with effort xhigh or max, and claude-haiku-* has no effort
setting (none is sent). A value that is not allowed is explained, never replaced. An application can also call configure(effort=...).
coding-agent --check shows the thinking and effort it sends. example.env lists every variable the agent reads: copy it to .env.
A Foundry endpoint always wins: with ANTHROPIC_FOUNDRY_ENDPOINT set, the agent uses Foundry even if
ANTHROPIC_API_KEY is also set.
DeepSeek instead of Claude
DeepSeek offers an Anthropic-compatible API (https://api.deepseek.com/anthropic), so the same client and the same tools work with a
DeepSeek model. Set DEEPSEEK_API_KEY (and nothing else: no ANTHROPIC_API_KEY, no Foundry endpoint), or CODEAGENT_PROVIDER=deepseek to
choose it over them. CODEAGENT_PROVIDER=claude does the opposite: Claude (Anthropic's API or Foundry) is used even when a DeepSeek key is set. The model is deepseek-flash by default; ANTHROPIC_MODEL=deepseek-v4-pro selects the larger one. Setting
ANTHROPIC_API_KEY to a DeepSeek key with ANTHROPIC_BASE_URL=https://api.deepseek.com/anthropic also works and is recognised.
coding-agent --check tests the connection.
What DeepSeek does not take, and what the agent does about it: no web_search tool (it is left out, Web search: off); no cache_control
(DeepSeek caches by itself); no PDF document blocks, so read_pdf sends pages as rendered images instead (pypdfium2) and scans are read
by OCR or by looking at the page; thinking is sent as disabled for CODEAGENT_THINKING=off. deepseek-flash looks at images;
deepseek-v4-pro does not (it answers "unsupported image"): the tools that return an image are then not offered and read_pdf has no visual
mode (AGENT_VISION=on|off overrides the list for a model that is not in it). Set AGENT_CONTEXT_WINDOW to the model's real window (the
default is 200,000 tokens). An application can hand the key over without any environment variable: configure(api_key=..., model=...).
Use it in the terminal
coding-agent -d path/to/project "Add input validation to the CLI"
coding-agent -d path/to/project # interactive
coding-agent -d path/to/project -r # resume the last conversation
coding-agent -d path/to/project --read D:/docs --read //server/share # also read (never write) other folders
coding-agent --check # Claude not answering? test each step of a call
coding-agent --help
In the interactive mode, the up arrow at the You: prompt recalls earlier questions, also from earlier
runs (kept in ~/.coding-agent/history; needs readline: Linux, macOS, WSL; the Windows console recalls
the questions of the running session by itself).
Use it as a library
from seedagent import session
from seedagent.ui import TerminalUI # or your own UI (subclass seedagent.ui.UI)
session.open_project("path/to/folder", ui=TerminalUI(),
tools=["read_excel", "edit_excel", "read_pdf", "ask_human"], # optional subset
system_prompt=None) # or your own instructions
ok, message = session.check_connection() # can Claude be reached?
session.send("Fill costs.xlsx from invoice.pdf")
session.close()
session.open_project(..., excel_first=True) (for an application that fills workbooks) refuses read_pdf
until read_excel has run, when the instruction mentions a spreadsheet; it is off by default.
An application that fills one workbook can also set, after open_project: state.excel_writable = {path} (no other workbook can be changed
or created), state.excel_protect_formulas = True (a formula can be neither replaced nor cleared), and state.excel_edit_sheets[path] = {"Sheet1"}
(only those sheets). They are checked by the tool, not asked of the model.
session.stop() stops the running instruction from another thread (e.g. a Stop button);
session.add_read_folder(path) lets the agent read (never write) another folder. Every approval,
question and progress message goes through the UI object, so a web or desktop front end can show them.
Your own tools
An application adds a tool of its own without changing the package:
import seedagent
def say_hello(who: str, loud: bool = False) -> str: # called as handler(**arguments)
return f"Hello {who}{'!' if loud else ''}"
seedagent.register_tool(
{"name": "say_hello", "description": "Greet someone.",
"input_schema": {"type": "object", "properties": {"who": {"type": "string"}, "loud": {"type": "boolean"}}, "required": ["who"]}},
say_hello)
session.open_project(folder, ui=ui, tools=["read_file", "say_hello"]) # a tool is offered only when the session lists it
The handler gets one keyword argument per property of the input_schema and returns a string or a list of content blocks
({"type": "text", ...}, {"type": "image", ...}). To report a failure Claude should see, it raises seedagent.common.ToolError; any other
exception also reaches Claude as an error result and does not stop the agent. It runs in the agent's thread and can use seedagent.state
(state.ui, state.workspace). CodeAgent's path checks and confirmations guard only its own tools: what the handler does is the application's
responsibility (to read a file the way the built-in tools do, seedagent.common.resolve_readable(path) applies the same rules). A built-in
tool cannot be replaced, registering the same handler twice does nothing, and a second handler under a taken name is an error.
Tools
45 tools; an application offers the subset it wants (session.open_project(tools=[...])).
| Group | Tools |
|---|---|
| Files | read_file, write_file, edit_file, list_directory, change_directory, grep, copy_path, delete_file, delete_folder |
| Documents | read_pdf, search_pdf, search_library, view_image, read_word, read_powerpoint |
| Excel | read_excel, edit_excel, view_excel, format_excel, add_chart, add_table, add_pivot_table, restore_backup, run_python_excel |
| Web | web_search, web_open, web_click, web_type, web_back, web_page, web_look, screenshot_page, web_sign_in, web_set_token, web_close, download_file |
| Mail and SMS | mail_login, mail_draft, send_mail, send_sms |
| Code | run_python (sandboxed), git (read-only), clone_repo |
| Other | load_skill, ask_human |
read_file reads UTF-8 and, failing that, Windows-1252 (the usual export of Excel and French accounting software; the result says so).
grep cuts a matching line at 300 characters and bounds its whole result, so a minified file cannot flood the conversation.
Documents, scans and the library
read_pdfreads text by default (page ranges). Its visual mode (pages rendered as images, at most 20 per call) is allowed once a text read happened in the conversation, to check what a scan or a table really shows; names, dates and amounts taken from a scan should be checked that way. A model that cannot see images gets no visual mode.- Scanned pages are read by OCR (tesseract and its language packs must be installed); the text is kept in
AGENT_OCR_DIRso a page is read once.coding-agent --ocr PDF...does it ahead of time. search_librarysearches a library of PDFs, Word and PowerPoint files and text files (the read-only folders and the project) through a full-text index (SQLite FTS5; alternatives witha | b | c, ranked by the words found). Files that are new or changed are indexed on the way, up to a size; bigger ones, and a first indexing with OCR, are done once withcoding-agent --index FOLDER...(AGENT_INDEX_DIRmoves the index).read_word(.docx: paragraphs and tables) andread_powerpoint(.pptx: slides and speaker notes) read the text only, with no extra dependency; a long document is read in parts withstart, a slide alone withslide. Both are in the search index (a slide is a page; a Word file is cut in blocks, located by character). A picture inside is not read.read_pdf,read_file,search_libraryandgrepalso read the folders given with--read/session.add_read_folder(never write).
Browsing the web
web_search finds pages; web_open reads one in a hidden browser that stays open, and lists its text
and its links, buttons and fields with numbers. The agent goes through a site with web_click 3 and
web_type 5 "weather paris" submit, reads long pages with web_page, goes back with web_back, sees
the page with web_look and ends with web_close. It needs Playwright and a browser, like
screenshot_page (uv sync --extra browser, then playwright install chromium; coding-agent --check-browser tests it).
What keeps it safe (in the tools, not only in the prompt):
- The first visit to a public site asks you, with the full URL (
[a]ll sites for this sessionstops the questions until the session ends;AGENT_WEB_APPROVE=offnever asks, for unattended runs); after that the agent can click and type on that site. Links to other sites are refused until the agent opens them withweb_open, which asks you. localhost, private addresses and workspace HTML files need no question. - It never types into password, sign-in or payment fields. When a site needs you signed in,
web_sign_inopens a visible window where you type the password yourself; the agent never sees it and keeps only the session (cookies) of that site, for 30 days (AGENT_WEB_SESSION_DAYS), in~/.coding-agent/web-sessions(owner-only files).coding-agent --forget-loginsdeletes them (--forget-logins SITEfor one). A saved session is as good as a password until it expires: keep it for sites you are comfortable with. - A form that sends data (POST), by a button or by pressing Enter, shows what it sends and asks first.
- Downloads are blocked, metadata addresses are refused, and nothing is kept on disk (apart from sign-ins you made with
web_sign_in): cookies and history end with the session (web_close, or the program ending). - The page text reaches Claude marked as untrusted content, and the tools say that pages cannot give it instructions.
Limits: content inside frames and shadow DOM is not listed, nothing is drawn on a canvas for the text
view (use web_look), and a site that blocks automated browsers will block this one too.
Excel: two backends
Who changes, formats and renders workbooks is set by AGENT_EXCEL_BACKEND:
openpyxl |
xlwings (uv sync --extra excel) |
|
|---|---|---|
| Runs on | anywhere | Windows or macOS with Excel installed |
| Saving | rewrites the file: charts, pictures, pivot tables can be damaged (you are warned first) | Excel saves: nothing is lost |
| Formulas after an edit | old results until the file is opened in Excel | recalculated at once |
view_excel |
a drawing of the cells (needs the browser extra); charts listed, not drawn |
what Excel prints, charts included |
| A workbook open in Excel | refused (locked) | written in that window, if it has no unsaved changes |
add_chart, add_table |
written by openpyxl | added by Excel |
add_pivot_table |
refused (openpyxl cannot build them) | built by Excel |
auto (the default) uses xlwings when it is installed and Excel can be started, else openpyxl.
Reading (read_excel) is the same in both: it never changes the file, lists the charts, pictures,
tables and pivot tables of a sheet, and shows a long cell whole when that one cell is read.
scripts/compare_excel_backends.py runs the same edits, formatting and views with both backends and
reports what each kept (run it on a PC with Excel: uv run --extra excel --extra browser python scripts/compare_excel_backends.py [your.xlsx ...]).
Excel scripts (run_python_excel)
For what no Excel tool does, the agent has run_python_excel: a Python
script run with Excel (xlwings backend only) on a workbook, which it gets as book. Excel can do far
more than the run_python sandbox can see (run macros, start programs, open and save any file), so:
- the script is checked before it runs, and refused if it uses macros (
Run,Evaluate,VBProject,ExecuteExcel4Macro...), programs or links (Shell,FollowHyperlink, DDE formulas such as=cmd|...), other files or workbooks (save,SaveAs,Open,books,app,Application,Parent), external data (QueryTables,WEBSERVICE), imports beyondmath,datetime,re,collections... , or ways around the check (eval,exec,getattrwith a computed name,_privateattributes); - you approve the script (unless autonomous mode is on), and a backup of the workbook is kept first
(
restore_backupundoes it); - it runs in its own invisible Excel with macros forced off and events off, inside the
run_pythonsandbox; macros it adds are refused and the workbook is put back; - you see what it changed: cells, sheets, charts, tables, pivot tables.
The check reads the script; it cannot prove what Excel will do. An application that passes its own
list of tools (session.open_project(tools=[...])) does not get it unless it lists it, and it is
refused in applications that restrict edits (chosen sheets, protected formulas, no formatting).
Safety
- It writes only inside the project folder. Reading elsewhere: in the terminal the agent asks you the first time it wants to read a folder outside the project (
Allow the agent to read (never change) files in ...?), and remembers the answer for the session; folders given with--readneed no question. Keys and settings (~/.ssh,~/.aws,~/.azure,~/.coding-agent,.envfiles,*.pem,*.key...) are never read. An application built on the package keeps reading limited to what it was given. - Every file or workbook change is shown (a diff, or a cell-by-cell table) and waits for your
approval; a copy of the previous version of each workbook is kept in
~/.coding-agent/backups/for 3 days, andrestore_backupputs one back ("undo your changes to costs.xlsx"). Applications can list and restore them withseedagent.backups(versions(),restore()). - Deleting always asks, even in autonomous mode; downloads and clones always ask; the agent cannot
modify its own files;
run_pythoncannot start processes or delete files. - No secret reaches code the model wrote.
run_pythonand Excel scripts run with an environment from which every variable that says it is a secret (*KEY*,*TOKEN*,*SECRET*,*PASSWORD*,*AUTH*), the variables of cloud and model services (ANTHROPIC_*,DEEPSEEK_*,AZURE_*,AWS_*,GITHUB_*...) and any copy of an API key are removed, and the output of the script has those values hidden. A document that steers the model cannot make it print your keys. - The agent never reads
~/.coding-agent(credentials and its own settings), so a knowledge folder must live elsewhere.
What stays on your machine
Everything is in ~/.coding-agent ($HOME, or your user folder on Windows), cleaned automatically
(seedagent/cleanup.py): once per process, at the start of coding-agent and the first time an application
opens a project (session.open_project). AGENT_CLEANUP=off switches that off; an application that runs for
weeks (a server) can call seedagent.cleanup.run() itself, daily (a call counts as the run):
| What | Where | Kept |
|---|---|---|
| Copies of workbooks before each change | backups/ |
3 days (AGENT_BACKUP_DAYS) |
| What the agent learned about each project | memory/<project>-<code>/ |
notes capped in size; deleted after 90 days unused (AGENT_MEMORY_DAYS) |
The saved conversation (--resume) |
memory/<project>-<code>/conversation.json |
30 days (AGENT_CONVERSATION_DAYS) |
AGENT_MEMORY=off stops the agent from writing memory at all.
For developers, to see and delete what the agent remembers:
coding-agent --memories # every project's memory: name, last used, size, notes
coding-agent --memories myapp # one project's notes, in full
coding-agent --forget myapp # delete one project's memory (asks first)
coding-agent --forget . -d path # the memory of that project folder
coding-agent --forget all # delete every project's memory (asks first)
Development
uv sync --all-extras # the exact versions the tests ran with (uv.lock), with the browser and Excel extras
uv run playwright install chromium # the browser tools and their tests need it
uv run pytest
uv build # dist/seedagent-<version>.tar.gz and .whl
The CI (.github/workflows/package.yml) runs the same on Linux and Windows, Python 3.10 and 3.14, with every extra and Chromium
installed, then installs the built wheel in a fresh environment and runs the tests there. A test must not depend on a .env, on the
order of files or on the repository layout (the wheel's tests run from a copy of tests/).
Releasing (maintainers)
The workflow (.github/workflows/package.yml) publishes to PyPI by trusted publishing (no token stored). The project is seedagent; its
publisher on pypi.org is owner DemePS, repository CodeAgent, workflow package.yml, environment pypi (also an environment pypi in
the repository's settings). PyPI refuses a name that equals an existing one once -, _ and . are removed (codeagent was refused:
code-agent exists), so check a new name against the full list before registering it.
To publish a version, tag the commit whose pyproject.toml has that version, with CI green, and push the tag:
git fetch origin
git tag -a v0.7.0 origin/main -m "v0.7.0"
git push origin v0.7.0
The tag runs the tests and the build, publishes to PyPI and creates a GitHub Release. A tag whose version differs from pyproject.toml is
refused. A published version can never be replaced: fix forward with a new version. Applications then depend on seedagent==<version>.
License
MIT (see LICENSE).
Metadata
Release files for seedagent 0.8.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| seedagent-0.8.0.tar.gz | 232.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| seedagent-0.8.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 437.4 kB
Release files / seedagent-0.8.0.tar.gz
| Download URL | seedagent-0.8.0.tar.gz |
|---|---|
| Size | 232.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
00fa52f63acbd203baaa3d8acdb0b0cfa8c886e95097053712407f427432a08c
|
|
BLAKE2b-256 checksum How to use checksums |
f062a1f9d0882e454bc9c4318d90cdace73c6939d207cb1945d294226e01494c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency logRelease files / seedagent-0.8.0-py3-none-any.whl
| Download URL | seedagent-0.8.0-py3-none-any.whl |
|---|---|
| Size | 204.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
0e69bb69c2cccca5ea6f3d3115bd5061743828b054fc438e69c1498c3fed82b0
|
|
BLAKE2b-256 checksum How to use checksums |
c04a9547af78ffebb9d9ead664549f5259ec35ed22bbb936e75f88dc6ce51b1b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 11, 2026.
Transparency log