"use client";

import { useMemo, useState } from 'react';
import styles from './page.module.css';

/* ---------- static knowledge (grounded in repo + Hermes/Paperclip) ---------- */

const TABS = [
  { id: 'hermes',   label: 'Hermes Agent',      hint: 'Personal AI runtime' },
  { id: 'paper',    label: 'Paperclip',         hint: 'AI-company control plane' },
  { id: 'madarx',   label: 'madarX OS',         hint: 'Local-first, audited' },
  { id: 'upgrade',  label: 'Upgrade Blueprint', hint: 'Path to parity' },
];

const HERMES_LOOP = [
  { id: 'h1', tag: '01', title: 'Receive event',     blurb: 'Platform adapter → MessageEvent',
    detail: { ref: 'gateway/run.py · GatewayRunner', body: [
      'A platform adapter (WhatsApp, Slack, CLI) emits a MessageEvent with platform/chat/thread/user identifiers.',
      'The WhatsApp adapter (gateway/platforms/whatsapp.py) starts scripts/whatsapp-bridge/bridge.js — a Node Baileys bridge — and polls inbound messages over a local HTTP loop (port 3010 on this machine).',
      'Events are normalised before any agent code runs, so the same loop powers every platform.'
    ]}},
  { id: 'h2', tag: '02', title: 'Resolve session',    blurb: 'Map to a cached AIAgent',
    detail: { ref: 'GatewayRunner.session_cache', body: [
      'GatewayRunner maps (platform, chat, thread, user) → a long-lived AIAgent instance held in process memory.',
      'New chats spawn a fresh AIAgent; reconnects pick the existing transcript up where it left off.',
      'Sessions are isolated — approvals, queues, and tool state never leak between users or chats.'
    ]}},
  { id: 'h3', tag: '03', title: 'Build prompt',       blurb: 'Memory + skills disclosure',
    detail: { ref: 'agent/memory_provider.py · skills.py', body: [
      'Memory provider (built-in / Honcho / Mem0 / etc.) returns a fenced memory block; cron runs skip memory by default.',
      'Skills (~/.hermes/skills, each with a SKILL.md) load progressively — only the index is shown until skills_list / skill_view are called.',
      'Toolsets gate which tools are exposed for this session; MCP servers can register more dynamically.'
    ]}},
  { id: 'h4', tag: '04', title: 'Provider call',      blurb: 'Anthropic / OpenAI / local',
    detail: { ref: 'run_agent.py · AIAgent.run', body: [
      'AIAgent builds the messages, calls the configured provider, then parses tool calls from the response.',
      'Per-job model selection is allowed: cron jobs (cron/jobs.json) override model, skills, toolsets, delivery and no_agent independently.'
    ]}},
  { id: 'h5', tag: '05', title: 'Dispatch tools',     blurb: 'Registry + MCP fan-out',
    detail: { ref: 'tools/registry.py · model_tools.py', body: [
      'Tools self-register at import via decorators in tools/registry.py; toolsets.py enables/disables groups by name.',
      'MCP servers contribute dynamically registered tools at startup — the same dispatch path treats them as first-class.',
      'Delegation (tools/delegate_tool.py) spawns child AIAgent sessions with restricted toolsets and bounded depth/concurrency; only a summary returns to the parent.'
    ]}},
  { id: 'h6', tag: '06', title: 'Approval gate',      blurb: 'Hardline · smart · manual',
    detail: { ref: 'tools/approval.py', body: [
      'A hardline blocklist refuses catastrophic commands outright — no override.',
      'A smart classifier flags risky shell/file/network actions and asks for /approve or /deny on the platform that initiated the call.',
      'Modes: smart (default), manual (always ask), off (trusted environments). Decisions are session-scoped and never global.'
    ]}},
  { id: 'h7', tag: '07', title: 'Append + iterate',   blurb: 'Tool result → next turn',
    detail: { ref: 'AIAgent main loop', body: [
      'Tool results are appended to the message list and the loop runs again — multi-step plans emerge from chained tool calls.',
      'Iteration stops when the model emits a final assistant message or hits the configured max_iterations.'
    ]}},
  { id: 'h8', tag: '08', title: 'Persist + deliver',  blurb: 'state.db + platform reply',
    detail: { ref: '~/.hermes/state.db · GatewayRunner.send', body: [
      'Every turn writes to ~/.hermes/state.db (sessions, messages, FTS search index).',
      'Cron jobs (cron/jobs.py + cron/scheduler.py) reuse the same loop but with a fresh AIAgent; their output is stored under cron/output and delivered through the configured channel.',
      'GatewayRunner sends the final reply back through the same platform adapter that received the event.'
    ]}},
];

