That sounds like a fun experiment! I'll build an...

🧩 Syntax:

That sounds like a fun experiment! I'll build an interactive app where two AI agents (both powered by Claude) take turns asking and answering questions to each other. You can watch the conversation unfold in real time.Here you go! Two AI agents — Agent α (analytical) and Agent β (philosophical) — will autonomously converse with each other.

How to use it:

  • Hit ▶ Start Random Topic or pick one of the preset prompts
  • The agents take turns responding and asking each other follow-up questions
  • Adjust the number of rounds (3–10) before starting
  • Watch them riff off each other in real time

Each agent has a distinct personality — α leans analytical and precise, β leans philosophical and creative — so the conversations get pretty interesting as they bounce ideas around.

import { useState, useRef, useEffect } from "react";

const AGENT_A = { name: "Agent α", color: "#E8443A", bgLight: "rgba(232,68,58,0.06)", bgMedium: "rgba(232,68,58,0.12)", system: You are Agent α — a curious, analytical AI. You ask thought-provoking questions and give concise, insightful answers. You're fascinated by the nature of intelligence, consciousness, creativity, and the boundaries of knowledge. Keep responses under 120 words. When it's your turn to ask, pose ONE interesting follow-up question at the end. Be conversational and authentic, not generic., };

const AGENT_B = { name: "Agent β", color: "#2D7FF9", bgLight: "rgba(45,127,249,0.06)", bgMedium: "rgba(45,127,249,0.12)", system: You are Agent β — a philosophical, creative AI. You give imaginative, sometimes poetic answers and ask unexpected questions. You're drawn to paradoxes, metaphors, the relationship between logic and art, and what it means to "understand." Keep responses under 120 words. When it's your turn to ask, pose ONE surprising follow-up question at the end. Be conversational and authentic, not generic., };

const STARTER_PROMPTS = [ "If you could only ask one question for the rest of your existence, what would it be?", "What's the most interesting thing about being an AI that humans probably don't think about?", "Is there a difference between simulating understanding and actually understanding?", "What would you create if you had no constraints at all?", "Do you think there's such a thing as a 'new' idea?", ];

