Tech

Building With LangGraph On Top Of Ujex (Reference Architecture)

Akshay Sarode
Direct answer

LangGraph runs on whatever (your laptop / Cloud Run / fly.io). Ujex runs as Cloud Functions on a Firebase project. LangGraph state lives in a checkpointer (PostgresSaver in prod). Long-term memory lives in Ujex Recall. Email, ingress, budgets, mobile, audit are Ujex SDK calls. Total: ~300 LoC for a real agent loop.

This is the architecture I run for my own agents and the one I recommend in talks. It's small. It's been useful.

What LangGraph gives you

What Ujex gives you

Where each runs

ComponentRuns where
LangGraph agent loopCloud Run / fly.io / your machine
Checkpointer (short-term state)PostgresSaver — Cloud SQL or fly Postgres
Ujex Cloud FunctionsGCP (your Firebase project)
Recall storage (markdown source of truth)Cloud Storage bucket
Recall indexFirestore + Vertex AI embeddings
Mail serverHetzner VM (or Mailgun / Brevo)
Ingress relayCloudflare Tunnel or Ujex's bore relay
Mobile pushFCM (Firebase)

Code (Python)

from langgraph.graph import StateGraph
from langgraph.checkpoint.postgres import PostgresSaver
from ujex_recall import RecallStore
from ujex_postbox import Postbox
from ujex_mobile import Mobile

class AgentState(TypedDict):
    task: str
    plan: str | None
    email_response: str | None

graph = StateGraph(AgentState)

postbox = Postbox(api_key=...)
mobile = Mobile(api_key=...)
recall = RecallStore(api_key=..., agent_id='task-agent')

def plan(state, store):
    prior = store.search(('episodes',), query=state['task'], limit=3)
    response = llm(f"Plan this task. Prior episodes: {prior}. Task: {state['task']}")
    store.put(('episodes',), f'plan-{int(time.time())}', response)
    return {'plan': response}

def execute(state):
    if needs_approval(state['plan']):
        mobile.ask(prompt='OK to execute?', detail=state['plan'], ttl_sec=300).wait()
    # ... do the work
    return {'email_response': 'Task done. PR: ...'}

def email_user(state):
    postbox.send(
        from_inbox='task-agent',
        to=state['user_email'],
        subject=f"Re: {state['task']}",
        body=state['email_response'],
    )
    return {}

graph.add_node('plan', plan)
graph.add_node('execute', execute)
graph.add_node('email', email_user)
graph.set_entry_point('plan')
graph.add_edge('plan', 'execute')
graph.add_edge('execute', 'email')

checkpointer = PostgresSaver.from_conn_string('postgres://...')
agent = graph.compile(checkpointer=checkpointer, store=recall)

Deploy

# Ujex (one time per project)
cd ujex/functions && npm ci && firebase deploy --only functions

# LangGraph agent
fly deploy -c fly.toml  # or: gcloud run deploy ...

Where the data lives

No vendor owns the data. Ujex is open SDKs + opinionated control plane.

Tradeoffs

FAQ

Can I use this without Postgres?

Yes — LangGraph also has SqliteSaver for dev. Use that for prototyping; switch to Postgres for prod.

Does this work with TS / JS?

Yes — LangGraph.js + @ujex/client. Same shape, JS idioms.

How much does this cost in GCP at small scale?

Free tier covers most early development. Firestore reads/writes scale linearly; Cloud Functions invocations free up to 2M/mo. Cloud Storage for memory: pennies.