Skip to content

Repository files navigation

Tantra — Visual AI Agent Orchestration


License: MIT Python FastAPI React Java PostgreSQL


Tantra is a self-hosted AI agent orchestration platform. Draw workflows on a visual canvas, connect nodes that call LLMs, run ReAct agent loops, retrieve from your knowledge base, and hit external APIs — then schedule them, deploy them as REST endpoints, and expose them as MCP tools. Every run is traced at the node level, recoverable across worker restarts, and scoped to isolated workspaces.

No LangChain. No LlamaIndex. The DAG executor, ReAct loop, chunking pipeline, and retrieval are written directly against provider SDKs — that is the engineering point.


What it does

Capability What it means in practice
Visual DAG canvas Draw workflows by dragging and wiring nodes — no YAML, no code required to compose
ReAct agent loop Bounded step + token budgets; real HTTP tool dispatch per ACTION: step; tolerant reply parsing
RAG pipeline PDF / TXT / Markdown upload → sentence chunking → Gemini embeddings (1536-dim HNSW) → hybrid retrieval
MCP client Any registered MCP server's tools appear in the node library and execute with response caching
MCP server Any deployed workflow is consumable as an MCP tool from Claude, Cursor, or any MCP client
Deploy as API One click → versioned REST endpoint with API-key auth, scoped per workspace
Cron scheduling QStash-powered tick fan-out; 30-min minimum granularity; documented constraint
Durable queue FOR UPDATE SKIP LOCKED PostgreSQL queue; lease heartbeats; bounded retry; crash recovery
Secrets vault AES-256-GCM encryption; workspace_id as AAD — cross-tenant decryption fails cryptographically
Multi-tenancy Pooled DB with Row-Level Security; two-layer enforcement (app + Postgres RLS)
SSE streaming Real-time run events pushed to the frontend as they happen; streams close on terminal status
AI canvas builder Chat with an assistant that edits the current workflow graph in-place

Canvas editor

Canvas editor — node palette, dotted grid, workflow toolbar

The canvas is built on React Flow (@xyflow/react). Each node type has a distinct coloured icon tile; the config inspector lives in a right-hand panel — clicking a node selects it, never expands it inline. Keyboard shortcuts, undo/redo history, and a floating zoom toolbar are wired in. The AI assistant panel docks into the same right-hand rail and edits the graph live via the API.

Node library

The node palette lists every available node type grouped by category — triggers, AI, data, control flow, integrations. The search bar filters the list instantly as you type.

Tantra ships 15+ built-in node types:

Category Nodes
AI & Agents agent (ReAct loop) · llm (single LLM call)
Data & Retrieval rag_retrieve · transform · filter · merge
Control flow conditional · switch · loop · wait
Integrations http_tool · mcp_tool · integration (Gmail / GitHub / Slack via Composio or Clerk OAuth) · browser
Triggers manual_trigger · webhook_trigger
Agent-to-Agent a2a delegate (stretch goal)

Workflow dashboard

Workflows — create, filter, run, edit, delete

The workflows page lets you create, filter (All / Active / Idle), run, edit, and delete workflows. Import from the template gallery with one click — templates are ordinary workflows pushed through the same public API.


Knowledge bases

Knowledge — RAG knowledge base management

Upload PDF, TXT, or Markdown files into a named knowledge base. The ingestion pipeline chunks by sentence, generates 1536-dimensional Gemini embeddings, and stores them in pgvector with an HNSW index. Retrieval uses cosine similarity with an optional BM25 re-rank pass.


Operations — schedules & deployments

Operations — cron schedules and REST deployments

From the Operations tab you schedule any workflow on a cron cadence (QStash-powered, 30-min minimum) and deploy workflows as versioned REST endpoints. Each deployment gets a unique URL and an API key scoped to the workspace.


Settings

Settings — account, workspace, and API credentials

The settings page manages account info, workspace membership, and third-party provider credentials (Groq, Gemini, OpenAI). Credentials are stored encrypted (AES-256-GCM) and never returned in API responses.


Architecture