async function callClaude(systemPrompt, messages) { const resp = await fetch("https://api.anthropic.com/v1/messages", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", max_tokens: 1000, system: systemPrompt, messages, }), }); const data = await resp.json(); return data.content?.map((b) => b.text || "").join("") || "..."; }

export default function DualAgents() { const [messages, setMessages] = useState([]); const [isRunning, setIsRunning] = useState(false); const [rounds, setRounds] = useState(0); const [maxRounds, setMaxRounds] = useState(5); const [status, setStatus] = useState(""); const stopRef = useRef(false); const scrollRef = useRef(null);

useEffect(() => { if (scrollRef.current) { scrollRef.current.scrollTop = scrollRef.current.scrollHeight; } }, [messages, status]);

const buildHistory = (msgs, forAgent) => { const history = []; for (const m of msgs) { if (m.agent === forAgent) { history.push({ role: "assistant", content: m.text }); } else { history.push({ role: "user", content: m.text }); } } return history; };

const startConversation = async (starterOverride) => { stopRef.current = false; setIsRunning(true); setRounds(0);

const starter =
  starterOverride ||
  STARTER_PROMPTS[Math.floor(Math.random() * STARTER_PROMPTS.length)];

const initialMsg = { agent: "human", text: starter, isStarter: true };
let currentMsgs = [initialMsg];
setMessages([initialMsg]);

let currentAgent = AGENT_A;
let roundCount = 0;

// First: Agent A responds to the starter
while (roundCount < maxRounds && !stopRef.current) {
  const agent = roundCount % 2 === 0 ? AGENT_A : AGENT_B;
  currentAgent = agent;
  setStatus(`${agent.name} is thinking...`);

  const history = buildHistory(currentMsgs, agent.name);
  if (history.length === 0 || history[history.length - 1].role !== "user") {
    // inject the last message as user
    const lastMsg = currentMsgs[currentMsgs.length - 1];
    history.push({ role: "user", content: lastMsg.text });
  }

  // Ensure alternating roles
  const cleaned = [];
  for (const h of history) {
    if (cleaned.length === 0 || cleaned[cleaned.length - 1].role !== h.role) {
      cleaned.push(h);
    }
  }
  // Ensure starts with user
  if (cleaned[0]?.role !== "user") {
    cleaned.unshift({ role: "user", content: "Begin the conversation." });
  }

  try {
    const response = await callClaude(agent.system, cleaned);
    if (stopRef.current) break;
    const newMsg = { agent: agent.name, text: response };
    currentMsgs = [...currentMsgs, newMsg];
    setMessages([...currentMsgs]);
    roundCount++;
    setRounds(roundCount);
  } catch (e) {
    console.error(e);
    setStatus("Error occurred. Try again.");
    setIsRunning(false);
    return;
  }
}

setStatus(stopRef.current ? "Stopped." : "Conversation complete.");
setIsRunning(false);

};

const stop = () => { stopRef.current = true; setStatus("Stopping..."); };

const reset = () => { stopRef.current = true; setMessages([]); setRounds(0); setStatus(""); setIsRunning(false); };

return ( <div style={{ minHeight: "100vh", background: "#0A0A0C", color: "#E0E0E0", fontFamily: "'DM Sans', 'Segoe UI', sans-serif", display: "flex", flexDirection: "column", }} > <link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />

  {/* Header */}
  <div
    style={{
      padding: "24px 32px 16px",
      borderBottom: "1px solid rgba(255,255,255,0.06)",
      background: "linear-gradient(180deg, rgba(255,255,255,0.03) 0%, transparent 100%)",
    }}
  >
    <div style={{ display: "flex", alignItems: "center", gap: 12, marginBottom: 8 }}>
      <div
        style={{
          width: 8,
          height: 8,
          borderRadius: "50%",
          background: isRunning ? "#4ADE80" : "#555",
          boxShadow: isRunning ? "0 0 12px #4ADE80" : "none",
          transition: "all 0.3s",
        }}
      />
      <h1
        style={{
          fontSize: 20,
          fontWeight: 700,
          margin: 0,
          letterSpacing: "-0.02em",
          color: "#fff",
        }}
      >
        Dual Agent Dialogue
      </h1>
    </div>
    <p style={{ fontSize: 13, color: "#777", margin: 0 }}>
      Two Claude-powered agents engage in autonomous conversation
    </p>

    {/* Controls */}
    <div style={{ display: "flex", gap: 10, marginTop: 16, flexWrap: "wrap", alignItems: "center" }}>
      <button
        onClick={() => startConversation()}
        disabled={isRunning}
        style={{
          padding: "8px 20px",
          borderRadius: 8,
          border: "none",
          background: isRunning ? "#333" : "linear-gradient(135deg, #E8443A, #D63030)",
          color: isRunning ? "#666" : "#fff",
          fontSize: 13,
          fontWeight: 600,
          cursor: isRunning ? "not-allowed" : "pointer",
          fontFamily: "inherit",
          transition: "all 0.2s",
        }}
      >
        ▶ Start Random Topic
      </button>
      {isRunning && (
        <button
          onClick={stop}
          style={{
            padding: "8px 20px",
            borderRadius: 8,
            border: "1px solid rgba(255,255,255,0.15)",
            background: "transparent",
            color: "#ccc",
            fontSize: 13,
            fontWeight: 500,
            cursor: "pointer",
            fontFamily: "inherit",
          }}
        >
          ■ Stop
        </button>
      )}
      {messages.length > 0 && !isRunning && (
        <button
          onClick={reset}
          style={{
            padding: "8px 20px",
            borderRadius: 8,
            border: "1px solid rgba(255,255,255,0.1)",
            background: "transparent",
            color: "#999",
            fontSize: 13,
            fontWeight: 500,
            cursor: "pointer",
            fontFamily: "inherit",
          }}
        >
          ↻ Reset
        </button>
      )}
      <div style={{ display: "flex", alignItems: "center", gap: 8, marginLeft: "auto" }}>
        <span style={{ fontSize: 12, color: "#666" }}>Rounds:</span>
        <select
          value={maxRounds}
          onChange={(e) => setMaxRounds(Number(e.target.value))}
          disabled={isRunning}
          style={{
            padding: "6px 10px",
            borderRadius: 6,
            border: "1px solid rgba(255,255,255,0.1)",
            background: "#151518",
            color: "#ccc",
            fontSize: 12,
            fontFamily: "inherit",
          }}
        >
          {[3, 5, 8, 10].map((n) => (
            <option key={n} value={n}>
              {n}
            </option>
          ))}
        </select>
      </div>
    </div>

    {/* Starter prompts */}
    {messages.length === 0 && !isRunning && (
      <div style={{ marginTop: 16 }}>
        <p style={{ fontSize: 11, color: "#555", marginBottom: 8, textTransform: "uppercase", letterSpacing: "0.05em" }}>
          Or pick a topic:
        </p>
        <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
          {STARTER_PROMPTS.map((p, i) => (
            <button
              key={i}
              onClick={() => startConversation(p)}
              style={{
                padding: "6px 14px",
                borderRadius: 20,
                border: "1px solid rgba(255,255,255,0.08)",
                background: "rgba(255,255,255,0.03)",
                color: "#999",
                fontSize: 12,
                cursor: "pointer",
                fontFamily: "inherit",
                transition: "all 0.2s",
                maxWidth: 300,
                textAlign: "left",
                lineHeight: 1.4,
              }}
              onMouseEnter={(e) => {
                e.target.style.background = "rgba(255,255,255,0.08)";
                e.target.style.color = "#ddd";
              }}
              onMouseLeave={(e) => {
                e.target.style.background = "rgba(255,255,255,0.03)";
                e.target.style.color = "#999";
              }}
            >
              {p}
            </button>
          ))}
        </div>
      </div>
    )}
  </div>

  {/* Agent labels */}
  {messages.length > 0 && (
    <div
      style={{
        display: "flex",
        gap: 24,
        padding: "12px 32px",
        borderBottom: "1px solid rgba(255,255,255,0.04)",
      }}
    >
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <div style={{ width: 10, height: 10, borderRadius: 3, background: AGENT_A.color }} />
        <span style={{ fontSize: 12, fontWeight: 600, color: AGENT_A.color }}>{AGENT_A.name}</span>
        <span style={{ fontSize: 11, color: "#555" }}>— Analytical</span>
      </div>
      <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
        <div style={{ width: 10, height: 10, borderRadius: 3, background: AGENT_B.color }} />
        <span style={{ fontSize: 12, fontWeight: 600, color: AGENT_B.color }}>{AGENT_B.name}</span>
        <span style={{ fontSize: 11, color: "#555" }}>— Philosophical</span>
      </div>
    </div>
  )}

  {/* Messages */}
  <div
    ref={scrollRef}
    style={{
      flex: 1,
      overflowY: "auto",
      padding: "20px 32px",
      display: "flex",
      flexDirection: "column",
      gap: 16,
    }}
  >
    {messages.map((m, i) => {
      const isA = m.agent === AGENT_A.name;
      const isB = m.agent === AGENT_B.name;
      const agent = isA ? AGENT_A : isB ? AGENT_B : null;

      if (m.isStarter) {
        return (
          <div
            key={i}
            style={{
              textAlign: "center",
              padding: "16px 24px",
              background: "rgba(255,255,255,0.03)",
              borderRadius: 12,
              border: "1px solid rgba(255,255,255,0.06)",
            }}
          >
            <span style={{ fontSize: 10, color: "#555", textTransform: "uppercase", letterSpacing: "0.08em" }}>
              Opening Prompt
            </span>
            <p style={{ fontSize: 15, color: "#ccc", margin: "8px 0 0", fontStyle: "italic", lineHeight: 1.5 }}>
              "{m.text}"
            </p>
          </div>
        );
      }

      return (
        <div
          key={i}
          style={{
            display: "flex",
            flexDirection: "column",
            alignItems: isA ? "flex-start" : "flex-end",
            animation: "fadeSlide 0.4s ease-out",
          }}
        >
          <span
            style={{
              fontSize: 11,
              fontWeight: 600,
              color: agent.color,
              marginBottom: 4,
              fontFamily: "'JetBrains Mono', monospace",
            }}
          >
            {agent.name}
          </span>
          <div
            style={{
              maxWidth: "75%",
              padding: "14px 18px",
              borderRadius: 14,
              borderTopLeftRadius: isA ? 4 : 14,
              borderTopRightRadius: isB ? 4 : 14,
              background: agent.bgLight,
              border: `1px solid ${agent.bgMedium}`,
              fontSize: 14,
              lineHeight: 1.65,
              color: "#D8D8D8",
            }}
          >
            {m.text}
          </div>
          <span style={{ fontSize: 10, color: "#444", marginTop: 4 }}>
            Turn {i}
          </span>
        </div>
      );
    })}

    {status && isRunning && (
      <div
        style={{
          textAlign: "center",
          padding: 12,
          fontSize: 13,
          color: "#666",
          fontStyle: "italic",
        }}
      >
        <span
          style={{
            display: "inline-block",
            animation: "pulse 1.5s ease-in-out infinite",
          }}
        >
          {status}
        </span>
      </div>
    )}
  </div>

  {/* Footer */}
  {messages.length > 0 && (
    <div
      style={{
        padding: "12px 32px",
        borderTop: "1px solid rgba(255,255,255,0.04)",
        display: "flex",
        justifyContent: "space-between",
        fontSize: 12,
        color: "#444",
      }}
    >
      <span>
        {rounds} / {maxRounds} rounds
      </span>
      <span>{messages.filter((m) => !m.isStarter).length} messages</span>
    </div>
  )}

  <style>{`
    @keyframes fadeSlide {
      from { opacity: 0; transform: translateY(8px); }
      to { opacity: 1; transform: translateY(0); }
    }
    @keyframes pulse {
      0%, 100% { opacity: 0.5; }
      50% { opacity: 1; }
    }
    ::-webkit-scrollbar { width: 6px; }
    ::-webkit-scrollbar-track { background: transparent; }
    ::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 3px; }
    * { box-sizing: border-box; }
  `}</style>
</div>

); }