Talk is a lightweight, batteries-included orchestration engine that lets multiple LLM-powered agents collaborate on a codebase autonomously.
It was inspired by systems like Aider but rebuilt around a compositional “blackboard” pattern for maximum transparency and hackability.
- Blackboard pattern – every agent writes its output to a shared, structured datastore. Nothing is hidden in “agent-to-agent” chats.
- Specialised agents – minimal, single-responsibility agents (CodeAgent, FileAgent, TestAgent) do one thing well.
- Deterministic workflow – an explicit
PlanRunnerexecutes a graph of Steps so you can see exactly what happens and when. - Filesystem-first – Talk works on plain files, no remote sandboxes or proprietary formats.
┌────────────┐ write ┌──────────────┐ read
│ CodeAgent │ ───────────────▶ │ │◀─────────────┐
│ (LLM diff) │ │ Blackboard │ │
└────────────┘◀─────────────── │ (in-memory) │ write │
▲ read └──────────────┘ │
│ ┌──▼─────────┐
│ read / write │ TestAgent │
│ │ (pytest) │
┌─────▼───────┐ apply diff ┌────────────┐ └────────────┘
│ FileAgent │──────────────────────▶│ Files │
│ (patch) │ └────────────┘
└─────────────┘
- CodeAgent takes the task & current files → emits a unified diff.
- FileAgent applies the diff to disk (with automatic backups).
- TestAgent runs tests (
pytestby default) and reports structured results. - The PlanRunner decides which step comes next based on success / failure.
Everything each agent does is appended to the Blackboard (BlackboardEntry), giving you full provenance and an easy way to inspect or replay sessions.
- Autonomous code generation (LLM-driven diffs).
- Safe patch application with automatic file backups.
- Test execution with timeout & detailed parsing of results.
- Versioned working directories (
talk1/,talk2/, …) so nothing is overwritten. - Interactive or fully automatic mode.
- 30-minute default timeout guard.
- Works with any OpenAI-compatible LLM; configurable provider list out-of-the-box (OpenAI, Anthropic, Gemini, Perplexity, Fireworks, …).
IMPORTANT: The PYTHONPATH environment variable MUST be set to the Talk installation directory (usually ~/talk, but may differ based on installation location).
# Typical setup
export PYTHONPATH=~/talk
# Or wherever Talk is installed
export PYTHONPATH=/path/to/talkALL imports in the Talk codebase are relative to the PYTHONPATH root. This means:
# CORRECT - imports relative to ~/talk
from agent.agent import Agent
from special_agents.code_agent import CodeAgent
from tests.utilities.test_output_writer import TestOutputWriter
# WRONG - do not use relative imports or sys.path manipulation
import sys
sys.path.insert(0, '../../../') # Never do this!
from ..agent import Agent # Don't use relative importsCRITICAL:
- DO NOT set or modify
PYTHONPATHin code - DO NOT use
sys.path.insert()or other path manipulations - If
PYTHONPATHis not set correctly, ask the user to:- Set
PYTHONPATHto the Talk installation directory - Resume the conversation with
--resume
- Set
Example check:
import os
if 'PYTHONPATH' not in os.environ or not os.environ['PYTHONPATH'].endswith('talk'):
print("Please set PYTHONPATH to your Talk installation directory and --resume")
sys.exit(1)In general, do not create any new mocking or monkey patching code. Strive to build production ready code, and only fall back as a last resort and after requesting specific permission to do so.
Talk uses a thin Pydantic-powered settings layer (agent/settings.py).
Configuration values come from four sources — in order of precedence (highest → lowest):
- Runtime overrides – e.g.
CodeAgent(overrides={...}) - Environment variables – prefixed with
TALK_…(see below) - Global force override –
TALK_FORCE_MODELshort-circuits every other model field - Built-in defaults – provider=
google, model=gemini-1.5-flash
Built-in:
# agent/settings.py
class GoogleSettings(BaseSettings):
model_name: str = "gemini-1.5-flash"# change only Google provider’s default
export TALK_GOOGLE_MODEL_NAME="gemini-1.5-pro"
# override *all* providers everywhere
export TALK_FORCE_MODEL="gemini-2.0-flash"# one-off per run
python talk/talk.py --task "Add logging" --model gemini-1.5-proPriority order for the model used by CodeAgent:
--model CLI flag ▶ TALK_FORCE_MODEL ▶ TALK_GOOGLE_MODEL_NAME ▶ built-in default.
If you prefer OpenAI models:
export TALK_LLM_PROVIDER="openai"
export TALK_OPENAI_MODEL_NAME="gpt-4o-mini"Everything else (paths, logging cadence, debug flags) follows the same pattern:
TALK_<SECTION>_<FIELD> environment variables override the defaults.
see code/docs/INSTALL.md
python talk/talk.py --task "Add a Fibonacci function with tests"Talk will:
- Create a directory
talkN/ - Generate diffs
- Apply them
- Run tests until they pass or the timeout hits
python talk/talk.py -t "Refactor utils.py for PEP-8" -iYou’ll be prompted before each step and can inspect / edit files in another editor before continuing.
python talk/talk.py \
--task "Implement CLI argument parsing" \
--model gpt-4o-mini \
--dir ./my_project| Agent | Responsibility | Important Methods |
|---|---|---|
CodeAgent |
Prompt an LLM to output unified diffs | run(prompt_str) |
FileAgent |
Apply diffs, backup originals, list/read files | run(diff), list_files() |
TestAgent |
Run pytest/unittest, parse results | run("pytest"), discover_tests() |
All inherit from the generic Agent facade and therefore support switch_provider, conversation logging, and provenance IDs.
The Agent contract is prompt in ==> completion out. That is it. Any other communication is done through the agent's tools. For example, a json could be sent by creating .talk/scratch/xyz.json, and then the model would have the filepath in its completion so the next agent can find the data. The scratch folder is dedicated to this type of "side effect" communication and should be used whenever an agent wants to create and send a file to another agent (subdirs to scratch and uuids used in the filenames should be used to prevent collisions). If a file already existed, or was created as part of a project or existing directory structure, the Agent should just reference that existing file and not make a copy of it in the scratch folder.
generate_code ─▶ apply_changes ─▶ run_tests ─▶ check_results
▲ │
└──────── (on failure) ◀─────┘
Implementation: four Step objects wired together; executed by runtime.plan_runner.PlanRunner.
The suite uses pytest + unittest.mock.
pytest -qCoverage:
pytest --cov=talk --cov-report=term-missing| Symptom | Fix |
|---|---|
patch: command not found |
Install patch and make sure it’s on PATH |
OPENAI_API_KEY missing |
Export the key in your shell or .env file |
| “Execution timed out” | Increase --timeout or break your task into smaller units |
| Diff fails to apply | Check conflicting local edits; run in -i mode and fix manually |
Enable verbose logging by setting env var:
export TALK_LOG_LEVEL=DEBUG- Fork the repo & create a branch.
- Run
pre-commit installto get black/ruff hooks. - Add or adjust tests for every new feature or bug-fix.
- Ensure
pytest && pytest -qpasses. - Submit a PR — include a concise description and reference any issues.
All contributions welcome: features, bug reports, docs, or just ideas!
Feel free to open a discussion if you’re unsure.
MIT – see LICENSE file for details.