# How the Preprompts System Works in gpt-engineer: A Complete Guide to Customizing Agent Behavior

> Learn how gpt-engineer's preprompts system works to customize agent behavior. Use plain text files to define roles, philosophies, and formats, overriding defaults without code changes.

- Repository: [Anton Osika/gpt-engineer](https://github.com/AntonOsika/gpt-engineer)
- Tags: deep-dive
- Published: 2026-03-06

---

**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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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`](https://github.com/AntonOsika/gpt-engineer/blob/main/roadmap.txt) – Defines the high-level approach and planning strategy
- [`generate.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/generate.txt) – Contains instructions for initial code generation
- [`entrypoint.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/entrypoint.txt) – Specifies how to identify or create entry points
- [`philosophy.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/philosophy.txt) – Establishes coding standards and best practices
- [`improve.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/improve.txt) – Guides the code improvement and refactoring process
- [`file_format.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/file_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`](https://github.com/AntonOsika/gpt-engineer/blob/main/gpt_engineer/core/preprompts_holder.py), this class provides the primary interface for accessing preprompts:

```python
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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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:

```python

# 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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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`:

```python

# 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`:

```python

# 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`](https://github.com/AntonOsika/gpt-engineer/blob/main/philosophy.txt) or output format via [`file_format.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/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).

```python
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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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.

```python

# 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.

```python
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`](https://github.com/AntonOsika/gpt-engineer/blob/main/gpt_engineer/core/preprompts_holder.py)) loads these files into a dictionary, while **`SimpleAgent`** ([`gpt_engineer/core/default/simple_agent.py`](https://github.com/AntonOsika/gpt-engineer/blob/main/gpt_engineer/core/default/simple_agent.py)) injects the holder into every step.
- **Step functions** in [`gpt_engineer/core/default/steps.py`](https://github.com/AntonOsika/gpt-engineer/blob/main/gpt_engineer/core/default/steps.py) compose the final system prompt by concatenating specific preprompts (e.g., [`roadmap.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/roadmap.txt) + [`generate.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/generate.txt) + [`philosophy.txt`](https://github.com/AntonOsika/gpt-engineer/blob/main/philosophy.txt)).
- **Customization** requires no code changes—simply create a new directory with overridden `.txt` files and pass it to `PrepromptsHolder`, 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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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`](https://github.com/AntonOsika/gpt-engineer/blob/main/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.