How the Preprompts System Works in gpt-engineer: A Complete Guide to Customizing Agent Behavior
The preprompts system in gpt-engineer uses a directory of plain-text files to compose dynamic system prompts that define agent roles, coding philosophies, and output formats, allowing you to customize behavior by overriding specific files or entire directories without modifying core source code.
The preprompts subsystem is the mechanism gpt-engineer uses to give the LLM a stable "system-prompt" that defines its role, philosophy, file-format expectations and task-specific instructions. This article explains the architecture of the preprompts system and demonstrates how to customize agent behavior across different projects using the actual implementation from the AntonOsika/gpt-engineer repository.
What Is the Preprompts System?
At its core, the preprompts system separates prompt engineering from Python code. Instead of hard-coding system instructions in string literals throughout the codebase, gpt-engineer stores these instructions as plain text files in a dedicated directory. The PrepromptsHolder class loads these files into memory, and agent steps consume them to construct the final system prompt sent to the LLM.
This design allows you to modify agent behavior by editing text files rather than Python code, making it accessible to non-developers and enabling project-specific customizations without forking the repository.
Core Components of the Preprompts Architecture
The Preprompts Directory Structure
The repository defines a constant PREPROMPTS_PATH in gpt_engineer/core/default/paths.py that points to a top-level directory named preprompts. Each file in this directory contains a piece of the overall system prompt:
roadmap.txt– Defines the high-level approach and planning strategygenerate.txt– Contains instructions for initial code generationentrypoint.txt– Specifies how to identify or create entry pointsphilosophy.txt– Establishes coding standards and best practicesimprove.txt– Guides the code improvement and refactoring processfile_format.txt– Defines output formatting requirements
PrepromptsHolder and DiskMemory
PrepromptsHolder is a thin wrapper around DiskMemory that reads every file in the directory and returns a dictionary mapping filename → contents. Located in gpt_engineer/core/preprompts_holder.py, this class provides the primary interface for accessing preprompts:
from gpt_engineer.core.preprompts_holder import PrepromptsHolder
from pathlib import Path
holder = PrepromptsHolder(Path("preprompts"))
prompts = holder.get_preprompts() # Returns {"generate.txt": "...", "philosophy.txt": "...", ...}
The holder lazily loads files from disk, ensuring that modifications to the preprompts directory are reflected without restarting the Python process.
SimpleAgent Integration
SimpleAgent (the default agent implementation in gpt_engineer/core/default/simple_agent.py) receives a PrepromptsHolder instance during construction, defaulting to the global PREPROMPTS_PATH. The agent forwards this holder to every high-level step:
# From simple_agent.py lines 47-55
def __init__(
self,
path: Union[str, Path],
preprompts_holder: PrepromptsHolder = None,
# ... other args
):
self.preprompts_holder = preprompts_holder or PrepromptsHolder(PREPROMPTS_PATH)
This dependency injection pattern ensures that any step function can access the preprompts without hard-coding paths or loading logic.
How Preprompts Are Loaded and Composed
Step Functions and System Prompt Construction
Each step function in gpt_engineer/core/default/steps.py consumes the preprompts dictionary to construct task-specific system prompts:
Code Generation (setup_sys_prompt)
The setup_sys_prompt function concatenates roadmap, a formatted generate (with a placeholder replaced by the file_format prompt), and philosophy:
# From steps.py lines 75-94
def setup_sys_prompt(preprompts: Dict[str, str]) -> str:
return (
preprompts["roadmap"]
+ preprompts["generate"].replace("FILE_FORMAT", preprompts["file_format"])
+ preprompts["philosophy"]
)
Entry-Point Generation (gen_entrypoint)
The entrypoint step uses the entrypoint preprompt directly to instruct the LLM on how to identify or create the main execution script.
Improvement (setup_sys_prompt_existing_code)
When improving existing code, setup_sys_prompt_existing_code builds a prompt from roadmap, improve (substituting the diff format), and philosophy:
# From steps.py lines 97-117
def setup_sys_prompt_existing_code(preprompts: Dict[str, str]) -> str:
return (
preprompts["roadmap"]
+ preprompts["improve"].replace("FILE_FORMAT", preprompts["file_format"])
+ preprompts["philosophy"]
)
This modular composition allows you to modify specific aspects of the agent's behavior (e.g., coding style via philosophy.txt or output format via file_format.txt) without affecting other instructions.
How to Customize Agent Behavior Across Projects
Because the holder is just a path to a directory of plain-text files, you can tailor the agent in four distinct ways:
Method 1: Replace the Entire Preprompts Folder
Create a sibling directory my_preprompts/ next to the repository root and instantiate the agent with PrepromptsHolder(Path("my_preprompts")). This approach is ideal when you need completely different agent personalities for different project types (e.g., a "data-science" agent vs. a "web-dev" agent).
from pathlib import Path
from gpt_engineer.core.preprompts_holder import PrepromptsHolder
from gpt_engineer.core.default.simple_agent import SimpleAgent
# Use a custom preprompt directory
custom_holder = PrepromptsHolder(Path("my_preprompts"))
agent = SimpleAgent.with_default_config(
path="/tmp/project",
preprompts_holder=custom_holder
)
Method 2: Override Specific Files
Drop a file with the same name (e.g., generate.txt) into your custom folder. The holder will read it instead of the default one. This method allows surgical modifications—changing only the code generation instructions while keeping the default philosophy and roadmap.
Method 3: Runtime Programmatic Modification
Edit the dictionary returned by get_preprompts() before passing it to a step. This is useful for injecting dynamic, project-specific values that change between runs.
# Load defaults, then adjust a single entry
holder = PrepromptsHolder(Path("preprompts"))
prompts = holder.get_preprompts()
prompts["philosophy"] = "You are an expert in data-science pipelines. Follow best-practice conventions."
# Use these prompts when calling a step manually
files = gen_code(ai, prompt, memory, holder)
Method 4: Layering Multiple Directories
Combine multiple preprompt directories by merging their dictionaries. This allows you to maintain a base set of defaults while layering project-specific modifications on top.
from pathlib import Path
base = PrepromptsHolder(Path("preprompts"))
ext = PrepromptsHolder(Path("my_extra_preprompts"))
combined = {**base.get_preprompts(), **ext.get_preprompts()}
# Feed combined dict manually to a step
files = gen_code(ai, prompt, memory, lambda: combined)
Summary
- The preprompts system separates LLM instructions from Python code by storing system prompts as plain-text files in a
preprompts/directory. PrepromptsHolder(gpt_engineer/core/preprompts_holder.py) loads these files into a dictionary, whileSimpleAgent(gpt_engineer/core/default/simple_agent.py) injects the holder into every step.- Step functions in
gpt_engineer/core/default/steps.pycompose the final system prompt by concatenating specific preprompts (e.g.,roadmap.txt+generate.txt+philosophy.txt). - Customization requires no code changes—simply create a new directory with overridden
.txtfiles and pass it toPrepromptsHolder, or modify the dictionary at runtime for dynamic behavior.
Frequently Asked Questions
How do I change the coding style or philosophy of the agent?
Edit the philosophy.txt file in your custom preprompts directory. This file is concatenated into every system prompt via setup_sys_prompt in gpt_engineer/core/default/steps.py, affecting all code generation and improvement steps. You can specify conventions like "follow PEP 8," "prefer functional programming," or "optimize for readability over performance."
Can I use different preprompts for different projects without modifying the gpt-engineer source code?
Yes. The PrepromptsHolder accepts any Path object, allowing you to maintain project-specific preprompt directories outside the repository. Instantiate your agent with PrepromptsHolder(Path("my_project/preprompts")) to use custom instructions for that specific project while leaving the default gpt-engineer installation untouched.
What happens if my custom preprompts directory is missing a file that exists in the defaults?
The PrepromptsHolder only reads files present in the specified directory. If you pass a custom directory missing philosophy.txt, the holder will not fall back to defaults automatically—you must ensure your custom directory contains all required files, or merge dictionaries manually using the layering technique shown in Method 4 above.
How do I debug which preprompts are actually being sent to the LLM?
Inspect the dictionary returned by holder.get_preprompts() before passing it to step functions. You can print specific entries or iterate through all files to verify contents. For example: for name, content in holder.get_preprompts().items(): print(f"{name}: {content[:100]}..."). This helps verify that your customizations are loaded correctly before the LLM receives the composed system prompt.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →