agent-orchestrator/ARCHITECTURE.md

310 lines
6.9 KiB
Markdown

# Final Architecture Plan
## Core Principles
1. **Convention over configuration** - Auto-derive everything possible
2. **Single source of truth** - Config file in repo, runtime data in `~/.agent-orchestrator/`
3. **Zero path configuration** - All paths determined automatically
4. **Global uniqueness** - Hash-based namespacing prevents collisions
---
## 1. Directory Structure
```
Repo (versioned):
~/any/path/to/agent-orchestrator/
agent-orchestrator.yaml ← Config file (only this matters)
packages/
...
Runtime Data (not versioned):
~/.agent-orchestrator/ ← Single parent directory
a3b4c5d6e7f8-integrator/ ← {hash}-{projectId}
sessions/
int-1 ← Session metadata files (no hash prefix)
int-2
worktrees/
int-1/ ← Git worktrees (no hash prefix)
int-2/
archive/
int-3_2026-02-17T10-30-00
.origin ← Config path reference
a3b4c5d6e7f8-backend/ ← Same hash (same config!)
sessions/
be-1 ← No hash prefix (already namespaced)
worktrees/
be-1/
.origin
```
**Hash Derivation (from config location):**
```typescript
const configDir = path.dirname(configPath); // /Users/alice/code/agent-orchestrator
const hash = sha256(configDir).slice(0, 12); // a3b4c5d6e7f8
// Each project managed by this config gets a directory
// Format: {hash}-{projectId}
const projectId = path.basename(projectPath); // integrator, backend, etc.
const instanceId = `${hash}-${projectId}`; // a3b4c5d6e7f8-integrator
// Not configurable!
const projectBaseDir = `~/.agent-orchestrator/${instanceId}`;
const sessionsDir = `${projectBaseDir}/sessions`;
const worktreesDir = `${projectBaseDir}/worktrees`;
```
**Key insight:** All projects from the same config share the same hash prefix!
---
## 2. Config File (Minimal)
```yaml
# agent-orchestrator.yaml
projects:
- path: ~/repos/integrator # Required: where is the repo?
repo: ComposioHQ/integrator # Required: GitHub repo
defaultBranch: next # Required: base branch
# Optional overrides:
name: Composio Integrator # Display name (default: folder name)
sessionPrefix: int # Override auto-generated prefix
```
**Auto-derived:**
- Project ID: `basename(path)``integrator`
- Session prefix: `generatePrefix("integrator")``int`
- Worktree path: `{worktreeDir}/integrator/`
**That's it! No dataDir, no worktreeDir, no explicit IDs.**
---
## 3. Session Naming
### User-Facing Names (Elegant)
```
{sessionPrefix}-{num}
int-1, int-2 (integrator)
ao-1, ao-2 (agent-orchestrator)
ss-1, ss-2 (safe-split)
```
### Tmux Session Names (Globally Unique)
```
{hash}-{sessionPrefix}-{num}
a3b4c5d6e7f8-int-1
a3b4c5d6e7f8-ao-1
f1e2d3c4b5a6-int-1 (different checkout, no collision!)
```
### Prefix Generation (Clean Heuristic)
```typescript
function generateSessionPrefix(projectId: string): string {
if (projectId.length <= 4) return projectId.toLowerCase();
// CamelCase: PyTorch → pt
const uppercase = projectId.match(/[A-Z]/g);
if (uppercase?.length > 1) {
return uppercase.join("").toLowerCase();
}
// kebab-case: agent-orchestrator → ao
if (projectId.includes("-") || projectId.includes("_")) {
const sep = projectId.includes("-") ? "-" : "_";
return projectId
.split(sep)
.map((w) => w[0])
.join("")
.toLowerCase();
}
// Single word: integrator → int
return projectId.slice(0, 3).toLowerCase();
}
```
---
## 4. Metadata Storage
### File Structure (One Directory Per Project)
```
~/.agent-orchestrator/a3b4c5d6e7f8-integrator/
sessions/
int-1 ← Metadata file (user-facing session name)
int-2
worktrees/
int-1/
int-2/
archive/
int-3_2026-02-17T10-30-00
```
### Metadata File Format (key=value)
```
project=integrator
issue=INT-100
branch=feat/INT-100
status=working
tmuxName=a3b4c5d6e7f8-int-1
worktree=/Users/alice/.agent-orchestrator/a3b4c5d6e7f8-integrator/worktrees/int-1
createdAt=2026-02-17T10:30:00Z
pr=https://github.com/ComposioHQ/integrator/pull/123
```
**Key fields:**
- `project` - Which project this session belongs to (for filtering)
- `issue` - Linear/GitHub issue ID
- `branch` - Git branch name
- `worktree` - Path to git worktree
- `status` - working/idle/pr_open/merged
---
## 5. User Commands (Simple)
```bash
# List all sessions
ao list
# List sessions for specific project
ao list integrator
# Spawn new session
ao spawn integrator INT-100
# Attach to session (orchestrator finds tmux name)
ao attach int-1
# Kill session
ao kill int-1
# Show instance info
ao info
```
**No config paths in commands! Everything auto-discovered.**
---
## 6. Multi-Instance Support
### Same Config → Same Hash
```yaml
# ~/code/my-orchestrator/agent-orchestrator.yaml
projects:
- path: ~/repos/integrator
- path: ~/repos/backend
```
Results in:
```
~/.agent-orchestrator/
a3b4c5d6e7f8-integrator/ ← Same hash (same config)
a3b4c5d6e7f8-backend/ ← Same hash (same config)
```
### Different Config Locations → Different Hashes
```
~/code/orchestrator/ → hash: a3b4c5d6e7f8
~/code/orchestrator-v2/ → hash: f1e2d3c4b5a6
~/splitly-orchestrator/ → hash: 9876abcd5432
```
Results in:
```
~/.agent-orchestrator/
a3b4c5d6e7f8-integrator/ ← From ~/code/orchestrator
f1e2d3c4b5a6-integrator/ ← From ~/code/orchestrator-v2 (different checkout!)
9876abcd5432-safesplit/ ← From ~/splitly-orchestrator
# Sessions (no collisions):
a3b4c5d6e7f8-int-1 (main checkout)
f1e2d3c4b5a6-int-1 (v2 checkout)
9876abcd5432-ss-1 (splitly)
```
**Each orchestrator checkout gets unique hash. Projects within same config share that hash.**
---
## 7. Complete Example
```yaml
# ~/code/my-orchestrator/agent-orchestrator.yaml
projects:
- path: ~/repos/integrator
repo: ComposioHQ/integrator
defaultBranch: next
- path: ~/repos/backend
repo: ComposioHQ/backend
defaultBranch: main
sessionPrefix: be # Override auto-generated "bac"
```
**Results in:**
```
Config location:
~/code/my-orchestrator/
→ Hash: a3b4c5d6e7f8
Runtime data:
~/.agent-orchestrator/
a3b4c5d6e7f8-integrator/ ← Project 1
sessions/
int-1
worktrees/
int-1/
a3b4c5d6e7f8-backend/ ← Project 2 (same hash!)
sessions/
be-1
worktrees/
be-1/
Session names:
User-facing: int-1, be-1
Tmux: a3b4c5d6e7f8-int-1, a3b4c5d6e7f8-be-1
Commands:
ao spawn integrator INT-100
ao attach int-1
```
---
## Summary: What Users Configure
**Required (3 fields per project):**
1. `path` - Where is the repo?
2. `repo` - GitHub owner/repo
3. `defaultBranch` - Base branch name
**Optional:**
- `sessionPrefix` - Override auto-generated prefix
- `name` - Display name
**That's it! Everything else is automatic.**