┌─────────────────────────────────────────────────────────────────┐
│                   React Frontend (Vite + TypeScript)             │
│          React Flow Canvas · TanStack Query · SSE streams        │
└──────────────────────────┬──────────────────────────────────────┘
                           │  REST + SSE
           ┌───────────────▼──────────────────┐
           │    Engine  (Python 3.12 / FastAPI) │
           │  DAG executor · ReAct agent loop  │
           │  RAG · MCP client+server · Vault  │
           │  Alembic migrations · Clerk / JWT │
           └──────┬───────────────────┬────────┘
    asyncpg       │                   │  HTTP (internal)
 ┌────────────────▼──────┐   ┌────────▼───────────────────────┐
 │ PostgreSQL 16          │   │  Scheduler (Java 21 / Spring)  │
 │ + pgvector (HNSW)      │   │  QStash tick → cron fan-out    │
 │ Durable queue (RLS)    │   │  /tick · /health · /webhooks   │
 └────────────────────────┘   └────────────────────────────────┘
          ▲  atomic claim + lease
 ┌────────┴──────────────────────────────────────────────────────┐
 │  Worker pool (4 concurrent) · heartbeat · retry · recovery    │
 └───────────────────────────────────────────────────────────────┘

Component map

Component Language / Framework Deployed on Port
Engine Python 3.12 / FastAPI Hugging Face Spaces (free CPU) 7860
Scheduler Java 21 / Spring Boot 3 Render free tier 8080
Worker Python 3.12 (in-process) Same container as engine
Frontend React 19 / Vite / TypeScript Vercel Hobby 3000 (local)
Database PostgreSQL 16 + pgvector Supabase free 5432
Queue clock QStash schedule Upstash free

Request flow — single execution

User triggers run
    │
    ▼
POST /api/v1/workflows/:id/run
    │   Engine validates the graph (Pydantic)
    │   Enqueues a Run row  (status=queued, dedup key)
    │
    ▼
Worker polls:  SELECT ... FOR UPDATE SKIP LOCKED
    │   Claims the run, sets lease_expires_at
    │
    ▼
DAG executor  (Kahn topological sort — deterministic)
    │   For each node in declaration order:
    │     looks up the registered node handler
    │     passes upstream output as node input
    │     captures StepTrace {status, output, tokens, duration}
    │     on failure: marks run FAILED, skips all downstream nodes
    │
    ▼
RunEvents appended  (sequence-locked INSERT)
    │   SSE endpoint streams events to frontend in real time
    │
    ▼
Run reaches terminal status  (success / failed / cancelled)
    └── Evaluation suites advance if linked test cases exist

How the agent loop works

The agent node is a bounded ReAct-style reasoning loop written directly against Groq / Gemini SDKs — no framework wrapper.

while steps < max_steps and tokens < token_budget:
    prompt = build_prompt(history, tools)
    reply  = call_llm(model, prompt)          # streaming, 5-second timeout
    action = parse_action(reply)              # tolerant regex; handles malformed output

    if action.type == "FINAL_ANSWER":
        break

    tool_result = dispatch_tool(action)       # real HTTP call or MCP RPC
    history.append((action, tool_result))
    steps += 1

Each iteration appends a StepTrace. If the LLM produces a malformed ACTION: block the loop retries with an explicit correction prompt before giving up.


RAG pipeline

Upload (PDF / TXT / Markdown)
    │
    ▼ text extraction  (pdfminer.six · plain read)
    │
    ▼ sentence chunking  (target ≈ 512 tokens; sentence-boundary aware)
    │
    ▼ embedding  (Gemini text-embedding-004 · 1536 dim · batch of 100)
    │
    ▼ pgvector INSERT  (cosine metric · HNSW m=16 ef=200)
    │
    ▼ at query time: top-k cosine → optional BM25 re-rank → context window

The pipeline runs synchronously in the upload handler. No background job or message queue is needed for typical document sizes.


MCP integration

Tantra is simultaneously an MCP client and an MCP server.

As a client: Add any stdio or SSE MCP server in Settings → Credentials. Its tools are discovered at startup and surfaced as mcp_tool nodes in the canvas. Tool results are cached per-run to avoid duplicate external calls.

As a server: Deploy any workflow from the Operations tab. The resulting endpoint speaks the MCP protocol — any MCP client can call it by passing the endpoint URL and API key as server config.


Secrets vault

Every credential stored by Tantra is encrypted with AES-256-GCM before it reaches the database:

key_material = PBKDF2HMAC(master_key, salt=workspace_id, iterations=600_000)
ciphertext   = AES256GCM.encrypt(plaintext, aad=workspace_id)

