// Agents view — non-technical playbook builder for L1/L2 investigation assistants. // // Agents here are declarative specs (trigger, steps, data access, outputs, guardrails) — // plain-English playbooks an analyst can author without writing code. The AI execution // layer isn't wired up yet (see the AI Assist chat in the top bar); this is where the // instructions it will run get authored and managed in the meantime. const { useState: useAgState, useMemo: useAgMemo, useEffect: useAgEffect } = React; const AGENT_STORAGE_KEY = "mossad_agents_v1"; const TRIGGER_OPTIONS = [ { value: "new_alert", label: "When a new alert is created" }, { value: "case_open", label: "When an analyst opens a case" }, { value: "escalation", label: "When a case is escalated to L2" }, { value: "scheduled", label: "On a schedule (e.g. daily)" }, { value: "on_demand", label: "On demand — analyst asks" }, ]; const DATA_SOURCE_OPTIONS = [ { key: "transactions", label: "Transaction history" }, { key: "kyc", label: "Customer / KYC profile" }, { key: "priorCases", label: "Prior alerts & cases" }, { key: "watchlists", label: "Watchlists" }, { key: "riskRatings", label: "Risk ratings" }, { key: "network", label: "Network / entity links" }, { key: "sarHistory", label: "SAR history" }, ]; const OUTPUT_OPTIONS = [ { key: "summary", label: "Draft summary" }, { key: "riskLevel", label: "Recommended risk level" }, { key: "nextAction", label: "Suggested next action" }, { key: "sarDraft", label: "Draft SAR narrative" }, { key: "citations", label: "Evidence citations" }, ]; const GUARDRAIL_OPTIONS = [ { key: "citeEvidence", label: "Always cite source records" }, { key: "noAutoAction", label: "Never auto-close, auto-escalate, or auto-file — analyst approves every action" }, { key: "flagUncertainty", label: "Flag uncertainty instead of guessing" }, { key: "humanSignoff", label: "Require analyst sign-off before output is used" }, ]; const AGENT_STATUS_PILL = { active: "ok", draft: "med", paused: "warn" }; const AGENT_TEMPLATES = { l1_triage: { name: "L1 Alert Triage Assistant", level: "L1", status: "active", description: "Gives first-line analysts a fast, structured read on a new alert so they can triage in seconds instead of minutes.", trigger: "new_alert", steps: [ "Summarize the alert: which rule fired, why, and the transaction(s) involved.", "Pull the customer's profile and recent transaction history for context.", "Check if the customer or counterparties appear on any watchlist.", "Look for prior alerts or cases on this entity in the last 12 months.", "Suggest a disposition — escalate to L2, request more info, or close as false positive — with reasoning.", ], dataSources: ["transactions", "kyc", "priorCases", "watchlists"], outputs: ["summary", "riskLevel", "nextAction", "citations"], guardrails: ["citeEvidence", "noAutoAction", "flagUncertainty", "humanSignoff"], }, l2_deepdive: { name: "L2 Deep-Dive Investigator", level: "L2", status: "active", description: "Helps senior investigators build a full picture of an entity before deciding whether to escalate to a SAR.", trigger: "escalation", steps: [ "Build a timeline of the entity's transactions and alerts across the case window.", "Identify related entities — shared counterparties, addresses, devices — and map the network.", "Compare transaction patterns against known typologies (structuring, layering, rapid movement of funds).", "Summarize findings from prior case notes and comments on this entity.", "Draft an investigation narrative with supporting evidence for the case file.", ], dataSources: ["transactions", "kyc", "priorCases", "watchlists", "riskRatings", "network", "sarHistory"], outputs: ["summary", "riskLevel", "nextAction", "sarDraft", "citations"], guardrails: ["citeEvidence", "noAutoAction", "flagUncertainty", "humanSignoff"], }, sar_drafter: { name: "SAR Narrative Drafter", level: "L2", status: "draft", description: "Turns an investigator's case evidence into a structured first-draft SAR narrative for review.", trigger: "on_demand", steps: [ "Gather the case's alerts, transactions, and investigator notes.", "Organize findings into the standard SAR narrative structure (who / what / when / where / why suspicious).", "Cite every factual claim to a specific transaction, alert, or note.", "Flag any gaps in evidence the investigator should fill in before filing.", ], dataSources: ["transactions", "kyc", "priorCases", "sarHistory"], outputs: ["sarDraft", "citations"], guardrails: ["citeEvidence", "noAutoAction", "humanSignoff"], }, periodic_review: { name: "Periodic Review Assistant", level: "L1", status: "draft", description: "Summarizes what's changed for customers due for periodic review, so analysts start with context instead of a blank file.", trigger: "scheduled", steps: [ "List customers due for periodic review in the next 7 days.", "Summarize changes in risk score, transaction behavior, and KYC data since the last review.", "Flag anything that should accelerate the review — new adverse media, watchlist hits, etc.", ], dataSources: ["kyc", "riskRatings", "watchlists", "transactions"], outputs: ["summary", "riskLevel"], guardrails: ["citeEvidence", "flagUncertainty", "humanSignoff"], }, }; function loadAgents() { try { const raw = localStorage.getItem(AGENT_STORAGE_KEY); if (raw) return JSON.parse(raw); } catch (e) {} return Object.entries(AGENT_TEMPLATES).map(([id, tpl], i) => ({ id, ...tpl, updated: new Date(Date.now() - i * 86400000).toISOString(), })); } function saveAgents(agents) { try { localStorage.setItem(AGENT_STORAGE_KEY, JSON.stringify(agents)); } catch (e) {} } function triggerLabel(v) { return (TRIGGER_OPTIONS.find(t => t.value === v) || {}).label || v; } function ChkChip({ label, on, onClick }) { return ( ); } function AgentEditor({ agent, onSave, onCancel }) { const isNew = !agent; const [form, setForm] = useAgState(() => agent ? { ...agent } : { name: "", level: "L1", status: "draft", description: "", trigger: "new_alert", steps: [""], dataSources: [], outputs: [], guardrails: ["citeEvidence", "noAutoAction", "humanSignoff"], }); const [error, setError] = useAgState(null); const update = (key, value) => setForm(f => ({ ...f, [key]: value })); const toggleIn = (key, value) => setForm(f => ({ ...f, [key]: f[key].includes(value) ? f[key].filter(v => v !== value) : [...f[key], value], })); const updateStep = (i, value) => setForm(f => ({ ...f, steps: f.steps.map((s, idx) => idx === i ? value : s) })); const addStep = () => setForm(f => ({ ...f, steps: [...f.steps, ""] })); const removeStep = (i) => setForm(f => ({ ...f, steps: f.steps.length > 1 ? f.steps.filter((_, idx) => idx !== i) : f.steps })); const moveStep = (i, dir) => setForm(f => { const steps = [...f.steps]; const j = i + dir; if (j < 0 || j >= steps.length) return f; [steps[i], steps[j]] = [steps[j], steps[i]]; return { ...f, steps }; }); const applyTemplate = (tpl) => setForm(f => ({ ...f, ...tpl, steps: [...tpl.steps] })); const submit = (status) => { if (!form.name.trim()) { setError("Give the agent a name."); return; } if (!form.steps.some(s => s.trim())) { setError("Add at least one step."); return; } setError(null); onSave({ ...form, status, steps: form.steps.map(s => s.trim()).filter(Boolean), updated: new Date().toISOString(), }); }; return (

