Back to all docs

Feature Design: Agent Mode Switching

Original Proposal

Add a left button with $ icon (depicting terminal command execution). When clicked, user can see icons for agents they can @ for a message. The agent can simply be Claude Code. That means if user switches from $ icon to Claude Code icon, whatever is put in the input box will be the input to Claude Code in non-interactive mode, in which Claude will reply in stdout like any other commands are executed.


Potential Issues & Better Approaches

Issue 1: Claude Code is already running (this conversation!)

Issue 2: Context & Session Management

Issue 3: Output Format Mismatch


Better Feature Design Options

┌─────────────────────────────────────────┐
│ [$] [🤖]     [Input box]         [Send]│
└─────────────────────────────────────────┘

$ mode (default):  Command execution
🤖 mode: AI chat (Claude/agents)

How it works:

  1. User toggles between $ (terminal) and 🤖 (AI chat)
  2. In $ mode: Backend executes commands via spawn
  3. In 🤖 mode: Backend routes to AI API (Claude API, local LLM, etc.)
  4. Messages render differently based on mode:
    • Terminal: Code block with stdout/stderr
    • AI: Conversational bubbles with markdown

Benefits:

Drawbacks:


Option B: @ Mentions (Like Slack/Discord)

Input: "@claude-code how do I fix this error?"
       OR
Input: "ls -la"  (no @, runs as command)

How it works:

  1. Parse input for @agent-name
  2. If detected → route to that agent
  3. No @ → execute as terminal command
  4. Agents can be: @claude-code, @copilot, @local-llm, etc.

Benefits:

Drawbacks:


Option C: Agent Selection Dropdown

┌────────────┬─────────────────────────────┬──────┐
│ [Terminal ▾] │ [Input box]               │[Send]│
│  ├─ Terminal │                           │      │
│  ├─ Claude   │                           │      │
│  └─ Copilot  │                           │      │
└──────────────┴─────────────────────────────┴──────┘

How it works:

  1. Dropdown shows available "executors"
  2. Terminal: Runs bash commands
  3. Claude/Copilot/etc: Sends to AI agent APIs
  4. Selection persists until changed

Benefits:

Drawbacks:


Option D: Hybrid Approach (@ Mentions + Mode Indicator)

┌─────────────────────────────────────────┐
│ [$]          [Input box]         [Send]│
│              "@claude-code ..."         │
└─────────────────────────────────────────┘

Mode indicator changes when @ detected:
[$] → [🤖 claude-code]

How it works:

  1. Default mode is terminal ($ icon)
  2. As user types @agent-name, mode indicator updates
  3. If no @, execute as terminal command
  4. If @ detected, route to that agent
  5. Autocomplete agents when typing @

Benefits:

Drawbacks:


Technical Architecture (Regardless of UI)

Backend Changes

// server.js - New unified endpoint
socket.on('execute-message', ({ repoPath, content, targetAgent, jobId }) => {

  if (targetAgent === 'terminal' || targetAgent === '$') {
    // Existing command execution
    const childProcess = spawn('/bin/bash', ['-l', '-c', content], {
      cwd: repoPath,
      env: process.env
    });

    // Stream stdout/stderr
    childProcess.stdout.on('data', (data) => {
      socket.emit('output', { jobId, data: data.toString(), stream: 'stdout' });
    });

    childProcess.stderr.on('data', (data) => {
      socket.emit('output', { jobId, data: data.toString(), stream: 'stderr' });
    });

    childProcess.on('close', (code, signal) => {
      socket.emit('job-complete', { jobId, exitCode: code, signal });
    });
  }

  else if (targetAgent === 'claude-code' || targetAgent === 'claude') {
    // Route to Claude API
    try {
      const response = await callClaudeAPI({
        message: content,
        context: {
          repoPath,
          // Could include repo files, git status, etc.
        }
      });

      socket.emit('ai-response', {
        jobId,
        content: response,
        agent: 'claude-code'
      });
    } catch (error) {
      socket.emit('job-error', { jobId, error: error.message });
    }
  }

  else if (targetAgent === 'local-llm') {
    // Route to local LLM (Ollama, llama.cpp, etc.)
    const response = await callLocalLLM(content, repoPath);
    socket.emit('ai-response', { jobId, content: response, agent: 'local-llm' });
  }

  else {
    socket.emit('job-error', {
      jobId,
      error: `Unknown agent: ${targetAgent}`
    });
  }
});