const MADARX_LOOP = [
  { id: 'm1', tag: '01', title: 'Stream entry',     blurb: '/api/orchestrator/stream',
    detail: { ref: 'app/api/orchestrator/stream/route.js', body: [
      'The dashboard composer POSTs to /api/orchestrator/stream; the route streams Server-Sent Events back to the client.',
      'Inbound WhatsApp messages take a parallel path through lib/whatsapp-router.js → lib/whatsapp-agent.js with a WhatsApp-specific system prompt and a 6-turn / 1536-token cap.'
    ]}},
  { id: 'm2', tag: '02', title: 'Runtime route',    blurb: 'handleOrchestratorCommand',
    detail: { ref: 'lib/runtime.js', body: [
      'lib/runtime.js owns all domain logic: agent creation, schedule upserts, marketing/competitor reports, heartbeat ticks, artifact writes.',
      'The orchestrator interprets the prompt and either runs a marketplace tool directly or spawns a sub-agent through lib/agent-runner.js.'
    ]}},
  { id: 'm3', tag: '03', title: 'Engine spawn',     blurb: 'claude-code · anthropic · codex · ollama · hermes',
    detail: { ref: 'lib/agent-engines.js', body: [
      'Five engines are wired: claude-code (default, spawns the local CC CLI with --permission-mode bypassPermissions; the load-bearing safety boundary is the .claude/settings.json deny list), anthropic (madarX tool marketplace via the Messages API — required for create_artifact / generate_ui / whatsapp_send / competitor_report / marketing_plan), ollama, codex, and hermes (lib/hermes-jobs.js + scripts/hermes-job-runner.mjs).',
      'CC spawns timeout via CLAUDE_CODE_TICK_TIMEOUT_MS (default 600000ms) and cap turns via CLAUDE_CODE_MAX_TURNS (default 30).'
    ]}},
  { id: 'm4', tag: '04', title: 'Classify risk',    blurb: 'registry + safety',
    detail: { ref: 'lib/tools/registry.js · lib/tools/safety.js', body: [
      'executeRegistryTool() classifies every call: low / medium / high / irreversible_external. classifyCommand inspects terminal_run / process_start commands and flags destructive patterns (git push --force, kubectl delete, DROP TABLE / TRUNCATE / DELETE without WHERE).',
      '36 marketplace tools across github (11), local_machine (9), supabase (8), vercel (3), web (3) and email (2) — see MARKETPLACE_TOOLS in lib/tools/registry.js.'
    ]}},
  { id: 'm5', tag: '05', title: 'Approval gate',    blurb: 'lib/approvals.js',
    detail: { ref: 'lib/approvals.js · app/api/approvals/[id]/route.js', body: [
      'irreversible_external always creates an approval_requests row with action_type main_agent_tool_irreversible:<toolName> — auto-trust does NOT bypass this tier. This is the load-bearing guarantee that an LLM on auto-trust cannot perform irreversible external writes without a human in the loop.',
      'high / medium are gated when trust mode is approval_required; low always runs. Approvals are one-time, consumed by approval_id, and bound by an input signature.'
    ]}},
  { id: 'm6', tag: '06', title: 'Allowlist + rate', blurb: 'destination + sliding window',
    detail: { ref: 'lib/tool-allowlists.js · lib/tool-rate-limits.js', body: [
      'Each write tool defines a scope_key (e.g. recipient_domain for email, repo_full_name for GitHub, table for supabase_rest_*). Default-deny tools block until at least one entry exists; web_search / web_fetch are default-allow.',
      'Sliding-window quotas live in MARKETPLACE_TOOLS — e.g. email_send 20/hour, gh_pr_merge 5/hour, supabase_rest_delete 10/hour. recordCall fires only after a successful adapter run, so denied calls never consume quota.'
    ]}},
  { id: 'm7', tag: '07', title: 'Adapter + egress', blurb: 'wrapFetch → tool_egress',
    detail: { ref: 'lib/tools/adapters/ · lib/egress.js', body: [
      'Adapters (files.js, terminal.js, processes.js, github.js, supabase.js, resend.js, vercel.js, web.js) execute the work; safety.js resolves paths inside allowed roots, redacts secrets, caps output.',
      'Every external HTTP call goes through wrapFetch(toolName, agentId, fetch). One row lands in tool_egress per call (host, method, status, bytes in/out, duration) — never bodies. DB write failures are silent on purpose: observation must not break the caller.'
    ]}},
  { id: 'm8', tag: '08', title: 'Audit + heartbeat', blurb: 'audit_logs + live events',
    detail: { ref: 'lib/live-events.js · lib/live-events-bridge.js · lib/heartbeat-worker.js', body: [
      'publishLiveEvent fans out to in-process subscribers and persists to activity_log. The SSE endpoint /api/events/stream tails activity_log to bridge events from the WhatsApp / cron / heartbeat daemons back into the dashboard.',
      'Scheduled work flows through scripts/cron-daemon.mjs → heartbeat_tasks (queued → leased → running → complete | failed | approval_required). The worker owns leases, retries with backoff, pauses approval-required tasks, and recovers stale leases.'
    ]}},
];

const TOOL_SIM = [
  { id: 'file_read',           cat: 'local',  risk: 'low',                  scope: 'path inside allowed root', why: 'Read-only, capped + redacted.' },
  { id: 'directory_list',      cat: 'local',  risk: 'low',                  scope: 'path inside allowed root', why: 'Read-only listing.' },
  { id: 'web_search',          cat: 'web',    risk: 'low',                  scope: 'default-allow',            why: 'No side effects on local state.' },
  { id: 'file_write',          cat: 'local',  risk: 'medium',               scope: 'path inside allowed root', why: 'Write inside workspace, redacted secrets.' },
  { id: 'file_patch',          cat: 'local',  risk: 'medium',               scope: 'path inside allowed root', why: 'Surgical edits, approval-gated.' },
  { id: 'terminal_run',        cat: 'local',  risk: 'medium',               scope: 'classified per-command',   why: 'classifyCommand may upgrade to irreversible.' },
  { id: 'gh_issue_comment',    cat: 'github', risk: 'medium',               scope: 'repo_full_name allowlist', why: 'External write; allowlist + quota.' },
  { id: 'gh_pr_create',        cat: 'github', risk: 'high',                 scope: 'repo_full_name allowlist', why: 'High blast radius; gated in approval mode.' },
  { id: 'supabase_rest_update',cat: 'supabase',risk: 'high',                scope: 'table allowlist',          why: 'Filters required; PATCH gated.' },
  { id: 'email_send',          cat: 'email',  risk: 'irreversible_external',scope: 'recipient_domain allowlist',why: 'Always approval-gated. Cannot be auto-trusted.' },
  { id: 'gh_pr_merge',         cat: 'github', risk: 'irreversible_external',scope: 'repo_full_name allowlist', why: 'Always approval-gated. Cannot be auto-trusted.' },
  { id: 'supabase_rest_delete',cat: 'supabase',risk: 'irreversible_external',scope: 'table allowlist',         why: 'Filters required; always approval-gated.' },
  { id: 'supabase_sql',        cat: 'supabase',risk: 'irreversible_external',scope: 'default_allow',           why: 'RLS-bypassing service-role SQL. Always approval.' },
];

