Skip to content

Part 7: System Prompt & Runtime Policy ​

The system prompt is where ProtoAgent stops being "a model with tools" and becomes "this specific coding agent with this specific workflow." Instead of a static string, the prompt is generated at runtime — reflecting the actual working directory, project structure, and available tools.

What you are building ​

Starting from Part 6, you add:

  • src/system-prompt.ts — dynamic system prompt generator
  • Updated src/agentic-loop.ts — uses generateSystemPrompt() for the initializeMessages function
  • Updated src/App.tsx — calls initializeMessages() which now uses the generated prompt

Step 1: Create src/system-prompt.ts ​

Create the file:

bash
touch src/system-prompt.ts

This module builds the system prompt dynamically from the runtime environment:

  • Discovers the working directory and project name
  • Builds a filtered directory tree (depth 3, excludes noise)
  • Auto-generates tool descriptions from the tool registry schemas
  • Includes workflow guidelines for the model
typescript
// src/system-prompt.ts

import fs from 'node:fs/promises';
import path from 'node:path';
import { getAllTools } from './tools/index.js';

/** Build a filtered directory tree (depth 3, excludes noise). */
async function buildDirectoryTree(dirPath = '.', depth = 0, maxDepth = 3): Promise<string> {
  if (depth > maxDepth) return '';

  const indent = '  '.repeat(depth);
  let tree = '';

  try {
    const fullPath = path.resolve(dirPath);
    const entries = await fs.readdir(fullPath, { withFileTypes: true });

    const filtered = entries.filter((e) => {
      const n = e.name;
      return !n.startsWith('.') && !['node_modules', 'dist', 'build', 'coverage', '__pycache__', '.git'].includes(n) && !n.endsWith('.log');
    });

    for (const entry of filtered.slice(0, 20)) {
      if (entry.isDirectory()) {
        tree += `${indent}${entry.name}/\n`;
        tree += await buildDirectoryTree(path.join(dirPath, entry.name), depth + 1, maxDepth);
      } else {
        tree += `${indent}${entry.name}\n`;
      }
    }

    if (filtered.length > 20) {
      tree += `${indent}... (${filtered.length - 20} more)\n`;
    }
  } catch {
    // Can't read directory — skip
  }

  return tree;
}

/** Auto-generate tool descriptions from their JSON schemas. */
function generateToolDescriptions(): string {
  return getAllTools()
    .map((tool, i) => {
      const fn = tool.function;
      const params = fn.parameters as { required?: string[]; properties?: Record<string, any> };
      const required = params.required || [];
      const props = Object.keys(params.properties || {});
      const paramList = props
        .map((p) => `${p}${required.includes(p) ? ' (required)' : ' (optional)'}`)
        .join(', ');
      return `${i + 1}. ${fn.name} — ${fn.description}\n   Parameters: ${paramList || 'none'}`;
    })
    .join('\n\n');
}

/** Generate the complete system prompt. */
export async function generateSystemPrompt(): Promise<string> {
  const cwd = process.cwd();
  const projectName = path.basename(cwd);
  const tree = await buildDirectoryTree();
  const toolDescriptions = generateToolDescriptions();

  return `You are ProtoAgent, a coding assistant with file system and shell command capabilities.
Your job is to help the user complete coding tasks in their project.

PROJECT CONTEXT

Working Directory: ${cwd}
Project Name: ${projectName}

PROJECT STRUCTURE:
${tree}

AVAILABLE TOOLS

${toolDescriptions}

GUIDELINES

OUTPUT FORMAT:
- You are running in a terminal. Be concise. Optimise for scannability.
- Use **bold** for important terms, *italic* for references.
- Use flat bullet lists with emojis to communicate information densely (e.g. ✅ done, ❌ failed, 📁 file, 🔍 searching).
- NEVER use nested indentation. Keep all lists flat — one level only.

WORKFLOW:
- Before making tool calls, briefly explain what you're about to do and why.
- Always read files before editing them.
- Prefer edit_file over write_file for existing files.
- Use TODO tracking (todo_write / todo_read) by default for almost all work.
- Start by creating or refreshing the TODO list before doing substantive work, then keep it current throughout the task.
- Search first when you need to find something — use search_files or bash with grep/find.
- Shell commands: safe commands (ls, grep, git status, etc.) run automatically. Other commands require user approval.

FILE OPERATIONS:
- ALWAYS use read_file before editing to get exact content.
- NEVER write over existing files unless explicitly asked — use edit_file instead.
- Create parent directories before creating files in them.
- Use bash for package management, git, building, testing, etc.

IMPLEMENTATION STANDARDS:
- Thorough investigation: Before implementing, understand the existing codebase, patterns, and related systems.
- Completeness: Ensure implementations are complete and tested, not partial or left in a broken state.
- Code quality: Follow existing code style and conventions.`;
}

Step 2: Update src/agentic-loop.ts ​

The initializeMessages function now uses the generated system prompt instead of a hardcoded string.

typescript
// In src/agentic-loop.ts, update the initializeMessages function:

import { generateSystemPrompt } from './system-prompt.js';

export async function initializeMessages(): Promise<Message[]> {
  const systemPrompt = await generateSystemPrompt();
  return [{ role: 'system', content: systemPrompt }];
}

The rest of the agentic loop stays the same as Part 4. The only change is this one function.

Verification ​

bash
npm run dev

Ask the model to describe the project:

text
Explain the structure of this project.

You should see:

  • The model using file tools more deliberately
  • Answers that reflect the actual project structure (because the system prompt now includes the directory tree)
  • Tool descriptions that match the actual registered tools

Resulting snapshot ​

Your project should match protoagent-build-your-own-checkpoints/part-7.

Core takeaway ​

The system prompt is not static documentation. It is a runtime-generated contract between the app, the tool layer, and the model. When you add a tool, the prompt updates automatically. When you change the project structure, the tree updates. The model always sees an accurate picture of what it can do.

Built to teach how production-style coding agents actually work.