{isNew ? "New agent" : "Edit agent"}

{error &&
{error}
} {isNew && (
{Object.values(AGENT_TEMPLATES).map(tpl => ( ))}
)}
1 Basics
update("name", e.target.value)} placeholder="e.g. L1 Alert Triage Assistant" />
update("description", e.target.value)} placeholder="e.g. Summarizes a new alert and suggests a disposition" />
2 When should it run?
{TRIGGER_OPTIONS.map(t => ( update("trigger", t.value)} /> ))}
3 What should it do? — plain-English steps, in order
{form.steps.map((s, i) => (
{i + 1} updateStep(i, e.target.value)} placeholder="e.g. Check if the counterparty appears on any watchlist" />
))}
4 What can it read? — read-only access
{DATA_SOURCE_OPTIONS.map(o => ( toggleIn("dataSources", o.key)} /> ))}
5 What should it produce?
{OUTPUT_OPTIONS.map(o => ( toggleIn("outputs", o.key)} /> ))}
6 Guardrails
{GUARDRAIL_OPTIONS.map(o => ( toggleIn("guardrails", o.key)} /> ))}
); } function AgentCard({ agent, onEdit, onDuplicate, onToggleStatus, onDelete }) { return (
{agent.name}
{triggerLabel(agent.trigger)}
{agent.level} {agent.status}
{agent.description && (
{agent.description}
)}
{agent.steps.length} step{agent.steps.length === 1 ? "" : "s"}
    {agent.steps.slice(0, 3).map((s, i) =>
  1. {s}
  2. )} {agent.steps.length > 3 &&
  3. +{agent.steps.length - 3} more…
  4. }
{agent.dataSources.length > 0 && (
{agent.dataSources.map(k => ( {(DATA_SOURCE_OPTIONS.find(o => o.key === k) || {}).label || k} ))}
)}
); } function AgentsView() { const [agents, setAgents] = useAgState(loadAgents); const [editing, setEditing] = useAgState(null); const [levelFilter, setLevelFilter] = useAgState("all"); useAgEffect(() => { saveAgents(agents); }, [agents]); const filtered = useAgMemo(() => { if (levelFilter === "all") return agents; return agents.filter(a => a.level === levelFilter); }, [agents, levelFilter]); const handleSave = (payload) => { setAgents(list => { if (editing && editing.id) { return list.map(a => a.id === editing.id ? { ...a, ...payload } : a); } return [...list, { ...payload, id: `agent_${Date.now()}` }]; }); setEditing(null); }; const handleDuplicate = (agent) => { setAgents(list => [...list, { ...agent, id: `agent_${Date.now()}`, name: `${agent.name} (copy)`, status: "draft" }]); }; const handleToggleStatus = (agent) => { setAgents(list => list.map(a => a.id === agent.id ? { ...a, status: a.status === "active" ? "paused" : "active" } : a)); }; const handleDelete = (agent) => { if (!confirm(`Delete agent "${agent.name}"?`)) return; setAgents(list => list.filter(a => a.id !== agent.id)); }; if (editing) { return (
setEditing(null)} />
); } return (

Agents

Build step-by-step playbooks that help L1 analysts triage alerts faster and L2 investigators dig deeper — no code required. The AI execution layer isn't connected yet (see AI Assist in the top bar) — for now this is where each agent's instructions get defined for when it is.
{filtered.map(a => ( ))}
{filtered.length === 0 && (
No agents match this filter.
)}
); } Object.assign(window, { AgentsView });