AI-powered ticket refinement engine. Takes rough feature requests, runs them through multiple AI agents for review, and outputs structured Linear issues ready for sprint planning.
- You describe a feature in plain text
- 5 AI agents (Draft, QA, Dev, PO, Supervisor) iteratively refine it
- You review and edit the refined document (human-in-the-loop)
- Structured output: epics and stories with acceptance criteria, technical notes, and team assignments
- Push to Linear: each epic routed to the right team, cross-team projects created automatically
- Chat with Linear: ask questions about your workspace in natural language
┌─────────────┐ ┌──────────────────────────────────────────────┐
│ Next.js UI │────▶│ FastAPI Backend │
│ (port 3000) │ │ │
└─────────────┘ │ ┌──────────┐ ┌────────────┐ ┌──────────┐ │
│ │ LangGraph │ │ LangChain │ │ pgvector │ │
│ │ Workflow │ │ + LiteLLM │ │ RAG │ │
│ └──────────┘ └────────────┘ └──────────┘ │
│ │
│ ┌──────────┐ ┌────────────┐ │
│ │ Linear │ │ GitHub │ │
│ │ GraphQL │ │ Codebase │ │
│ └──────────┘ └────────────┘ │
└──────────────────────────────────────────────┘
Input -> Retrieve Context -> Draft -> [QA, Dev, PO] parallel -> Merge -> Supervisor
▲ │
└────── loop if quality < threshold ─────────┘
│
▼ (approved)
USER_EDITING (pause)
│
Finalize -> Structure -> Push to Linear
- Max 5 iteration rounds
- QA, Dev, and PO agents run in parallel (fan-out/fan-in)
- Supervisor decides: loop for another round or approve
- Human-in-the-loop: edit the refined document before structuring
- Structurer assigns each epic to the right Linear team based on descriptions
Agents receive context from two sources:
- Linear issues: similar existing tickets via pgvector cosine similarity
- Codebase chunks: relevant code from indexed repositories
This grounds the output in your actual codebase and existing ticket patterns.
| Layer | Technology |
|---|---|
| Frontend | Next.js 15, React, Tailwind, ky |
| Backend | FastAPI, uvicorn, SSE streaming |
| AI | LangChain, LangGraph, Gemini (via langchain-google-genai) |
| Database | PostgreSQL 16, SQLAlchemy 2.0 async, pgvector, Alembic |
| Integrations | Linear GraphQL API, GitHub API |
| Testing | pytest, ruff |
| Package manager | uv |
- Python 3.12+
- Node.js 18+ and pnpm
- Docker (for PostgreSQL + pgvector)
- Gemini API key
- Linear API token (optional, for push/chat features)
- GitHub token (optional, for private repo indexing)
cp .env.example .env# Required
GOOGLE_GENERATIVE_AI_API_KEY=AIza...
GEMINI_API_KEY=AIza...
DATABASE_URL=postgresql+asyncpg://postgres:gamole_dev@localhost:5432/gamole
SESSION_SECRET=your-secret-minimum-32-characters-long
# Optional
LINEAR_API_TOKEN=lin_api_...
GITHUB_TOKEN=ghp_...
CORS_ORIGINS=http://localhost:3000docker compose up -d postgresPorts bind to 127.0.0.1 only (not exposed to internet).
uv sync
cd apps/api
PYTHONPATH=.:../../ uv run uvicorn app.main:app --port 3001pnpm install
cd apps/web
NEXT_PUBLIC_API_URL=http://localhost:3001 pnpm devuv run pytest apps/api/tests/ -v # unit tests (no DB needed)
uv run ruff check packages/ apps/api/ # lintingDB-dependent tests skip automatically when PostgreSQL isn't running.
The API uses JWT bearer tokens for authentication. All /api/* routes (except /health) require a valid token in the Authorization header.
How it works:
- A JWT is created with
create_session(user_id, workspace_id)fromapps/api/app/auth/jwt.py - The token is signed with
SESSION_SECRET(HS256) and expires after 7 days - Clients send it as
Authorization: Bearer <token> - The
auth_dependencymiddleware decodes and validates the token on every protected request
Generate a dev token:
# From the project root (requires the API container running)
docker compose exec api python -c "
from app.auth.jwt import create_session
print(create_session(user_id='dev-user'))"Or without Docker:
cd apps/api
PYTHONPATH=.:../../ python -c "
from app.auth.jwt import create_session
print(create_session(user_id='dev-user'))"Use the token:
TOKEN="<paste token here>"
curl http://localhost:3001/api/generation -H "Authorization: Bearer $TOKEN"The token payload contains userId, iat, and exp. Optionally includes workspaceId if provided at creation.
| Method | Path | Description |
|---|---|---|
| POST | /api/generation |
Start a new generation workflow |
| GET | /api/generation |
List all generations |
| GET | /api/generation/{id} |
Get generation status and output |
| GET | /api/generation/{id}/output |
Get structured epics/stories |
| GET | /api/generation/{id}/stream |
SSE stream for real-time progress |
| PUT | /api/generation/{id}/document |
Edit refined document (human-in-the-loop) |
| POST | /api/generation/{id}/finalize |
Trigger structuring after editing |
| Method | Path | Description |
|---|---|---|
| POST | /api/linear/validate |
Pre-push validation (count issues) |
| POST | /api/linear/push |
Push structured output to Linear |
| POST | /api/linear/push-generation |
Push a generation directly |
| POST | /api/chat/linear |
Chat with Linear in natural language |
| POST | /api/chat/search |
Semantic search over cached issues |
| Method | Path | Description |
|---|---|---|
| GET/POST | /api/repositories |
Manage codebase repositories |
| GET | /api/repositories/github/available |
Browse GitHub repos for selection |
| POST | /api/repositories/{id}/index |
Trigger codebase indexing |
| GET/POST | /api/teams |
Manage Linear teams with descriptions |
| POST | /api/teams/sync |
Import teams from Linear API |
| POST | /api/sync/linear |
Sync Linear issues to pgvector cache |
Each epic is assigned to the most appropriate Linear team by the AI, based on team descriptions you provide. A feature spanning payments and origination will split into epics for each team, linked under a shared Linear project.
Every generation tracks token usage per agent with estimated costs (Gemini Flash pricing). Stored as JSONB on the workflow record and returned in API responses.
After AI approval, the workflow pauses in USER_EDITING state. Edit the document, then finalize to trigger structuring. The SSE stream emits user_edit_required so the frontend knows to show an editor.
Ask questions like "How many tickets did Nelly create last 3 weeks?" or "What's blocked in origination?" The LLM converts to GraphQL, executes against Linear's API, self-corrects on errors, and returns a natural language answer with sources.
apps/
api/ # FastAPI backend
app/
routes/ # API endpoints
orchestrator/ # LangGraph workflow
auth/ # JWT auth
config.py # pydantic-settings
web/ # Next.js frontend
packages/
gamole_ai/ # LangChain agents, embeddings, retrieval, codebase indexer
gamole_db/ # SQLAlchemy models, session management
gamole_linear/ # Linear GraphQL client, batch push, sync
gamole_types/ # Pydantic schemas (shared types)
Features planned but not yet implemented:
- Feedback loop: Track which stories get edited after Linear push. Submit feedback via the API to identify which AI output fields need improvement. Show statistics on what changes most to inform prompt tuning. (Backend scaffolding exists in DB schema —
DocumentVersion.feedback_jsoncolumn andFEEDBACKtype enum are ready.) - Codebase sync scheduling: Automatic periodic re-indexing of repositories (currently manual only)
- Codebase orphan cleanup: Detect and remove chunks for deleted/renamed files during re-indexing
- Diff-based indexing: Only re-index changed files instead of full repository walks
- Push response codes: Return 207 Multi-Status or
ok: falsewhen some issues fail to push
Private.