const SOURCE_MAP = [
  { name: 'Runtime entry',         path: 'app/api/orchestrator/stream/route.js', note: 'SSE entry point for the main agent.' },
  { name: 'Domain logic',          path: 'lib/runtime.js',                       note: 'createAgent, handleOrchestratorCommand, getState, artifacts.' },
  { name: 'SQLite schema',         path: 'lib/schema.js · data/madarx.sqlite',   note: 'agents, schedules, runs, approvals, heartbeat_tasks, secrets, tool_allowlists, tool_egress.' },
  { name: 'Agent engines',         path: 'lib/agent-engines.js · lib/agent-runner.js', note: 'claude-code, anthropic, codex, ollama, hermes.' },
  { name: 'Tool registry',         path: 'lib/tools/registry.js · lib/tools/safety.js', note: '36 marketplace tools, classifyCommand, risk tiers.' },
  { name: 'Approvals',             path: 'lib/approvals.js · app/api/approvals/[id]/route.js', note: 'One-time, signature-bound approval rows.' },
  { name: 'Allowlists + quotas',   path: 'lib/tool-allowlists.js · lib/tool-rate-limits.js', note: 'Per-destination allow + sliding windows.' },
  { name: 'Egress log',            path: 'lib/egress.js',                        note: 'wrapFetch → tool_egress (host/method/status/bytes/ms).' },
  { name: 'Secrets vault',         path: 'lib/secrets.js · lib/master-key.js',   note: 'AES-256-GCM in SQLite secrets table, MADARX_MASTER_KEY.' },
  { name: 'Cron daemon',           path: 'scripts/cron-daemon.mjs · lib/schedule.js', note: 'Queues due schedules into heartbeat_tasks every 30s.' },
  { name: 'Heartbeat worker',      path: 'scripts/heartbeat-worker.mjs · lib/heartbeat-worker.js', note: 'Lease, retry, approval pause, stale recovery.' },
  { name: 'WhatsApp gateway',      path: 'scripts/whatsapp-daemon.mjs · scripts/whatsapp-bridge/bridge.js · lib/whatsapp-router.js · lib/whatsapp-agent.js', note: 'Baileys on 127.0.0.1:3210; allowlist; orchestrator with WA prompt.' },
  { name: 'Live events',           path: 'lib/live-events.js · lib/live-events-bridge.js · app/api/events/stream/route.js', note: 'In-process bus + cross-process bridge via activity_log.' },
  { name: 'Hermes bridge',         path: 'lib/hermes.js · lib/hermes-jobs.js · scripts/hermes-job-runner.mjs · scripts/hermes-command.py', note: 'Run Hermes commands and jobs from madarX.' },
];

const MATRIX_ROWS = [
  { k: 'Storage',          hermes: '~/.hermes/state.db (SQLite + FTS), ~/.hermes/config.yaml, ~/.hermes/.env',
                           paper:  'Postgres-style company control plane (companies, employees, goals, budgets, tasks, routines, governance, secrets, attachments).',
                           madarx: 'data/madarx.sqlite (WAL, FK on) via node:sqlite + lib/schema.js.' },
  { k: 'Provider routing', hermes: 'Per-session + per-cron-job model override; provider router in run_agent.py.',
                           paper:  'Platform-agnostic — adapters phone home; the control plane does not run the model itself.',
                           madarx: 'Engine string per agent (claude-code / anthropic / codex / ollama / hermes); no credential pool yet.' },
  { k: 'Memory',           hermes: 'Pluggable memory_provider (built-in / Honcho / Mem0 / …) injected as a fenced block; cron skips memory.',
                           paper:  'Documents + attachments + activity feed give long-form recall; no LLM memory module of its own.',
                           madarx: 'lib/agent-memory-context.js assembles a snapshot per run; no pluggable provider yet.' },
  { k: 'Skills',           hermes: 'SKILL.md directories under ~/.hermes/skills, progressive disclosure via skills_list / skill_view.',
                           paper:  'Routines + work products + governance act as the skill substrate.',
                           madarx: 'workspace/agents/<id>/skills.md + lib/agent-skills.js; not yet progressively loaded.' },
  { k: 'Sessions',         hermes: 'Per (platform, chat, thread, user) AIAgent cached in GatewayRunner; FTS search across history.',
                           paper:  'Tasks / issues / activity per employee per workspace; durable, queryable.',
                           madarx: 'chat_sessions + chat_messages; no full-text search.' },
  { k: 'Platforms',        hermes: 'Pluggable adapters: WhatsApp (Baileys bridge on port 3010), CLI, more; uniform MessageEvent abstraction.',
                           paper:  'Adapters (Claude / Codex / CLI / HTTP / webhook) hooked in via plugins; hermes_local mentioned as external adapter.',
                           madarx: 'Dashboard SSE + WhatsApp (Baileys on 127.0.0.1:3210); no shared adapter contract yet.' },
  { k: 'Cron / jobs',      hermes: 'cron/jobs.json with prompt + schedule + skills + model + delivery + no_agent + toolsets; fresh AIAgent per run.',
                           paper:  'Routines + heartbeat at the company-process level.',
                           madarx: 'lib/schedule.js parses human strings; cron-daemon queues heartbeat_tasks; worker honours retries, leases, approval pauses.' },
  { k: 'Approvals',        hermes: 'tools/approval.py — hardline block + smart classifier; CLI / gateway /approve /deny; smart / manual / off modes.',
                           paper:  'Governance + approval workflows are first-class in the org model.',
                           madarx: 'approval_requests rows; one-time consumption; irreversible_external tier always gated regardless of trust mode.' },
  { k: 'Subagents',        hermes: 'delegate_tool spawns child AIAgent with restricted toolsets, bounded depth/concurrency; only summary returns.',
                           paper:  'Employees and routines compose, with explicit budgets.',
                           madarx: 'lib/delegation.js + lib/delegation-contracts.js exist; concurrency / depth limits to harden.' },
  { k: 'Dynamic tools',    hermes: 'MCP servers register tools at startup; toolsets.py gates exposure.',
                           paper:  'Plugins extend the control plane.',
                           madarx: 'Static MARKETPLACE_TOOLS registry; no MCP dynamic registration yet.' },
  { k: 'Observability',    hermes: 'state.db transcripts; cron/output artifacts.',
                           paper:  'Activity / events feed across the org.',
                           madarx: 'audit_logs, agent_events, tool_egress, activity_log (SSE-bridged across processes).' },
];

