Amplify's AI workflow is a sophisticated pipeline that transforms your natural language prompt into working code through a multi-stage process: Prompt → Plan → Execute → Verify → Respond.
Chat pipeline
When you send a message in the chat, it goes through this pipeline:
- Message processing :
mcpService.processToolInvocations()handles tool invocations - Context budget :
shouldSummarize()checks if messages exceed 70% of the usable context window - Summarization (if triggered) — Earlier messages collapsed into a CHAT SUMMARY block
- Context selection :
selectContext()picks relevant workspace files for injection - Tool setup :
mcpService.toolsWithoutExecute+stopWhen: isStepCount(maxLLMSteps) - Skills & memory injection : Available skills and user memory injected into system prompt
- Stream text call :
streamText()invokes the LLM with tools, context, and streaming params - Response stream :
writer.merge(result.toUIMessageStream({ sendReasoning: true })) - Continuation : If
finishReason === 'length', auto-continues (max 2 segments)
Context budget calculations
SYSTEM_PROMPT_RESERVE = 8192tokens reserved for system promptSUMMARIZATION_THRESHOLD = 0.7— triggers at 70% of usable budgetusableBudget = maxTokenAllowed - maxCompletionTokens - SYSTEM_PROMPT_RESERVE- Last 3 messages kept verbatim; older messages summarized
SSE streaming
All chat responses use Server-Sent Events (SSE) via Vercel AI SDK v7's createUIMessageStream().
The streaming pipeline:
createUIMessageStream() → JsonToSseTransformStream → TextEncoderStream → HTTP response
Progress events sent via writeProgress(writer, annotation):
{ "type": "progress", "label": "summary|context|response", "status": "in-progress|complete", "order": 1, "message": "Analyzing workspace..." }
Annotation events sent via writeAnnotation(writer, annotation):
{ "type": "chatSummary", "summary": "...", "chatId": "..." }
{ "type": "codeContext", "files": ["src/App.tsx", "src/index.tsx"] }
{ "type": "usage", "value": { "completionTokens": 1200, "promptTokens": 5000, "totalTokens": 6200 } }
Stream recovery
The
StreamRecoveryManagermonitors stream activity with:- Timeout: 45 seconds default
- Max retries: 2 (configured in
api.chat.ts) - Detection: Monitors
_lastActivityon each stream part - Error recovery: Detects HTML error pages (502/503/504) and provides clean error messages
Continuation pattern
When the LLM hits
finishReason === 'length'(max output tokens reached):- Appends assistant message with partial content
- Adds synthetic user message:
[Model: X] [Provider: Y] CONTINUE_PROMPT - Calls
streamText()again with remaining context - Limited to
MAX_RESPONSE_SEGMENTS = 2continuation segments
The
SwitchableStreamcustom TransformStream enables switching source streams mid-stream without breaking the SSE connection.
Planning engine and sub-chat workers
For complex tasks, Amplify's planning engine breaks work into sub-chat workers that execute independently.
Plan endpoint (/api/plan):
- Uses GLM-4.7-Flash (Z.ai provider) with
PLANNER_SYSTEM_PROMPT - Returns enriched Task Contracts:
{ ok: true, plan }with PlanPoints - Each PlanPoint is a self-contained task with context, constraints, and expected output
Sub-chat engine (app/lib/planning/sub-chat-engine.ts):
- Each PlanPoint executed as an independent sub-chat
- Context built by
ContextBuilderwith labeled sections:TASK— Current plan point descriptionPROJECT— Project metadata and constraintsSKILLS— Relevant skill instructionsTOOL RESULTS— Results from previous tool callsWORKSPACE— Current file stateCONSTRAINTS— Project-specific limitationsUSER REQUEST— Original user prompt
- Checkpoints every N tool calls — resume from latest checkpoint after interruption
- Uses
AbortControllerfor cancellation support
Sub-chat isolation
Each sub-chat worker operates in isolation. It doesn't see other sub-chat conversations. This prevents context pollution and ensures each task focuses on its specific PlanPoint. Results from previous sub-chats are extracted into ProjectContextVectorStore and injected as project context.
FlowVerifier
After each sub-chat PlanPoint completes, the FlowVerifier validates the output:
- Lint check : Runs ESLint or equivalent on modified files
- Type check : TypeScript compilation verification
- Flow validation : Ensures code flow is consistent (imports resolve, exports match)
- Context extraction : Results stored in
ProjectContextVectorStorefor future sub-chats
If verification fails, the sub-chat can retry or the planning engine can adjust subsequent PlanPoints.
System prompt construction
The system prompt is built via PromptLibrary.getPropmtFromLibrary(promptId, options) and includes multiple dynamic sections:
| Section | Source | Content |
| Identity | new-prompt.ts | "You are Amplify, an expert AI assistant..." |
<available_skills> | SkillLoader | XML-tagged skill list: <skill name="id" description="desc"/> |
<user_memory> | memoryStore | Bulleted memory list from localStorage |
<user_context> | UserProfileVectorStore | BM25-retrieved user preferences (500 token budget) |
<project_context> | ProjectContextVectorStore | BM25-retrieved project facts (1000 token budget) |
<response_requirements> | new-prompt.ts | Formatting and structure rules |
<technology_preferences> | new-prompt.ts | Preferred frameworks and patterns |
<workspace_guardrails> | workspaceHasProject() | Prevents template re-injection for existing projects |
<chat_naming> | First message only | One-shot <chatname>X</chatname> tag |
XML-based action format
The AI uses an XML-based action format for code generation:
xml<amplifyArtifact> <amplifyAction type="file" filePath="src/App.tsx"> // File content here </amplifyAction> <amplifyAction type="shell"> npm install react-router-dom </amplifyAction> </amplifyArtifact>Modifications use the
amplify_file_modificationstag name for inline edits.
Mutation signal pattern
When mutating native tools execute, they return JSON mutation signals that the browser applies to the WebContainer.
interface FileMutationSignal {
type: 'amplify_file_mutation';
operations: FileMutationOperation[];
}
type FileMutationOperation =
| { op: 'create'; filePath: string; content: string }
| { op: 'replace'; filePath: string; oldString: string; newString: string }
| { op: 'multi_replace'; filePath: string; edits: Array<{ oldString: string; newString: string }> };
The flow:
- AI calls
create_fileorreplace_string_in_filetool - Tool returns
amplify_file_mutationsignal in its result Chat.client.tsxdetects the signal viaisFileMutationSignal(value)- Signal parsed via
parseFileMutationSignal(value) - Mutations applied to the workbench file store
- File store writes changes to WebContainer filesystem
Client-side only
Mutation signals are NOT applied server-side. The server only returns the signal — the client applies it. This ensures the WebContainer (which lives in the browser) stays in sync with the file store.
Thinking/reasoning configuration
Amplify supports extended thinking and reasoning for models that support it, configured per-provider in app/lib/.server/llm/thinking.ts:
| Provider | Models | Configuration |
| Anthropic | Claude 3.7, Opus 4, Sonnet 4 | { type: 'enabled', budgetTokens: >= 1024 } |
| Anthropic | Claude 4.6+ | { type: 'adaptive' } (no budget_tokens) |
| Gemini 2.5 | thinkingBudget (0 = disabled) + includeThoughts | |
| Gemini 3.x | thinkingLevel ('minimal' | |
| OpenAI | o1, o3, GPT-5 | reasoningEffort + reasoningSummary: 'auto' |
| xAI | Grok 3 | reasoningEffort via providerOptions.openai |
| Mistral | Magistral | `reasoningEffort: 'high' |
| DeepSeek | Reasoner | Automatic (no providerOptions needed) |
The isReasoningModel() regex pattern detects reasoning models: /^(o1|o3|gpt-5|gemini-2\.5|gemini-3|deepseek-r|qwen.*think|kimi-thinking|gemma)/i
Learn more about model configuration