Version: 3.0 (supersedes execution-plan.md V2.2) Date: 2026-02-13 Owner: Yohay Etsion Status: GO — the build guide Aligned To: PRD V1.5, Architecture Stack V1.4, System Prompt Deep Dive, Vercel Constraints Deep Dive
This is the step-by-step build plan. Each phase has a clear entry gate, specific deliverables, file-level implementation tasks, test criteria, and an exit gate. Open this before every build session, check off what's done, pick up where you left off.
Builder: Yohay + Claude Code (backend, architecture, integration) + Cursor (frontend UI, rapid iteration) Pace: ~15-20 hrs/week Total MVP: 8-10 weeks
| Layer | Technology | Package |
|---|---|---|
| Frontend | Next.js 14+ (App Router) | next |
| Styling | Tailwind CSS 4 + Radix UI | tailwindcss, @radix-ui/* |
| Client State | Zustand | zustand |
| Server State | TanStack Query | @tanstack/react-query |
| Agent Runtime | Vercel AI SDK v6 | ai, @ai-sdk/anthropic, @ai-sdk/openai |
| Database | PostgreSQL 16 (Neon) | @neondatabase/serverless, drizzle-orm |
| ORM | Drizzle | drizzle-orm, drizzle-kit |
| Auth | Clerk | @clerk/nextjs |
| Payments | Stripe | stripe, @stripe/stripe-js |
| Cloud Storage | Google Drive API v3 | googleapis |
| Search | Typesense | typesense |
| Object Storage | Cloudflare R2 | @aws-sdk/client-s3 (S3-compatible) |
| Monitoring | Sentry | @sentry/nextjs |
| Analytics | PostHog | posthog-js |
Hosting: Vercel (single deployment — frontend + API routes) Monthly infra: ~$162
npx create-next-app@latest legionis \
--typescript --tailwind --app --src-dir \
--import-alias "@/*"
cd legionis
git init && git add -A && git commit -m "Initial scaffold"
Directory structure (target):
legionis/
├── src/
│ ├── app/ # Next.js App Router pages
│ │ ├── (auth)/ # Auth pages (sign-in, sign-up)
│ │ ├── (dashboard)/ # Main app (workspace, chat, settings)
│ │ ├── api/ # API routes
│ │ │ ├── chat/ # Agent/skill execution endpoint
│ │ │ ├── drive/ # Google Drive proxy routes
│ │ │ ├── webhooks/ # Stripe, Clerk webhooks
│ │ │ └── health/ # Health check
│ │ └── layout.tsx # Root layout
│ ├── components/ # React components
│ │ ├── chat/ # Chat UI components
│ │ ├── explorer/ # File explorer components
│ │ ├── shared/ # Shared/common components
│ │ └── ui/ # Radix-based primitives
│ ├── lib/ # Core libraries
│ │ ├── agent/ # Agent runtime (Vercel AI SDK wrappers)
│ │ ├── db/ # Drizzle schema + queries
│ │ ├── drive/ # Google Drive client
│ │ ├── prompt/ # Prompt compilation + caching
│ │ ├── stripe/ # Stripe helpers
│ │ └── utils/ # Shared utilities
│ ├── tools/ # Custom AI SDK tool definitions
│ │ ├── read-file.ts
│ │ ├── write-file.ts
│ │ ├── edit-file.ts
│ │ ├── glob-files.ts
│ │ ├── grep-content.ts
│ │ ├── list-directory.ts
│ │ └── spawn-agent.ts
│ ├── personas/ # Compiled agent personas (build output)
│ └── middleware.ts # Clerk auth middleware
├── scripts/
│ ├── compile-prompts.ts # SKILL.md → compiled persona JSON
│ ├── seed-skills.ts # Seed skill metadata into DB
│ └── migrate.ts # DB migration runner
├── drizzle/ # Migration files
├── os-source/ # Git submodules for OS content
│ ├── product-org-os/ # PUBLIC submodule: 13 agents, 61 skills, 9 knowledge packs
│ └── extension-teams/ # PRIVATE submodule: 68 agents, 34 knowledge packs, 15 integrations
├── public/
├── .env.local # Local env vars (not committed)
├── drizzle.config.ts
├── next.config.ts
├── tailwind.config.ts
└── tsconfig.json
Create accounts and collect credentials before writing code:
| Service | Action | Env Var(s) | Status |
|---|---|---|---|
| Vercel | Create project, link repo | Auto-configured via vercel link | ✅ Done |
| Neon | Create project + database | DATABASE_URL | ✅ Done |
| Clerk | Create application (Google + MS + LinkedIn + email) | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY, CLERK_SECRET_KEY | ✅ Done |
| Stripe | Create products + prices ($10/$7/$5/$25) + trial config | STRIPE_SECRET_KEY, NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY, STRIPE_WEBHOOK_SECRET, 4x STRIPE_*_PRICE_ID | ✅ Done (test mode) |
| Google Cloud | Create OAuth 2.0 client (Drive API scopes) | GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET | ✅ Done |
| Cloudflare R2 | Create bucket | R2_ACCESS_KEY_ID, R2_SECRET_ACCESS_KEY, R2_BUCKET_NAME, R2_ENDPOINT | Pending (Week 4) |
| Typesense | Create cloud cluster | TYPESENSE_HOST, TYPESENSE_API_KEY | Pending (Week 8) |
| Sentry | Create Next.js project | SENTRY_DSN | Pending (Week 8) |
| PostHog | Create project | NEXT_PUBLIC_POSTHOG_KEY | Pending (Week 8) |
5 of 9 services configured (Feb 14). Remaining 4 are not needed until later phases.>
Vercel env vars complete (Feb 16): All 16 production env vars added to Vercel dashboard (Production + Preview + Development). Includes: Clerk (7), Neon (1), Stripe (7), Google Drive (3 + ENCRYPTION_KEY). GCP redirect URI also added to OAuth client.
Both content sources are integrated as git submodules (open core model):
os-source/
├── product-org-os/ ← PUBLIC submodule (yohayetsion/product-org-os @ v3.0.1)
│ └── product-org-plugin/
│ ├── skills/ # 13 OS agent personas + 61 skill templates
│ ├── rules/ # 22 rules files
│ ├── reference/knowledge/ # 9 knowledge packs
│ └── integrations/ # 6 integration templates
└── extension-teams/ ← PRIVATE submodule (yohayetsion/extension-teams)
├── {9 team dirs}/ # 68 Extension Team agent SKILL.md files
├── reference/knowledge/ # 34 knowledge packs
└── integrations/ # 15 integration templates
Update workflow: cd os-source/
Status: ✅ Done (Feb 15). Submodules pinned, 81 total agents available for compile-prompts.
Push scaffold to Vercel. Verify https://legionis.vercel.app (or custom domain) shows the default Next.js page. This is the "it deploys" checkpoint.
Completed Feb 13 — commit2348bec.legionis.vercel.applive. Auto-deploys on push.
Exit gate: Repo exists, deploys to Vercel, all external accounts created with env vars in .env.local and Vercel project settings.
Substantially met. 5/9 services configured, all deployed to Vercel (Feb 16). Remaining 4 (R2, Typesense, Sentry, PostHog) not needed until later phases.
Goal: User can sign up, see a dashboard, subscribe, and hit a billing wall. No AI yet.
Create src/lib/db/schema.ts:
// Core tables — define with drizzle-orm
users // Clerk user ID, email, created_at, subscription_status
workspaces // id, user_id, name, drive_folder_id, drive_access_token (encrypted), created_at
api_keys // id, user_id, provider (anthropic|openai), encrypted_key, last_four, validated_at
conversations // id, workspace_id, title, created_at, updated_at
messages // id, conversation_id, role (user|assistant|system|tool), content, metadata, created_at
context_entries // id, workspace_id, type (decision|bet|feedback|learning), entry_id (DR-YYYY-NNN), title, content, metadata (JSON), created_at
file_references // id, workspace_id, drive_file_id, path, name, mime_type, context_entry_id (optional)
subscriptions // id, user_id, stripe_customer_id, stripe_subscription_id, plan, status, trial_ends_at, current_period_end
Tasks:
src/lib/db/schema.tsdrizzle-kit generatedrizzle-kit migratesrc/lib/db/index.ts — Neon serverless client + Drizzle instancesrc/lib/db/queries/ — typed query helpers per tableCompleted Feb 14 — commitc78bd8d. 8 tables: users, workspaces, api_keys, conversations, messages, context_entries, file_references, subscriptions. Migration0000_whole_wonder_man.sql.
Tasks:
@clerk/nextjs to root layoutsrc/middleware.ts — protect /dashboard/* routes, allow /sign-in, /sign-up, /api/webhookssrc/app/(auth)/sign-in/[[...sign-in]]/page.tsxsrc/app/(auth)/sign-up/[[...sign-up]]/page.tsxsrc/app/api/webhooks/clerk/route.ts — sync user creation to users tableCompleted Feb 14 — commitc78bd8d. Svix signature verification on webhook. User CRUD insrc/lib/db/queries/users.ts. Root page auth-aware redirect. Tested: Google sign-in → dashboard → Neon user row verified.
Tasks:
src/app/api/webhooks/stripe/route.ts — handle checkout.session.completed, customer.subscription.created, customer.subscription.updated, customer.subscription.deleted, invoice.payment_failedsrc/lib/stripe/checkout.ts — create checkout sessionsrc/lib/stripe/portal.ts — create billing portal sessionsubscriptions.status before agent executionsrc/app/dashboard/settings/billing/page.tsx + billing-client.tsx — plan display, trial countdown, upgrade CTACompleted Feb 14 — commit>ac749da. Stripe account "Legionis" (test mode). 4 products created via API. Webhook endpoint registered athttps://legionis.vercel.app/api/webhooks/stripe. Full billing UI with plan cards, status badges, checkout buttons, portal link. Price IDs: individualprice_1T0lKDCzBUEHrjdqtWL8qlKl, teamprice_1T0m4eCzBUEHrjdqpARE8Ym2, addonprice_1T0m4nCzBUEHrjdqynA3See4, full-orgprice_1T0m4xCzBUEHrjdqbxtseHCp.
Note: Pricing updated from plan's $10/$8 to actual Agent Catalog pricing: $10/$7/$5/$25. Subscription gating middleware deferred — will implement when chat API route is built (Week 2).>
~~Action needed: Add 7 Stripe env vars to Vercel dashboard.~~ ✅ Done (Feb 16).
Tasks:
src/app/dashboard/layout.tsx — sidebar + main content areasrc/app/dashboard/page.tsx — personalized greeting from DB, QuickAction cardssrc/app/dashboard/settings/page.tsx + layout.tsx — settings layout with tabbed nav (Profile, API Keys, Billing, Storage) using lucide-react iconsCompleted Feb 14 — commitce76779. Also created:settings/api-keys/page.tsx(Anthropic + OpenAI cards, disabled),settings/storage/page.tsx(Drive connection placeholder). Clerk UserButton integrated in sidebar. lucide-react installed for icons.
Week 1 Exit Gate:
Week 1 substantially complete. One exit gate item (subscription gating) deferred to Week 2 — it requires the chat API route to gate against.
Goal: A single agent (PM) can receive a message, use tools to read/write files in memory, and stream a response. No Google Drive yet — local/mock storage first.
compile-prompts.ts)The build script that transforms OS source into SaaS-ready prompts. This is the bridge between the open-source plugin and the SaaS product.
Input: os-source/product-org-os/product-org-plugin/skills//SKILL.md + os-source/extension-teams//SKILL.md + os-source/product-org-os/product-org-plugin/rules/*.md
Output: src/personas/compiled-personas.json + src/personas/compiled-rules.json
Three-layer architecture (from system prompt deep dive):
| Layer | Content | Tokens | Caching |
|---|---|---|---|
| L1: Core Protocol | Compiled rules (response format, routing, V2V, context, delegation, principles, meeting mode, no-fabrication) | ~1,500 | Global cache (all users, all agents) |
| L2: Agent Persona | Compressed SKILL.md (identity, how-I-think, RACI, deliverables, collaboration, skills, V2V phase) | ~500 | Per-agent cache (shared across users) |
| L3: Domain Rules | Conditional rules (decision-system, strategy, roadmaps, GTM, requirements, maturity, auto-context, context-graph) + knowledge packs | ~300-500 | Per-task (loaded based on skill/domain) |
Tasks:
scripts/compile-prompts.tscompiled-personas.json with structure: {
"coreProtocol": "...(L1 text)...",
"agents": {
"product-manager": { "persona": "...(L2)", "skills": [...], "primaryPhases": [...], "emoji": "📝", "displayName": "Product Manager" },
...
},
"domainRules": {
"decisions": "...(L3 conditional)...",
"strategy": "...",
...
},
"knowledgePacks": {
"prioritization": "...",
"pricing-frameworks": "...",
...
},
"skillTemplates": {
"prd": { "description": "...", "template": "...", "phase": 3 },
...
}
}
compile-prompts to package.json scripts — runs as prebuild stepsrc/lib/agent/)The core engine that executes agents using Vercel AI SDK.
src/lib/agent/runtime.ts — Main agent execution:
// Core function signature
async function executeAgent(params: {
agentKey: string; // "product-manager", "vp-product", etc.
userMessage: string; // User's input
conversationHistory: Message[];
workspace: Workspace; // Drive connection, file context
apiKey: string; // User's decrypted API key
provider: 'anthropic' | 'openai';
model: string; // "claude-sonnet-4-5-20250929", etc.
onStream?: (chunk: string) => void;
}): Promise
Tasks:
src/lib/agent/runtime.ts — main executeAgent() functioncache_control: { type: "ephemeral" } on L1 and L2 blockssrc/lib/agent/provider-factory.ts — create provider instance from user's API key: // Per-request provider creation (BYOT)
function createProvider(apiKey: string, provider: 'anthropic' | 'openai') {
if (provider === 'anthropic') return createAnthropic({ apiKey });
if (provider === 'openai') return createOpenAI({ apiKey });
}
src/lib/agent/tools.ts — register all custom tools with the agentgenerateText() call with maxSteps: 15 and tool definitionsstreamText() variant for streaming responsesonStepFinish hook for tool call loggingonFinish hook for usage tracking and ROI calculationsrc/tools/)These replace Claude Code's built-in Read, Write, Edit, Glob, Grep. In MVP, they operate on an in-memory file system (Week 4 swaps to Google Drive).
src/tools/read-file.ts:
export const readFileTool = tool({
description: 'Read a file from the workspace',
parameters: z.object({
path: z.string().describe('File path relative to workspace root'),
}),
execute: async ({ path }, { workspace }) => {
return await workspace.readFile(path);
}
});
Tasks:
src/tools/read-file.ts — read file contents by pathsrc/tools/write-file.ts — create or overwrite file at pathsrc/tools/edit-file.ts — find-and-replace in file (old_string → new_string)src/tools/glob-files.ts — list files matching glob patternsrc/tools/grep-content.ts — search file contents with regexsrc/tools/list-directory.ts — list directory contentssrc/tools/spawn-agent.ts — spawn a sub-agent (nested generateText() call)src/lib/workspace/memory-fs.ts — in-memory file system for testing (Week 4 replaces with Drive)src/app/api/chat/route.ts:
export async function POST(req: Request) {
// 1. Auth check (Clerk)
// 2. Get workspace + API key for user
// 3. Determine agent (from @mention or auto-route)
// 4. Assemble system prompt (L1 + L2 + L3)
// 5. Call streamText() with tools
// 6. Return SSE stream via .toDataStreamResponse()
}
Tasks:
src/app/api/chat/route.ts@agent mention or apply auto-routingstreamText() call with all tools registered.toDataStreamResponse() for SSE streaming/api/chat with PM agent → streamed responsecompile-prompts.ts produces valid compiled output from OS sourceGoal: Working chat interface where a user can type messages, invoke skills with /, mention agents with @, and see streaming responses with agent identity.
src/components/chat/)Tasks:
src/components/chat/chat-container.tsx — main chat area with message list + inputsrc/components/chat/message.tsx — individual message rendering (user vs assistant)src/components/chat/agent-message.tsx — agent-styled message with emoji + display name headersrc/components/chat/streaming-message.tsx — renders tokens as they arrive via useChat hooksrc/components/chat/tool-call-indicator.tsx — inline "📁 Reading file..." indicatorssrc/components/chat/input.tsx — text input with submit button, keyboard shortcuts (Cmd+Enter)src/components/chat/meeting-mode.tsx — PLT/gateway multi-agent response display (collapsible sections, alignment/tension/synthesis)useChat from ai/react for streamingTasks:
src/components/chat/skill-palette.tsx — command palette triggered by / key/)/prd with cursor positioned for argument/ → palette appears → filter to "de" → see decision-record, decision-charter → select → insertedTasks:
src/components/chat/agent-selector.tsx — selector triggered by @ key@pm with cursor positioned@ → selector appears → filter to "biz" → see @bizops, @bizdev → selectWhen user invokes /prd authentication, the system must:
Tasks:
src/lib/agent/skill-dispatcher.ts:/skill-name [arguments] from user input
- Look up skill template from compiled personas
- Determine owning agent (from SKILL.md frontmatter)
- Inject skill template into system prompt as additional context
- Execute via executeAgent() with skill context
src/lib/agent/auto-router.ts:@agent override
/prd authentication → executes as PM with PRD template → produces PRD outputTasks:
compile-prompts.ts correctly processes all 39 SKILL.md files:/ triggers skill palette with fuzzy search across 61 skills@ triggers agent selector with 39 agents + 5 gatewaysGoal: Replace in-memory filesystem with real Google Drive. User connects Drive, selects workspace folder, and all agent file operations go through Drive API.
Tasks:
src/app/api/drive/auth/route.ts — initiate OAuth flowsrc/app/api/drive/callback/route.ts — handle OAuth callback, exchange code for tokenshttps://www.googleapis.com/auth/drive.file (access to files created/opened by app)workspaces tablesrc/app/(dashboard)/onboarding/drive/page.tsx — "Connect Google Drive" UITasks:
src/lib/drive/client.ts — Google Drive API client wrappersrc/lib/drive/workspace.ts — workspace initialization: [Selected Folder]/
├── context/
│ ├── decisions/
│ ├── bets/
│ ├── feedback/
│ ├── learnings/
│ ├── interactions/
│ ├── portfolio/
│ ├── documents/
│ ├── assumptions/
│ ├── handoffs/
│ ├── roi/
│ └── index.json # Structured context index
└── deliverables/
- Store folder_id as workspace root in database
src/app/(dashboard)/onboarding/workspace/page.tsx — folder selection UISwap in-memory FS with real Drive API calls in all tools:
Tasks:
src/lib/drive/operations.ts:readFile(folderId, path) — resolve path to file ID → download content
- writeFile(folderId, path, content) — create or update file at path
- editFile(folderId, path, oldStr, newStr) — download, replace, re-upload
- listDirectory(folderId, path) — list files in subfolder
- globFiles(folderId, pattern) — search by name pattern
- grepContent(folderId, pattern, path?) — download + search content
- getFileById(fileId) — direct ID-based access
file_references table (avoid repeated Drive lookups)src/tools/ to use Drive operations instead of memory FSTasks:
src/components/explorer/file-tree.tsx — tree view of workspacesrc/components/explorer/file-item.tsx — individual file/folder nodesrc/components/explorer/file-preview.tsx — markdown preview panel~~Action needed: AddGOOGLE_REDIRECT_URI=https://legionis.vercel.app/api/drive/callbackto Vercel dashboard env vars.~~ ✅ Done (Feb 16). Also addedhttps://legionis.vercel.app/api/drive/callbackas authorized redirect URI in Google Cloud Console. Whenapp.legionis.aigoes live, add that URL too.
Week 4 Exit Gate:
Goal: All 39 agents operational with BYOT key routing. Context layer (save, recall, feedback) works against Drive storage.
Tasks:
src/lib/crypto/envelope.ts — envelope encryption for API keys:ENCRYPTION_MASTER_KEY)
- Per-key DEK (data encryption key) generated on save
- AES-256-GCM encryption
src/app/(dashboard)/settings/api-keys/page.tsx:src/lib/agent/key-router.ts:/api/chat/route.tsGoal: Users control the quality-cost tradeoff with a global setting that overrides per-agent model defaults.
Three-position setting: Maximum Quality (always top-tier models) | Balanced (default — uses SKILL.md model per agent) | Maximum Efficiency (always cheapest sufficient model).
Current compiled persona model distribution: 12 agents use "sonnet" (OS agents), 69 agents use "opus" (Extension Teams). provider-factory.ts already resolves abstract model names via MODEL_MAP. The toggle intercepts agentModel before resolveModel().
Tasks:
quality_preference column to workspaces table: enum('maximum_quality', 'balanced', 'maximum_efficiency'), default 'balanced'src/lib/agent/quality-override.ts:applyQualityPreference(preference, agentModel) → returns overridden model
- maximum_quality → always returns "opus"
- balanced → returns agentModel unchanged (SKILL.md default)
- maximum_efficiency → always returns "haiku"
executeAgent() in runtime.ts: call applyQualityPreference() before resolveModel()src/app/(dashboard)/settings/ai-config/page.tsx:Tasks:
The context layer is the "organizational memory" that persists across sessions. Stored in the user's Google Drive workspace.
Tasks:
src/lib/context/manager.ts — context layer operations:saveDecision(workspace, decision) → write to context/decisions/YYYY/ + update index
- saveBet(workspace, bet) → write to context/bets/YYYY/ + update index
- saveFeedback(workspace, feedback) → write to context/feedback/YYYY/ + update index
- saveLearning(workspace, learning) → write to context/learnings/index.md
- recallByTopic(workspace, topic) → search index.json topicIndex
- getPortfolioStatus(workspace) → read context/portfolio/active-bets.md
- logInteraction(workspace, interaction) → append to daily log + update index.json
src/lib/context/index-manager.ts:context/index.json (structured JSON index with topic, product, phase, status indexes)
- Auto-generate IDs: DR-YYYY-NNN, SB-YYYY-NNN, FB-YYYY-NNN, etc.
- Cross-reference management: link decisions ↔ bets ↔ feedback ↔ learnings
src/lib/context/auto-inject.ts:/context-save → calls saveDecision or saveBet
- /context-recall → calls recallByTopic
- /feedback-capture → calls saveFeedback
- /feedback-recall → searches feedback index
- /portfolio-status → reads portfolio state
Goal: Users who don't want to manage their own API keys can opt into Legionis Tokens — platform-provided AI at a 30% markup over provider costs. BYOT remains the default and recommended path.
Architecture: When a user selects "Managed Tokens", requests route through Legionis's pooled API keys instead of the user's own keys. Every request is metered for billing.
Tasks:
src/lib/tokens/managed-provider.ts:LEGIONIS_ANTHROPIC_KEY, LEGIONIS_OPENAI_KEY)
- Provider selection logic: pick provider based on agent model + user preference
- Per-request cost calculation: track input/output tokens, apply provider pricing, add 30% markup
usage_events: usage_events: id, user_id, workspace_id, conversation_id, agent_key, model, provider,
input_tokens, output_tokens, provider_cost_micros, markup_micros, total_cost_micros,
quality_preference, token_source (byot|managed), created_at
src/lib/tokens/metering.ts:usage_events table
- Provider cost lookup table: current rates for all models (claude-opus, claude-sonnet, claude-haiku, gpt-4o, gpt-4o-mini)
- Calculate: provider_cost = (input_tokens × input_rate) + (output_tokens × output_rate)
- Calculate: markup = provider_cost × 0.30
- Calculate: total_cost = provider_cost + markup
src/lib/tokens/spend-tracking.ts:src/lib/agent/provider-factory.ts:token_source setting: 'byot' or 'managed'
- If BYOT: use user's decrypted key (existing flow)
- If Managed: use LEGIONIS_ANTHROPIC_KEY or LEGIONIS_OPENAI_KEY, enable metering
usage_events → report to Stripe
- Monthly invoice includes: platform subscription + token usage
LEGIONIS_ANTHROPIC_KEY and LEGIONIS_OPENAI_KEY in Vercel env vars
Tasks:
messages table after each turn/prd on a topic with existing decisions → decisions surfacedGoal: Agents can spawn sub-agents, invoke skills, and collaborate using the 4 delegation patterns. Full skill execution pipeline works for all 61 skills.
The spawnAgent tool allows an agent to create a child agent (nested generateText() call).
Tasks:
src/tools/spawn-agent.ts:generateText() (not streaming — parent agent consumes result)
- Enforce max depth: 3 levels
- Return child agent's response to parent
[CONSULTATION], [DELEGATION], [REVIEW], [DEBATE] prefixes in spawn prompts
- Tag child agent response with pattern type for attribution
Tasks:
/strategic-intent or /market-analysis
- Phase 2: /decision-record or /pricing-strategy
- Phase 3: /prd or /user-story
- Phase 4: /campaign-brief or /sales-enablement
- Phase 5: /value-realization-report
- Phase 6: /outcome-review or /retrospective
- Context: /context-save, /context-recall, /feedback-capture
/prd authentication → creates new PRD
- UPDATE mode: /prd update auth PRD with MFA scope → updates existing
- FIND mode: /prd find authentication → lists related PRDs
context/decisions/YYYY/
- PRDs → deliverables/ (or user-specified path)
- Context entries → appropriate context subfolder
context/documents/index.mdTasks:
src/lib/roi/calculator.ts:reference/roi-baselines.md)
- Assess complexity: simple (0.5×), standard (1.0×), complex (1.5×), enterprise (2.0×)
- Calculate: base time × complexity factor
- Track elapsed time per operation
src/lib/roi/tracker.ts:context/roi/session-log.md in workspace
- Update monthly history in context/roi/history/YYYY-MM.md
⏱️ ~X hrs/min saved in Ys using Nk tokens (vs. manual equivalent)/prd → see ROI line with token count → invoke /decision-record → see updated session total/strategic-intent → /decision-record → /prd → context persists between all threeGoal: @product and @plt gateways work. PLT spawns 3-4 agents in parallel, displays individual perspectives with alignment/tension/synthesis.
Tasks:
src/lib/agent/gateway.ts:@product, @plt, @design, @architecture, @marketing)
- Analyze request to determine ownership complexity (SINGLE / PRIMARY+ / MULTI)
- Select agents based on RACI mapping + domain analysis
- For SINGLE: spawn one agent, pass through response
- For PRIMARY+: spawn lead + supporting agents
- For MULTI: spawn all needed agents in parallel
src/lib/agent/parallel-executor.ts:generateText() calls concurrently (Promise.allSettled)
- Collect responses with timing data
- Handle partial failures (some agents succeed, some fail)
- Enforce max parallel: 4 agents
- Stagger start times by 500ms to reduce rate limit risk
@product gateway logic (from SKILL.md):@plt gateway logic:@design, @architecture, @marketing):@plt should we prioritize webhooks or SDK? → spawns VP Product + PM + PMM Dir → individual responses + synthesisTasks:
src/components/chat/meeting-mode.tsx:Tasks:
⏱️ Total: ~X hrs saved (vs. manual equivalent) └─ 📈 VP: ~A min | 📝 PM: ~B min | 📣 Dir PMM: ~C min
@product gateway routes correctly (SINGLE/PRIMARY+/MULTI)@plt spawns 3-4 agents in parallel@design, @architecture, @marketing) workGoal: Complete onboarding flow, settings page, and polish for beta readiness. Everything works end-to-end.
First-time user experience:
Sign up → Connect API Key → Connect Google Drive → Select/Create Workspace → Guided First Interaction
Tasks:
src/app/(dashboard)/onboarding/page.tsx — stepper component:/prd or /decision-record invocation
Tasks:
settings/profile — name, email, avatar (from Clerk)settings/api-keys — add/update/delete API keys (Anthropic, OpenAI)settings/storage — Google Drive connection status, disconnect/reconnect, workspace selectionsettings/billing — current plan, trial countdown, upgrade button, Stripe portal linksettings/model — default model selection (Haiku/Sonnet/Opus), display cost implicationssettings/about — version info, prompt cost efficiency display ("Prompt caching saves ~90%")Tasks:
src/lib/search/client.ts — Typesense client/context-recall uses Typesense for fast full-text searchTasks:
Tasks:
user_signed_up, trial_started, trial_converted, subscription_cancelled
- skill_invoked (which skill, agent, duration)
- agent_spawned (which agent, duration, tool_calls)
- plt_session (agents involved, duration)
- file_created, context_saved, context_recalled
- onboarding_step_completed, onboarding_completed
GET /api/health → DB connection, Drive API, Anthropic API status/api/healthGoal: 5-10 beta users test the full product. Identify and fix P0/P1 bugs.
Tasks:
Each beta user should test:
| Area | Test Script |
|---|---|
| Onboarding | Sign up → connect API key → connect Drive → create workspace → first skill |
| Skills | Invoke 5 different skills, verify output quality and file creation |
| Agents | Spawn 3 different agents, verify persona + domain accuracy |
| PLT | Run 1 PLT session, verify Meeting Mode display |
| Context | Save 3 decisions → recall by topic → verify cross-references |
| File Explorer | Browse workspace, preview files, verify Drive sync |
| Billing | View trial status, check billing page |
Tasks:
Goal: Production-ready. Domain configured, landing page live, monitoring active.
Tasks:
app.legionis.aiTasks:
legionis.ai:Tasks:
Run through PRD Section 12 launch checklist:
Technical:
After launch, focus shifts to retention, enrichment, expansion, and external tool integrations.
Goal: Users can connect external tools (Jira, Slack, GitHub, Figma, etc.) via OAuth. Agents use connected tools to perform real operations. Graceful fallback when tools aren't connected.
The framework provides a generic OAuth + API call pipeline that all integrations share. Individual integrations are configuration on top of this framework.
Architecture: Hybrid OAuth model. Users click "Connect [Tool]" → standard OAuth 2.0 flow → Legionis stores encrypted tokens in tool_connections table → agents call tool APIs directly using stored tokens. The OS integration templates inform what API calls to make; the platform provides the authenticated HTTP client. No MCP servers needed in production.
DB Schema:
tool_connections:
id, user_id, workspace_id,
service (jira|slack|github|figma|...),
provider_config (JSON: instance_url, project_key, etc.),
encrypted_oauth_tokens (access_token, refresh_token, expiry),
scopes,
status (connected|expired|error),
connected_at, last_used_at
Tasks:
tool_connections table (Drizzle migration)src/lib/integrations/oauth.ts — generic OAuth 2.0 framework:initiateOAuth(service, scopes) → redirect URL
- handleCallback(service, code) → exchange for tokens → encrypt → store
- refreshToken(connectionId) → auto-refresh before expiry
- Support both OAuth 2.0 Authorization Code (most tools) and API key (simpler tools)
src/lib/integrations/registry.ts — integration registry:isConnected(userId, service) → boolean
src/lib/integrations/api-client.ts — authenticated HTTP client:fetch with OAuth token injection
- Auto-refresh on 401
- Rate limiting and retry logic
- Audit logging (all external API calls logged)
src/tools/external-tool.ts — agent-facing tool:{ service: "jira", operation: "create_issue", params: {...} }
- Tool checks: user has service connected?
- If yes: execute API call with stored tokens → return result
- If no: return graceful fallback ("Jira is not connected. Here are the issues to create manually: [list]")
src/app/(dashboard)/settings/connections/page.tsx — Connections settings page:src/app/api/integrations/callback/route.tsPriority integrations for launch-adjacent value. These cover the most common "where does the output go?" question.
Jira / Atlassian (Operations, Product):
read:jira-work, write:jira-work, read:jira-usersrc/lib/integrations/services/jira.tssearchIssues(jql) — POST /rest/api/3/search/jql
- getIssue(key) — GET /rest/api/3/issue/{key}
- createIssue(project, type, summary, description) — POST /rest/api/3/issue
- getBoard(id) — GET /rest/agile/1.0/board/{id}
- getSprint(id) — GET /rest/agile/1.0/sprint/{id}
chat:write, channels:read, channels:historysrc/lib/integrations/services/slack.tspostMessage(channel, text) — POST /api/chat.postMessage
- listChannels() — GET /api/conversations.list
- getHistory(channel) — GET /api/conversations.history
repo, read:orgsrc/lib/integrations/services/github.tslistPRs(repo), getPR(repo, number), createIssue(repo, title, body)
- getSecurityAlerts(repo), searchCode(query)
src/lib/integrations/services/linear.ts)searchIssues(query), createIssue(team, title, description), getCycles(team)
Figma (Design Team):
src/lib/integrations/services/figma.tsgetFile(fileKey), getComponents(fileKey), getStyles(fileKey), getComments(fileKey)
src/lib/integrations/services/analytics.tsrunReport(property, metrics, dimensions, dateRange)
- runRealtimeReport(property)
- runFunnelReport(property, steps)
src/lib/integrations/services/email-platform.tsgetCampaignStats(id), getListInfo(id), createCampaign(params), getDeliverability()
src/lib/integrations/services/search-console.tsqueryAnalytics(site, dateRange, dimensions), listSitemaps(site), getIndexStatus(site)
Xero / QuickBooks (Finance Team):
src/lib/integrations/services/accounting.tsgetProfitLoss(period), getBalanceSheet(date), getCashFlow(period)
- getInvoices(status, dateRange), getBankTransactions(dateRange)
src/lib/integrations/services/stripe-billing.tsgetSubscriptions(status), getRevenue(period), getCustomers(params)
- getMRR(), getChurnRate(period), getInvoices(status)
src/lib/integrations/services/contract-management.tssearchAgreements(query), getAgreement(id), getAuditTrail(id)
- listTemplates(), getEnvelopeStatus(id)
src/lib/integrations/services/compliance.tsgetTests(framework), getFrameworkStatus(), getVendors(), getEvidence(testId)
src/lib/integrations/services/itsm.tsgetIncidents(query), createIncident(params), getChanges(query)
- getCMDB(ciType), getSLAStatus(), getKnowledgeArticles(query)
src/lib/integrations/services/identity.tslistUsers(filter), getUser(id), listGroups(), getGroupMembers(id)
- listApplications(), getSignInLogs(dateRange), getPrivilegedRoles()
src/lib/integrations/services/crm.tsqueryRecords(soql), getRecord(type, id), createRecord(type, data)
- getPipeline(id), getDeals(stage), getActivities(recordId)
src/lib/integrations/services/research-data.tssearchCompanies(query), getCompany(id), getFundingRounds(companyId)
- getInvestors(companyId), getComparables(criteria)
src/lib/integrations/services/knowledge-base.tssearchPages(query), getPage(id), createPage(parent, title, content)
- queryDatabase(id, filter), updatePage(id, content)
src/lib/integrations/services/miro.tsgetBoards(), getBoard(id), createStickyNote(boardId, content)
- createShape(boardId, shape), createConnector(boardId, start, end)
| Wave | Week | Integrations | Teams Served | Platforms |
|---|---|---|---|---|
| 1 | 11 | Project Mgmt + Communication + Code | Operations, Product, Architecture | Jira, Slack, GitHub, Linear |
| 2 | 12 | Design + Marketing | Design, Marketing | Figma, GA4, Mailchimp/SendGrid/HubSpot, Search Console |
| 3 | 13-14 | Finance + Legal + IT + Corp Dev + Knowledge | Finance, Legal, IT, Corp Dev, Operations | Xero/QBO, Stripe, DocuSign/PandaDoc, Vanta/Drata, ServiceNow/JSM, Okta/Entra, Salesforce/HubSpot CRM, PitchBook/Crunchbase, Notion/Confluence, Miro |
Total: ~30 platforms across 15 integration categories serving all 9 teams + Product Org.
Exit Gate (Week 14):
| Focus | Deliverable | Week |
|---|---|---|
| Per-agent model override | Users can set Opus vs Sonnet per agent in settings. Stored in user_agent_preferences table. Enhances the global Quality/Efficiency toggle from Week 5.1b with per-agent granularity. Settings UI: agent grid with model dropdown. | 15 |
| Knowledge packs | All 43 packs loaded conditionally per agent domain | 15 |
| Team personalities | Each of the 10 teams has a configurable personality tagline. Stored in team_config table. Injected into L2 persona layer. Default taglines ship; workspace admins can customize. | 16 |
| File explorer v2 | Create, rename, delete, drag-drop, bulk operations | 16 |
| Additional model providers | Google Gemini via @ai-sdk/google, Mistral via @ai-sdk/mistral | 17 |
| Voice input | Push-to-talk, transcription via Whisper API | 18 |
| OneDrive integration | Microsoft Graph API OAuth + file operations (cloud storage, not tool integration) | 19 |
| Dropbox integration | Dropbox API v2 OAuth + file operations | 20 |
| Advanced onboarding | Role-based skill recommendations, example workspaces | 20 |
These decisions are MADE. Do not re-litigate during build.
| Decision | Choice | Rationale | Reference |
|---|---|---|---|
| Agent runtime | Vercel AI SDK v6 | Pure API, multi-model, BYOT, unlimited subagents | sdk-comparison-presentation.html |
| Backend | Next.js API Routes (not separate Hono) | Single deployment, native AI SDK integration | Architecture Stack V1.4 |
| Storage | Google Drive first (not local FS) | Stable file IDs, cloud-native, cross-device | Architecture Stack V1.4 |
| Prompt architecture | 3-layer cached (L1+L2+L3) | 87% token reduction, ~$0.71/mo user cost | System Prompt Deep Dive |
| Auth | Clerk | Pre-built UI, social login, org support | Architecture Stack V1.4 |
| Database | Neon PostgreSQL + Drizzle | Serverless, branching, scale-to-zero, type-safe ORM | Architecture Stack V1.4 |
| Billing | Stripe | 1-month trial, $10/mo individual, $8/seat/mo team | PRD V1.5 |
| PLT timeout | 300s Fluid Compute (default) | 5x headroom vs 60s target | Vercel Constraints Deep Dive |
| Subagent depth | Max 3 levels | Prevents runaway chains, sufficient for delegation | PRD V1.5 |
| Model default | Claude Sonnet 4.5 | Best balance of quality and speed | Architecture Stack V1.4 |
| Token plan | BYOT default + Managed Tokens at 30% markup | BYOT preserves transparency USP; managed tokens reduce onboarding friction | Gateway meeting 2026-02-18 |
| Model routing | 3-tier global toggle (Quality/Balanced/Efficiency) | Users control cost-quality tradeoff; intercepted before resolveModel() | Gateway meeting 2026-02-18 |
| Tool integrations | Hybrid OAuth (not MCP in production) | Standard OAuth UX; direct API calls; OS templates inform operations | Gateway meeting 2026-02-18 |
| Integration rollout | 3 waves post-launch (Weeks 11-14) | Not a launch blocker; graceful fallback already works | Gateway meeting 2026-02-18 |
Auth (Clerk)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
CLERK_WEBHOOK_SECRET=whsec_...Database (Neon)
DATABASE_URL=postgresql://...Stripe
STRIPE_SECRET_KEY=sk_...
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_...
STRIPE_WEBHOOK_SECRET=whsec_...
STRIPE_INDIVIDUAL_PRICE_ID=price_...
STRIPE_TEAM_PRICE_ID=price_...Google Drive
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
GOOGLE_REDIRECT_URI=https://app.legionis.ai/api/drive/callbackEncryption
ENCRYPTION_MASTER_KEY=...Cloudflare R2
R2_ACCESS_KEY_ID=...
R2_SECRET_ACCESS_KEY=...
R2_BUCKET_NAME=legionis-storage
R2_ENDPOINT=https://...r2.cloudflarestorage.comTypesense
TYPESENSE_HOST=...
TYPESENSE_API_KEY=...
TYPESENSE_PORT=443
TYPESENSE_PROTOCOL=httpsMonitoring
SENTRY_DSN=https://...
NEXT_PUBLIC_POSTHOG_KEY=phc_...
NEXT_PUBLIC_POSTHOG_HOST=https://us.i.posthog.comApp
NEXT_PUBLIC_APP_URL=https://app.legionis.aiLegionis Tokens (Managed Plan) — Week 5.4
LEGIONIS_ANTHROPIC_KEY=sk_ant_...
LEGIONIS_OPENAI_KEY=sk_...
STRIPE_METERED_PRICE_ID=price_... # Metered price for token consumptionOAuth — External Tool Integrations (Weeks 11-14)
Per-service OAuth client IDs/secrets added as integrations are built
JIRA_CLIENT_ID=...
JIRA_CLIENT_SECRET=...
SLACK_CLIENT_ID=...
SLACK_CLIENT_SECRET=...
GITHUB_CLIENT_ID=...
GITHUB_CLIENT_SECRET=...
Additional service OAuth creds added per wave
Start each build session by:
End each session by:
Document Status: V3.0 — Definitive Build Guide Supersedes: execution-plan.md V2.2 Start Condition: This document is ready to execute. All architectural decisions are made, all dependencies are available, all service accounts need creation.