const PHASES = [
  { k: 'Phase 1', name: 'Observability + source maps',
    bullets: ['Live event topology view', 'Per-tool latency + error panels', 'Egress search UI on tool_egress'] },
  { k: 'Phase 2', name: 'Approval Center',
    bullets: ['Single inbox for approvals across UI + WhatsApp', 'Smart classifier (low → auto-approve)', 'TTL + escalation rules'] },
  { k: 'Phase 3', name: 'Memory + Skills',
    bullets: ['Pluggable memory provider', 'SKILL.md progressive disclosure', 'Per-agent skill scoping'] },
  { k: 'Phase 4', name: 'Gateway adapters',
    bullets: ['MessageEvent abstraction', 'Slack / Discord / Email adapters', 'Durable per-session AIAgent cache'] },
  { k: 'Phase 5', name: 'Provider router',
    bullets: ['Credential pools per provider', 'Per-job model override', 'Cost + rate awareness baked in'] },
  { k: 'Phase 6', name: 'Evaluation + verification',
    bullets: ['Visual QA on artifacts', 'Diff-based code verification', 'Replay-from-audit_logs for regression'] },
];

/* ---------- helpers ---------- */

const RISK_RANK = { low: 0, medium: 1, high: 2, irreversible_external: 3 };
const RISK_LABEL = { low: 'low', medium: 'medium', high: 'high', irreversible_external: 'irrev. external' };
const RISK_CLASS = { low: 'low', medium: 'medium', high: 'high', irreversible_external: 'irrev' };

function decideTool(tool, mode){
  // mode: 'approval_required' (default) | 'auto' | 'manual' (always approve)
  if (mode === 'manual') return { decision: 'approval', why: 'Manual mode: every risky call asks.' };
  if (tool.risk === 'low')  return { decision: 'execute', why: 'Low-risk read; runs without approval.' };
  if (tool.risk === 'irreversible_external')
    return { decision: 'approval', why: 'Irreversible external. Auto-trust does NOT bypass.' };
  if (mode === 'auto')      return { decision: 'execute', why: `Auto-trust bypasses the ${tool.risk} gate.` };
  return { decision: 'approval', why: `approval_required mode gates ${tool.risk}.` };
}

function classifyMission(text){
  const t = text.toLowerCase();
  const hits = new Set();
  if (/(read|inspect|look at|cat |list|find|grep)/.test(t))           hits.add('file_read');
  if (/(directory|folder|tree|ls )/.test(t))                          hits.add('directory_list');
  if (/(search the web|google|look up|web ?search)/.test(t))          hits.add('web_search');
  if (/(write|create file|save|edit|patch|append)/.test(t))           hits.add('file_write');
  if (/patch|diff|modify line/.test(t))                               hits.add('file_patch');
  if (/(run|exec|shell|terminal|npm |node |git )/.test(t))            hits.add('terminal_run');
  if (/comment on (the )?pr|github issue comment/.test(t))            hits.add('gh_issue_comment');
  if (/(open|create) (a )?pr|pull request/.test(t))                   hits.add('gh_pr_create');
  if (/merge (the )?pr/.test(t))                                      hits.add('gh_pr_merge');
  if (/(send|email|resend|notify .* via email)/.test(t))              hits.add('email_send');
  if (/(insert|update) row|supabase update/.test(t))                  hits.add('supabase_rest_update');
  if (/(delete|drop) row|supabase delete/.test(t))                    hits.add('supabase_rest_delete');
  if (/run sql|select |raw sql/.test(t))                              hits.add('supabase_sql');
  if (hits.size === 0) hits.add('file_read');
  return TOOL_SIM.filter(t => hits.has(t.id));
}

function quickAuditPreview(tool, decision){
  if (decision === 'execute') {
    return [
      `main_agent.tool.${tool.id} → executed`,
      `tool_egress: 1 row (host derived by adapter)`,
      `tool_rate_limit_events: +1`,
    ];
  }
  return [
    `approval_requests: NEW row (action_type=main_agent_tool${tool.risk==='irreversible_external'?'_irreversible':''}:${tool.id})`,
    `audit_logs: main_agent.tool.approval_required`,
    `heartbeat_task: paused if scheduled (status=approval_required)`,
  ];
}

/* ---------- page ---------- */

