9plan MCP Server: Implementation Design Document #
Version: 1.2.0
Date: January 2026
Status: Draft
Table of Contents #
- Executive Summary
- Architecture Overview
- Technology Stack
- Project Structure
- Data Model
- Tool Specifications
- Prompt Specifications
- File Formats
- Logging
- Error Handling
- Configuration
- Dependency Injection & Testability
- Implementation Phases
- Known Limitations
1. Executive Summary #
1.1 Purpose #
9plan is an MCP server that provides a session-scoped work queue for long time-horizon agent task sequencing. It enables AI agents to decompose complex tasks into discrete, self-contained plans that can be executed across conversation boundaries—even surviving context compaction.
1.2 Core Value Proposition #
| Challenge | 9plan Solution |
|---|---|
| Agents lose context during long tasks | Persistent sessions with three-word identifiers for easy resumption |
| Complex tasks require decomposition | Structured plans with explicit inputs/outputs for dependency tracking |
| Work ordering is ambiguous | Single queue with front/back positioning semantics |
| Progress is lost on interruption | Plan files serve as checkpoints; notes preserve state |
1.3 Design Principles #
- Self-Contained Plans: Each plan contains enough context for an agent to execute without asking clarifying questions
- Semantic Dependencies: Cross-plan dependencies are resolved by searching history, not by ID references
- Single Queue Discipline: No priority tiers or backlogs—one queue that must eventually empty
- Hybrid Storage: SQLite for structural integrity, flat files for agent-accessible content
- Session Breadcrumbs: Tool responses include session names to aid context recovery
2. Architecture Overview #
2.1 High-Level Architecture #
┌─────────────────────────────────────────────────────────────────┐
│ MCP Client │
│ (Claude, Cursor, VS Code, etc.) │
└─────────────────────────┬───────────────────────────────────────┘
│ stdio / HTTP
▼
┌─────────────────────────────────────────────────────────────────┐
│ 9plan MCP Server │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────────┐ │
│ │ Tools │ │ Prompts │ │ Server Instructions │ │
│ │ (12 total) │ │ (bootstrap) │ │ (plan format spec) │ │
│ └──────┬──────┘ └─────────────┘ └─────────────────────────┘ │
│ │ │
│ ┌──────▼──────────────────────────────────────────────────┐ │
│ │ Core Services │ │
│ │ ┌────────────┐ ┌────────────┐ ┌────────────────────┐ │ │
│ │ │ Session │ │ Queue │ │ History │ │ │
│ │ │ Manager │ │ Manager │ │ Search (FTS5) │ │ │
│ │ └────────────┘ └────────────┘ └────────────────────┘ │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────────▼──────────────────────────────┐ │
│ │ Storage Layer │ │
│ │ SQLite (session.db) + Plan Files (plans/*.txt) │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
2.2 Plan Lifecycle State Machine #
┌──────────────────┐
│ Created │
│ (queued) │
└────────┬─────────┘
│ 9plan_queue_pull
▼
┌──────────────────┐
┌──────────│ Active │──────────┐
│ └──────────────────┘ │
│ │ │
9plan_plan_defer 9plan_plan_complete 9plan_plan_discard
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌──────────────────┐ ┌──────────────┐
│ Queued │ │ Completed │ │ Discarded │
│ (deferred) │ │ (in history) │ │ (deleted) │
└──────────────┘ └──────────────────┘ └──────────────┘
2.3 Key Architectural Decisions #
| Decision | Rationale |
|---|---|
| SQLite + flat files | SQLite protects queue integrity; flat files allow agent filesystem access |
| stdio transport | Most MCP clients spawn servers via stdio; HTTP can be added later |
| Single queue (no priorities) | Prevents agents from abandoning low-priority work |
| Three-word session names | Human-memorable, easy to communicate |
| Alphanumeric plan IDs | Short (5 chars), collision-resistant |
| FTS5 for history search | Built into SQLite, semantic search over completed plans |
3. Technology Stack #
3.1 Core Dependencies #
| Component | Technology | Version | Rationale |
|---|---|---|---|
| Runtime | Node.js | ≥22.5.0 | Required for built-in node:sqlite |
| MCP Framework | @modelcontextprotocol/sdk | ^1.22.0 | Official SDK |
| MCP Transport | @karashiiro/mcp | ^1.0.0 | Handles stdio + HTTP with sessions |
| Schema Validation | Zod | ^3.25.0 | Runtime validation, env validation |
| Database | node:sqlite | built-in | Zero deps, FTS5 support |
| Session Names | unique-names-generator | ^4.7.1 | Pre-built dictionaries |
| Plan IDs | nanoid | ^5.1.6 | Cryptographically secure, custom alphabets |
| App Directories | env-paths | ^3.0.0 | Cross-platform standard directories |
| Logging | pino | ^9.0.0 | Ultra-fast structured JSON logging |
3.2 Optional Dependencies (HTTP Transport) #
| Component | Technology | Version |
|---|---|---|
| Web Framework | hono | ^4.0.0 |
| Node Adapter | @hono/node-server | ^1.0.0 |
3.3 Development Dependencies #
| Component | Technology |
|---|---|
| Build | TypeScript ^5.8.0 |
| Dev Runner | tsx |
| Testing | Vitest |
| Log Formatting | pino-pretty |
3.4 package.json #
{
"name": "9plan-mcp-server",
"version": "1.0.0",
"type": "module",
"main": "dist/index.js",
"scripts": {
"build": "tsc",
"dev": "tsx watch src/index.ts",
"start": "node dist/index.js",
"test": "vitest"
},
"dependencies": {
"@karashiiro/mcp": "^1.0.0",
"@modelcontextprotocol/sdk": "^1.22.0",
"env-paths": "^3.0.0",
"nanoid": "^5.1.6",
"pino": "^9.0.0",
"unique-names-generator": "^4.7.1",
"zod": "^3.25.0"
},
"optionalDependencies": {
"hono": "^4.0.0",
"@hono/node-server": "^1.0.0"
},
"devDependencies": {
"@types/node": "^22.0.0",
"pino-pretty": "^11.0.0",
"tsx": "^4.0.0",
"typescript": "^5.8.0",
"vitest": "^3.0.0"
},
"engines": {
"node": ">=22.5.0"
}
}
4. Project Structure #
9plan-mcp-server/
├── src/
│ ├── index.ts # Entry point, transport setup
│ ├── server.ts # McpServer factory, tool/prompt registration
│ ├── types.ts # Core TypeScript interfaces
│ ├── env.ts # Environment config with Zod
│ ├── logger.ts # Pino logger configuration
│ │
│ ├── tools/
│ │ ├── index.ts # Tool registration aggregator
│ │ ├── session-create.ts
│ │ ├── session-resume.ts
│ │ ├── queue-add.ts
│ │ ├── queue-pull.ts
│ │ ├── plan-defer.ts
│ │ ├── plan-complete.ts
│ │ ├── plan-discard.ts
│ │ ├── history-search.ts
│ │ ├── history-get.ts
│ │ ├── admin-validate.ts # Invariant checking
│ │ ├── admin-sessions.ts # List all sessions
│ │ └── admin-state.ts # Detailed state dump
│ │
│ ├── prompts/
│ │ └── bootstrap.ts
│ │
│ ├── db/
│ │ ├── schema.sql
│ │ └── session-store.ts
│ │
│ ├── generators/
│ │ ├── session-name.ts # Uses unique-names-generator
│ │ └── plan-id.ts # Uses nanoid
│ │
│ └── files/
│ └── plan-files.ts
│
├── tests/
│ ├── unit/
│ │ ├── session-store.test.ts
│ │ ├── plan-files.test.ts
│ │ └── generators.test.ts
│ └── integration/
│ └── workflow.test.ts
│
├── package.json
├── tsconfig.json
├── vitest.config.ts
└── README.md
5. Data Model #
5.1 SQLite Schema #
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS sessions (
name TEXT PRIMARY KEY,
task_description TEXT,
created_at TEXT DEFAULT (datetime('now'))
) STRICT;
CREATE TABLE IF NOT EXISTS plans (
id TEXT PRIMARY KEY,
session_name TEXT NOT NULL REFERENCES sessions(name) ON DELETE CASCADE,
status TEXT NOT NULL CHECK (status IN ('queued', 'active', 'completed', 'discarded')),
queue_position INTEGER,
goal TEXT NOT NULL,
context TEXT,
inputs TEXT,
outputs TEXT,
approach TEXT,
success_criteria TEXT,
notes TEXT,
outcome TEXT,
created_at TEXT DEFAULT (datetime('now')),
completed_at TEXT
) STRICT;
CREATE INDEX IF NOT EXISTS idx_plans_queue
ON plans(session_name, status, queue_position)
WHERE status = 'queued';
CREATE INDEX IF NOT EXISTS idx_plans_active
ON plans(session_name, status)
WHERE status = 'active';
-- FTS5 for history search
CREATE VIRTUAL TABLE IF NOT EXISTS plans_fts USING fts5(
id, goal, context, inputs, outputs, outcome,
content='plans', content_rowid='rowid'
);
-- Triggers to keep FTS in sync
CREATE TRIGGER IF NOT EXISTS plans_ai AFTER INSERT ON plans BEGIN
INSERT INTO plans_fts(rowid, id, goal, context, inputs, outputs, outcome)
VALUES (new.rowid, new.id, new.goal, new.context, new.inputs, new.outputs, new.outcome);
END;
CREATE TRIGGER IF NOT EXISTS plans_ad AFTER DELETE ON plans BEGIN
INSERT INTO plans_fts(plans_fts, rowid, id, goal, context, inputs, outputs, outcome)
VALUES ('delete', old.rowid, old.id, old.goal, old.context, old.inputs, old.outputs, old.outcome);
END;
CREATE TRIGGER IF NOT EXISTS plans_au AFTER UPDATE ON plans BEGIN
INSERT INTO plans_fts(plans_fts, rowid, id, goal, context, inputs, outputs, outcome)
VALUES ('delete', old.rowid, old.id, old.goal, old.context, old.inputs, old.outputs, old.outcome);
INSERT INTO plans_fts(rowid, id, goal, context, inputs, outputs, outcome)
VALUES (new.rowid, new.id, new.goal, new.context, new.inputs, new.outputs, new.outcome);
END;
5.2 TypeScript Interfaces #
export type PlanStatus = 'queued' | 'active' | 'completed' | 'discarded';
export interface Session {
name: string;
taskDescription: string | null;
createdAt: string;
}
export interface Plan {
id: string;
sessionName: string;
status: PlanStatus;
queuePosition: number | null;
goal: string;
context: string | null;
inputs: string | null;
outputs: string | null;
approach: string | null;
successCriteria: string | null;
notes: string | null;
outcome: string | null;
createdAt: string;
completedAt: string | null;
}
export interface QueuedPlan {
id: string;
goal: string;
queuePosition: number;
}
export interface SessionState {
sessionName: string;
sessionPath: string;
taskDescription: string | null;
queue: QueuedPlan[];
activePlan: { id: string; filePath: string } | null;
completedCount: number;
}
export interface HistoryMatch {
id: string;
goal: string;
outcome: string | null;
relevanceScore: number;
}
6. Tool Specifications #
All tools use the prefix 9plan_ with snake_case.
6.1 Session Tools #
9plan_session_create #
Creates a new session with a randomly-generated three-word identifier.
Input: { task_description?: string }
Output: { session_name: string, session_path: string }
Behavior:
- Generate unique three-word name
- Create session directory with
session.dbandplans/ - Initialize SQLite with schema
- Return session name and path
9plan_session_resume #
Loads an existing session by name.
Input: { session_name: string } (format: word-word-word)
Output: Full session state including queue, active plan, and completed count.
6.2 Queue Tools #
9plan_queue_add #
Adds a new plan to the queue.
Input:
{
context: string, // Where this plan fits in overall task
goal: string, // What this plan accomplishes
approach: string, // How to accomplish the goal
success_criteria: string,
inputs?: string, // Dependencies from other plans
outputs?: string, // What this produces
position?: 'front' | 'back' // Default: 'back'
}
Output: { plan_id: string, plan_path: string, queue_position: number }
Position Semantics:
front: Blocking work that must complete firstback: Work that should happen eventually
9plan_queue_pull #
Removes the front plan from queue and marks it active.
Input: None
Output: { plan_id: string, plan_path: string, goal: string }
Errors:
PLAN_ALREADY_ACTIVE: Must complete/defer/discard current plan firstQUEUE_EMPTY: Task complete! 🎉
6.3 Plan Lifecycle Tools #
9plan_plan_defer #
Returns active plan to queue without completing it.
Input: { reason: string, position?: 'front' | 'back' }
Behavior: Appends reason to Notes section with timestamp, returns to queue.
Use Cases:
- Blocked: Can't proceed →
position: 'front' - Decomposition: Breaking into subplans →
position: 'back'
9plan_plan_complete #
Marks active plan as done.
Input: { outcome: string } — Summary including outputs produced
Behavior:
- Store outcome in database
- Index into FTS5 for history search
- Delete plan file
- Return queue status
9plan_plan_discard #
Abandons active plan without completing.
Input: { reason: string }
Behavior: Marks as discarded (NOT in searchable history), deletes file.
Use When: Plan is obsolete, unnecessary, or misconceived.
6.4 History Tools #
9plan_history_search #
Searches completed plans for outputs matching a query.
Input: { query: string, max_results?: number }
Output: Array of matches with { id, goal, outcome, relevance_rank }
Uses FTS5 MATCH with BM25 ranking across goal, context, inputs, outputs, outcome.
9plan_history_get #
Retrieves a specific completed plan by ID.
Input: { plan_id: string } (5 alphanumeric chars)
Output: Full plan content including outcome.
6.5 Admin Tools #
Admin tools support validation and debugging. Prefixed with admin_ to distinguish from core workflow tools.
9plan_admin_validate #
Checks session state against all invariants.
Input: None (uses current session)
Output:
{
valid: boolean,
invariants: {
single_active_plan: boolean,
queue_order_preserved: boolean,
files_match_database: boolean,
no_orphaned_files: boolean
},
issues: string[]
}
Use Case: Verify session consistency during debugging or validation testing.
9plan_admin_sessions #
Lists all sessions on the system.
Input: None
Output:
{
sessions: Array<{
name: string,
created: string,
task_description: string | null,
queue_length: number,
completed_count: number,
has_active_plan: boolean
}>
}
Use Case: Find existing sessions, clean up old ones, verify session creation.
9plan_admin_state #
Returns detailed state dump for current session.
Input: None (uses current session)
Output:
{
session_name: string,
task_description: string | null,
queue: Array<{ id: string, goal: string, position: number }>,
active_plan: { id: string, goal: string, file_path: string } | null,
completed_count: number,
plan_files: string[],
file_db_match: boolean
}
Use Case: Detailed inspection during validation or debugging.
7. Prompt Specifications #
bootstrap #
Guides initial session setup and planning decomposition.
Arguments: { task?: string }
Content:
You are helping the user set up a new 9plan session for a complex task.
WORKFLOW:
1. CLARIFY: Ask questions to understand scope, components, dependencies
2. CREATE SESSION: Use 9plan_session_create with task description
3. DECOMPOSE: Break into discrete, self-contained plans
4. CAPTURE DEPENDENCIES: Specify inputs/outputs for each plan
5. ENQUEUE: Add plans with 9plan_queue_add (respect dependency order)
6. START WORK: Pull first plan with 9plan_queue_pull
PLAN QUALITY CHECKLIST:
□ Goal is specific and measurable
□ Approach has concrete steps
□ Success criteria are observable
□ Inputs reference other plans by description
□ Outputs are specific enough to search for
NOTE: Adding A, B, C at 'front' gives [C, B, A] — add in reverse order if needed.
8. File Formats #
8.1 Plan File Format #
Location: ~/.9plan/sessions/{session-name}/plans/{plan-id}.txt
# Context
{Where this plan fits in the overall task}
# Goal
{What this plan accomplishes}
# Inputs
{Dependencies - format: "- description: source"}
# Outputs
{What this produces - format: "- description: details"}
# Approach
{Detailed, actionable steps}
# Success Criteria
{Observable completion conditions}
# Notes
{Working scratchpad - updated during execution}
9. Logging #
9.1 Logger Setup #
// src/logger.ts
import pino from 'pino';
import { env } from './env.js';
export const logger = process.env.NODE_ENV === 'development'
? pino({
level: env.NINEPLAN_LOG_LEVEL,
transport: {
target: 'pino-pretty',
options: { colorize: true, translateTime: 'SYS:standard' },
},
})
: pino({ level: env.NINEPLAN_LOG_LEVEL });
export type Logger = pino.Logger;
9.2 Session-Scoped Child Loggers #
export class SessionStore {
private log: Logger;
constructor(sessionName: string) {
this.log = logger.child({ session: sessionName });
}
addPlan(plan: Plan): void {
this.log.info({ planId: plan.id, goal: plan.goal }, 'Plan added');
}
}
9.3 Log Levels #
| Level | Usage |
|---|---|
debug |
SQL queries, queue state |
info |
Session created, plan pulled/completed |
warn |
Collision retry, missing optional field |
error |
DB error, file write failure |
10. Error Handling #
10.1 Error Categories #
| Category | Description |
|---|---|
SESSION_NOT_FOUND |
Session does not exist |
PLAN_NOT_FOUND |
Plan ID does not exist |
NO_ACTIVE_PLAN |
Operation requires active plan |
PLAN_ALREADY_ACTIVE |
Cannot pull; another plan is active |
QUEUE_EMPTY |
Queue is empty (task complete) |
VALIDATION_ERROR |
Input validation failed |
FILE_SYSTEM_ERROR |
Filesystem operation failed |
DATABASE_ERROR |
SQLite operation failed |
10.2 Error Response Format #
[Session: {name}] Error: {category}
{message}
Suggested action: {suggestion}
11. Configuration #
11.1 Environment Variables #
// src/env.ts
import { z } from 'zod';
import envPaths from 'env-paths';
import { join } from 'node:path';
const paths = envPaths('9plan', { suffix: '' });
const envSchema = z.object({
NINEPLAN_SESSIONS_PATH: z.string().default(join(paths.data, 'sessions')),
NINEPLAN_LOG_LEVEL: z.enum(['debug', 'info', 'warn', 'error']).default('info'),
NINEPLAN_TRANSPORT: z.enum(['stdio', 'http']).default('stdio'),
NINEPLAN_HTTP_PORT: z.coerce.number().default(8080),
NINEPLAN_HTTP_HOST: z.string().default('127.0.0.1'),
});
export const env = envSchema.parse(process.env);
11.2 Entry Point #
// src/index.ts
import { serveStdio } from "@karashiiro/mcp/stdio";
import { serveHttp } from "@karashiiro/mcp/http";
import { createServer } from "./server.js";
import { logger } from "./logger.js";
import { env } from "./env.js";
if (env.NINEPLAN_TRANSPORT === "http") {
const handle = await serveHttp(createServer, {
port: env.NINEPLAN_HTTP_PORT,
host: env.NINEPLAN_HTTP_HOST,
endpoint: "/mcp",
});
logger.info({ port: env.NINEPLAN_HTTP_PORT }, '9plan running (HTTP)');
process.on("SIGINT", async () => {
await handle.close();
process.exit(0);
});
} else {
logger.info('9plan running (stdio)');
await serveStdio(createServer);
}
11.3 MCP Client Configuration #
Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json):
{
"mcpServers": {
"9plan": {
"command": "node",
"args": ["/path/to/9plan-mcp-server/dist/index.js"]
}
}
}
12. Dependency Injection & Testability #
We use constructor injection for testability without a DI framework. This keeps the codebase simple while allowing full mockability in tests.
12.1 The Pattern #
All classes accept their dependencies via an options object in the constructor:
// src/types.ts - Dependency interfaces
export interface SessionStoreDeps {
logger: Logger;
generatePlanId: () => string;
planFiles: PlanFileHandler;
}
export interface PlanFileHandlerDeps {
logger: Logger;
}
12.2 Implementation Example #
// src/db/session-store.ts
import type { Logger } from '../logger.js';
import type { Plan, SessionStoreDeps } from '../types.js';
export class SessionStore {
private log: Logger;
private generatePlanId: () => string;
private planFiles: PlanFileHandler;
constructor(
private sessionName: string,
private sessionPath: string,
deps: SessionStoreDeps,
) {
this.log = deps.logger.child({ session: sessionName });
this.generatePlanId = deps.generatePlanId;
this.planFiles = deps.planFiles;
}
addPlan(input: PlanInput): Plan {
const id = this.generatePlanId();
this.log.info({ planId: id, goal: input.goal }, 'Adding plan');
// ... implementation
}
}
12.3 Production Wiring #
// src/container.ts - Simple factory functions, no framework needed
import { logger } from './logger.js';
import { SessionStore } from './db/session-store.js';
import { PlanFileHandler } from './files/plan-files.js';
import { generatePlanId } from './generators/plan-id.js';
export function createPlanFileHandler(sessionPath: string): PlanFileHandler {
return new PlanFileHandler(sessionPath, { logger });
}
export function createSessionStore(
sessionName: string,
sessionPath: string,
): SessionStore {
return new SessionStore(sessionName, sessionPath, {
logger,
generatePlanId,
planFiles: createPlanFileHandler(sessionPath),
});
}
12.4 Testing with Mocks #
// tests/unit/session-store.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { SessionStore } from '../../src/db/session-store.js';
describe('SessionStore', () => {
// Mock factory - creates fresh mocks for each test
const createMocks = () => ({
logger: {
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
child: vi.fn(() => ({
info: vi.fn(),
error: vi.fn(),
warn: vi.fn(),
debug: vi.fn(),
})),
},
generatePlanId: vi.fn(() => 'test1'),
planFiles: {
write: vi.fn(),
read: vi.fn(),
delete: vi.fn(),
exists: vi.fn(() => false),
},
});
it('should generate ID and log when adding plan', () => {
const mocks = createMocks();
const store = new SessionStore('test-session', '/tmp/test', mocks);
store.addPlan({
goal: 'Test the thing',
context: 'Testing context',
approach: 'Test approach',
successCriteria: 'Tests pass',
});
expect(mocks.generatePlanId).toHaveBeenCalled();
expect(mocks.logger.child).toHaveBeenCalledWith({ session: 'test-session' });
});
it('should write plan file when adding plan', () => {
const mocks = createMocks();
const store = new SessionStore('test-session', '/tmp/test', mocks);
store.addPlan({
goal: 'Test the thing',
context: 'Testing context',
approach: 'Test approach',
successCriteria: 'Tests pass',
});
expect(mocks.planFiles.write).toHaveBeenCalled();
});
it('should throw when plan file write fails', () => {
const mocks = createMocks();
mocks.planFiles.write.mockImplementation(() => {
throw new Error('Disk full');
});
const store = new SessionStore('test-session', '/tmp/test', mocks);
expect(() => store.addPlan({
goal: 'Test',
context: 'Context',
approach: 'Approach',
successCriteria: 'Criteria',
})).toThrow('Disk full');
});
});
12.5 Testing Tools #
Tools receive their dependencies through the session store:
// tests/unit/tools/queue-add.test.ts
import { describe, it, expect, vi } from 'vitest';
import { handleQueueAdd } from '../../../src/tools/queue-add.js';
describe('9plan_queue_add', () => {
it('should return plan ID and path on success', async () => {
const mockStore = {
addPlan: vi.fn(() => ({
id: 'abc12',
queuePosition: 1,
})),
getSessionPath: vi.fn(() => '/tmp/sessions/test-session'),
};
const result = await handleQueueAdd(mockStore, {
goal: 'Do something',
context: 'Context here',
approach: 'Step by step',
success_criteria: 'It works',
});
expect(result.plan_id).toBe('abc12');
expect(result.plan_path).toContain('abc12.txt');
});
});
12.6 Why Not a DI Container? #
| Aspect | Manual Injection | DI Container (inversify/tsyringe) |
|---|---|---|
| Testability | ✅ Full | ✅ Full |
| Lines of setup | ~20 | ~80+ |
| Extra dependencies | None | reflect-metadata, decorators |
| Decorator noise | None | @injectable() on every class |
| Type safety | ✅ Native TS | Requires Symbol tokens |
| Learning curve | None | Moderate |
For a project with <10 injectable services, manual wiring is simpler and equally testable.
13. Implementation Phases #
Phase 1: Foundation #
Phase 2: Session Tools #
Phase 3: Queue Tools #
Phase 4: Plan Lifecycle Tools #
Phase 5: History Tools #
Phase 6: Admin Tools #
Phase 7: Polish #
Phase 8: Agent Validation Infrastructure #
14. Known Limitations #
Front-Insertion Order Reversal #
Adding A, B, C at front gives [C, B, A, ...]. Add subplans in reverse order to achieve correct sequence.
Context Compaction Mid-Plan #
If compaction occurs while executing a plan, the agent loses session awareness. Session name is included in all tool responses to aid recovery.
Single Agent Per Session #
No concurrent access support—designed for single-agent workflows.
Appendix: Server Instructions #
The MCP server's instructions field:
9plan manages session-scoped work queues for agent task sequencing.
QUICK START:
1. Create session: 9plan_session_create
2. Add plans: 9plan_queue_add ('front' for blocking, 'back' for eventual)
3. Get work: 9plan_queue_pull
4. Execute the plan (read file for full context)
5. Complete: 9plan_plan_complete (or defer/discard)
6. Repeat until queue is empty
PLAN FILE FORMAT:
# Context, # Goal, # Inputs, # Outputs, # Approach, # Success Criteria, # Notes
DEPENDENCY RESOLUTION:
- Use 9plan_history_search to find outputs from completed plans
- Inputs reference plans by description, not ID
SESSION RECOVERY:
- All responses include [Session: name] prefix
- Resume with: 9plan_session_resume
End of Implementation Design Document