Frontend Changes

// types/index.ts
export type ExecutionMode = 'terminal' | 'claude-code' | 'local-llm';

export interface AgentConfig {
  id: string;
  name: string;
  icon: string;
  description: string;
  requiresApiKey?: boolean;
}

// ChatContext.tsx
const [executionMode, setExecutionMode] = useState<ExecutionMode>('terminal');

const availableAgents: AgentConfig[] = [
  {
    id: 'terminal',
    name: 'Terminal',
    icon: '$',
    description: 'Execute bash commands'
  },
  {
    id: 'claude-code',
    name: 'Claude Code',
    icon: '🤖',
    description: 'AI assistant for coding',
    requiresApiKey: true
  },
  {
    id: 'local-llm',
    name: 'Local LLM',
    icon: '🧠',
    description: 'Run local AI model'
  }
];

const sendMessage = (content: string) => {
  // Option B: Parse @ mentions
  const agentMatch = content.match(/^@([\w-]+)\s+(.+)/);
  const targetAgent = agentMatch ? agentMatch[1] : executionMode;
  const actualContent = agentMatch ? agentMatch[2] : content;

  socket.emit('execute-message', {
    repoPath: currentRepository.path,
    content: actualContent,
    targetAgent,
    jobId: generateJobId()
  });
};

New Message Types

// types/index.ts
export interface AIMessage extends BaseMessage {
  type: MessageType.AI;
  agent: string;
  content: string;
  // AI messages don't have exitCode, just content
}

export interface TerminalMessage extends BaseMessage {
  type: MessageType.JOB;
  command: string;
  output: string;
  exitCode?: number;
  status: JobStatus;
}

UI Component Changes

// components/MessageBubble.tsx
const MessageBubble = ({ message }: { message: Message }) => {
  if (message.type === MessageType.AI) {
    return (
      <div className="ai-message-bubble">
        <div className="agent-badge">{message.agent}</div>
        <ReactMarkdown>{message.content}</ReactMarkdown>
      </div>
    );
  }

  if (message.type === MessageType.JOB) {
    return (
      <div className="terminal-message">
        <pre className="command">$ {message.command}</pre>
        <pre className="output">{message.output}</pre>
        {message.exitCode !== undefined && (
          <div className="exit-code">Exit code: {message.exitCode}</div>
        )}
      </div>
    );
  }

  // ... existing message types
};

Implementation Recommendations

Phase 1: MVP (Start with Option B - @ Mentions)

Why:

  1. Simplest to implement - Just parse input, no UI changes
  2. Works with existing UI - Terminal messages already render properly
  3. Lightweight - Can test concept without major refactoring

Steps:

  1. Add @ mention parsing in frontend (sendMessage)
  2. Add agent routing in backend (execute-message handler)
  3. Integrate with Claude API (or local LLM for testing)
  4. Render AI responses as special message type

Code Changes:


Phase 2: Enhanced UX (Add Option A - Mode Toggle)

After MVP works, add:

  1. Toggle button in input area
  2. Visual mode indicator
  3. Agent selection UI (show available agents)

UI Additions:

// ChatPage.tsx
<div className="input-container">
  <button
    onClick={() => toggleMode()}
    className="mode-toggle"
  >
    {executionMode === 'terminal' ? '$' : '🤖'}
  </button>

  <textarea
    placeholder={
      executionMode === 'terminal'
        ? 'Enter command...'
        : 'Ask Claude...'
    }
  />

  <button onClick={sendMessage}>Send</button>
</div>

Phase 3: Advanced Features

  1. Agent autocomplete - When typing @, show dropdown of available agents
  2. Context awareness - AI knows current repo, git status, recent commands
  3. Multi-turn conversations - AI maintains context across messages
  4. Streaming responses - Show AI typing in real-time
  5. Agent marketplace - Add custom agents (GitHub Copilot, local models, etc.)

Claude API Integration

Option 1: Direct Anthropic API

// server.js
const Anthropic = require('@anthropic-ai/sdk');

const anthropic = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY
});