The workspace_id is used as Additional Authenticated Data (AAD). A ciphertext from workspace A will fail authentication if decrypted with workspace B's key — cross-tenant decryption is cryptographically impossible, not just access-controlled.


Getting started

Prerequisites

  • Docker & Docker Compose
  • Node 20+
  • Python 3.12+ with uv
  • Java 21+ (scheduler only)

Run locally

# 1. Clone
git clone https://github.com/Iamsujithd/tantra.git
cd tantra

# 2. Copy env files and fill in your keys
cp engine/.env.example engine/.env
cp frontend/.env.example frontend/.env

# 3. Start Postgres + pgvector
docker compose up -d db

# 4. Run migrations
cd engine && uv run alembic upgrade head && cd ..

# 5. Start the engine
cd engine && uv run uvicorn app.main:app --reload --port 7860

# 6. Start the frontend (new terminal)
cd frontend && npm install && npm run dev

Open http://localhost:5173 — create an account and start building.

Environment variables

Variable Component Purpose
DATABASE_URL Engine asyncpg connection string
SECRET_KEY Engine JWT signing + vault master key
CLERK_SECRET_KEY Engine Auth (alternative to plain JWT)
GEMINI_API_KEY Engine Default embedding model
GROQ_API_KEY Engine Default LLM (can be overridden per workspace)
QSTASH_TOKEN Scheduler Schedule tick delivery
QSTASH_CURRENT_SIGNING_KEY Scheduler Validate incoming ticks
ENGINE_BASE_URL Scheduler Internal call-back URL
VITE_API_BASE_URL Frontend Engine API origin

Project layout

tantra/
├── engine/                 # Python 3.12 / FastAPI
│   ├── app/
│   │   ├── api/            # Route handlers (workflows, runs, kb, mcp, …)
│   │   ├── core/           # Config, security, DB session
│   │   ├── models/         # SQLAlchemy ORM models
│   │   ├── schemas/        # Pydantic request / response models
│   │   ├── services/
│   │   │   ├── dag/        # DAG executor + node registry
│   │   │   ├── agents/     # ReAct loop, tool dispatcher
│   │   │   ├── rag/        # Chunking, embedding, retrieval
│   │   │   └── mcp/        # MCP client + server transport
│   │   └── worker/         # Durable queue poller + lease heartbeat
│   └── alembic/            # DB migrations
│
├── scheduler/              # Java 21 / Spring Boot 3
│   └── src/main/java/
│       └── ai/tantra/scheduler/
│           ├── controller/ # /tick  /health  /webhooks
│           └── service/    # QStash signing + cron fan-out
│
├── frontend/               # React 19 / Vite / TypeScript
│   └── src/
│       ├── components/     # Reusable UI primitives
│       ├── pages/          # Route-level page components
│       ├── lib/            # API client, canvas utilities, Zustand stores
│       └── nodes/          # React Flow custom node definitions
│
└── docs/
    └── assets/             # README screenshots

Design decisions

Why no LangChain? Provider SDKs are used directly. This makes the token budget, retry logic, and streaming behaviour fully transparent and testable. LangChain's abstractions hide these details in ways that complicate debugging at the DAG level.

Why Postgres for the queue? Eliminating Redis as an operational dependency reduces the deployment surface. FOR UPDATE SKIP LOCKED gives exactly-once claim semantics that match a hosted Postgres tier (Supabase) without additional infrastructure.

Why Java for the scheduler? QStash's signing library ships a first-class Java SDK. The scheduler is stateless, speaks plain HTTP, and can run on any free-tier JVM host without needing the Python runtime — keeping the engine container focused on execution.

Why pgvector instead of a dedicated vector DB? Documents live in the same Postgres instance as workflow metadata. A rag_retrieve node can join vector search results against workspace-scoped metadata in a single query, with RLS enforcing tenant isolation automatically.


Known constraints

  • Cron scheduling has a 30-minute minimum granularity imposed by QStash's free tier. The scheduler documents this explicitly.
  • The canvas renders up to ~200 nodes before React Flow performance degrades. Large DAGs should be split into sub-workflows connected via a2a delegate nodes.
  • The free Hugging Face Spaces CPU tier cold-starts in ~30 seconds. A paid GPU Space or self-hosted deployment eliminates this.

License

MIT — see LICENSE.

About

Self-hosted AI agent orchestration platform — visual DAG canvas, ReAct loops, RAG, MCP client+server, cron scheduling, durable queue

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages