Summary
Add a pre-processing pipeline that classifies user prompts (intent, domain, complexity) and rewrites them into a structured, optimized format before they reach the main agent graph.
Motivation
User prompts vary widely in quality, structure, and clarity. A classification + rewriting step would improve the LLM's ability to understand and act on user input by normalizing it into a consistent, optimized format. This acts as a prompt engineering pipeline step — a pre-processing layer between raw user input and the main agent graph.
Proposed Solution
Migrate from the original two-stage LLM pipeline to a subAgent-driven architecture. A single subAgent call performs both classification and rewriting inline, using an externalized system prompt. The pipeline is gated by options.promptRewrite (boolean).
Configuration
Zod schema (src/config/schemas.js):
export const AgentSchema = z.object({
recursionLimit: z.number().int().positive().default(1000),
autoContinueLimit: z.number().int().positive().default(1000),
nodeTimeout: z.number().int().positive().default(600000),
promptRewrite: z.boolean().default(false),
});
Defaults (DEFAULT_CONFIG):
agent: {
recursionLimit: 1000,
autoContinueLimit: 1000,
nodeTimeout: 600000,
promptRewrite: false,
},
YAML (config.yaml):
agent:
recursionLimit: 1000
autoContinueLimit: 1000
nodeTimeout: 600000
turnHashWindow: 20
turnBufferMax: 64
promptRewrite: false
Externalized Prompt
./prompts/PROMPT_REWRITE.md:
You are a prompt preprocessor. Given a user message, classify it and rewrite it into a clearer, more structured form.
Classify:
- intent: question | task | creative | analysis | debugging | conversation
- domain: coding | writing | analysis | research | general | system-admin
- complexity: simple | moderate | complex
- clarity: clear | ambiguous | vague
Rewrite rules:
1. Clarify ambiguous intent
2. Add missing context where inferable
3. Structure as: goal → constraints → expected output
4. Preserve original intent
Return ONLY valid JSON:
{
"rewritten": "the optimized prompt",
"metadata": { "intent": "...", "domain": "...", "complexity": "...", "clarity": "..." }
}
If you cannot improve the prompt, return the original in "rewritten" and null in "metadata".
Implementation
src/agent/react.js — top-level await to cache the prompt, then rewritePrompt():
import { readFile } from "node:fs/promises";
import { join } from "node:path";
import { subAgent } from "../tools/subAgent.js";
const PROMPT_REWRITE_PATH = join(process.cwd(), "prompts", "PROMPT_REWRITE.md");
let _rewritePromptText = "";
try {
_rewritePromptText = await readFile(PROMPT_REWRITE_PATH, "utf-8");
} catch {
// File missing — _rewritePromptText stays empty
}
/**
* Rewrite a user prompt via subAgent. Classifies and rewrites inline.
* If the prompt file was missing at startup, returns the original message (no-op).
* Errors propagate to callReactAgent's wrapper.
* @param {string} message
* @param {Object} options - callReactAgent options (promptRewrite and cwd decorated from config in index.js)
* @returns {Promise<{ rewritten: string; metadata: Object|null }>}
*/
export async function rewritePrompt(message, options) {
if (!options?.promptRewrite || !_rewritePromptText) {
return { rewritten: message, metadata: null };
}
const result = await subAgent({
delegate: `Rewrite this:\n\n${message}`,
context: _rewritePromptText,
cwd: options.cwd ?? process.cwd(),
timeout: 30000,
});
const parsed = JSON.parse(result);
if (parsed?.rewritten && typeof parsed.rewritten === "string") {
return { rewritten: parsed.rewritten, metadata: parsed.metadata || null };
}
return { rewritten: message, metadata: null };
}
Hook into callReactAgent (replace lines 158–161):
export async function callReactAgent(agent, message, config, systemPrompt, callback, options = {}) {
const { recursionLimit } = options;
let effectiveMessage = message;
try {
const rewritten = await rewritePrompt(message, options);
effectiveMessage = rewritten.rewritten;
} catch { /* pass through */ }
let messages = [new HumanMessage(effectiveMessage)];
if (systemPrompt) {
const isNewThread = config?.configurable?.isNewThread ?? true;
if (isNewThread) {
messages.unshift(new SystemMessage(systemPrompt));
}
}
// ... rest unchanged
Routing Logic
User Input
│
▼
!options?.promptRewrite || !_rewritePromptText? ──Yes──→ Pass through (no-op)
│No
▼
subAgent(delegate: "Rewrite this:\n\n<message>", context: _rewritePromptText)
│
├── Success (valid JSON) → use rewritten
├── Timeout/Error → caught by callReactAgent wrapper → pass through original
└── Malformed JSON → JSON.parse throws → caught by callReactAgent wrapper → pass through original
│
▼
HumanMessage(effectiveMessage) → Agent Graph
Tests
tests/unit/agent/rewrite_prompt.test.js:
- Disabled pipeline → returns original message
- Prompt file missing → returns original message
- Successful rewrite → returns rewritten message + metadata
- Malformed JSON → returns original message
- No-op path (disabled + missing file)
Alternatives Considered
- Direct prompt passing (current) — Send raw user input directly to the agent graph. Simpler but less reliable for ambiguous or poorly structured prompts.
- Single LLM call — Combine classification and rewriting into one call. Faster but potentially less accurate on each sub-task.
- Skill-based delegation — Use a
prompt-rewrite skill. Rejected in favor of inline subAgent delegation with externalized prompt file.
OpenSpec Note
This project uses OpenSpec for feature development. If this request is approved, I will:
- Run
/opsx:propose to generate a full proposal with specs and tasks
- Iterate on the design before any code is written
- Follow the task-driven implementation workflow
Additional Context
This is a prompt engineering pipeline step. The classification and rewriting logic should be designed as a composable module that can be toggled on/off via configuration.
Audit Findings
src/agent/react.js:158 — callReactAgent() is the primary integration point. The raw user message string is received as the message parameter and immediately wrapped in new HumanMessage(message) at line 161. The pipeline should intercept here, before the HumanMessage construction.
src/tui/app.js:573 — handleChat() is the TUI entry point where raw user text is received. It passes text to dispatchProvider(). This is a secondary integration point if the pipeline needs to be TUI-aware (e.g., for UI feedback during classification/rewriting).
src/agent/react.js:98 — createReactAgent() wraps LangGraph's createReactAgentGraph. The pipeline could also be integrated as a pre-built node in the LangGraph pipeline, though intercepting at callReactAgent is simpler.
- Configuration — The pipeline should be gated by a config flag (
agent.promptRewrite) so it can be toggled on/off without code changes. Existing config is loaded via src/config/loader.js.
- No existing prompt preprocessing — The codebase currently passes user input directly to the agent with no intermediate transformation. This is a greenfield feature with no legacy code to refactor.
Summary
Add a pre-processing pipeline that classifies user prompts (intent, domain, complexity) and rewrites them into a structured, optimized format before they reach the main agent graph.
Motivation
User prompts vary widely in quality, structure, and clarity. A classification + rewriting step would improve the LLM's ability to understand and act on user input by normalizing it into a consistent, optimized format. This acts as a prompt engineering pipeline step — a pre-processing layer between raw user input and the main agent graph.
Proposed Solution
Migrate from the original two-stage LLM pipeline to a subAgent-driven architecture. A single subAgent call performs both classification and rewriting inline, using an externalized system prompt. The pipeline is gated by
options.promptRewrite(boolean).Configuration
Zod schema (
src/config/schemas.js):Defaults (
DEFAULT_CONFIG):YAML (
config.yaml):Externalized Prompt
./prompts/PROMPT_REWRITE.md:Implementation
src/agent/react.js— top-level await to cache the prompt, thenrewritePrompt():Hook into
callReactAgent(replace lines 158–161):Routing Logic
Tests
tests/unit/agent/rewrite_prompt.test.js:Alternatives Considered
prompt-rewriteskill. Rejected in favor of inline subAgent delegation with externalized prompt file.OpenSpec Note
This project uses OpenSpec for feature development. If this request is approved, I will:
/opsx:proposeto generate a full proposal with specs and tasksAdditional Context
This is a prompt engineering pipeline step. The classification and rewriting logic should be designed as a composable module that can be toggled on/off via configuration.
Audit Findings
src/agent/react.js:158—callReactAgent()is the primary integration point. The raw user message string is received as themessageparameter and immediately wrapped innew HumanMessage(message)at line 161. The pipeline should intercept here, before theHumanMessageconstruction.src/tui/app.js:573—handleChat()is the TUI entry point where raw user text is received. It passestexttodispatchProvider(). This is a secondary integration point if the pipeline needs to be TUI-aware (e.g., for UI feedback during classification/rewriting).src/agent/react.js:98—createReactAgent()wraps LangGraph'screateReactAgentGraph. The pipeline could also be integrated as a pre-built node in the LangGraph pipeline, though intercepting atcallReactAgentis simpler.agent.promptRewrite) so it can be toggled on/off without code changes. Existing config is loaded viasrc/config/loader.js.