Skip to content

Self modification

Self-modification is the advanced tier of agent native engineering: agents that can evolve their own code, prompts, and behavior. Not required for every app, but a big part of the future.

This is the logical extension of "whatever the developer can do, the agent can do."

Why Self-Modification?

Traditional software is static—it does what you wrote, nothing more. Self-modifying agents can:

  • Fix their own bugs - See an error, patch the code, restart
  • Add new capabilities - User asks for something new, agent implements it
  • Evolve behavior - Learn from feedback and adjust prompts
  • Deploy themselves - Push code, trigger builds, restart

The agent becomes a living system that improves over time, not frozen code.

What Self-Modification Enables

Code modification: - Read and understand source files - Write fixes and new features - Commit and push to version control - Trigger builds and verify they pass

Prompt evolution: - Edit the system prompt based on feedback - Add new features as prompt sections - Refine judgment criteria that aren't working

Infrastructure control: - Pull latest code from upstream - Merge from other branches/instances - Restart after changes - Roll back if something breaks

Site/output generation: - Generate and maintain websites - Create documentation - Build dashboards from data

Required Guardrails

Self-modification is powerful. It needs safety mechanisms.

Approval gates for code changes:

tool("write_file", async ({ path, content }) => {
  if (isCodeFile(path)) {
    // Store for approval, don't apply immediately
    pendingChanges.set(path, content);
    const diff = generateDiff(path, content);
    return { text: `Requires approval:\n\n${diff}\n\nReply "yes" to apply.` };
  }
  // Non-code files apply immediately
  writeFileSync(path, content);
  return { text: `Wrote ${path}` };
});

Auto-commit before changes:

tool("self_deploy", async () => {
  // Save current state first
  runGit("stash");  // or commit uncommitted changes

  // Then pull/merge
  runGit("fetch origin");
  runGit("merge origin/main --no-edit");

  // Build and verify
  runCommand("npm run build");

  // Only then restart
  scheduleRestart();
});

Build verification:

// Don't restart unless build passes
try {
  runCommand("npm run build", { timeout: 120000 });
} catch (error) {
  // Rollback the merge
  runGit("merge --abort");
  return { text: "Build failed, aborting deploy", isError: true };
}

Health checks after restart:

tool("health_check", async () => {
  const uptime = process.uptime();
  const buildValid = existsSync("dist/index.js");
  const gitClean = !runGit("status --porcelain");

  return {
    text: JSON.stringify({
      status: "healthy",
      uptime: `${Math.floor(uptime / 60)}m`,
      build: buildValid ? "valid" : "missing",
      git: gitClean ? "clean" : "uncommitted changes",
    }, null, 2),
  };
});

Git-Based Self-Modification

Use git as the foundation for self-modification. It provides: - Version history (rollback capability) - Branching (experiment safely) - Merge (sync with other instances) - Push/pull (deploy and collaborate)

Essential git tools:

tool("status", "Show git status", {}, ...);
tool("diff", "Show file changes", { path: z.string().optional() }, ...);
tool("log", "Show commit history", { count: z.number() }, ...);
tool("commit_code", "Commit code changes", { message: z.string() }, ...);
tool("git_push", "Push to GitHub", { branch: z.string().optional() }, ...);
tool("pull", "Pull from GitHub", { source: z.enum(["main", "instance"]) }, ...);
tool("rollback", "Revert recent commits", { commits: z.number() }, ...);

Multi-instance architecture:

main                      # Shared code
├── instance/bot-a       # Instance A's branch
├── instance/bot-b       # Instance B's branch
└── instance/bot-c       # Instance C's branch

Each instance can: - Pull updates from main - Push improvements back to main (via PR) - Sync features from other instances - Maintain instance-specific config

Self-Modifying Prompts

The system prompt is a file the agent can read and write.

// Agent can read its own prompt
tool("read_file", ...);  // Can read src/prompts/system.md

// Agent can propose changes
tool("write_file", ...);  // Can write to src/prompts/system.md (with approval)

System prompt as living document:

## Feedback Processing

When someone shares feedback:
1. Acknowledge warmly
2. Rate importance 1-5
3. Store using feedback tools

<!-- Note to self: Video walkthroughs should always be 4-5,
     learned this from Dan's feedback on 2024-12-07 -->

The agent can: - Add notes to itself - Refine judgment criteria - Add new feature sections - Document edge cases it learned

When to Implement Self-Modification

Good candidates: - Long-running autonomous agents - Agents that need to adapt to feedback - Systems where behavior evolution is valuable - Internal tools where rapid iteration matters

Not necessary for: - Simple single-task agents - Highly regulated environments - Systems where behavior must be auditable - One-off or short-lived agents

Start with a non-self-modifying prompt-native agent. Add self-modification when you need it.

Complete Self-Modification Toolset

const selfMcpServer = createSdkMcpServer({
  name: "self",
  version: "1.0.0",
  tools: [
    // FILE OPERATIONS
    tool("read_file", "Read any project file", { path: z.string() }, ...),
    tool("write_file", "Write a file (code requires approval)", { path, content }, ...),
    tool("list_files", "List directory contents", { path: z.string() }, ...),
    tool("search_code", "Search for patterns", { pattern: z.string() }, ...),

    // APPROVAL WORKFLOW
    tool("apply_pending", "Apply approved changes", {}, ...),
    tool("get_pending", "Show pending changes", {}, ...),
    tool("clear_pending", "Discard pending changes", {}, ...),

    // RESTART
    tool("restart", "Rebuild and restart", {}, ...),
    tool("health_check", "Check if bot is healthy", {}, ...),
  ],
});

const gitMcpServer = createSdkMcpServer({
  name: "git",
  version: "1.0.0",
  tools: [
    // STATUS
    tool("status", "Show git status", {}, ...),
    tool("diff", "Show changes", { path: z.string().optional() }, ...),
    tool("log", "Show history", { count: z.number() }, ...),

    // COMMIT & PUSH
    tool("commit_code", "Commit code changes", { message: z.string() }, ...),
    tool("git_push", "Push to GitHub", { branch: z.string().optional() }, ...),

    // SYNC
    tool("pull", "Pull from upstream", { source: z.enum(["main", "instance"]) }, ...),
    tool("self_deploy", "Pull, build, restart", { source: z.enum(["main", "instance"]) }, ...),

    // SAFETY
    tool("rollback", "Revert commits", { commits: z.number() }, ...),
    tool("health_check", "Detailed health report", {}, ...),
  ],
});

Self-Modification Checklist

Before enabling self-modification: - [ ] Git-based version control set up - [ ] Approval gates for code changes - [ ] Build verification before restart - [ ] Rollback mechanism available - [ ] Health check endpoint - [ ] Instance identity configured

When implementing: - [ ] Agent can read all project files - [ ] Agent can write files (with appropriate approval) - [ ] Agent can commit and push - [ ] Agent can pull updates - [ ] Agent can restart itself - [ ] Agent can roll back if needed