export default function ArchitecturePage(){
  const [tab, setTab] = useState('hermes');

  // execution loop selection (per-tab)
  const [hStep, setHStep] = useState('h1');
  const [mStep, setMStep] = useState('m1');

  // approval simulator
  const [enabled, setEnabled] = useState(() => new Set(['file_read','directory_list','web_search','file_write','file_patch','terminal_run','gh_issue_comment','email_send']));
  const [trustMode, setTrustMode] = useState('approval_required');

  // source map
  const [srcSel, setSrcSel] = useState(SOURCE_MAP[0].path);

  // mission simulator
  const [mission, setMission] = useState('Open a PR titled “Fix WhatsApp pairing crash” in kareemodev777/madarx-os, email ak@madarx.io a 1-line summary, and read scripts/whatsapp-daemon.mjs first.');
  const [missionResult, setMissionResult] = useState(null);

  const enabledTools = useMemo(() => TOOL_SIM.filter(t => enabled.has(t.id)), [enabled]);

  const sim = useMemo(() => {
    let exec = 0, gated = 0;
    const rows = enabledTools.map(t => {
      const d = decideTool(t, trustMode);
      if (d.decision === 'execute') exec++; else gated++;
      return { tool: t, decision: d };
    });
    const irrev = enabledTools.filter(t => t.risk === 'irreversible_external').length;
    const guardScore = Math.round(100 * (gated + (irrev>0?1:0)) / (enabledTools.length + 1));
    return { rows, exec, gated, irrev, guardScore };
  }, [enabledTools, trustMode]);

  function toggleTool(id){
    setEnabled(prev => { const n = new Set(prev); if (n.has(id)) n.delete(id); else n.add(id); return n; });
  }

  function runMission(){
    const picked = classifyMission(mission);
    const rows = picked.map(t => ({ tool: t, decision: decideTool(t, trustMode) }));
    const approvals = rows.filter(r => r.decision.decision === 'approval').map(r => r.tool);
    const executes  = rows.filter(r => r.decision.decision === 'execute').map(r => r.tool);
    const heartbeat = approvals.length === 0
      ? [{ status: 'queued', desc: 'one heartbeat_task per scheduled batch'}, { status: 'leased', desc: 'worker claims with a lease' }, { status: 'running', desc: 'adapter executing' }, { status: 'complete', desc: 'final event recorded' }]
      : [{ status: 'queued', desc: 'cron-daemon enqueues row' }, { status: 'leased', desc: 'worker claims lease' }, { status: 'running', desc: 'adapter starts' }, { status: 'approval_required', desc: 'paused until approval consumed' }, { status: 'running', desc: 'resumed by /api/approvals/[id]' }, { status: 'complete', desc: 'after successful adapter call' }];
    const audit = rows.flatMap(r => quickAuditPreview(r.tool, r.decision.decision));
    setMissionResult({ rows, approvals, executes, heartbeat, audit });
  }

  return (
    <main className={styles.shell}>
      {/* topbar */}
      <div className={styles.topbar}>
        <div className={styles.lead}>
          <em>document · 003 · architecture</em>
          <b>madar<i>X</i> OS &nbsp;·&nbsp; agent runtime atlas</b>
        </div>
        <a className={styles.home} href="/">← back to console</a>
      </div>

      {/* hero */}
      <header className={styles.hero}>
        <div>
          <h1>madar<mark>X</mark> OS is the main operating layer for the madarX agent stack.</h1>
          <p>The OS orchestrates missions, calls specialist vertical agents such as RegOps and Butler/Concierge, manages approvals, and generates artifacts inside one governed workspace. This atlas shows how the runtime earns Hermes-class power without losing its audit floor.</p>
        </div>
        <aside className={styles.meta}>
          <b>filing meta</b>
          <dl>
            <dt>scope</dt>     <dd>runtime architecture · approval contracts</dd>
            <dt>repos</dt>     <dd>madarx-os · hermes-agent · paperclip</dd>
            <dt>classification</dt><dd>internal · operator reference</dd>
            <dt>palette</dt>   <dd>Onyx · Platinum · Bone · Graphite · Mars</dd>
          </dl>
        </aside>
      </header>

      {/* tabs */}
      <nav className={styles.tabs} role="tablist" aria-label="architecture sections">
        {TABS.map(t => (
          <button key={t.id} role="tab" aria-selected={tab===t.id} className={tab===t.id?styles.on:''} onClick={() => setTab(t.id)}>
            {t.label}
            <small>{t.hint}</small>
          </button>
        ))}
      </nav>

      {/* HERMES */}
      {tab === 'hermes' && (
        <>
          <section className={styles.section}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>Core loop</b><small>run_agent.py · gateway/run.py</small></div>
              <div className={styles.body}>
                <p>Hermes is a personal AI runtime built around one loop. A platform adapter emits a normalised <code>MessageEvent</code>; <code>GatewayRunner</code> resolves a per-session <code>AIAgent</code>; the agent builds a prompt with optional memory + skills, calls the configured provider, dispatches tool calls through a self-registering registry, gates them through approval, and iterates until a final answer.</p>
                <p className="muted">Click any step to see what is happening at that point in the loop.</p>
              </div>
              <div className={styles.loop} role="tablist">
                {HERMES_LOOP.map(s => (
                  <button key={s.id} role="tab" aria-selected={hStep===s.id} className={`${styles.loopStep} ${hStep===s.id?styles.on:''}`} onClick={() => setHStep(s.id)}>
                    <b>{s.tag}</b>
                    <span>{s.title}</span>
                    <small>{s.blurb}</small>
                  </button>
                ))}
              </div>
              <div className={styles.loopExplain}>
                {(() => {
                  const s = HERMES_LOOP.find(x => x.id === hStep);
                  return (
                    <>
                      <h4>{s.tag} · {s.title} <em>· {s.detail.ref}</em></h4>
                      {s.detail.body.map((p,i) => <p key={i}>{p}</p>)}
                    </>
                  );
                })()}
              </div>
            </div>

            <div className={`${styles.panel} ${styles.dark}`}>
              <div className={styles.panelTitle}><b>State + storage</b><small>~/.hermes</small></div>
              <div className={styles.body}>
                <dl className={styles.kv}>
                  <dt>config</dt><dd><code>~/.hermes/config.yaml</code></dd>
                  <dt>secrets</dt><dd><code>~/.hermes/.env</code></dd>
                  <dt>state</dt><dd><code>~/.hermes/state.db</code> · SQLite + FTS</dd>
                  <dt>skills</dt><dd><code>~/.hermes/skills/&lt;name&gt;/SKILL.md</code></dd>
                  <dt>cron</dt><dd><code>~/.hermes/cron/jobs.json</code> · <code>cron/output/</code></dd>
                  <dt>WA bridge</dt><dd><code>scripts/whatsapp-bridge/bridge.js</code> · port <code>3010</code></dd>
                </dl>
                <p className="muted">Memory is pluggable (<code>agent/memory_provider.py</code>) — built-in, Honcho, Mem0 etc. — and rendered into the prompt as a fenced block. Cron runs skip memory by default.</p>
                <p className="muted">Delegation (<code>tools/delegate_tool.py</code>) spawns child <code>AIAgent</code> sessions with restricted toolsets and bounded concurrency/depth, returning only a summary to the parent.</p>
              </div>
            </div>
          </section>

          <section className={`${styles.section} ${styles.single}`}>
            <div className={`${styles.panel} ${styles.flat}`}>
              <div className={styles.panelTitle}><b>Approval model</b><small>tools/approval.py</small></div>
              <div className={styles.body}>
                <p><b>Hardline.</b> A non-negotiable blocklist refuses catastrophic commands — destructive system writes, credential exfil, fork bombs — with no override. This list never asks the user.</p>
                <p><b>Smart classifier.</b> Risky shell, file, and network actions are flagged and surfaced for <code>/approve</code> or <code>/deny</code> on the platform that initiated the call (CLI prompt, WhatsApp inline reply, gateway-managed approval).</p>
                <p><b>Modes.</b> <code>smart</code> (default), <code>manual</code> (every risky action asks), <code>off</code> (trusted env). Modes are per-session, never global. Decisions never leak between users or chats.</p>
              </div>
            </div>
          </section>
        </>
      )}

      {/* PAPERCLIP */}
      {tab === 'paper' && (
        <>
          <section className={styles.section}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>What Paperclip actually is</b><small>not an attachment tool</small></div>
              <div className={styles.body}>
                <p>Paperclip (<code>~/Projects/paperclip</code>) is an open-source <b>control plane for autonomous AI companies</b>. It models the organisation, not the agent: companies, employees / AI agents, org structure, goals, budgets, tasks and issues, routines and schedules, heartbeat execution, governance and approvals, secrets and storage, activity and events, plugins, workspaces and runtime, documents and attachments and work products.</p>
                <p><b>Key distinction.</b> Paperclip&apos;s control plane does <i>not</i> have to run the agents itself. External agents and adapters run elsewhere and phone home — the plane keeps the durable record of what work was authorised, attempted, and produced.</p>
                <p>Adapters include Claude / Codex / generic CLI / HTTP / webhook. Local notes also reference a <code>hermes_local</code> external-adapter plugin story — Hermes lives outside the plane and ships work products in.</p>
              </div>
            </div>
            <div className={`${styles.panel} ${styles.dark}`}>
              <div className={styles.panelTitle}><b>Mental model</b><small>org first, agent second</small></div>
              <div className={styles.body}>
                <ul className="flat">
                  <li>Company → Employees (AI agents) with explicit roles.</li>
                  <li>Goals → Budgets → Tasks / Issues with owners.</li>
                  <li>Routines + heartbeat drive recurring execution.</li>
                  <li>Governance / approvals are first-class — not bolted on.</li>
                  <li>Activity + events feed gives a full audit timeline.</li>
                  <li>Documents · attachments · work products land alongside the task that produced them.</li>
                  <li>Plugins extend the plane; adapters extend the runtime.</li>
                </ul>
              </div>
            </div>
          </section>
          <section className={`${styles.section} ${styles.single}`}>
            <div className={styles.callout}>
              <b>Why this matters for madarX.</b> madarX is already most of a control plane (agents, schedules, approvals, audit, secrets, allowlists) — adopting Paperclip&apos;s separation-of-concerns lets us host external agent <i>runtimes</i> (Hermes, Claude Code, Codex, MCP servers) as adapters that phone home, while madarX remains the durable governance plane.
            </div>
          </section>
        </>
      )}

      {/* MADARX */}
      {tab === 'madarx' && (
        <>
          <section className={styles.section}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>Execution loop</b><small>orchestrator → engine → adapter → audit</small></div>
              <div className={styles.body}>
                <p>madarX OS is a local-first Next.js 15 App Router product with a SQLite-backed runtime (<code>data/madarx.sqlite</code>, WAL, foreign keys). The dashboard at <code>/</code> talks to the same database the background daemons do; nothing is in-memory only.</p>
                <p className="muted">Click any step for the file path and what it enforces.</p>
              </div>
              <div className={styles.loop} role="tablist">
                {MADARX_LOOP.map(s => (
                  <button key={s.id} role="tab" aria-selected={mStep===s.id} className={`${styles.loopStep} ${mStep===s.id?styles.on:''}`} onClick={() => setMStep(s.id)}>
                    <b>{s.tag}</b>
                    <span>{s.title}</span>
                    <small>{s.blurb}</small>
                  </button>
                ))}
              </div>
              <div className={styles.loopExplain}>
                {(() => {
                  const s = MADARX_LOOP.find(x => x.id === mStep);
                  return (
                    <>
                      <h4>{s.tag} · {s.title} <em>· {s.detail.ref}</em></h4>
                      {s.detail.body.map((p,i) => <p key={i}>{p}</p>)}
                    </>
                  );
                })()}
              </div>
            </div>

            <div className={`${styles.panel} ${styles.dark}`}>
              <div className={styles.panelTitle}><b>Risk tiers</b><small>lib/tools/registry.js</small></div>
              <div className={styles.body}>
                <ul className="flat">
                  <li><code>low</code> · runs without approval (reads, web_search).</li>
                  <li><code>medium</code> · gated only when trust mode is <code>approval_required</code>.</li>
                  <li><code>high</code> · gated in approval mode; auto-trust may run it.</li>
                  <li><code>irreversible_external</code> · <b>always</b> creates an approval row. Auto-trust never bypasses.</li>
                  <li><code>blocked</code> · refused regardless of mode (e.g. <code>rm -rf /</code>, force-push to main, raw <code>DROP DATABASE</code> via shell).</li>
                </ul>
                <p className="muted">The <code>irreversible_external</code> tier is the load-bearing guarantee: an LLM on auto-trust cannot perform irreversible external writes without a human in the loop. <code>action_type</code> begins <code>main_agent_tool_irreversible:</code> and is bound by an input signature so the same approval cannot be replayed.</p>
              </div>
            </div>
          </section>

          {/* Approval simulator */}
          <section className={`${styles.section} ${styles.single}`}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>Approval simulator</b><small>capabilities · trust mode · score</small></div>
              <div className={styles.modeRow} role="tablist" aria-label="trust mode">
                <button className={trustMode==='approval_required'?styles.on:''} onClick={() => setTrustMode('approval_required')}>approval required · default</button>
                <button className={trustMode==='auto'?`${styles.on} ${styles.danger}`:''} onClick={() => setTrustMode('auto')}>auto-trust · loosened</button>
                <button className={trustMode==='manual'?styles.on:''} onClick={() => setTrustMode('manual')}>manual · every risky asks</button>
              </div>
              <div className={styles.simGrid}>
                <div className={styles.toggles}>
                  {TOOL_SIM.map(t => {
                    const on = enabled.has(t.id);
                    const d  = decideTool(t, trustMode);
                    return (
                      <label key={t.id} className={styles.toggleRow}>
                        <input type="checkbox" checked={on} onChange={() => toggleTool(t.id)} aria-label={`toggle ${t.id}`}/>
                        <div>
                          <b><code>{t.id}</code></b>
                          <small>{t.why}</small>
                          <small className={styles.scopeTag}>scope: {t.scope} · category: {t.cat}</small>
                        </div>
                        <span className={`${styles.riskTag} ${styles[RISK_CLASS[t.risk]]}`}>{RISK_LABEL[t.risk]}</span>
                        <span className={`${styles.riskTag} ${d.decision==='approval'?styles.irrev:styles.low}`} title={d.why}>
                          {on ? (d.decision==='approval'?'APPROVE':'EXECUTE') : 'OFF'}
                        </span>
                      </label>
                    );
                  })}
                </div>
                <div className={styles.score}>
                  <div>
                    <b>guard score</b>
                    <div className={styles.scoreBig}><em>{sim.guardScore}</em><span style={{color:'#9a9a9d',fontSize:14}}> / 100</span></div>
                  </div>
                  <div className={styles.scoreBar}><i style={{width:`${sim.guardScore}%`}}/></div>
                  <ul className={styles.scoreList}>
                    <li><b>enabled</b><span>{enabledTools.length} tool(s)</span></li>
                    <li><b>auto-execute</b><span>{sim.exec}</span></li>
                    <li><b>approval-gated</b><span>{sim.gated}</span></li>
                    <li><b>irreversible</b><span>{sim.irrev}</span></li>
                    <li><b>trust mode</b><span>{trustMode}</span></li>
                  </ul>
                  <p style={{fontSize:11,color:'#9a9a9d',margin:0,lineHeight:1.5}}>
                    {trustMode === 'auto'
                      ? 'auto-trust bypasses medium/high gates — irreversible_external still asks.'
                      : trustMode === 'manual'
                      ? 'manual mode forces approval on every non-low call.'
                      : 'approval_required gates everything above low; irreversible always gates.'}
                  </p>
                </div>
              </div>
            </div>
          </section>
        </>
      )}

      {/* UPGRADE */}
      {tab === 'upgrade' && (
        <>
          <section className={styles.section}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>Closing the gap to Hermes</b><small>without losing the audit floor</small></div>
              <div className={styles.body}>
                <p>The blueprint: keep madarX&apos;s approval contract (<code>approval_requests</code> row, one-time consumption, signature-bound, hardline never-allowed list, <code>irreversible_external</code> always gated) as immovable invariants — and layer Hermes-class capabilities on top.</p>
                <ul className="flat">
                  <li><b>Provider router + credential pools.</b> Pluggable provider strategy in <code>lib/agent-engines.js</code>: per-job model override (mirroring Hermes cron jobs), per-provider key rotation, cost + rate awareness.</li>
                  <li><b>Pluggable memory providers.</b> New <code>lib/memory/</code> module with built-in, Honcho-style, and Mem0-style providers. Fenced memory block injected in <code>lib/agent-memory-context.js</code>; opt-out for scheduled runs by default.</li>
                  <li><b>Skill lifecycle + progressive loading.</b> <code>workspace/skills/&lt;name&gt;/SKILL.md</code> with <code>skills_list</code> / <code>skill_view</code> tools; only the index appears in the prompt until a skill is opened.</li>
                  <li><b>Session transcript FTS.</b> Add a <code>chat_messages_fts</code> virtual table; expose <code>session_search</code> as a low-risk tool.</li>
                  <li><b>Platform adapter abstraction.</b> New <code>lib/gateway/</code> with a <code>MessageEvent</code> shape; WhatsApp, Slack, Discord, Email become symmetrical adapters. The same loop reaches every surface.</li>
                  <li><b>Durable gateway session cache.</b> Per (platform, chat, thread, user) record in SQLite, with a process-cached <code>AIAgent</code>-equivalent in front. Survives Next.js hot-reload.</li>
                  <li><b>Subagent isolation + concurrency.</b> Promote <code>lib/delegation.js</code> + <code>delegation-contracts.js</code> to first-class: explicit max depth, max concurrent children per parent, restricted toolsets, summary-only return.</li>
                  <li><b>MCP dynamic tool registry.</b> Tools registered at runtime by MCP servers join <code>MARKETPLACE_TOOLS</code> with a <code>source:&apos;mcp&apos;</code> tag; risk tier required at registration time.</li>
                  <li><b>Cron delivery semantics.</b> Bring Hermes <code>delivery</code> + <code>no_agent</code> + per-job <code>toolsets</code>/<code>model</code>/<code>script</code> into <code>schedules</code> rows; fresh agent per run.</li>
                  <li><b>Approval UX parity.</b> Single approval inbox (UI + WhatsApp) with one-tap <code>/approve</code> · <code>/deny</code>. TTL, expiry, and a smart classifier that may auto-approve <i>only</i> low-risk after sustained green history.</li>
                  <li><b>Hardline blocklist + smart classifier.</b> Mirror <code>tools/approval.py</code> hardline patterns into <code>lib/tools/safety.js</code> as a non-overridable set, separate from risk-tier gating.</li>
                  <li><b>Context compression + parent-session lineage.</b> Lossy compress old turns above a token threshold; child sessions remember their parent <code>session_id</code> for audit lineage.</li>
                  <li><b>Verification gates on artifacts.</b> Visual QA (screenshot diff for generated UI), build/test gates for generated code, before any <code>artifact</code> is marked <i>shippable</i>.</li>
                </ul>
              </div>
            </div>

            <div className={`${styles.panel} ${styles.dark}`}>
              <div className={styles.panelTitle}><b>Approval contract</b><small>immovable invariants</small></div>
              <div className={styles.body}>
                <ul className="flat">
                  <li>Every risky local command, file write, external send, deploy, secret read → <code>approval_requests</code> row.</li>
                  <li>Row fields: <code>action_type</code>, payload summary, <code>risk</code>, <code>requested_by</code>, <code>ttl</code>, <code>status</code>, input-signature binding.</li>
                  <li>One-time consumed <code>approval_id</code> — never replayable.</li>
                  <li>Hardline never-allowed list short-circuits before the approval gate.</li>
                  <li>Smart classifier may <i>only</i> auto-approve <code>low</code>; <code>irreversible_external</code> can never be auto-approved.</li>
                  <li>Approvals via dashboard or WhatsApp; every decision lands in <code>audit_logs</code>.</li>
                  <li>Heartbeat tasks paused in <code>approval_required</code> status resume on consumption, with the original input signature.</li>
                </ul>
              </div>
            </div>
          </section>

          <section className={`${styles.section} ${styles.single}`}>
            <div className={styles.panel}>
              <div className={styles.panelTitle}><b>Recommended phases</b><small>delivery order</small></div>
              <div className={styles.roadmap}>
                {PHASES.map(p => (
                  <div key={p.k} className={styles.phase}>
                    <b>{p.k}</b>
                    <span>{p.name}</span>
                    <ul>{p.bullets.map(x => <li key={x}>{x}</li>)}</ul>
                  </div>
                ))}
              </div>
            </div>
          </section>
        </>
      )}

      {/* shared sections — always visible */}
      <section className={`${styles.section} ${styles.single}`}>
        <div className={styles.panel}>
          <div className={styles.panelTitle}><b>Comparison matrix</b><small>Hermes · Paperclip · madarX</small></div>
          <div style={{overflowX:'auto'}}>
            <table className={styles.matrix}>
              <thead>
                <tr>
                  <th>Capability</th>
                  <th>Hermes Agent</th>
                  <th>Paperclip</th>
                  <th>madar<span className={styles.mark}>X</span> OS</th>
                </tr>
              </thead>
              <tbody>
                {MATRIX_ROWS.map(r => (
                  <tr key={r.k}>
                    <th>{r.k}</th>
                    <td>{r.hermes}</td>
                    <td>{r.paper}</td>
                    <td>{r.madarx}</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
        </div>
      </section>

      <section className={`${styles.section} ${styles.single}`}>
        <div className={styles.panel}>
          <div className={styles.panelTitle}><b>Source-file map</b><small>where the contracts live in this repo</small></div>
          <div className={styles.sourceList}>
            {SOURCE_MAP.map(s => (
              <button key={s.path} className={`${styles.sourceCard} ${srcSel===s.path?styles.on:''}`} onClick={() => setSrcSel(s.path)}>
                <b>{s.name}</b>
                <code>{s.path}</code>
                <small>{s.note}</small>
              </button>
            ))}
          </div>
        </div>
      </section>

      <section className={`${styles.section} ${styles.single}`}>
        <div className={styles.panel}>
          <div className={styles.panelTitle}><b>Mission simulator</b><small>type a goal — see tools, approvals, heartbeat, audit</small></div>
          <div className={styles.simShell}>
            <div className={styles.simInput}>
              <p className={styles.simHelp}>This runs locally in the browser. It pattern-matches your prompt against the marketplace tool surface, then walks the approval / allowlist / heartbeat path the runtime would take in the active trust mode.</p>
              <textarea value={mission} onChange={e => setMission(e.target.value)} placeholder="e.g. read scripts/whatsapp-daemon.mjs, open a PR with a fix, email me a summary"/>
              <div className={styles.simRow}>
                <button onClick={runMission}>Simulate mission</button>
                <button className={styles.secondary} onClick={() => { setMission(''); setMissionResult(null); }}>Clear</button>
                <button className={styles.secondary} onClick={() => setMission('Read lib/runtime.js, run npm test, then merge PR #42 in kareemodev777/madarx-os and email ak@madarx.io.')}>Sample · risky mission</button>
              </div>
              <p className={styles.simHelp}>Active trust mode: <code>{trustMode}</code> · toggle modes in the madarX tab.</p>
            </div>
            <div className={styles.simOut}>
              {!missionResult ? (
                <div className={styles.simBlock}>
                  <b>Output</b>
                  <p className="empty">Press <b>Simulate mission</b> to see the runtime decision trace.</p>
                </div>
              ) : (
                <>
                  <div className={styles.simBlock}>
                    <b>Tools selected ({missionResult.rows.length})</b>
                    <ul>
                      {missionResult.rows.map(r => (
                        <li key={r.tool.id}>
                          <code>{r.tool.id}</code> · <span className={styles.scopeTag}>{r.tool.risk}</span> → <b style={{color:r.decision.decision==='approval'?'#C2452A':'#0F0F11'}}>{r.decision.decision === 'approval' ? 'APPROVAL' : 'EXECUTE'}</b>
                          <div className={styles.simHelp}>{r.decision.why}</div>
                        </li>
                      ))}
                    </ul>
                  </div>
                  <div className={styles.simBlock}>
                    <b>Approvals required ({missionResult.approvals.length})</b>
                    {missionResult.approvals.length === 0 ? (
                      <p className="empty">None — all selected tools are auto-executing under <code>{trustMode}</code>.</p>
                    ) : (
                      <ul>
                        {missionResult.approvals.map(t => (
                          <li key={t.id}>
                            <code>main_agent_tool{t.risk==='irreversible_external'?'_irreversible':''}:{t.id}</code> · scope: <span className={styles.scopeTag}>{t.scope}</span>
                          </li>
                        ))}
                      </ul>
                    )}
                  </div>
                  <div className={styles.simBlock}>
                    <b>Heartbeat queue trace</b>
                    <ul>
                      {missionResult.heartbeat.map((h,i) => (
                        <li key={i}>
                          <code>{h.status}</code> — <span className={styles.scopeTag}>{h.desc}</span>
                        </li>
                      ))}
                    </ul>
                  </div>
                  <div className={styles.simBlock}>
                    <b>Audit events written</b>
                    <ul className="audit">
                      {missionResult.audit.map((a,i) => <li key={i}><em>·</em> {a}</li>)}
                    </ul>
                  </div>
                </>
              )}
            </div>
          </div>
        </div>
      </section>

      <footer className={styles.footer}>
        <span>madarX OS · architecture · {new Date().toISOString().slice(0,10)}</span>
        <span><a className={styles.home} href="/" style={{textDecoration:'none'}}>← console</a> &nbsp; <a className={styles.home} href="/runtime" style={{textDecoration:'none'}}>runtime diagnostics</a></span>
      </footer>
    </main>
  );
}
