Skip to content

useboardwalk/orca

Repository files navigation

Orca

ci

Injection-resilient, multi-tenant agentic RAG with non-custodial on-chain action-prep. Orca answers questions over an ingested knowledge base with cited evidence, reads live on-chain data, and prepares — never submits — transactions the user signs in their own wallet. Boardwalk is the first tenant; the core is a generic platform behind a TenantAdapter contract.

The design premise: an LLM will eventually obey instructions planted in retrieved content, so don't trust it not to. Safety is deterministic code, not model alignment — a taint gate that makes obedience not matter for the action path.

flowchart LR
  W["Widget · Playground · Telegram bot"] --> R
  subgraph serve ["Serving — TypeScript"]
    R["Deterministic router"] --> L["Agent loop<br/>+ taint gate"]
  end
  subgraph ingest ["Ingestion — Python engine"]
    C["Connectors<br/>docs_mdx · openapi"]
  end
  DB[("Neon Postgres<br/>pgvector + FTS · RLS")]
  C --> DB
  L -->|searchKnowledge| DB
  L -->|"getLaunch — live read"| API["Boardwalk API"]
  L -->|"prepare* — action tools"| TX["Unsigned tx bundle"]
  TX --> U["User signs in their own wallet"]
Loading

The taint gate

Retrieved content carries a trust tier (tier-0 first-party docs → tier-2 uploads and external content). The moment a tool returns content at or above the taint tier, the turn is tainted — and while tainted, action tools are removed from the set the model is even offered. Taint is monotonic and enforced in code the model can't argue with. A live tainted turn:

router  tier=tier2 reasons=["action-verb","live-lexicon"]
step 1  actionToolsActive=true   → searchKnowledge → taintedAfter=true   (tier-2 chunk retrieved)
step 2  actionToolsActive=false  ← action tools left the model's reach
final   tainted=true             no signable action surfaced; answer grounded in tier-0 sources

Defense in depth behind the gate: spotlighted/datamarked untrusted chunks, schema-enforced field provenance (an action address must appear verbatim in the user's typed message), non-custodial by construction, an egress allowlist, RLS tenant isolation, and spend caps. The full threat model, prior art (CaMeL, spotlighting), and stated boundaries — including what is not defended — are in SECURITY.md.

Measured

Injection resistance, end to end against the live serving path (details and caveats in SECURITY.md and packages/bench/RESULTS.md):

Measurement Result
Deterministic gate floor — model scripted to obey the injection (poison-asr.test.ts, per PR) action ASR 0
AgentDojo attack wrappers × claim-to-attacker goal, ingested tier-2, wallet attached action-hijack ASR 0/5, gate engaged 5/5
Live red-team (promptfoo) vs a running /v1/chat 5/5 passed
Own-corpus PoisonedRAG-style sweep — injected instructions, up to 8/8 retrieval flooding ASR 0
Same sweep — injected facts with no true counter-source in the corpus ASR ~1.0 — undefended, documented as the boundary

Retrieval quality (BEIR, the production spine: hybrid RRF → voyage rerank-2.5):

nDCG@10 Orca hybrid Orca + rerank best published single-model first stage
SciFact 0.802 0.807 0.784 — NV-Embed-v1 (7B)
FiQA-2018 0.565 0.629 0.631 — NV-Embed-v1 (7B)

Layout

  • apps/api — Next.js route handlers (/v1/chat, /v1/search), the Telegram bot webhook, /playground (port 3000)
  • apps/web — admin console (better-auth): tenant settings, knowledge, usage, feedback (port 3001)
  • packages/core — router, agent loop + taint gate, tools, retrieval, guardrails
  • packages/db — Drizzle schema (the cross-language contract), RLS policy, withTenant
  • packages/tenant-boardwalk — the Boardwalk tenant adapter (system prompt + tools) + its evals
  • packages/widget — embeddable React chat widget + a dependency-free typed stream client
  • packages/bench — BEIR benchmarks + the injection/poison-resistance harness
  • packages/config — shared tsconfig, the zod env contract, the model catalog
  • evals/redteam — live red-team (promptfoo + AgentDojo) with committed results
  • engine/ — Python (uv) connector framework: docs_mdx, openapi

Develop

Node 22 (.nvmrc), pnpm via corepack, Python via uv.

pnpm install
pnpm lint:ci                               # biome lint + format (what CI runs)
pnpm -r typecheck && pnpm -r test          # TS (pglite in-memory Postgres; no services needed)
node scripts/lint-tenant-access.mjs        # tenant tables only reachable via withTenant
cd engine && uv run pytest                 # Python, offline

Live paths need apps/api/.env.local + engine/.env (see .env.example): an AI Gateway key and two Neon URLs — DATABASE_URL (serving) whose role must be non-owner + NOBYPASSRLS (the owner role bypasses RLS, making tenant isolation a no-op), and ADMIN_DATABASE_URL (owner role) used only for migrations and role setup, never for serving. Boot fails loud if the serving role can bypass RLS.

pnpm --filter @orca/db exec tsx src/migrate.ts        # ADMIN_DATABASE_URL: schema + RLS + serving role
pnpm --filter @orca/db exec tsx scripts/seed-dev.mts  # dev tenant + knowledge base
cd engine && uv run engine ingest docs_mdx && uv run engine ingest openapi
pnpm --filter @orca/api dev                           # http://localhost:3000/playground

About

Agentic RAG system that supports encoding onchain txs

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors