GitHub - tam159/next-role: πŸš€ Level up your career with GenAI. πŸ“„ Tailor your CV to any JD, πŸ” automate company research, and πŸ—“οΈ generate custom interview prep plans for your next role or internal promotion.

GitHub

14 min read Original article β†—

What is NextRole?

Preparing for an interview takes hours of tedious resume tailoring and company research. NextRole automates the heavy lifting. Hand it your current CV and a target Job Description (or just a JD URL) β€” whether you're applying externally or angling for an internal move β€” and a team of specialized AI agents researches the company, rewrites your resume to fit, coaches you round-by-round, and prints a cheat sheet for the day of.

  • πŸ“„ Tailored resume β†’ PDF β€” your experience rewritten against the exact JD + company research, rendered with rendercv (editable & re-renderable).
  • πŸ” Deep company & role recon β€” live web research distilled into a match analysis.
  • 🎯 Structured interview prep β€” a self-introduction plus per-round STAR stories mapped to the role.
  • ⚑ Day-of battlecard β€” a one-page-per-round PDF cheat sheet for the final high-pressure review.
  • πŸ—“οΈ Time-boxed prep plans β€” a study plan that fits 1 month, 2 weeks, or just 3 hours.
  • πŸ”— Paste a JD URL β€” point it at a careers page; it extracts and processes the posting for you.
  • πŸ’¬ Iterate by chatting β€” "add a 4th round", "add React to my skills" β€” streaming multi-turn edits, with the right agent owning each file.
  • πŸ—‚οΈ Built-in workspace β€” upload, preview (PDF / MD / YAML / JSON / code), print-to-PDF, and swap the LLM at runtime.

Demo

next-role-demo.mp4

▢️ Watch the full walkthrough in HD on YouTube Β»

Quick Start

The whole stack β€” frontend, backend, Postgres, Redis, S3-compatible object storage β€” runs in Docker.

# 1. Clone & configure
git clone https://github.com/tam159/next-role.git
cd next-role
cp .env.example .env          # then fill in your API keys (see table below)

# 2. Launch everything
docker compose up -d

# 3. Find your host ports (set in .env, vary per machine)
docker ps                     # read the 0.0.0.0:<host>->... mappings

# 4. Open the app
#    Frontend UI      β†’  http://localhost:<FRONTEND_LOCAL_PORT>/
#    Backend API docs β†’  http://localhost:<LANGGRAPH_LOCAL_PORT>/docs

πŸ’‘ Pick your LLM in the app. Open the in-app Configuration dialog to set the main agent and subagent models β€” no rebuild needed. See LLM configuration below for recommended models and free / local options.

Environment variables β€” what to put in .env
Variable Required Purpose
OPENAI_API_KEY βœ… Default main + subagent models
TAVILY_API_KEY βœ… Web research (hiring-recon)
LLAMA_CLOUD_API_KEY βœ… Document parsing (LlamaParse)
POSTGRES_PASSWORD βœ… Local Postgres password
ANTHROPIC_API_KEY / GOOGLE_API_KEY ⬜ Alternative providers (swap at runtime)
OPENAI_API_BASE ⬜ Self-hosted / Azure / LM Studio endpoint
AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY / AWS_DEFAULT_REGION ⬜ AWS Bedrock models
LANGCHAIN_API_KEY + LANGCHAIN_TRACING_V2=true ⬜ LangSmith tracing (recommended)
AUTH_ENABLED / BETTER_AUTH_SECRET / LANGGRAPH_AUTH ⬜ Multi-user auth (opt-in) β€” steps in Authentication & multi-user
CAREER_AGENT_EXECUTE_APPROVAL preset Human approval for the agent's shell tool β€” see below
SANDBOX_PROVIDER (+ SANDBOX_E2B_*, SANDBOX_TEMPLATE) preset Where the shell tool executes: host or isolated sandbox β€” see below
FRONTEND_LOCAL_PORT / LANGGRAPH_LOCAL_PORT / POSTGRES_LOCAL_PORT / REDIS_LOCAL_PORT / OBJECT_STORE_LOCAL_PORT preset Host port mappings
OBJECT_STORE_* preset Artifact object storage β€” see below

Shell-command approval. Risky execute (bash) commands pause in the UI for approve / edit / reject; a conservative read-only allowlist auto-approves trivial pokes. The gate applies in both sandbox modes below β€” isolation contains blast radius, not exfiltration or API misuse. On by default β€” set CAREER_AGENT_EXECUTE_APPROVAL=false only for offline evals or emergency rollback (env changes need docker compose up -d backend to recreate, not restart). Full policy in backend/agents/career_agent/README.md.

Sandbox. SANDBOX_PROVIDER picks where execute commands (and rendercv renders) run. local (default) executes on the backend host β€” right for local dev and a trusted team, zero extra moving parts. e2b runs every command in an isolated remote microVM over the E2B API: point SANDBOX_E2B_API_URL at a self-hosted CubeSandbox (open source, Apache-2.0 β€” your data never leaves your infra; provisioning + template guide in deploy/cubesandbox/), or leave it empty to use E2B Cloud with the same code path. CubeSandbox requires its own Linux host (custom PVM kernel or native KVM), so it is reached over HTTP rather than run inside docker compose.

Object storage. Binary artifacts (uploads + rendered PDFs) live in S3-compatible object storage. Locally that's the compose object-store service (SeaweedFS) and the presets work as-is: S3 API on OBJECT_STORE_LOCAL_PORT, a browsable bucket UI on OBJECT_STORE_UI_LOCAL_PORT, and placeholder credentials that the local emulator accepts but doesn't enforce. For the cloud, point OBJECT_STORE_ENDPOINT / OBJECT_STORE_BUCKET / credentials at a managed bucket β€” AWS S3, GCS, Azure, or any S3-compatible store β€” with no code changes. Note: the AWS_* variables are reserved for Bedrock models; the object store reads only OBJECT_STORE_*.

Secrets live only in .env (gitignored); gitleaks runs on every commit.

LLM configuration β€” pick your models, run it for free or local

Models are swappable at runtime β€” no rebuild. Open the in-app Configuration dialog and set Main agent / Subagents to a <provider>:<model> string (e.g. anthropic:claude-sonnet-5); leave blank to use the defaults. Settings persist in your browser's local storage.

Recommended: Claude Sonnet 5, GPT-5.x, or Gemini 3.x β€” e.g. anthropic:claude-sonnet-5, openai:gpt-5.6-terra, google_genai:gemini-3.5-flash.

Run it for free or fully local:

  • Tavily and LlamaCloud both include a generous monthly free tier β€” plenty for local use.
  • Google AI Studio offers a free tier for Gemini flash / lite models.
  • Fully local β€” point OPENAI_API_BASE at LM Studio or Ollama (both expose an OpenAI-compatible API) and fill your local model in the UI.

Output quality tracks the model you pick β€” smaller local models trade some quality for zero cost.

Dev workflow β€” hot reload, restart, rebuild, stop
  • Code edits hot-reload in both containers β€” just save the file.
  • Add a frontend dep: pnpm --dir frontend add <pkg> β†’ docker compose restart frontend
  • Add a backend dep: uv add <pkg> β†’ docker compose up -d --build backend
  • Change .env: docker compose restart <service>
  • Stop: docker compose down (add -v to wipe the DB, Redis & object-storage volumes)

Architecture

NextRole is a supervisor agent orchestrating three specialist subagents on LangGraph + DeepAgents. The main agent handles intake, document processing, and the final battlecard; it delegates research, resume tailoring, and interview coaching to declarative subagents (defined in subagents.yaml, each with its own model, tools, and skills).

NextRole architecture

How It Works

A five-stage pipeline. Stage 4 runs the resume tailor and interview coach in parallel; Stage 6 routes follow-up edits to whichever agent owns the target file.

How NextRole works

Stage-by-stage detail
  1. Intake β€” the agent asks for your CV, the JD (file, URL, or pasted text), your prep timeline, and any extra context.
  2. Process documents β€” uploads are parsed to markdown via LlamaParse (parse_document); JD URLs are pulled via Tavily (extract_jd). Results land in /processed/, alongside a persisted intake note.
  3. Research β€” the hiring-recon subagent gathers company + role intel and a match analysis β†’ /research/<resume>/<jd>.md.
  4. Tailor & coach (parallel) β€” resume-tailor rewrites the CV as a rendercv YAML and renders a PDF; interview-coach writes a structured prep doc (self-intro + per-round STAR stories).
  5. Battlecard β€” the main agent assembles a one-page-per-round JSON and renders it to a day-of PDF via WeasyPrint.
  6. Multi-turn updates β€” ask for changes in chat; the owning agent reads the existing file, preserves everything you didn't name, and re-renders.

The full procedure (file layout, update routing, source-of-truth conventions) lives in backend/agents/career_agent/README.md. Per-feature design docs are in docs/prd/ β€” an OKF knowledge bundle you can render as an interactive graph; see docs/prd/README.md.

