diff --git a/src/App.tsx b/src/App.tsx index 04c98e2..c6ed987 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,6 +9,7 @@ const UseCasesPage = lazy(() => import('./pages/UseCasesPage').then(m => ({ defa const MultiAgentMemoryPage = lazy(() => import('./pages/MultiAgentMemoryPage').then(m => ({ default: m.MultiAgentMemoryPage }))) const PersonalAssistantMemoryPage = lazy(() => import('./pages/PersonalAssistantMemoryPage').then(m => ({ default: m.PersonalAssistantMemoryPage }))) const MultiAgentSharedContextPage = lazy(() => import('./pages/MultiAgentSharedContextPage').then(m => ({ default: m.MultiAgentSharedContextPage }))) +const StatewaveVsMem0Page = lazy(() => import('./pages/StatewaveVsMem0Page').then(m => ({ default: m.StatewaveVsMem0Page }))) const ConnectorsPage = lazy(() => import('./pages/ConnectorsPage').then(m => ({ default: m.ConnectorsPage }))) const DevelopersPage = lazy(() => import('./pages/DevelopersPage').then(m => ({ default: m.DevelopersPage }))) const CookiesPage = lazy(() => import('./pages/CookiesPage').then(m => ({ default: m.CookiesPage }))) @@ -36,6 +37,7 @@ export default function App() { } /> } /> } /> + } /> } /> } /> } /> diff --git a/src/components/Footer.tsx b/src/components/Footer.tsx index ddfdcc9..38e8d0d 100644 --- a/src/components/Footer.tsx +++ b/src/components/Footer.tsx @@ -27,6 +27,7 @@ export function Footer() {
  • How it works
  • Why Statewave
  • Use Cases
  • +
  • vs Mem0
  • Connectors
  • + + + + + + + + + + + + + + ) +} + +function ArrowIcon() { + return ( + + ) +} + +function GitHubIcon() { + return ( + + ) +} + +function HeroBundleVisual() { + return ( +
    + + ) +} + +function HeroStatStrip() { + const stats = [ + { value: '0.905', label: 'LoCoMo, n=1,540' }, + { value: '4', label: 'ranking signals, deterministic' }, + { value: '708', label: 'unit tests · 56 evals' }, + { value: '0', label: 'API keys to run' }, + ] + + return ( +
    +
    + {stats.map((stat, i) => ( +
    + {i > 0 &&
    + ))} +
    +
    + ) +} + +/* ─── The gap ─────────────────────────────────────────────────────────────── */ + +function GapSection() { + return ( +
    + {/* --soft: this section's Statewave panel carries its own saturated + chart series color below, so the ambient wash stays behind the + data instead of washing over it — see .section-glow--soft. */} +
    + ) +} + +/* ─── How order is decided ───────────────────────────────────────────────── */ + +const SIGNALS = [ + { label: 'KIND PRIORITY', range: '3–10', note: 'typed profile facts outrank raw episodes' }, + { label: 'RECENCY', range: '0–5', note: 'linear by age, newest scores highest' }, + { label: 'TASK RELEVANCE', range: '0–8', note: 'lexical overlap plus cosine similarity' }, + { label: 'TEMPORAL VALIDITY', range: '−4…+3', note: 'valid facts gain +3, expired ones lose 4' }, +] + +function SignalsStrip() { + return ( +
    +
    +
    + How order is decided + score = priority + recency + relevance + validity +
    +
    + {SIGNALS.map((s, i) => ( +
    +
    {s.label}
    +
    {s.range}
    +
    {s.note}
    +
    + ))} +
    +
    +
    + ) +} + +/* ─── Full comparison table ──────────────────────────────────────────────── */ + +interface CompareRow { + cap: string + m0: string + sw: string +} + +const RETRIEVAL_ROWS: CompareRow[] = [ + { cap: 'How context is selected', m0: 'Top-k embedding-nearest as the candidate pool, then re-scored', sw: 'Deterministic assembly, additively scored to a token budget' }, + { cap: 'Ranking signals', m0: 'Vector similarity fused with keyword and entity boosts; optional reranker', sw: 'Kind priority, recency, task relevance, temporal validity' }, + { cap: 'Stale / expired facts', m0: 'Optional expiry, off by default; no confidence score or decay', sw: 'Penalised −4 and dropped before assembly' }, + { cap: 'Same query, same result', m0: 'Varies with the index', sw: 'Byte-identical bundle every run' }, +] + +const GOVERNANCE_ROWS: CompareRow[] = [ + { cap: 'Proof of what the agent saw', m0: 'None', sw: 'Immutable, ULID-addressable receipt with an integrity hash' }, + { cap: 'Policy on the read path', m0: 'Implement it in your app', sw: 'Declarative bundles: deny or redact by label and caller' }, + { cap: 'Provenance to source', m0: 'Session ids automatic; links to source documents by hand', sw: 'Source episode ids, confidence, and validity per memory' }, + { cap: 'Subject deletion (GDPR)', m0: 'One call per subject; change history is retained', sw: 'One call clears episodes, memories, and receipts' }, +] + +const OPS_ROWS: CompareRow[] = [ + { cap: 'Storage', m0: 'Pluggable vector stores', sw: 'Postgres and pgvector, nothing else to run' }, + { cap: 'Graph / relationship memory', m0: 'OSS: graph config removed, relations no longer returned. Platform: graph affects the score, no graph payload to query', sw: 'Typed memories with provenance, no graph tier' }, + { cap: 'Interface', m0: 'Python and TypeScript SDKs, REST, CLI, hosted MCP server', sw: 'REST, Python and TypeScript SDKs, MCP server, connectors' }, + { cap: 'License', m0: 'Apache 2.0 core, paid platform', sw: 'Apache 2.0 throughout, runs fully offline' }, +] + +function CompareGroup({ title, rows }: { title: string; rows: CompareRow[] }) { + return ( + <> +
    + {title} +
    + + {/* Desktop: table. Mobile: stacked cards (see block below) — same + rationale as WhyPage's comparison table: a 3-column grid on a + phone either clips the Statewave column or shrinks everything + to unreadable widths. */} +
    + {rows.map((row) => ( +
    +
    {row.cap}
    +
    {row.m0}
    +
    {row.sw}
    +
    + ))} +
    + +
    + {rows.map((row) => ( +
    +

    {row.cap}

    +
    +
    +
    +
    {row.m0}
    +
    +
    +
    +
    {row.sw}
    +
    +
    +
    + ))} +
    + + ) +} + +function ComparisonSection() { + return ( +
    +
    + + Where each capability lives + +

    + Both are memory layers for agents. They diverge on what the runtime + enforces and what you have to build or pay for. +

    +
    + +
    +
    +
    CAPABILITY
    +
    + + Mem0 +
    +
    + + Statewave +
    +
    + + + + +
    + +

    + Mem0 v3 deleted the open-source graph-store drivers (Neo4j, Memgraph, + Kuzu, Apache AGE, Neptune); the graph_store config block + is no longer read, and search results no longer carry a{' '} + relations field. On the managed Platform there is no + graph store to configure and no graph payload in the response — + entity connections reach the caller only through the combined{' '} + score. Rows reflect each product's public docs and + source as of August 2026. +

    + +
    +
    +
    + + Reach for Mem0 when +
    +

    + You are giving a single assistant persistent memory, want a mature + SDK with broad framework integrations, and one identity scope + — user, agent, run, or app — describes how your data + partitions. +

    +
    + +
    + +
    +
    + ) +} + +/* ─── Worked example ─────────────────────────────────────────────────────── */ + +function CodePanel({ + badge, + title, + lines, + calloutTone, + callout, +}: { + badge: React.ReactNode + title: string + lines: React.ReactNode[] + calloutTone: 'muted' | 'accent' + callout: React.ReactNode +}) { + return ( +
    +
    + {badge} + {title} +
    +
    + {lines.map((line, i) =>
    {line}
    )} +
    +
    + {callout} +
    +
    + ) +} + +function WorkedExampleSection() { + return ( +
    + + One returning customer, two runtimes + + +

    + A support agent resumes a customer thread three weeks later. In + between, the customer moved house and pasted a card number into an + earlier message. The same episode history runs through each system. +

    + +
    + } + title="Mem0 · search by id" + calloutTone="muted" + lines={[ + # retrieve context for the reply, + client.search("where do I ship it",, +   user_id="cust_5521"), + # ranked by relevance, +
    +
    + old address · Elm St + stale, no expiry set +
    +
    + card 4242 4242 ···· + pii, no policy gate +
    +
    , + ]} + callout="Expiry, redaction and any policy on the read path are left to the application to build and keep correct." + /> + + } + title="Statewave · assemble + govern" + calloutTone="accent" + lines={[ + # assemble a ranked, bounded bundle, + assemble(subject="cust_5521",, +   task="where do I ship it", budget=1500), +
    +
    + new address · Oak Ave + valid +3 · ranked #1 +
    +
    + old address · Elm St + −4 expired · dropped +
    +
    + card ●●●● ●●●● ···· + label:pii · redacted +
    +
    , + ]} + callout="The runtime decides: the superseded address scores out, the card is redacted by its policy label, and the receipt stores an integrity hash of exactly what was delivered." + /> +
    +
    + ) +} + +/* ─── Every call leaves a receipt ────────────────────────────────────────── */ + +const MECHANISM_CARDS = [ + { icon: '{}', title: 'Policy engine', body: 'Content-hashed YAML or JSON bundles. Deny or redact by sensitivity label and caller identity; log_only records each decision so you can audit a policy before enforcing it.' }, + { icon: '#', title: 'Sensitivity labels', body: 'Per-memory pii, financial, and secret tags in a GIN-indexed array, so policy filters run inside the query rather than after it.' }, + { icon: '←', title: 'Full provenance', body: 'Every compiled memory keeps the source episode ids, confidence score, and validity window it was derived from.' }, + { icon: '⌫', title: 'Subject deletion', body: 'One GDPR-style call erases every episode, memory, and receipt for a subject, leaving no orphaned rows behind.' }, +] + +function ReceiptCard() { + const rows = [ + { label: 'profile_fact', meta: 'conf 0.92 · valid', src: '[ep_4, ep_9]', dim: false }, + { label: 'procedure', meta: 'conf 0.88 · valid', src: '[ep_2]', dim: false }, + { label: 'episode_summary', meta: 'superseded', src: 'dropped', dim: true }, + ] + + return ( +
    +