Skip to content

AI Workflow

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:

  1. Message processing : mcpService.processToolInvocations() handles tool invocations
  2. Context budget : shouldSummarize() checks if messages exceed 70% of the usable context window
  3. Summarization (if triggered) — Earlier messages collapsed into a CHAT SUMMARY block
  4. Context selection : selectContext() picks relevant workspace files for injection
  5. Tool setup : mcpService.toolsWithoutExecute + stopWhen: isStepCount(maxLLMSteps)
  6. Skills & memory injection : Available skills and user memory injected into system prompt
  7. Stream text call : streamText() invokes the LLM with tools, context, and streaming params
  8. Response stream : writer.merge(result.toUIMessageStream({ sendReasoning: true }))
  9. Continuation : If finishReason === 'length', auto-continues (max 2 segments)
Context budget calculations
  • SYSTEM_PROMPT_RESERVE = 8192 tokens reserved for system prompt
  • SUMMARIZATION_THRESHOLD = 0.7 — triggers at 70% of usable budget
  • usableBudget = 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):

json
{ "type": "progress", "label": "summary|context|response", "status": "in-progress|complete", "order": 1, "message": "Analyzing workspace..." }

Annotation events sent via writeAnnotation(writer, annotation):

json
{ "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 StreamRecoveryManager monitors stream activity with:

    • Timeout: 45 seconds default
    • Max retries: 2 (configured in api.chat.ts)
    • Detection: Monitors _lastActivity on 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):

    1. Appends assistant message with partial content
    2. Adds synthetic user message: [Model: X] [Provider: Y] CONTINUE_PROMPT
    3. Calls streamText() again with remaining context
    4. Limited to MAX_RESPONSE_SEGMENTS = 2 continuation segments

    The SwitchableStream custom 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 ContextBuilder with labeled sections:
    • TASK — Current plan point description
    • PROJECT — Project metadata and constraints
    • SKILLS — Relevant skill instructions
    • TOOL RESULTS — Results from previous tool calls
    • WORKSPACE — Current file state
    • CONSTRAINTS — Project-specific limitations
    • USER REQUEST — Original user prompt
  • Checkpoints every N tool calls — resume from latest checkpoint after interruption
  • Uses AbortController for 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:

  1. Lint check : Runs ESLint or equivalent on modified files
  2. Type check : TypeScript compilation verification
  3. Flow validation : Ensures code flow is consistent (imports resolve, exports match)
  4. Context extraction : Results stored in ProjectContextVectorStore for 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:

SectionSourceContent
Identitynew-prompt.ts"You are Amplify, an expert AI assistant..."
<available_skills>SkillLoaderXML-tagged skill list: <skill name="id" description="desc"/>
<user_memory>memoryStoreBulleted memory list from localStorage
<user_context>UserProfileVectorStoreBM25-retrieved user preferences (500 token budget)
<project_context>ProjectContextVectorStoreBM25-retrieved project facts (1000 token budget)
<response_requirements>new-prompt.tsFormatting and structure rules
<technology_preferences>new-prompt.tsPreferred frameworks and patterns
<workspace_guardrails>workspaceHasProject()Prevents template re-injection for existing projects
<chat_naming>First message onlyOne-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_modifications tag name for inline edits.

Mutation signal pattern

When mutating native tools execute, they return JSON mutation signals that the browser applies to the WebContainer.

typescript
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:

  1. AI calls create_file or replace_string_in_file tool
  2. Tool returns amplify_file_mutation signal in its result
  3. Chat.client.tsx detects the signal via isFileMutationSignal(value)
  4. Signal parsed via parseFileMutationSignal(value)
  5. Mutations applied to the workbench file store
  6. 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:

ProviderModelsConfiguration
AnthropicClaude 3.7, Opus 4, Sonnet 4{ type: 'enabled', budgetTokens: >= 1024 }
AnthropicClaude 4.6+{ type: 'adaptive' } (no budget_tokens)
GoogleGemini 2.5thinkingBudget (0 = disabled) + includeThoughts
GoogleGemini 3.xthinkingLevel ('minimal'
OpenAIo1, o3, GPT-5reasoningEffort + reasoningSummary: 'auto'
xAIGrok 3reasoningEffort via providerOptions.openai
MistralMagistral`reasoningEffort: 'high'
DeepSeekReasonerAutomatic (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