An application programming interface (API) lets a Python program send prompts to a model and receive its responses. chatlas handles this communication.
Python program
Provider’s API
Model
Response
Each API call sends the model name and the full conversation so far to the provider’s server, which runs the model and returns text. The model keeps no memory between calls, so chatlas resends earlier messages; hence the second question can refer to “that”.
A tool is a function made available to the model. The model requests a call and supplies its arguments. The surrounding software executes the function and returns its result.
The model never sees the code. It receives a text description of the tool (its name, docstring, and argument types) with every request, and decides from the prompt and that description whether a call would help. The model cannot run the function itself: it replies with a structured request, and chatlas executes multiply in the Python session on the researcher’s own computer.
Python adaptation of Wickham, Your LLM can’t math. chatlas tool calling.
One call to chat.chat() works as follows:
| Step | What happens |
|---|---|
| 1. Prompt | Use multiply to calculate 20598162 × 83106206. |
| 2. Model reasons | Exact eight-digit multiplication is error-prone; multiply is available. |
| 3. Tool call | The model replies multiply(x=20598162, y=83106206) |
| 4. Execution | Python, on your computer, returns 1711835094393372 |
| 5. Result | chatlas adds the result to the conversation and calls the model again |
| 6. Answer | 20598162 × 83106206 = 1,711,835,094,393,372. |
Steps 2 to 6 happen inside a single chat.chat() call. The provider is called twice: once to obtain the tool request, and once more, with the result added to the conversation, to obtain the answer.
The same mechanism gives a model access to project files. Reading supplies information; writing creates a saved research output.
from pathlib import Path
def read_file(path: str) -> str:
"""Read a UTF-8 text file."""
return Path(path).read_text(encoding="utf-8")
def write_file(path: str, content: str) -> str:
"""Save text to a UTF-8 file."""
Path(path).write_text(content, encoding="utf-8")
return f"Saved {path}"
chat.register_tool(read_file)
chat.register_tool(write_file)Adapted from Wickham, coding-agent tools.
read_file("directors.csv")write_file(...)An agent is this loop: after every tool result the model is called again, until it replies with text instead of a tool request. The model decides the next action and when the task is finished.
Coding agents supply tools for inspecting files, editing code, and running commands. Web tools let them find sources and inspect their contents.
| Tool | Example action |
|---|---|
| Read / list / search files | Locate and inspect directors.csv |
| Write / edit files | Create a Python retrieval script |
| Run a command | Execute the script and inspect its output |
| Web search | Find biographies or genealogy records |
| Web fetch / browser | Open a source and examine its contents |
Write Python
Run it
Inspect the result
Choose the next action
Coding agents such as Claude Code and Codex run in a terminal: a window in which programs are started by typing commands. Every command runs from a current folder, the working directory. An agent started in a project folder treats that folder as its workspace.
The project folder
director_research/
├── directors.csv
├── sources/
│ └── biography.pdf
├── code/
└── outputs/
A terminal session inside it
$ cd ~/research/director_research
$ ls
code directors.csv outputs sources
$ head -3 directors.csv
name,birth_year,firm,role
Frederik Hendrik Fentener van Vlissingen,1849,SHV,director
Gerard Philips,1858,Philips,director
cd enters the folder, ls lists it, and head prints the first lines of a file. A relative path such as sources/biography.pdf starts from the working directory. The agent’s file and command tools resolve paths in the same way.
Example prompt:
Read directors.csv. Write a Python script that counts directors by firm. Run it, inspect the result, and save the script and a summary under code/ and outputs/.
Actions the agent takes
Read directors.csv
Write code/count_by_firm.py
Bash python code/count_by_firm.py
Read (output of the script)
Write outputs/firm_counts.md
Each action runs on the researcher’s own machine, with the researcher’s permissions. The same tools can install packages, overwrite files, or delete them. Claude Code and Codex therefore ask for approval before edits and commands unless they have been permitted in advance.
Work in a dedicated project folder under version control (git), so that every change the agent makes can be inspected and reverted.
An agent works through its tools, and its file tools handle text. What a document requires before the model can use it depends on its format.
Plain text.txt .md .csv .py .json
Read directly
Packaged or laid-out.docx .pptx .pdf
Extract text first
Images of text
scans, photos, image-only PDFs
OCR first
Markdown is plain text with light markup for headings, lists, links, and tables. It keeps a document’s structure while remaining native to the agent. The practical rule: convert once, work on the Markdown copy, keep the original.
anydoc converts PDF, DOCX, PPTX, and similar formats into Markdown, one file per command. It requires Node.js and is installed once:
An agent does not know that anydoc is installed unless it is told. State it in the prompt, or once in the project’s instruction file.
anydoc documentation (version 0.2.4).
Start the agent from the project directory. Claude Code and Codex provide interactive sessions in which an agent can inspect files, run commands, and save work.
Enter a task in the session. Read the response, inspect the files it creates, and continue with follow-up instructions.
Many other coding agents follow the same design, among them Gemini CLI, GitHub Copilot CLI, and Cursor. Open-source agents such as OpenCode, Aider, and Goose are free to use and work with most providers; they can also run a model locally through Ollama, in which case no data leaves the computer, at the cost of a less capable model.
A task names three things: the input, the objective, and the output. Paths are given relative to the project folder.
Read directors.csv. Summarize the directors, firms, and periods represented in the file. Save the summary to outputs/director_summary.md. Report missing information explicitly and list the files created.
| Element | In this prompt |
|---|---|
| Input | directors.csv |
| Objective | A summary of directors, firms, and periods, with missing information reported |
| Output | outputs/director_summary.md |
A named output file makes the result available outside the conversation: the researcher inspects the file rather than relying on the agent’s description of it. File names can also be left to the agent, or fixed once for the whole project through naming conventions in AGENTS.md, as shown below.
The slowest way to work with an agent is also the most supervised: every response is read, and the next instruction corrects or extends it.
Initial instruction
Summarize the director list and save it to outputs/director_summary.md.
Follow-up
The period column describes service at the firm, not the lifespan. Make that distinction explicit.
Further instruction
Add a section on the information needed to distinguish directors with similar names.
This mode shows what the agent does and which instructions it lacks. Writing those instructions down lets later runs proceed with less supervision:
Interactive session
Persistent instructions
Saved prompts
Delegated research
An AGENTS.md file in the project folder holds instructions that apply to every session. Claude Code, Codex, and most other coding agents read it automatically when they start.
AGENTS.md
# Director research
## Commands
Run scripts with Python. Convert documents with `anydoc`.
## Project layout
sources/ holds original documents; never edit them.
code/ holds scripts; outputs/ holds results.
Name files in lowercase snake_case: outputs/firm_counts.csv
## Code and writing style
Run scripts from the project root with relative paths.
Write short functions with docstrings; use pandas for tables.
Write notes in full sentences and state uncertainty.
## Evidence
Compare names, dates, places, and family.
Save source URLs, identity decisions, and unresolved cases.The file typically records the commands available in the project, the project layout, naming conventions for new files, the style of code and notes, and the evidence standard.
With the layout and naming conventions in place, a prompt no longer has to name every output file: the agent places and names new files itself, consistently across sessions.
Corrections that had to be repeated in interactive sessions belong here.
A task that recurs, such as summarizing a list each time it is updated, can be saved as a plain text file and reused. The following file contains the same instruction as before:
prompts/summarize.md
The prompt refers to the list in directors.csv. Its first rows are:
directors.csv (first rows)
name,birth_year,firm,role
Frederik Hendrik Fentener van Vlissingen,1849,SHV,director
Gerard Philips,1858,Philips,director
Samuel van den Bergh,1864,Van den Bergh,director
Ernst Heldring,1871,KNSM,director
The next slide runs this prompt file without opening an interactive session.
Claude Code and Codex both have a non-interactive mode. The agent reads the prompt, carries out the task, writes its files, and exits.
Claude Code
Here -p runs the agent without an interactive session, < supplies the contents of the prompt file, and > saves the agent’s final message to a file.
Codex
The summary itself is written to outputs/director_summary.md, as the prompt requests. The command can be rerun whenever the list changes; permissions must be granted in advance, because no one is present to approve them.
The file directors.csv lists company directors. For each director, the aim is to identify the parents, spouse, and children, and then to establish with which firms those relatives were affiliated, in which role, and in which period. The result is a dataset with one row per relative and affiliation, each linked to its source.
The worked example is one director from the list: Frederik Hendrik Fentener van Vlissingen, born in 1849, director of SHV.
Four steps followed in this task
1. Find the director in the civil records
2. Retrieve the family
3. Research the relatives’ affiliations
4. Combine into a dataset
Steps 1 and 2 rely on Dutch civil records, Step 3 on web search.
Director and family details. Workflow adapted from the genealogical research in my papers.
Since 1811, Dutch municipalities have registered births, marriages, and deaths in the civil registry (burgerlijke stand). Each certificate names several people in fixed roles:
| Record | People named on the certificate |
|---|---|
| Birth | The child, the father, and the mother |
| Marriage | Groom and bride, with ages and birthplaces, and the parents of both |
| Death | The deceased, the parents, and the spouse |
OpenArchieven indexes these certificates from Dutch regional archives and makes them searchable by name. It also offers an API: a web address that returns search results as structured data (JSON) instead of a web page, so that a Python script can query it.
An agent proceeds as a researcher would: search the director’s name, open his marriage record for parents and spouse, then find records naming him as father.
An agent can search the web much as a researcher uses Google: it submits a query, reads the list of results, opens the pages that look relevant, and extracts names, dates, and roles from them. Two tools make this possible.
Web search takes a query and returns a list of results: titles, URLs, and short snippets. Results are leads.
Web fetch takes a URL, downloads the page, and returns it as text or Markdown for the model to read. Some sites block automated access.
Tool calls for the example director:
WebSearch "Fentener van Vlissingen" 1849 SHV
→ genealogieonline.nl, huygens.knaw.nl, ...
WebFetch genealogieonline.nl/.../I11117.php
→ spouse, marriage date, children
WebFetch resources.huygens.knaw.nl/.../fentener
→ the son's firms and roles
Search and fetch suit open-ended exploration, where the next query depends on what the last page said. Where a source offers an API, as OpenArchieven does, a Python script retrieves structured records more reliably. Step 1 does that.
Step 1 of 4
The agent writes a short Python script that searches OpenArchieven for the director’s full name, runs it, and reads the results.
The search returns 71 person mentions, not 71 people. The son has the identical name (1882–1962), and the director also appears as father in the son’s death record.
The agent compares birth dates, roles, and spouses. The 1879 marriage record names a groom born on 21 September 1849, matching the data file; this record becomes the anchor for the family.
OpenArchieven API query, retrieved 30 September 2026; 1879 marriage record.
Step 2 of 4
Once the director is identified, the agent continues without further prompts. It reads the parents and spouse from the marriage record, searches for records that name the director as father, checks each candidate against names and dates, and then moves to the next director on the list. This is the agent loop from the first section, applied to archival research.
Read the roles recorded on the marriage certificate
Simon Paulus + Anna Maria Veldhuijs
↓ parents
F.H., born 1849 + Lijntje Roosenburg
↓ son
F.H. (Frits), born 1882
Step 3 of 4
For each relative found in Step 2, the agent searches the web for firm affiliations. The task is written once as a template; for the director’s son:
Find the firms with which F.H. Fentener van Vlissingen (born 1882) was affiliated. Record each firm, role, and period with its source.
Searches: name variants with firms and roles
"Frits Fentener van Vlissingen" commissaris
"F.H. Fentener van Vlissingen" Hoogovens
"Frederik Hendrik Fentener van Vlissingen" KLM
Extracted from a biography of the son
Hoogovens: delegated supervisory director, 1918–1945; chairman of the supervisory board, 1945–1953.
New firms, such as KLM, become the next queries. The step repeats for every relative.
Step 4 of 4
Python combines the family links (Step 2) and affiliations (Step 3) into outputs/relatives.csv: one row per relative and affiliation, each with its source.
| Relative | Relation | Born | Firm | Role | Period |
|---|---|---|---|---|---|
| S.P. Fentener van Vlissingen | Father | not yet established | |||
| Anna Maria Veldhuijs | Mother | not yet established | |||
| Lijntje Roosenburg | Spouse | 1860 | not yet established | ||
| F.H. Fentener van Vlissingen | Son | 1882 | Hoogovens | Delegated supervisory director | 1918–45 |
| F.H. Fentener van Vlissingen | Son | 1882 | Hoogovens | Chairman, supervisory board | 1945–53 |
Open rows mark research still to be done. Identity decisions and open questions are saved in identities.md and unresolved.md.
Family: 1879 marriage, 1962 death. Affiliations: Biografisch Woordenboek van Nederland.
The four steps of the previous section can be written down once, as a prompt file that takes a single row of directors.csv as its input. The agent that receives it chooses its own queries and follow-up actions.
prompts/research_director.md
You are given one row of directors.csv: name, birth year, and firm.
1. Find the director in OpenArchieven; distinguish namesakes by birth date and spouse.
2. Retrieve the parents, spouse, and children from the civil records.
3. Search the web for the firms, roles, and periods of each relative.
4. Save outputs/directors/<name-birthyear>/relatives.csv, one row per relative
and affiliation with its source URL, and list open questions in unresolved.md.The file is identical for every director on the list; only the row it receives changes. Each director’s results are saved in a separate folder.
A subagent is an agent started by the main agent, with its own conversation and tool calls. The main agent can start one for every row of the list.
Read directors.csv. For each row, spawn a subagent with that row and the instructions in prompts/research_director.md. Run them in parallel; each writes only to its own folder under outputs/directors/. Wait for all of them, then report which finished and which left questions open.
Main agent reads directors.csv
Fentener van Vlissingen, 1849
Philips, 1858
Van den Bergh, 1864
Heldring, 1871
Main agent collects the results
Read each director’s relatives.csv and unresolved.md. Check that the identities and sources support the rows, and send a subagent back where evidence is missing. Use Python to combine the reviewed rows into outputs/relatives.csv and the open questions into outputs/unresolved.md.
outputs/
├── directors/
│ ├── fentener-van-vlissingen-1849/
│ ├── philips-1858/
│ ├── van-den-bergh-1864/
│ └── heldring-1871/
├── relatives.csv
└── unresolved.md
The combined file has the columns of the dataset from Step 4, with an added column naming the director. The main agent checks and combines; the researcher decides whether the evidence is sufficient for the analysis.
The workflow developed for one director thus extends to the whole list. Saved prompts, code, sources, and open questions keep each agent’s work inspectable.
Coding Agents for Historical Research