The DeepAgents stack β€” an agent defined by filesystem primitives

The agent's behavior is configured by files, not hardcoded β€” making it easy to read, diff, and extend:

Primitive Where Role When loaded
Memory CAREER_AGENT.md Per-stage procedure manual (semantic memory) Always (system prompt)
Skills skills/<consumer>/<name>/SKILL.md Task workflows (procedural memory) On demand, per consumer
Subagents subagents.yaml Specialist delegates β†’ the task tool Always
Tools tools.py + DeepAgents built-ins parse_document, extract_jd, render_resume_pdf, render_battlecard_pdf, list_files, plus read/write/edit_file, ls/glob/grep, delete, execute β€”
Filesystem CompositeBackend Routes virtual paths to the right store (see below) β€”
Middleware middleware.py ModelOverrideMiddleware (runtime LLM swap) + UtcDatetimeMiddleware β€”

Subagents only receive the tools they opt into in YAML β€” tool whitelisting keeps interview-coach, for example, from inheriting the main agent's full toolset.

Memory & storage architecture

A single CompositeBackend gives the agent one virtual filesystem while routing each path prefix to the right physical store β€” Postgres for text artifacts, S3-compatible object storage for uploads and rendered PDFs (SeaweedFS locally; S3 / GCS / Azure in the cloud), and a shell route for execute whose scratch space holds only render working files: a host shell backend by default, or an isolated remote sandbox (CubeSandbox / E2B) with SANDBOX_PROVIDER=e2b.

flowchart LR
    Agent["Agent filesystem tools<br/>read_file Β· write_file Β· edit_file<br/>ls Β· glob Β· grep Β· execute"]
    CB{{"CompositeBackend<br/>routes virtual paths"}}
    Agent --> CB
    subgraph Shell["Shell default route Β· SANDBOX_PROVIDER"]
        SH["`execute` β€” local: host subprocess,<br/>/virtual/path β†’ on-disk path Β·<br/>e2b: remote E2B microVM<br/>(CubeSandbox self-hosted / E2B Cloud)<br/>renders run in a throwaway scratch dir"]
    end
    subgraph Store["StoreBackend Β· Postgres + pgvector"]
        ST["/memory/ Β· /processed/ Β· /research/<br/>/interview_coach/<br/>/large_tool_results/ Β· /workspace/"]
    end
    subgraph Obj["ObjectStoreBackend Β· S3-compatible<br/>SeaweedFS local Β· S3/GCS/Azure cloud"]
        OB["/upload/ Β· /tailored_resume/<br/>/interview_battlecard/"]
    end
    CB -->|default: shell + scratch| Shell
    CB -->|KV routes| Store
    CB -->|artifact routes| Obj
    Sem["Semantic memory Β· CAREER_AGENT.md"] -. loaded into system prompt .-> Agent
    Proc["Procedural memory Β· skills/*/SKILL.md"] -. loaded on demand .-> Agent
    Work["Working memory Β· LangGraph thread"] -. drives .-> Agent
    Store --- Epi["Episodic memory Β· persisted artifacts<br/>(incl. /memory/ auto-memory)"]
    Obj --- Epi
Loading

Mapped to memory types:

  • Working memory β€” the live LangGraph conversation thread.
  • Semantic memory β€” CAREER_AGENT.md, always in the system prompt.
  • Procedural memory β€” skills/*/SKILL.md, loaded on demand.
  • Episodic memory β€” persisted artifacts in Postgres + disk, including auto-memory: standing preferences saved to the /memory/ route and auto-applied across sessions.
Authentication & multi-user β€” opt-in login & per-user isolation

NextRole runs zero-login single-user by default β€” docker compose up and start prepping, no accounts. Flip on multi-user mode for a shared or cloud deployment and every user gets private threads, files, and memory.

  • Login β€” Google OAuth and/or email + password, via self-hosted Better Auth inside the Next.js app (its tables live in your Postgres; no third-party auth vendor).
  • Isolation β€” the agent server verifies a short-lived JWT (JWKS) on every request; threads/runs/crons are owner-scoped in SQL (unowned β†’ 404), and store namespaces + object keys are scoped per user (users/<id>/…). Design details in backend/ARCHITECTURE.md Β§8.

Enable it β€” set these in .env, then docker compose up -d frontend backend:

  1. AUTH_ENABLED=true and BETTER_AUTH_SECRET=$(openssl rand -base64 32).

  2. Create the Better Auth tables once (it owns user / session / account / jwks, separate from backend/storage/migrations/):

    AUTH_DATABASE_URL="postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@localhost:<POSTGRES_LOCAL_PORT>/<POSTGRES_DB>" \
    BETTER_AUTH_SECRET=<same secret> \
      pnpm --dir frontend dlx @better-auth/cli@latest migrate --config src/lib/auth/server.ts
  3. LANGGRAPH_AUTH β€” turns on backend enforcement. (Login without this is fine for trying the UI, but provides no isolation.) One line:

    LANGGRAPH_AUTH={"path": "/deps/next-role/backend/agents/auth.py:auth", "disable_studio_auth": true, "openapi": {"securitySchemes": {"bearerAuth": {"type": "http", "scheme": "bearer", "bearerFormat": "JWT", "description": "Better Auth session JWT. While signed in, the frontend's GET /api/auth/token returns one."}}, "security": [{"bearerAuth": []}], "paths": {"/ok": {"GET": []}, "/info": {"GET": []}, "/metrics": {"GET": []}, "/docs": {"GET": []}}}}

    The optional "openapi" block documents auth in the API reference: /docs gets a Bearer Token field for authenticated Test Requests (paste a JWT from the frontend's GET /api/auth/token), while the public meta endpoints (/ok, /info, /metrics, /docs) stay marked auth-free.

  4. Optional Google sign-in: AUTH_GOOGLE_ENABLED=true + GOOGLE_AUTH_CLIENT_ID / GOOGLE_AUTH_CLIENT_SECRET (OAuth client redirect URI http://localhost:<FRONTEND_LOCAL_PORT>/api/auth/callback/google). Email + password works without it.

Cloud hardening checklist β€” beyond enabling auth
  • REQUIRE_AUTH=true β€” the backend refuses to boot if LANGGRAPH_AUTH is missing, so a misconfigured deploy fails loudly instead of serving everyone's data.
  • HTTPS everywhere β€” set BETTER_AUTH_URL / AUTH_JWT_ISSUER / AUTH_JWT_AUDIENCE to the public https origin, and AUTH_JWKS_URL to the in-network frontend URL the backend can reach.
  • Pin CORS β€” CORS_ALLOW_ORIGINS=https://your-frontend.example (the default * is local-only).
  • Block the unauthenticated meta routes at the ingress β€” /metrics leaks thread/run counts; also /docs, /openapi.json, /info.
  • Gate MCP / A2A β€” authentication-only today (no per-resource authz), so disable via LANGGRAPH_HTTP "disable_mcp": true / "disable_a2a": true until audited.
  • Close the Studio backdoors β€” never set LANGSMITH_LANGGRAPH_API_VARIANT=local_dev in production; disable_studio_auth: true (above) closes the header-based one.
  • Reverse proxy must never forward a client-controlled root_path (the in-process /noauth loopback bypass stays internal-only).
Tech stack
Layer Stack
Backend Python 3.13 Β· LangChain v1 Β· LangGraph 1.x Β· DeepAgents 0.6 Β· uv Β· served by NextRole's own self-hosted agent server (backend/ARCHITECTURE.md)
Agent I/O Tavily (web search) Β· LlamaParse / LlamaCloud (document parsing) Β· rendercv (resume β†’ PDF) Β· WeasyPrint (battlecard β†’ PDF)
Frontend Next.js 16 Β· React 19 Β· TypeScript Β· Tailwind Β· pnpm Β· @langchain/react (v2 useStream)
Data PostgreSQL 18 + pgvector Β· Redis 8 Β· S3-compatible object storage (SeaweedFS locally; S3 / GCS / Azure in the cloud)
Infra Docker Compose (frontend Β· backend Β· core-server Β· postgres Β· redis Β· object-store)
Observability LangSmith
Expose the agent β€” MCP & A2A

Because NextRole ships its own agent server implementing the LangGraph Server API (see backend/ARCHITECTURE.md), the career_agent assistant is also reachable by other tools and agents β€” no extra code:

  • MCP β€” exposed as Model Context Protocol tools at /mcp (Streamable HTTP), usable by any MCP-compliant client. β†’ docs
  • A2A β€” Google's Agent2Agent protocol at /a2a/{assistant_id} (JSON-RPC 2.0; message/send + message/stream). β†’ docs
  • The full server API is browsable at the /docs endpoint of your deployment.

In multi-user mode these endpoints are authentication-gated but not yet per-user authorized β€” disable them (disable_mcp / disable_a2a) in a shared deployment until that lands. See backend/ARCHITECTURE.md Β§8.

NextRole Agent Expose

Observability β€” LangSmith tracing

Set LANGCHAIN_API_KEY and LANGCHAIN_TRACING_V2=true in .env, and every run β€” each LLM call, tool call, and nested subagent β€” is traced at smith.langchain.com under the LANGCHAIN_PROJECT you configure. Optional, but invaluable for debugging the multi-agent flow.

Roadmap

  • πŸ’€ "Auto-dream" consolidation β€” sleep-time compaction that prunes stale notes and merges insights into durable memory.
  • πŸ” Wider execute allowlist in sandbox mode β€” remote sandboxes shipped (SANDBOX_PROVIDER=e2b β†’ self-hosted CubeSandbox or E2B Cloud); next, let sandboxed deployments auto-approve more than the conservative read-only list.
  • πŸ“Š Agent evaluation β€” LangSmith evals over the workflow (the @pytest.mark.eval marker is already reserved).
  • 🎨 Enhanced UI β€” richer artifact editing, diff views, and inline regeneration.
  • πŸ”Œ MCP / A2A examples β€” sample integrations driving career_agent from external agents and IDEs.
  • ☁️ Cloud deployment β€” binary artifacts already live in S3-compatible object storage (SeaweedFS locally; point OBJECT_STORE_* at S3 / GCS / Azure). Remaining: managed bucket provisioning (versioning, SSE, IAM) and presigned-URL delivery.
  • 🌐 More sources & ATS-aware tailoring β€” pluggable retrievers + keyword/ATS optimization passes.

Limitations

Multi-user mode isolates data; before opening signups to untrusted users, also move shell execution off the default local mode (SANDBOX_PROVIDER=e2b).

  • πŸ”’ Local shell execution is the default β€” with SANDBOX_PROVIDER=local, execute and render commands run via subprocess on the host (HiL-gated). Safe locally and for a trusted team; for untrusted multi-tenant use switch to e2b so every command runs in an isolated microVM (deploy/cubesandbox/).
  • πŸ§ͺ LLM evals deferred β€” current tests are unit + local-DB integration; automated quality evals aren't wired up yet.
  • 🧠 Personalization is preferences-only β€” the agent persists and auto-applies the preferences you state across sessions, but doesn't yet infer your style/history on its own or consolidate memory over time (see roadmap).
  • ⏱️ Latency β€” a full run makes several LLM and tool calls across multiple agents; expect minutes, not seconds.

πŸ—ΊοΈ Explore the codebase graph

This repo ships a pre-built architecture knowledge graph in .ua/ β€” the whole codebase mapped by Understand-Anything into 1,100+ nodes across 10 architectural layers, with a guided tour. Browse it as an interactive dashboard:

Open the dashboard β€” one command, nothing to install
# From the repo root β€” needs only Node.js (no install, no clone, no build):
npx --yes "https://github.com/Egonex-AI/Understand-Anything/releases/latest/download/understand-anything-viewer.tgz" .

Open the printed πŸ”‘ Dashboard URL (include the ?token=… β€” the plain URL hits an access gate).

Using Claude Code? Install the understand-anything plugin and run /understand-dashboard in this repo instead.

The graph is for humans: AI coding assistants are configured to ignore .ua/ (Claude Code deny rules, .cursorignore, a CLAUDE.md instruction) so they keep reading the real source instead of a large generated snapshot.

NextRole codebase knowledge graph β€” architectural layers, dependencies, and project stats in the Understand-Anything dashboard

The same treatment exists for the product's design history: every feature ships with a PRD in docs/prd/ β€” an OKF knowledge bundle whose committed interactive graph (docs/prd/viz.html) maps how features extend and supersede each other. The codebase graph maps the code; the PRD graph maps the decisions. See docs/prd/README.md to browse or regenerate it.

Contributing

PRs and issues are welcome! Start with CONTRIBUTING.md β€” it walks through the fork β†’ PR workflow, local setup, the CI quality gate (code quality + backend tests + frontend tests), testing, and conventions. Stack-specific details live in backend/CLAUDE.md and frontend/CLAUDE.md; commits follow Conventional Commits.

New here? Issues labelled good first issue are a gentle place to start, and questions are welcome in Discussions.

License

MIT Β© 2026 Tam Nguyen

Acknowledgements

Built on DeepAgents, LangChain / LangGraph / LangSmith, rendercv, WeasyPrint, Tavily, and LlamaIndex / LlamaParse.