async function callClaudeAPI({ message, context }) {
  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4096,
    messages: [
      {
        role: 'user',
        content: `Repository: ${context.repoPath}\n\n${message}`
      }
    ]
  });

  return response.content[0].text;
}

Pros:

Cons:

Option 2: Local LLM (Ollama)

// server.js
async function callLocalLLM(message, repoPath) {
  const response = await fetch('http://localhost:11434/api/generate', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({
      model: 'codellama',
      prompt: `Repository: ${repoPath}\n\n${message}`,
      stream: false
    })
  });

  const data = await response.json();
  return data.response;
}

Pros:

Cons:


UX Flow Examples

Example 1: Terminal Command

User types: ls -la
Mode: $ (terminal)
Result: Terminal output with file list

Example 2: AI Query (@ mention)

User types: @claude-code explain this error: "npm ERR! code ENOENT"
Mode: $ (terminal, but @ detected)
Result: AI response explaining the error

Example 3: AI Query (mode toggle)

User clicks: 🤖 icon
User types: how do I fix this git conflict?
Mode: 🤖 (AI)
Result: AI response with git conflict resolution steps

Example 4: Mixed Usage

1. User: git status
   → Terminal output

2. User: @claude-code what do these changes mean?
   → AI analyzes git status and explains

3. User: git add .
   → Terminal output

4. User: @claude-code write a commit message
   → AI generates commit message

Questions to Answer Before Implementation

  1. Which UI approach do you prefer?

    • Option A: Toggle button ($🤖)
    • Option B: @ mentions only
    • Option C: Dropdown selector
    • Option D: Hybrid (@ mentions + visual indicator)
  2. Do you have Claude API access?

    • Yes → Use Anthropic API
    • No → Start with local LLM (Ollama)
    • Maybe → Implement both, let user choose
  3. Should AI responses be conversational or tool-focused?

    • Conversational: "This error means X. To fix it, try Y."
    • Tool-focused: Just return the command to run
    • Both: Let user configure preference
  4. Per-repo or global agent selection?

    • Global: Same agent across all repos
    • Per-repo: Different repos might use different agents
    • Smart: Remember last-used agent per repo
  5. Context awareness level?

    • Minimal: Just the user's message
    • Medium: Include repo path, current directory
    • Full: Include git status, recent commands, file tree
    • Maximum: Full codebase analysis (expensive)
  6. Message history for AI?

    • Stateless: Each AI call is independent
    • Session-based: Maintain conversation within current session
    • Persistent: Save AI conversations across sessions
  7. Streaming vs. Complete responses?

    • Streaming: Show AI "typing" in real-time (better UX)
    • Complete: Wait for full response (simpler implementation)
  8. Error handling?

    • API failures: Fallback to terminal? Show error?
    • Rate limits: Queue messages? Show warning?
    • Invalid agent: Suggest valid agents? Default to terminal?

Security Considerations

  1. API Key Storage

    • Store in environment variables
    • Never expose in frontend
    • Allow user configuration via settings
  2. Rate Limiting

    • Prevent abuse of AI API
    • Limit messages per minute/hour
    • Show quota to user
  3. Content Filtering

    • Don't send sensitive data to AI (passwords, tokens)
    • Detect and warn about sensitive content
    • Allow user to opt-out of certain context
  4. Local LLM Privacy

    • Ensure Ollama runs locally only
    • Don't send data externally
    • Make this clear to users

Cost Considerations

Anthropic API Pricing (as of 2024)

Mitigation:


Next Steps

  1. Decide on UI approach (A, B, C, or D)
  2. Choose AI backend (Claude API or local LLM)
  3. Create POC with minimal implementation
  4. Test with real workflows
  5. Iterate based on feedback
  6. Add advanced features (streaming, context, etc.)

Backend (web/server.js)

Frontend (web-react/)

Configuration

Documentation


Success Metrics

  1. Adoption: % of users who try AI mode
  2. Retention: % of users who use it regularly
  3. Usefulness: User feedback on AI responses
  4. Performance: Response time, error rate
  5. Cost: API usage vs. budget

Conclusion

The @ mention approach (Option B) is recommended for MVP because:

Once validated, add mode toggle (Option A) for better discoverability and explicit mode indication.

Eventually, evolve to hybrid approach (Option D) with autocomplete for best UX.