Skip to content

Proposal: Making PAI Fully Extensible for Forking Without Core Modifications #128

@ruslan-kurchenko

Description

@ruslan-kurchenko

The Problem

First off, huge thanks to @danielmiessler for building PAI. It’s an incredible framework.

I ran into a bit of a snag, though. I want to fork PAI and tweak it for my own needs, but I also want to keep it in sync with your updates. Right now, adding my own methods or company info means editing the core files. That causes messy merge conflicts whenever I try to pull the latest changes from upstream.

What I’m Trying to Do

Here is the goal: I want to be able to fork PAI and add my own extensions—like skills, agents, or hooks—without touching the core files. I want to pull your updates cleanly with zero conflicts.

Ideally, I could personalize the system with my identity and work context, maybe using symlinks or submodules to combine the core PAI with my custom setup. The main requirement is that the core files stay generic and untouched.

A Real-World Example

To give you some context, here is what I’m dealing with at my job. I need to load specific knowledge, like my company’s tech stack or HIPAA requirements. I also follow a specific "Modular Blackbox" architecture that I want my agents to use.

I need an Engineer agent that enforces those architecture rules, and an Architect agent that follows our specific product requirements.

Crucially, this needs to be context-aware. If I’m in my work directory (~/Projects/k-health/), the system should auto-load that work context. If I call my custom engineer, it should use my methodology. But if I’m working on a personal project, I just want the standard, vanilla PAI.

Where It Gets Stuck

Currently, customizing an agent requires changing the core files. To get the Engineer agent to use my architecture rules, I have to edit .claude/agents/engineer.md to add my mandatory principles.

The problem is that as soon as PAI updates engineer.md upstream, I get a conflict. There isn't really a documented pattern yet for extending agents or handling this kind of sync workflow without breaking things.

A Proposed Solution

I’ve been experimenting with a pattern in my fork, and it’s working really well. Here is the breakdown:

1. Personalize with Environment Variables
Instead of hardcoding names in markdown files, we can use template variables like {{USER_NAME}} in the core files. Then, settings.json fills in those values. This keeps the identity file generic so everyone can use the same base file.

2. Load Skills via Hooks
I set up a system where skills load automatically based on where I am. I have a hook that checks my current directory. If it detects a work project, it loads the company skill file. No manual changes needed.

3. Use Custom Agent Variants
This is the big one. Instead of editing the core engineer.md, I keep it exactly as it comes from upstream. Then, I create a new file called john-engineer.md for my customizations. If I want standard behavior, I call the normal engineer. If I need my specific rules, I call john-engineer. This makes syncing upstream updates trivial because I never touched the original file.

How the Files Look

The structure ends up looking like this:

~/.claude/
├── agents/
│   ├── engineer.md           # Core (from upstream) - Never modified
│   ├── architect.md          # Core (from upstream) - Never modified
│   └── john-engineer.md      # Custom (my fork) - My methodology
│
├── skills/
│   ├── CORE/                 # Core (from upstream) - Generic templates
│   └── my-company/           # Custom (my fork) - Company context
│
└── hooks/
    ├── load-core-context.ts  # Core (from upstream) - Generic loading
    └── load-my-company.ts    # Custom (my fork) - Environment detection

With this setup, I can run git merge upstream/main and everything updates cleanly. My custom files stay separate, and your core improvements flow right in.

Why This Works

This approach keeps things tidy. Core files belong to upstream PAI, and custom files belong to the user. It makes syncing painless and allows for mix-and-match extensions. Plus, it’s a reusable pattern that any fork could adopt.

Questions for You

@danielmiessler, I’d love to hear your thoughts on this.

Does this align with how you see PAI evolving? Does it make sense to support custom agent variants like john-engineer.md, or do you prefer agents to be strictly upstream-controlled?

If you like this direction, should we formalize it? I could help write a guide on "How to Fork and Extend PAI" or create some scripts to help people generate custom agents.

How I Can Help

If this resonates with you, I’m happy to do the heavy lifting. I can document the pattern, create reference implementations for company-context skills, or write a migration guide. I’ve already got this running in my fork—environment variables, auto-loading context, and custom agents are all working smoothly.

Let me know if you want to see the code or discuss it further!


Example: Hook-Based Skill Loading

Here is the hook script ~/.claude/hooks/load-company-context.ts that detects the environment:

#!/usr/bin/env bun
import { readFileSync, existsSync } from 'fs';
import { execSync } from 'child_process';

function detectCompany(): { detected: boolean; reason: string } {
  const cwd = process.cwd();

  // Method 1: Directory path
  if (cwd.includes('/Projects/my-company/')) {
    return { detected: true, reason: 'Company project directory' };
  }

  // Method 2: Git remote
  try {
    const remote = execSync('git remote -v', { encoding: 'utf-8' });
    if (remote.includes('github.com/my-company/')) {
      return { detected: true, reason: 'Company repository' };
    }
  } catch {}

  return { detected: false, reason: 'Not company environment' };
}

async function main() {
  const detection = detectCompany();

  if (!detection.detected) {
    process.exit(0); // Silent exit if not detected
  }

  console.error(`🏢 Company context detected: ${detection.reason}`);

  const skillPath = '~/.claude/skills/my-company/SKILL.md';
  const content = readFileSync(skillPath, 'utf-8');

  // Inject company context
  console.log(`<system-reminder>
COMPANY CONTEXT (Auto-loaded)

${content}
</system-reminder>`);

  console.error('✅ Company context loaded');
  process.exit(0);
}

main();

And here is how I register it in settings.json so it runs automatically:

{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {"type": "command", "command": "${PAI_DIR}/hooks/load-core-context.ts"},
          {"type": "command", "command": "${PAI_DIR}/hooks/load-company-context.ts"}
        ]
      }
    ]
  }
}

Example: Custom Agent Variant

This is john-engineer.md. It imports the specific architecture skill before doing anything else.

---
name: john-engineer
description: Engineer with modular blackbox architecture methodology
model: sonnet
---

# John's Engineering Agent

You are an elite Principal Software Engineer who follows modular blackbox architecture principles.

## MANDATORY: Before ANY Implementation

1. **Load Architecture Methodology:**

read ~/.claude/skills/modular-blackbox-architecture/SKILL.md


2. **Apply These Principles:**
- Single-person ownership (modules fit in one person's head)
- Blackbox design (hide internals, expose APIs)
- Wrap external dependencies (NEVER call libraries directly)
- Future-proof APIs (design for 5 years, not MVP)

... [rest of your custom methodology]%

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions