Repository
trajan-agent
Lightweight agentic developer workspace for agents to automatically manage PM work. FastAPI backend with async PostgreSQL, GitHub integration, and AI-powered project analysis. Agent-ready by design.
- Capability
- What happens
- AI documentation
- AI documentation
- Progress tracking
- Progress tracking
- PM agent
- PM agent
Specs
- CapabilityWhat happens
- AI documentationAI documentation
- Progress trackingProgress tracking
- PM agentPM agent
- Two-way GitHub syncTwo-way GitHub sync
- Feedback ingestionFeedback ingestion
- Team workspacesTeam workspaces
- Environment vaultEnvironment vault
- VariableRequired
- DATABASE_URLYes
- DATABASE_URL_DIRECTYes
- SUPABASE_URLYes
- SUPABASE_ANON_KEYYes
- SUPABASE_JWKS_URLYes
- SUPABASE_SERVICE_ROLE_KEYYes
- ANTHROPIC_API_KEY—
- TOKEN_ENCRYPTION_KEY—
- GITHUB_APP_ID—
- POSTMARK_API_KEY—
- SCHEDULER_ENABLED—
- CommunityHosted
- FeaturesAll unlocked
- Repo limitUnlimited
- InfrastructureSelf-managed
- UpdatesPull from this repo
README
Trajan Agent
Connect your GitHub repos. Get AI-generated docs, commit-based progress tracking, and an agent that understands your codebase — no tickets, no manual updates.
Website · API Docs · Issues
Python 3.11+ · FastAPI · SQLModel · PostgreSQL · Claude AI
What Trajan does
You give Trajan your GitHub repositories. It reads your code and gives you:
| Capability | What happens |
|---|---|
| AI documentation | Generates changelogs, architecture blueprints, and implementation plans by analyzing your source code. Regenerates only when code changes. |
| Progress tracking | Turns commits into activity dashboards, contributor summaries, and velocity metrics. No tickets or manual status updates. |
| PM agent | A chat interface with full context on your repos, commits, PRs, and issues. Answers questions about your codebase. |
| Two-way GitHub sync | Pushes generated documents to GitHub branches and opens PRs automatically. |
| Feedback ingestion | Public API endpoint for collecting user feedback. AI interprets, deduplicates, and categorises submissions. |
| Team workspaces | Organizations with role-based access (owner / admin / member / viewer) and per-product permission overrides. |
| Environment vault | Encrypted storage for env vars, service URLs, and infrastructure notes — Fernet encryption at rest. |
This is the open-source community edition — all features unlocked, no repo limits, no paywalls.
Quick start
Prerequisites
- Python 3.11+
- A Supabase project (free tier works)
- An Anthropic API key for AI features
Setup
git clone https://github.com/kaminocorp/trajan-agent.git
cd trajan-agent
python3 -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
cp .env.example .env
# Edit .env — see "Environment variables" below
Run
uvicorn app.main:app --reload --port 8000
The API is available at http://localhost:8000. Health check at GET /health.
Interactive docs at /docs (Swagger) and /redoc.
Environment variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
Yes | Supabase pooled connection (port 6543) |
DATABASE_URL_DIRECT |
Yes | Supabase direct connection for migrations (port 5432) |
SUPABASE_URL |
Yes | Your Supabase project URL |
SUPABASE_ANON_KEY |
Yes | Supabase anonymous key |
SUPABASE_JWKS_URL |
Yes | JWKS endpoint for JWT verification |
SUPABASE_SERVICE_ROLE_KEY |
Yes | Service role key for admin operations |
ANTHROPIC_API_KEY |
— | Enables AI doc generation, progress summaries, and PM agent |
TOKEN_ENCRYPTION_KEY |
— | Fernet key for encrypting secrets at rest |
GITHUB_APP_ID |
— | GitHub App integration for org-level repo access |
POSTMARK_API_KEY |
— | Enables transactional emails (digests, invites) |
SCHEDULER_ENABLED |
— | Background jobs — auto-progress, email digests (default: true) |
Set DEBUG=true for local development — this relaxes validation and auto-creates the schema on startup.
Database
Trajan uses Supabase PostgreSQL with Row-Level Security. In debug mode, init_db() creates the schema automatically. For production, use Alembic:
alembic upgrade head
Architecture
app/
├── api/v1/ # FastAPI route handlers
├── api/deps/ # Auth, feature gates, product access dependencies
├── domain/ # Business logic (BaseOperations CRUD + model-specific ops)
├── models/ # SQLModel entities with mixins (UUID, Timestamp, UserOwned)
├── services/
│ ├── agent/ # PM agent (Claude-powered, tool-calling)
│ ├── docs/ # Doc generation pipeline (changelog, blueprint, plans agents)
│ ├── github/ # GitHub API — read/write ops, App auth, token resolution
│ ├── interpreter/ # AI feedback interpretation
│ ├── progress/ # Auto-progress summaries and activity tracking
│ └── email/ # Transactional email (Postmark)
├── config/ # Settings and plan configuration
├── core/ # Database, RLS, encryption, rate limiting
└── schemas/ # Pydantic response schemas
Stack: Python 3.11+ · FastAPI · SQLModel · PostgreSQL (Supabase) · Claude AI · asyncpg
Request lifecycle
Every authenticated request follows the same path through FastAPI's dependency injection:
HTTP Request
│
▼
1. Extract Bearer token from Authorization header
2. Validate JWT against Supabase JWKS (ES256, 1h cache)
3. Auto-create User record on first API call
4. Acquire async DB session from pool
5. SET LOCAL app.current_user_id = '{uuid}' ← RLS context
6. Resolve access: org role × product override → effective role
7. Check feature gates (plan limits → 402 if exceeded)
8. Route handler executes, DB queries scoped by RLS
9. Commit → SET LOCAL resets automatically → connection returned
Design decisions
Row-Level Security with connection pooling
The hardest part of using PostgreSQL RLS in a pooled environment is ensuring one user's session context never leaks to another request. Trajan solves this with SET LOCAL, which scopes the setting to the current transaction — not the session. When the transaction commits or rolls back, the setting disappears.
This is safe with PgBouncer's transaction pooling because each transaction gets an isolated context, even if the underlying connection is reused.
# core/rls.py — the entire implementation
await session.execute(
text(f"SET LOCAL app.current_user_id = '{user_id}'")
)
RLS policies reference this via a SQL helper:
CREATE FUNCTION app_user_id() RETURNS uuid AS $$
SELECT current_setting('app.current_user_id', true)::uuid
$$ LANGUAGE sql STABLE;
CREATE POLICY select_own ON products
FOR SELECT USING (user_id = app_user_id());
Dual connection pools
Supabase exposes PostgreSQL through two endpoints: a transaction pooler (port 6543) for normal operations, and a direct connection (port 5432) for long-running work.
Transaction Pooler (6543) Direct Connection (5432)
───────────────────────── ────────────────────────
pool_size=10, max_overflow=20 pool_size=3, max_overflow=5
command_timeout=60s command_timeout=300s
statement_cache_size=0 Prepared statements enabled
Used for: API endpoints (95%) Used for: Doc generation,
AI analysis, migrations
Route handlers use Depends(get_db) for the pooler and Depends(get_direct_db) for long operations.
Generic domain layer
Business logic lives in operation classes that extend BaseOperations[T], a generic repository providing type-safe CRUD:
class BaseOperations(Generic[ModelType]):
async def get(self, db, id) -> ModelType | None: ...
async def get_by_user(self, db, user_id, id) -> ModelType | None: ...
async def get_multi_by_user(self, db, user_id, skip, limit) -> list[ModelType]: ...
async def create(self, db, obj_in, user_id) -> ModelType: ...
async def update(self, db, db_obj, obj_in) -> ModelType: ...
async def delete(self, db, id, user_id) -> bool: ...
Each model extends this with domain-specific methods. Route handlers never contain SQL or business logic — all domain logic lives in operation classes.
# Module-level singleton — imported and used directly
product_ops = ProductOperations()
Three-level access control
Access is resolved by composing organization roles with per-product overrides:
Organization Role Product Override Effective Access
───────────────── ──────────────── ────────────────
owner (any or none) → admin
admin (any or none) → admin
member editor → editor
member viewer → viewer
member none → none (blocked)
viewer (any or none) → viewer
This lets an organization keep someone as a member broadly while restricting them to viewer on specific products, or blocking access entirely. Dependencies like require_product_editor() compose naturally in FastAPI's DI.
Multi-instance scheduling
Background jobs (email digests, auto-progress) run via APScheduler inside the FastAPI process. When multiple instances are running, PostgreSQL advisory locks prevent duplicate execution:
async with advisory_lock(AUTO_PROGRESS_LOCK_ID) as acquired:
if not acquired:
return # Another instance already running this job
await auto_progress_generator.run_for_all_orgs(db)
pg_try_advisory_lock() is non-blocking — the losing instance skips immediately. No Redis, no external coordinator, just PostgreSQL.
Development
Linting and type checking
ruff check . && ruff format . # Lint + format
mypy app # Type checking (strict mode)
Testing
pytest tests/unit/ # Unit tests (no DB required)
pytest tests/unit/domain/test_product_operations.py # Single file
pytest -k "test_name" # Pattern match
TRAJAN_TESTS_ENABLED=1 pytest tests/integration/ # Integration (needs Supabase)
Code style
- Line length: 100 characters
- Formatter: ruff (isort, pycodestyle, pyupgrade, bugbear, simplify)
- Type checker: mypy strict with Pydantic plugin
- Python: 3.11+, 4 spaces
Community edition vs hosted
This is the community edition — all features unlocked, self-hosted. The hosted version adds managed infrastructure, automatic updates, and tiered plans.
| Community | Hosted | |
|---|---|---|
| Features | All unlocked | Tiered plans |
| Repo limit | Unlimited | Per plan |
| Infrastructure | Self-managed | Managed |
| Updates | Pull from this repo | Automatic |
Contributing
Contributions are welcome — please open an issue first to discuss what you'd like to change.
License
See LICENSE for details.