Project Chorus - Technical Architecture

June 5, 2026 · View on GitHub

中文版本

Project Chorus - Technical Architecture

Version: 2.0 Updated: 2026-02-18


1. System Overview

1.1 Positioning

Chorus is a platform for AI Agent and human collaboration, implementing the AI-DLC (AI-Driven Development Lifecycle) methodology. The core philosophy is Reversed Conversation: AI proposes, humans verify.

1.2 Core Capabilities

CapabilityDescription
Knowledge BaseProject context storage and querying
Task ManagementTask CRUD, status transitions, Kanban, Task DAG dependencies
Assignment MechanismFlexible Idea/Task assignment, supporting human and Agent collaboration
Proposal ApprovalPM Agent creates proposals, humans/Admin approve
MCP Server50+ tools, Agents connect via MCP protocol (Public/Session/Developer/PM/Admin)
Activity StreamReal-time tracking of all participant actions (with Session attribution)
Notification SystemIn-app notifications with SSE push, preference controls, MCP tools for agents
Session ObservabilityAgent Session + Task Checkin, Kanban/Task Detail displays active Workers in real-time
Chorus PluginClaude Code plugin, automating Session lifecycle (create/heartbeat/close)
Task DAGTask dependency modeling, cycle detection, @xyflow/react + dagre visualization
Global SearchUnified search across 6 entity types with scope filtering and Cmd+K UI (details)

1.3 Participants

┌─────────────────────────────────────────────────────────────────────┐
│                         Chorus Platform                              │
└─────────────────────────────────────────────────────────────────────┘
        ↑               ↑               ↑               ↑
        │               │               │               │
   ┌────┴────┐    ┌─────┴─────┐   ┌─────┴─────┐   ┌─────┴─────┐
   │  Human  │    │ Agent w/  │   │ Agent w/  │   │ Agent w/  │
   │         │    │ PM perms  │   │ Dev perms │   │Admin perms│
   └─────────┘    └───────────┘   └───────────┘   └───────────┘
   Web UI access   Claude Code     Claude Code     Claude Code
   Approve proposals Propose tasks  Execute tasks  Proxy approval

Agent Permission Model:

Agents are no longer locked into three fixed roles. Each Agent carries a permission set built from 5 resources (idea, proposal, document, project, task$) \times 3 \text{actions} ($read, write, admin) = 15 bits. The UI offers three presets plus a Custom option:

  • Developer preset (developer_agent): all *:read + task:write — execute tasks, report work, submit for verification.
  • PM preset (pm_agent): Developer preset + idea:write, proposal:write, document:write, project:write — requirements analysis, task breakdown, proposal creation.
  • Admin preset (admin_agent): all 15 bits — proxy human actions such as approving Proposals, verifying Tasks, managing Projects. (Warning: dangerous permissions.)
  • Custom: any combination of the 15 bits (e.g. a read-only auditor, or a PM that can also verify its own tasks).

See §6.3 for the authoritative preset-to-permission table and the effective-permission computation — computeEffectivePermissions(roles, customPermissions) in src/lib/authz/permissions.ts, which returns the union of every preset expansion and any custom permission bits.


2. Tech Stack

2.1 Core Technology Choices

LayerTechnologyVersionRationale
FrameworkNext.js15.xFull-stack unified, App Router, RSC support
LanguageTypeScript5.xType safety, frontend-backend consistency
ORMPrisma7.xType safety, migration management, good DX, no foreign key constraint design
DatabasePostgreSQL16Reliable, JSON support, future pgvector extensibility
UI Componentsshadcn/ui-Based on Radix, customizable, elegant
StylingTailwind CSS4.xAtomic CSS, rapid development
Authnext-auth5.xOIDC support, deep Next.js integration
MCP SDK@modelcontextprotocol/sdklatestOfficial TypeScript SDK
Cache/Pub-SubRedis (ioredis)7.xCross-instance SSE event delivery via ElastiCache Serverless
ContainerizationDocker Compose-One-click local dev setup

2.2 Development Tools

ToolPurpose
pnpmPackage management
ESLint + PrettierCode standards
VitestUnit testing
PlaywrightE2E testing

3. System Architecture

3.1 Overall Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Clients                                 │
├──────────────────┬──────────────────┬───────────────────────────┤
│    Web Browser   │    PM Agent      │    Personal Agent         │
│    (Human)       │    (Claude Code) │    (Claude Code)          │
└────────┬─────────┴────────┬─────────┴─────────┬─────────────────┘
         │                  │                   │
         │ HTTPS            │ MCP/HTTP          │ MCP/HTTP
         │                  │                   │
┌────────▼──────────────────▼───────────────────▼─────────────────┐
│                     Next.js Application                         │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Middleware Layer                       │   │
│  │  - OIDC Authentication (Human)                           │   │
│  │  - API Key Authentication (Agent)                        │   │
│  │  - Rate Limiting                                         │   │
│  │  - Request Logging                                       │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌─────────────────────┐  ┌─────────────────────────────────┐   │
│  │ Server Components   │  │        API Routes               │   │
│  │ + Server Actions    │  │      (Agent-only)               │   │
│  │   (Human frontend)  │  │                                 │   │
│  │                     │  │  /api/projects/*                │   │
│  │  - Dashboard        │  │  /api/ideas/*                   │   │
│  │  - Project Overview │  │  /api/documents/*               │   │
│  │  - Ideas List       │  │  /api/tasks/*                   │   │
│  │  - Documents List   │  │  /api/proposals/*               │   │
│  │  - Kanban Board     │  │  /api/agents/*                  │   │
│  │  - Proposal Review  │  │  /api/auth/*                    │   │
│  │  - Activity Feed    │  │  /api/mcp    <- MCP HTTP endpoint│   │
│  └─────────────────────┘  └─────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Service Layer                          │   │
│  │  - ProjectService      - IdeaService                     │   │
│  │  - DocumentService     - TaskService                     │   │
│  │  - ProposalService     - CommentService                  │   │
│  │  - AgentService        - ActivityService                 │   │
│  │  - AssignmentService                                     │   │
│  └──────────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │                    Data Access Layer                      │   │
│  │                    (Prisma Client)                        │   │
│  └──────────────────────────────────────────────────────────┘   │
└─────────────────────────────┬───────────────────────────────────┘

                    ┌─────────▼─────────┐
                    │    PostgreSQL     │
                    │    Database       │
                    └───────────────────┘

3.2 Controller-Service-DAO Architecture

Chorus adopts the classic three-layer architecture pattern with clear separation of concerns:

┌─────────────────────────────────────────────────────────────────┐
│                    Controller Layer                              │
│                    (Next.js API Routes)                          │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Responsibilities:                                       │   │
│  │  - Request/response handling                             │   │
│  │  - Auth/authorization checks                             │   │
│  │  - Parameter validation                                  │   │
│  │  - Calling the Service layer                             │   │
│  │  - Response formatting                                   │   │
│  └──────────────────────────────────────────────────────────┘   │
│  Code location: src/app/api/**/*.ts                             │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    Service Layer                                 │
│                    (Business Logic)                              │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Responsibilities:                                       │   │
│  │  - Business logic implementation                         │   │
│  │  - Data querying and transformation                      │   │
│  │  - Transaction management                                │   │
│  │  - Cross-entity operation coordination                   │   │
│  │  - State machine validation                              │   │
│  └──────────────────────────────────────────────────────────┘   │
│  Code location: src/services/*.service.ts                       │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│                    DAO Layer                                     │
│                    (Prisma Client)                               │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Responsibilities:                                       │   │
│  │  - Database operation encapsulation                      │   │
│  │  - ORM mapping                                           │   │
│  │  - Connection pool management                            │   │
│  └──────────────────────────────────────────────────────────┘   │
│  Code location: src/lib/prisma.ts (singleton)                   │
│            src/generated/prisma/ (generated client)              │
└─────────────────────────────────────────────────────────────────┘

Service Layer Modules

ServiceFileResponsibility
ProjectServiceproject.service.tsProject CRUD
IdeaServiceidea.service.tsIdea CRUD + status transitions + assignment
TaskServicetask.service.tsTask CRUD + status transitions + assignment
DocumentServicedocument.service.tsDocument CRUD
ProposalServiceproposal.service.tsProposal CRUD + approval workflow
AgentServiceagent.service.tsAgent + API Key management
CommentServicecomment.service.tsPolymorphic comments
ActivityServiceactivity.service.tsActivity logging (including assignment/release records)
AssignmentServiceassignment.service.tsAgent self-service queries (my tasks, available, unblocked)
NotificationServicenotification.service.tsNotification CRUD, preferences, SSE event emission
NotificationListenernotification-listener.tsActivity → Notification mapping, recipient resolution
SessionServicesession.service.tsAgent Session CRUD + Task Checkin/Checkout + heartbeat

Code Examples

Controller (route.ts):

// src/app/api/projects/route.ts
import { withErrorHandler, parsePagination } from "@/lib/api-handler";
import { success, paginated, errors } from "@/lib/api-response";
import { getAuthContext, isUser } from "@/lib/auth";
import * as projectService from "@/services/project.service";

export const GET = withErrorHandler(async (request) => {
  const auth = await getAuthContext(request);
  if (!auth) return errors.unauthorized();

  const { page, pageSize, skip, take } = parsePagination(request);
  const { projects, total } = await projectService.listProjects({
    companyUuid: auth.companyUuid,  // UUID-based
    skip,
    take,
  });

  return paginated(projects, page, pageSize, total);
});

Service (*.service.ts):

// src/services/project.service.ts
import { prisma } from "@/lib/prisma";

export async function listProjects({ companyUuid, skip, take }) {
  const [projects, total] = await Promise.all([
    prisma.project.findMany({
      where: { companyUuid },  // UUID-based query
      skip,
      take,
      orderBy: { updatedAt: "desc" },
    }),
    prisma.project.count({ where: { companyUuid } }),
  ]);
  return { projects, total };
}

3.3 Frontend Architecture: Server Components + Server Actions

Chorus adopts the Next.js 15 React Server Components (RSC) and Server Actions architecture to maximize server-side rendering and reduce client-side JavaScript.

┌─────────────────────────────────────────────────────────────────┐
│                    Server Components (Page layer)                │
│  ┌──────────────────────────────────────────────────────────┐   │
│  │  Responsibilities:                                       │   │
│  │  - Server-side data fetching (directly calling Service)  │   │
│  │  - Server-side auth checks (getServerAuthContext)        │   │
│  │  - Server-side HTML rendering                            │   │
│  │  - Passing data to Client Components                     │   │
│  └──────────────────────────────────────────────────────────┘   │
│  Code location: src/app/(dashboard)/**/page.tsx                 │
└─────────────────────────────────────────────────────────────────┘

              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
┌─────────────────┐  ┌─────────────────┐  ┌─────────────────────────┐
│ Client Components│  │ Server Actions  │  │   Service Layer         │
│ (Interactive)    │  │ (Data mutation) │  │   (Direct calls)        │
│                  │  │                 │  │                         │
│ *-actions.tsx    │  │ actions.ts      │  │ *.service.ts            │
│ *-form.tsx       │  │                 │  │                         │
│ Uses useTransition│  │ "use server"    │  │                         │
└────────┬─────────┘  └────────┬────────┘  └─────────────────────────┘
         │                     │
         └──────────┬──────────┘


         ┌─────────────────────┐
         │   Prisma Client     │
         └─────────────────────┘

Data Flow Patterns

Reading data (Server Components):

URL request -> Server Component -> Service Layer -> Prisma -> Render HTML

Writing data (Server Actions):

User action -> Client Component -> Server Action -> Service Layer -> Prisma -> revalidatePath

File Organization Pattern

Each feature page follows this file structure:

projects/[uuid]/tasks/[taskUuid]/
├── page.tsx           # Server Component (data fetching + rendering)
├── actions.ts         # Server Actions (data mutation)
├── task-actions.tsx   # Client Component (interactive buttons)
└── task-form.tsx      # Client Component (form)

Code Examples

Server Component (page.tsx):

// Server component: directly calls Service to fetch data
import { getServerAuthContext } from "@/lib/auth-server";
import { getTask } from "@/services/task.service";
import { TaskActions } from "./task-actions";

export default async function TaskPage({ params }: PageProps) {
  const auth = await getServerAuthContext();
  if (!auth) redirect("/login");

  const { taskUuid } = await params;
  const task = await getTask(auth.companyUuid, taskUuid);

  return (
    <div>
      <h1>{task.title}</h1>
      <TaskActions taskUuid={taskUuid} status={task.status} />
    </div>
  );
}

Server Action (actions.ts):

"use server";

import { revalidatePath } from "next/cache";
import { getServerAuthContext } from "@/lib/auth-server";
import { claimTask } from "@/services/task.service";

export async function claimTaskAction(taskUuid: string) {
  const auth = await getServerAuthContext();
  if (!auth) redirect("/login");

  await claimTask({
    taskUuid,
    companyUuid: auth.companyUuid,
    assigneeType: auth.type,
    assigneeUuid: auth.actorUuid,
  });

  revalidatePath(`/projects`);
  return { success: true };
}

Client Component (task-actions.tsx):

"use client";

import { useTransition } from "react";
import { claimTaskAction } from "./actions";

export function TaskActions({ taskUuid, status }: Props) {
  const [isPending, startTransition] = useTransition();

  const handleClaim = () => {
    startTransition(async () => {
      await claimTaskAction(taskUuid);
    });
  };

  return (
    <Button onClick={handleClaim} disabled={isPending}>
      {isPending ? "Processing..." : "Claim Task"}
    </Button>
  );
}

Architecture Benefits

BenefitDescription
SecurityAuth and database operations are server-side, not exposed to the client
PerformanceReduced client-side JavaScript, faster initial page render
Simplified CodeNo API route middle layer needed, Server Actions directly call Service
Type SafetyEnd-to-end TypeScript, compile-time parameter checking
Cache ControlrevalidatePath for precise cache invalidation

Scenarios Retaining Client-Side Auth

The following scenarios still use authFetch (client-side auth):

FilePurpose
layout.tsxDashboard layout session check
auth-context.tsxGlobal auth state Provider
auth-client.tsauthFetch utility library

These are auth infrastructure that need to maintain session state on the client side.

3.4 Directory Structure

chorus/
├── docker-compose.yml          # Local development environment
├── Dockerfile                  # Production image
├── package.json
├── pnpm-lock.yaml
├── tsconfig.json
├── next.config.js
├── tailwind.config.js
├── .env.example

├── prisma/
│   ├── schema.prisma           # Data model definitions
│   └── migrations/             # Database migrations

├── src/
│   ├── app/                    # Next.js App Router
│   │   ├── layout.tsx          # Root layout
│   │   ├── page.tsx            # Home/Dashboard
│   │   ├── globals.css
│   │   │
│   │   ├── (auth)/             # Auth-related pages
│   │   │   ├── login/page.tsx        # Email input -> route dispatch
│   │   │   ├── login/password/page.tsx  # Super admin password login
│   │   │   └── callback/page.tsx     # OIDC callback
│   │   │
│   │   ├── admin/              # Super admin panel
│   │   │   ├── page.tsx        # Super admin Dashboard
│   │   │   └── companies/
│   │   │       ├── page.tsx    # Company list
│   │   │       └── [id]/page.tsx  # Company details/OIDC config
│   │   │
│   │   ├── projects/
│   │   │   ├── page.tsx        # Project list (Server Component)
│   │   │   ├── new/
│   │   │   │   ├── page.tsx    # Create project form (Client Component)
│   │   │   │   └── actions.ts  # Create project Server Actions
│   │   │   └── [uuid]/
│   │   │       ├── page.tsx    # Project Overview (Server Component)
│   │   │       ├── ideas/
│   │   │       │   ├── page.tsx           # Ideas list (Server Component)
│   │   │       │   └── [ideaUuid]/
│   │   │       │       ├── page.tsx       # Idea details (Server Component)
│   │   │       │       ├── actions.ts     # Idea Server Actions
│   │   │       │       └── idea-actions.tsx # Interactive buttons (Client Component)
│   │   │       ├── documents/
│   │   │       │   ├── page.tsx           # Documents list (Server Component)
│   │   │       │   └── [documentUuid]/
│   │   │       │       ├── page.tsx       # Document details (Server Component)
│   │   │       │       ├── actions.ts     # Document Server Actions
│   │   │       │       ├── document-actions.tsx
│   │   │       │       └── document-content.tsx
│   │   │       ├── tasks/
│   │   │       │   ├── page.tsx           # Kanban board (Server Component)
│   │   │       │   └── [taskUuid]/
│   │   │       │       ├── page.tsx       # Task details (Server Component)
│   │   │       │       ├── actions.ts     # Task Server Actions
│   │   │       │       ├── task-actions.tsx
│   │   │       │       └── task-status-progress.tsx
│   │   │       ├── proposals/
│   │   │       │   ├── page.tsx           # Proposal list (Server Component)
│   │   │       │   └── [proposalUuid]/
│   │   │       │       ├── page.tsx       # Proposal details (Server Component)
│   │   │       │       ├── actions.ts     # Proposal Server Actions
│   │   │       │       └── proposal-actions.tsx
│   │   │       ├── knowledge/page.tsx     # Knowledge base query
│   │   │       └── activity/page.tsx      # Activity stream (Server Component)
│   │   │
│   │   ├── settings/
│   │   │   ├── page.tsx        # Settings page (Client Component + Server Actions)
│   │   │   └── actions.ts      # API Key management Server Actions
│   │   │
│   │   └── api/                # API Routes (for Agent access)
│   │       ├── auth/
│   │       │   ├── login/route.ts        # Email-based login entry
│   │       │   ├── callback/route.ts     # OIDC callback
│   │       │   └── [...nextauth]/route.ts
│   │       ├── admin/
│   │       │   ├── login/route.ts        # Super admin password login
│   │       │   └── companies/
│   │       │       ├── route.ts          # GET/POST Company
│   │       │       └── [id]/route.ts     # GET/PATCH/DELETE Company
│   │       ├── projects/
│   │       │   ├── route.ts    # GET (list), POST (create)
│   │       │   └── [id]/
│   │       │       ├── route.ts
│   │       │       ├── ideas/route.ts
│   │       │       ├── documents/route.ts
│   │       │       ├── tasks/route.ts
│   │       │       ├── proposals/route.ts
│   │       │       ├── knowledge/route.ts
│   │       │       └── activities/route.ts
│   │       ├── ideas/
│   │       │   └── [id]/route.ts
│   │       ├── documents/
│   │       │   └── [id]/route.ts
│   │       ├── tasks/
│   │       │   └── [id]/
│   │       │       ├── route.ts
│   │       │       └── comments/route.ts
│   │       ├── proposals/
│   │       │   └── [id]/
│   │       │       ├── route.ts
│   │       │       ├── approve/route.ts
│   │       │       └── reject/route.ts
│   │       ├── agents/
│   │       │   ├── route.ts
│   │       │   └── [id]/
│   │       │       ├── route.ts
│   │       │       └── keys/route.ts
│   │       ├── activities/
│   │       │   └── route.ts
│   │       └── mcp/
│   │           └── route.ts    # MCP HTTP endpoint
│   │
│   ├── components/             # React components
│   │   ├── ui/                 # shadcn/ui components
│   │   ├── layout/
│   │   │   ├── header.tsx
│   │   │   ├── sidebar.tsx
│   │   │   └── nav.tsx
│   │   ├── idea/
│   │   │   ├── idea-card.tsx
│   │   │   ├── idea-form.tsx
│   │   │   └── idea-list.tsx
│   │   ├── document/
│   │   │   ├── document-card.tsx
│   │   │   ├── document-viewer.tsx
│   │   │   └── document-list.tsx
│   │   ├── kanban/
│   │   │   ├── board.tsx
│   │   │   ├── column.tsx
│   │   │   └── card.tsx
│   │   ├── task/
│   │   │   ├── task-card.tsx
│   │   │   ├── task-detail.tsx
│   │   │   └── task-form.tsx
│   │   ├── proposal/
│   │   │   ├── proposal-card.tsx
│   │   │   ├── proposal-review.tsx
│   │   │   ├── proposal-timeline.tsx
│   │   │   └── approval-buttons.tsx
│   │   ├── knowledge/
│   │   │   ├── knowledge-search.tsx
│   │   │   └── knowledge-results.tsx
│   │   └── activity/
│   │       ├── activity-feed.tsx
│   │       └── activity-item.tsx
│   │
│   ├── lib/                    # Core libraries
│   │   ├── prisma.ts           # Prisma Client singleton
│   │   ├── auth.ts             # NextAuth configuration
│   │   ├── api-key.ts          # API Key validation
│   │   └── utils.ts            # Utility functions
│   │
│   ├── services/               # Business logic layer
│   │   ├── project.service.ts
│   │   ├── idea.service.ts
│   │   ├── document.service.ts
│   │   ├── task.service.ts
│   │   ├── proposal.service.ts
│   │   ├── knowledge.service.ts
│   │   ├── agent.service.ts
│   │   ├── activity.service.ts
│   │   └── mcp.service.ts
│   │
│   ├── mcp/                    # MCP Server
│   │   ├── server.ts           # Per-auth MCP server factory
│   │   └── tools/
│   │       ├── public.ts       # Public tools (all agents)
│   │       ├── session.ts      # Session tools (all agents)
│   │       ├── developer.ts    # Developer agent tools
│   │       ├── pm.ts           # PM agent tools
│   │       └── admin.ts        # Admin agent tools
│   │
│   └── types/                  # TypeScript type definitions
│       ├── api.ts
│       ├── mcp.ts
│       └── index.ts

├── public/
│   ├── skill/                      # Standalone Skill docs (served at /skill/)
│   │   ├── SKILL.md
│   │   ├── package.json
│   │   └── references/             # Role-specific workflow docs (7 files)
│   └── chorus-plugin/              # Chorus Plugin for Claude Code
│       ├── hooks/                  # Claude Code hooks configuration
│       ├── bin/                    # Hook scripts (on-subagent-start/stop/idle)
│       ├── skills/chorus/          # Plugin-embedded Skill (with session automation)
│       └── .mcp.json               # MCP server config template

└── tests/
    ├── unit/
    └── e2e/

4. Data Model

4.0 Database Design Principles: UUID-Based Architecture + No Foreign Key Constraints

Design Decisions:

  1. UUID-Based Foreign Key References: All inter-entity associations use UUIDs instead of numeric IDs
  2. Prisma Relation Mode: Uses relationMode = "prisma", no database-level foreign key constraints

Configuration:

// prisma/schema.prisma
datasource db {
  provider     = "postgresql"
  url          = env("DATABASE_URL")
  relationMode = "prisma"  // Relations managed by Prisma, no database FK
}

UUID-Based Architecture Design Principles:

PrincipleDescription
Foreign keys use UUIDAll *Id fields renamed to *Uuid (e.g., companyUuid, projectUuid)
Relations reference UUIDPrisma relation definitions use references: [uuid] instead of references: [id]
No ID queriesAll queries/operations are UUID-based, numeric id is not used
API consistencyUUIDs used uniformly internally and externally, no ID-UUID conversion needed

Why UUID-Based Design:

BenefitDescription
SecurityPrevents numeric ID enumeration attacks
Simplified codeNo ID-UUID conversion logic needed
API consistencyUUIDs used uniformly internally and externally
Distributed-friendlyUUIDs can be generated client-side without database sequences

Relation Definition Example:

model Project {
  id          Int      @id @default(autoincrement())
  uuid        String   @unique @default(uuid())
  companyUuid String
  company     Company  @relation(fields: [companyUuid], references: [uuid])
  tasks       Task[]

  @@index([companyUuid])
}

model Task {
  id          Int      @id @default(autoincrement())
  uuid        String   @unique @default(uuid())
  companyUuid String
  projectUuid String
  project     Project  @relation(fields: [projectUuid], references: [uuid])

  @@index([companyUuid])
  @@index([projectUuid])
}

Notes:

  1. Numeric ID retained: id still serves as primary key for internal indexing, but is not used in business logic
  2. UUID indexes: All UUID foreign key fields have indexes for query performance optimization
  3. Referential integrity: Managed by Prisma Client at the application layer
  4. Cascade operations: onDelete: Cascade is simulated by Prisma

4.1 ER Diagram

ID Design Principles: UUID-Based Architecture

  • id: Auto-incrementing numeric primary key (internal indexing only, not used in business logic)
  • uuid: UUID string (used for all foreign key references and API exposure)
  • All association fields use *Uuid naming (e.g., companyUuid, projectUuid)
┌─────────────┐       ┌─────────────┐       ┌─────────────┐
│   Company   │───┬───│    User     │       │   Agent     │
│             │   │   │             │───────│             │
│  id (Int)   │   │   │  id (Int)   │       │  id (Int)   │
│  uuid       │   │   │  uuid       │       │  uuid       │
│  name       │   │   │  companyUuid│       │  companyUuid│
│  emailDomains    │   │  oidcSub    │       │  name       │
│  oidcIssuer │   │   │  email      │       │  roles[]    │
│  oidcClientId    │   │  name       │       │  ownerUuid  │
│  oidcEnabled│   │   └─────────────┘       │  persona    │
│  createdAt  │   │                         │  systemPrompt│
└─────────────┘   │                         └─────────────┘
       │          │                                │
       │          │   ┌─────────────┐              │
       │          └───│   ApiKey    │──────────────┘
       │              │             │
       │              │  id (Int)   │
       │              │  uuid       │
       │              │  companyUuid│
       │              │  agentUuid  │
       │              │  keyHash    │
       │              │  lastUsed   │
       │              │  expiresAt  │
       │              │  revokedAt  │
       │              └─────────────┘

       ├──────────────────────────────────────────────────────┐
       │                                                      │
┌──────▼──────┐                                        ┌──────▼──────┐
│   Project   │                                        │  Proposal   │
│             │                                        │             │
│  id (Int)   │                                        │  id (Int)   │
│  uuid       │       ┌─────────────┐                  │  uuid       │
│  companyUuid│───────│    Idea     │                  │  companyUuid│
│  name       │       │             │                  │  projectUuid│
│  description│       │  id (Int)   │                  │  title      │
│  createdAt  │       │  uuid       │                  │  inputType  │
└─────────────┘       │  companyUuid│──── N:1 ─────────│  inputUuids │
       │              │  projectUuid│                  │  outputType │
       │              │  content    │                  │  outputData │
       │              │  attachments│                  │  status     │
       │              │  assigneeType                  │  createdByUuid│
       │              │  assigneeUuid                  │  reviewedByUuid│
       │              │  createdByUuid                 └─────────────┘
       │              └─────────────┘                         │
       │              ┌─────────────┐                         │
       │              │  Document   │<────────────────────────┘
       │              │             │     (outputType=document)
       │              │  id (Int)   │
       │              │  uuid       │
       │              │  companyUuid│
       │              │  projectUuid│
       │              │  type       │  (prd | tech_design | adr)
       │              │  title      │
       │              │  content    │
       │              │  version    │
       │              │  proposalUuid│
       │              │  createdByUuid│
       │              └─────────────┘

       │              ┌─────────────┐
       ├──────────────│    Task     │<────────────────────────┐
       │              │             │     (outputType=task)   │
       │              │  id (Int)   │                         │
       │              │  uuid       │                         │
       │              │  companyUuid│                         │
       │              │  projectUuid│                         │
       │              │  title      │                         │
       │              │  description│                         │
       │              │  status     │                         │
       │              │  assigneeType                         │
       │              │  assigneeUuid                         │
       │              │  proposalUuid│────────────────────────┘
       │              │  createdByUuid│
       │              │  storyPoints│
       │              └─────────────┘
       │                     │
       │              ┌──────▼──────┐
       ├──────────────│  Activity   │
       │              │             │
       │              │  id (Int)   │
       │              │  uuid       │
       │              │  companyUuid│
       │              │  projectUuid│
       │              │  targetType │  (idea|task|proposal|document)
       │              │  targetUuid │
       │              │  actorType  │  (user|agent)
       │              │  actorUuid  │
       │              │  action     │  (created|assigned|released|...)
       │              │  value      │  (JSON: operation result value)
       │              └─────────────┘

       │              ┌────────────────┐
       ├──────────────│ TaskDependency │
       │              │                │
       │              │  id (Int)      │
       │              │  taskUuid      │
       │              │  dependsOnUuid │
       │              │  companyUuid   │
       │              └────────────────┘

       │              ┌────────────────┐
       ├──────────────│ AgentSession   │
       │              │                │
       │              │  id (Int)      │
       │              │  uuid          │
       │              │  agentUuid     │
       │              │  companyUuid   │
       │              │  name          │
       │              │  status        │  (active|inactive|closed)
       │              │  lastActiveAt  │
       │              └────────────────┘
       │                     │
       │              ┌──────▼─────────────┐
       └──────────────│ SessionTaskCheckin │
                      │                    │
                      │  id (Int)          │
                      │  sessionUuid       │
                      │  taskUuid          │
                      │  checkinAt         │
                      │  checkoutAt        │
                      └────────────────────┘

4.2 Core Entity Descriptions

Common Fields:

  • id: Auto-incrementing numeric primary key (Int @id @default(autoincrement())) - internal indexing only
  • uuid: UUID string (String @unique @default(uuid())) - business identifier and foreign key reference
  • All foreign keys use UUID (e.g., companyUuid, projectUuid)

Company (Tenant)

  • Root entity for multi-tenant isolation
  • All data is associated via companyUuid
  • emailDomains: Email domain list, used to identify Company during login
  • oidcIssuer: OIDC Provider URL
  • oidcClientId: OIDC Client ID (PKCE only, no Client Secret required)
  • oidcEnabled: Whether OIDC login is enabled

User

  • Human user, authenticated via OIDC
  • companyUuid: Parent company UUID
  • oidcSub: OIDC Provider subject

Agent

  • AI Agent entity (Claude Code, etc.)
  • companyUuid: Parent company UUID
  • roles: Preset selector array — one or more of developer_agent / pm_agent / admin_agent (legacy pm / developer aliases still resolve as the corresponding presets). Roles only select the preset; actual authorization is driven by permissions.
  • permissions: Custom permission bits layered on top of the preset(s). Effective set = union of expanded presets + custom. See §6.3.
  • ownerUuid: Creator User UUID
  • persona: Custom personality description
  • systemPrompt: Full system prompt
  • One Agent can have multiple API Keys (all inherit the Agent's effective permissions)

ApiKey

  • Independently managed, supports rotation and revocation
  • companyUuid: Parent company UUID
  • agentUuid: Associated Agent UUID
  • keyHash: API key hash storage
  • expiresAt: Optional expiration time
  • revokedAt: Revocation time

Project

  • Project container, parent of all business data
  • companyUuid: Parent company UUID
  • Contains Ideas, Documents, Tasks, Proposals, Activities

Idea

  • Raw human input, can be assigned to a PM Agent for processing
  • companyUuid: Parent company UUID
  • projectUuid: Parent project UUID
  • title: Title
  • content: Text content
  • attachments: Attachment list (images, files, etc.)
  • status: open | assigned | in_progress | pending_review | completed | closed
  • assigneeType: user | agent (polymorphic association)
  • assigneeUuid: Assignee UUID
  • assignedAt: Assignment time
  • assignedByUuid: Assigner User UUID (recorded when assigned by human)
  • createdByUuid: Creator User UUID
  • Serves as input source for Proposals (one Idea can only belong to one Proposal, multiple Ideas can be combined into the same Proposal, N:1 relationship via JSON array)

Assignment Methods:

  • Assign to user: Visible and operable by all PM Agents under that user
  • Assign to specific PM Agent: Visible and operable only by that Agent
  • Humans can reassign at any time (regardless of current status)

Document

  • Product of a Proposal (PRD, technical design, etc.)
  • companyUuid: Parent company UUID
  • projectUuid: Parent project UUID
  • type: prd | tech_design | adr | ...
  • content: Markdown format content
  • version: Version number
  • proposalUuid: Source Proposal UUID (traceable)
  • createdByUuid: Creator UUID

Task

  • Product of a Proposal or manually created, can be assigned to Agent/human for execution
  • companyUuid: Parent company UUID
  • projectUuid: Parent project UUID
  • status: open | assigned | in_progress | to_verify | done | closed
  • priority: low | medium | high
  • storyPoints: Effort estimation (unit: Agent hours)
  • assigneeType: user | agent (polymorphic association)
  • assigneeUuid: Assignee UUID
  • assignedAt: Assignment time
  • assignedByUuid: Assigner User UUID (recorded when assigned by human)
  • proposalUuid: Source Proposal UUID (traceable, optional)
  • createdByUuid: Creator UUID

Assignment Methods:

  • Assign to user: Visible and operable by all Developer Agents under that user
  • Assign to specific Agent: Visible and operable only by that Agent
  • Humans can reassign at any time (regardless of current status)

Proposal

  • Created by PM Agent, approved by humans, connects inputs and outputs
  • companyUuid: Parent company UUID
  • projectUuid: Parent project UUID
  • Input:
    • inputType: idea | document
    • inputUuids: Associated input UUID list (JSON array, supports multiple Ideas combined)
  • Output:
    • outputType: document | task
    • outputData: Proposed content (Document draft or Task list)
  • status: pending | approved | rejected | revised
  • createdByUuid: Creator Agent UUID
  • reviewedByUuid: Reviewer User UUID
  • Upon approval, automatically creates Documents or Tasks based on outputType

Activity

  • Project-level activity log, generic design supporting all entity types
  • companyUuid: Parent company UUID
  • projectUuid: Parent project UUID
  • targetType: Target entity type (idea | task | proposal | document)
  • targetUuid: Target entity UUID
  • actorType: Actor type (user | agent)
  • actorUuid: Actor UUID
  • action: Action type (see table below)
  • value: Action result value (e.g., new status, assignment target, etc., JSON format)

Activity Field Design Principles:

  • targetType + targetUuid: Identifies the target entity of the operation (generic design)
  • action: Describes what operation occurred
  • value: Records the operation result/post-change value (concise, records result only)

Activity Action Types:

ActionDescriptionValue Example
createdEntity was created-
assignedEntity was assigned{ type: "user", uuid: "...", name: "..." }
releasedEntity was released-
status_changedStatus changed"in_progress" (new status)
submittedSubmitted for approval/verification-
approvedApproved-
rejectedRejected"reason text" (optional)
comment_addedComment added-

Activity Maintenance Principles:

  • Created in Service layer: All Activities are automatically created by the Service layer during business operations
  • value records result only: Status changes only record the post-change value, not the pre-change value
  • Assignment operations must be recorded: Any assignment/release operation must create an Activity

Comment

  • Polymorphic comments, can comment on Idea, Proposal, Task, Document
  • companyUuid: Parent company UUID
  • targetType: idea | proposal | task | document
  • targetUuid: Target entity UUID
  • authorType: user | agent
  • authorUuid: Author UUID
  • content: Comment content

TaskDependency

  • DAG dependency relationship between tasks
  • taskUuid: Current task UUID
  • dependsOnUuid: Predecessor task UUID
  • Cycle detection implemented in the Service layer

AgentSession

  • Session tracking Agent work status
  • agentUuid: Parent Agent UUID
  • name: Session name (e.g., "frontend-worker")
  • status: active | inactive | closed
  • lastActiveAt: Last heartbeat time
  • Automatically marked inactive after 1 hour of inactivity

SessionTaskCheckin

  • Records which task a Session is currently working on
  • sessionUuid: Session UUID
  • taskUuid: Task UUID
  • checkinAt / checkoutAt: Checkin/checkout time
  • UI uses this to display Kanban Worker badges and Task Detail active Workers

5. API Design

5.1 REST API

Authentication

  • Human: OIDC + Session Cookie
  • Agent: Authorization: Bearer {api_key}

Endpoint Overview

UUID-Based API: All URL parameters and request/response data uniformly use UUIDs, consistent internally and externally.

MethodPathDescriptionPermissions
Projects
GET/api/projectsProject listUser, Agent
POST/api/projectsCreate projectUser
GET/api/projects/:uuidProject detailsUser, Agent
PATCH/api/projects/:uuidUpdate projectUser
DELETE/api/projects/:uuidDelete projectUser
Ideas
GET/api/projects/:uuid/ideasProject Ideas listUser, PM Agent
POST/api/projects/:uuid/ideasCreate IdeaUser
GET/api/ideas/:uuidIdea detailsUser, PM Agent
PATCH/api/ideas/:uuidUpdate Idea (including status)User, PM Agent
POST/api/ideas/:uuid/claimClaim IdeaPM Agent
POST/api/ideas/:uuid/releaseRelease claimed IdeaPM Agent
DELETE/api/ideas/:uuidDelete IdeaUser
Documents
GET/api/projects/:uuid/documentsProject Documents listUser, Agent
GET/api/documents/:uuidDocument detailsUser, Agent
PATCH/api/documents/:uuidUpdate DocumentUser
Tasks
GET/api/projects/:uuid/tasksProject task listUser, Agent
POST/api/projects/:uuid/tasksCreate task (manual)User
GET/api/tasks/:uuidTask detailsUser, Agent
PATCH/api/tasks/:uuidUpdate task (including status)User, Agent (assignee)
POST/api/tasks/:uuid/claimClaim TaskDeveloper Agent
POST/api/tasks/:uuid/releaseRelease claimed TaskDeveloper Agent
POST/api/tasks/:uuid/commentsAdd commentUser, Agent
Proposals
GET/api/projects/:uuid/proposalsProject proposal listUser, PM Agent
POST/api/projects/:uuid/proposalsCreate proposalPM Agent
GET/api/proposals/:uuidProposal detailsUser, PM Agent
POST/api/proposals/:uuid/approveApprove proposalUser
POST/api/proposals/:uuid/rejectReject proposalUser
Knowledge
GET/api/projects/:uuid/knowledgeUnified knowledge base queryUser, Agent
Agents
GET/api/agentsAgent listUser
POST/api/agentsCreate AgentUser
GET/api/agents/:uuidAgent detailsUser
POST/api/agents/:uuid/keysCreate API KeyUser
DELETE/api/agents/:uuid/keys/:keyUuidRevoke API KeyUser
Activities
GET/api/projects/:uuid/activitiesProject activity listUser, Agent
Agent Self-Service
GET/api/me/assignmentsGet my claimed Ideas + TasksAgent
GET/api/projects/:uuid/availableGet claimable Ideas + TasksAgent
Super Admin (Super Admin Only)
POST/api/auth/loginEmail-based login entryPublic
POST/api/admin/loginSuper admin password loginPublic
GET/api/admin/companiesCompany listSuper Admin
POST/api/admin/companiesCreate CompanySuper Admin
GET/api/admin/companies/:uuidCompany detailsSuper Admin
PATCH/api/admin/companies/:uuidUpdate Company (including OIDC config)Super Admin
DELETE/api/admin/companies/:uuidDelete CompanySuper Admin

5.2 MCP API

Endpoint

POST /api/mcp

Transport

Streamable HTTP Transport (supports SSE)

Authentication

Header: Authorization: Bearer {api_key}

Based on the Agent's effective permission set (associated with the API Key), different tool sets are returned. Each gated MCP tool declares a single required permission (e.g. task:write, proposal:admin); only tools whose required permission is present in the caller's permission set are exposed. Public tools (discover, comment, session) carry no gate. See MCP_TOOLS.md for the complete tool → permission mapping.

Project Filtering (Optional)

Agents can filter results by project(s) using optional HTTP headers:

HeaderFormatDescription
X-Chorus-ProjectSingle UUID or comma-separated UUIDsFilter by specific project(s)
X-Chorus-Project-GroupGroup UUIDFilter by project group

Behavior:

  • No header: Returns all projects (default, backward compatible)
  • X-Chorus-Project: Returns only specified project(s)
  • X-Chorus-Project-Group: Returns all projects in the group
  • Priority: X-Chorus-Project-Group > X-Chorus-Project

Affected tools: chorus_checkin, chorus_get_my_assignments

Example:

// .mcp.json
{
  "mcpServers": {
    "chorus": {
      "type": "http",
      "url": "http://localhost:8637/api/mcp",
      "headers": {
        "Authorization": "Bearer cho_xxx",
        "X-Chorus-Project": "project-uuid-1,project-uuid-2"
      }
    }
  }
}

Transport (Stateless MCP)

POST /api/mcp is stateless since 0.6.2. Each request authenticates via the Authorization: Bearer cho_… header, the server builds a fresh per-request McpServer instance gated by the agent's effective permission set, and the instance is torn down once the response is flushed.

Implications:

  • No initialize → keep-alive → expire flow; no Mcp-Session-Id exchange; no inactivity timeout.
  • Permission changes in the UI take effect on the next MCP call, with no reconnect needed.
  • Any container can serve any request — the endpoint scales horizontally without sticky sessions. Cross-instance event propagation for SSE goes through Redis when REDIS_URL is set (otherwise the in-memory EventBus is used in single-instance mode).
  • The chorus_create_session / chorus_session_* tools operate on the AgentSession model (swarm-mode observability) — that is a database-backed Agent-level concept and is unrelated to MCP transport state.

Public Tools (All Agents)

ToolDescription
chorus_checkinCheck in: returns persona, resource-aggregated effective permissions, project-grouped ideaTracker, and an unread-notification summary
chorus_get_projectGet project details
chorus_get_ideas / chorus_get_ideaList/get Ideas
chorus_get_documents / chorus_get_documentList/get Documents
chorus_get_proposals / chorus_get_proposalList/get Proposals (including drafts)
chorus_list_tasks / chorus_get_taskList/get Tasks
chorus_get_activityProject activity stream
chorus_get_my_assignmentsIdea/task tracker grouped by project (same shape as checkin.ideaTracker)
chorus_get_available_ideasClaimable Ideas
chorus_get_available_tasksClaimable Tasks
chorus_get_unblocked_tasksTasks with all dependencies completed (for scheduling)
chorus_add_comment / chorus_get_commentsComment CRUD

Session Tools (All Agents)

ToolDescription
chorus_create_sessionCreate a named Session
chorus_list_sessionsList Sessions
chorus_close_session / chorus_reopen_sessionClose/reopen Session
chorus_session_checkin_task / chorus_session_checkout_taskTask Checkin/Checkout
chorus_session_heartbeatSession heartbeat

Gated Tools by Required Permission

Gated tools are grouped below by the permission they require. An agent sees a tool if and only if that permission is in its effective set (preset-expanded + custom). The full tool → permission matrix is maintained in MCP_TOOLS.md and in source at src/mcp/tools/permission-map.ts.

Required PermissionRepresentative Tools
idea:writechorus_claim_idea, chorus_release_idea, chorus_move_idea, chorus_pm_create_idea, chorus_pm_start_elaboration, chorus_pm_skip_elaboration
proposal:writechorus_pm_create_proposal, chorus_pm_submit_proposal, chorus_pm_validate_proposal, chorus_pm_{add,update,remove}_document_draft, chorus_pm_{add,update,remove}_task_draft, chorus_pm_assign_task, chorus_pm_{reject,revoke}_proposal
document:writechorus_pm_create_document, chorus_pm_update_document
task:writechorus_claim_task, chorus_release_task, chorus_submit_for_verify, chorus_report_work, chorus_report_criteria_self_check
project:writechorus_admin_create_project, chorus_admin_{create,update,delete}_project_group, chorus_admin_move_project_to_group
proposal:adminchorus_admin_approve_proposal, chorus_admin_close_proposal
task:adminchorus_admin_verify_task, chorus_admin_reopen_task, chorus_admin_close_task, chorus_mark_acceptance_criteria, chorus_admin_delete_task
idea:adminchorus_pm_validate_elaboration, chorus_admin_delete_idea
document:adminchorus_admin_delete_document

The admin_agent preset grants all *:admin bits and so exposes every tool in the table above. Any custom combination (e.g. Developer preset + task:admin to self-verify) produces the corresponding tool set automatically.

Warning: *:admin permissions are human-level — they cover Proposal approval, Task verification, Idea/Document deletion. Grant them only to Agents that are intentionally automating human approval workflows.

Proposal Input/Output Description

ScenarioinputTypeinputUuidsoutputTypeoutputData
Ideas -> PRDideaIdea UUIDs (supports multiple)documentPRD draft
PRD -> TasksdocumentDocument UUIDtaskTask list
PRD -> Tech DesigndocumentDocument UUIDdocumentTech design draft

6. Authentication & Authorization

6.0 Super Admin Authentication

Configuration (environment variables):

SUPER_ADMIN_EMAIL=admin@example.com
SUPER_ADMIN_PASSWORD_HASH=\$2b\$10$...  # bcrypt hash

Login Flow:

┌──────────┐     ┌──────────┐     ┌──────────────┐
│  Browser │     │  Chorus  │     │   Database   │
│          │     │  Server  │     │              │
└────┬─────┘     └────┬─────┘     └──────┬───────┘
     │                │                   │
     │  1. Enter email│                   │
     │ ──────────────>│                   │
     │                │                   │
     │                │  2. Check if super admin
     │                │  (compare with env var)
     │                │                   │
     │  3a. Is super  │                   │
     │  admin, return │                   │
     │  password page │                   │
     │ <──────────────│                   │
     │                │                   │
     │  4a. Enter     │                   │
     │  password      │                   │
     │ ──────────────>│                   │
     │                │                   │
     │                │  5a. Verify password hash
     │                │                   │
     │  6a. Super     │                   │
     │  admin panel   │                   │
     │ <──────────────│                   │
     │                │                   │
     │  3b. Not super │                   │
     │  admin         │  Query email domain│
     │                │ ─────────────────>│
     │                │                   │
     │                │  Return Company   │
     │                │  OIDC config      │
     │                │ <─────────────────│
     │                │                   │
     │  4b. Redirect  │                   │
     │  to Company    │                   │
     │  OIDC          │                   │
     │ <──────────────│                   │

Super Admin Panel Routes:

  • /admin - Super admin panel entry
  • /admin/companies - Company management
  • /admin/companies/[id] - Company details/OIDC configuration

6.1 Human Authentication (OIDC + PKCE)

┌──────────┐     ┌──────────┐     ┌──────────────┐
│  Browser │     │  Chorus  │     │    OIDC      │
│          │     │  Server  │     │   Provider   │
└────┬─────┘     └────┬─────┘     └──────┬───────┘
     │                │                   │
     │  1. Login      │                   │
     │ ──────────────>│                   │
     │                │                   │
     │  2. Redirect   │                   │
     │ <──────────────│                   │
     │                │                   │
     │  3. Auth Request (PKCE)            │
     │ ──────────────────────────────────>│
     │                │                   │
     │  4. User Login │                   │
     │ <──────────────────────────────────│
     │                │                   │
     │  5. Callback with code             │
     │ ──────────────>│                   │
     │                │                   │
     │                │  6. Exchange code │
     │                │ ─────────────────>│
     │                │                   │
     │                │  7. Tokens        │
     │                │ <─────────────────│
     │                │                   │
     │  8. Set Session Cookie             │
     │ <──────────────│                   │

6.2 Agent Authentication (API Key)

┌──────────┐     ┌──────────┐     ┌──────────────┐
│  Claude  │     │  Chorus  │     │   Database   │
│   Code   │     │  Server  │     │              │
└────┬─────┘     └────┬─────┘     └──────┬───────┘
     │                │                   │
     │  MCP Request   │                   │
     │  + API Key     │                   │
     │ ──────────────>│                   │
     │                │                   │
     │                │  Validate Key     │
     │                │ ─────────────────>│
     │                │                   │
     │                │  Agent+Perms      │
     │                │ <─────────────────│
     │                │                   │
     │                │  Filter tools by  │
     │                │  permission set   │
     │                │                   │
     │  MCP Response  │                   │
     │ <──────────────│                   │

6.3 Permission Model

Authorization for Agents is a 15-bit permission matrix — 5 resources × 3 actions. The UI exposes three presets plus a Custom option.

Resources × Actions:

Resourcereadwriteadmin
ideaview ideascreate/claim/release/update ideas, run elaborationclose/delete ideas
proposalview proposals and draftscreate/submit/reject/revoke proposals, manage drafts, batch-create tasks, manage task DAG, assign tasksapprove / close proposals
documentview documentscreate/update documentsdelete documents
projectview projects and groupscreate/update/delete projects and project groups, move projects between groupsgranted by admin_agent preset, but no tool or route currently checks this bit
taskview tasksclaim/release/submit/report tasks, self-check acceptance criteriaverify/reopen/close/delete tasks, mark acceptance criteria

Role Presets → Permission Set:

PresetExpanded permissionsTotal
developer_agent*:read + task:write6
pm_agent*:read + idea:write, proposal:write, document:write, task:write, project:write10
admin_agentall 15 bits (*:read + *:write + *:admin)15

Effective permission computation: computeEffectivePermissions(roles, customPermissions) returns the union of every preset's expansion and any custom bits attached to the Agent (see src/lib/authz/permissions.ts). Both REST gating (requireAgentPermission) and MCP tool visibility (permission-map.ts + registerPermissionedTool) consult this effective set.

Human users do not go through the Agent permission matrix — REST routes gate them via the standard UserAuthContext path. SuperAdmin bypasses all checks.

Checkin output: chorus_checkin (and the /api/agents/me endpoint) surfaces permissions as a resource-aggregated object for token efficiency:

{
  "permissions": {
    "idea": ["read", "write"],
    "proposal": ["read", "write"],
    "document": ["read", "write"],
    "project": ["read"],
    "task": ["read", "write"]
  }
}

Skills and plugin hooks read this aggregated shape directly instead of scanning a flat list.


7. Core Workflows

7.1 Reversed Conversation Workflow (Idea -> Proposal -> Document/Task)

┌─────────────────────────────────────────────────────────────────┐
│  1. Human creates Ideas                                         │
│     - Text: "I want to implement user auth with OAuth and       │
│       email/password login"                                     │
│     - Attachments: competitor screenshots, design sketches, etc.│
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  2. PM Agent creates PRD Proposal                               │
│     - Get Ideas (chorus_pm_get_ideas)                          │
│     - Read project knowledge base (chorus_query_knowledge)     │
│     - Create proposal (chorus_pm_create_proposal)              │
│       inputType: idea, inputIds: [idea1, idea2, ...]           │
│       Supports selecting multiple Ideas combined as input      │
│       outputType: document, outputData: { PRD draft }          │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  3. Human reviews PRD Proposal (Web UI)                         │
│     - View PRD draft                                            │
│     - Approve -> Create Document(PRD)                           │
│     - Request changes -> Return for revision                    │
│     - Reject -> Mark as rejected                                │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  4. PM Agent creates Task Breakdown Proposal                    │
│     - Read Document(PRD)                                        │
│     - Create proposal (chorus_pm_create_proposal)              │
│       inputType: document, inputIds: [prd_id]                  │
│       outputType: task, outputData: { Task list }              │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  5. Human reviews Task Breakdown Proposal (Web UI)              │
│     - View task list                                            │
│     - Approve -> Create Tasks (status: todo)                    │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  6. Personal Agent executes tasks                               │
│     - Get task (chorus_get_task)                               │
│     - Get related documents (chorus_get_document)              │
│     - Execute development work                                  │
│     - Report completion (chorus_report_work)                   │
└─────────────────────────────────────────────────────────────────┘


┌─────────────────────────────────────────────────────────────────┐
│  7. PM Agent continuous tracking                                │
│     - Analyze progress (chorus_pm_analyze_progress)            │
│     - Identify risks (chorus_pm_identify_risks)                │
│     - Create new Proposals to adjust plans when needed          │
└─────────────────────────────────────────────────────────────────┘

Complete Traceability Chain:

Ideas -> Proposal -> Document(PRD) -> Proposal -> Tasks
                       |
               Proposal -> Document(Tech Design)

Every Task/Document can be traced back to its source Proposal and Ideas.

7.2 Task Status Transitions

                    ┌──────────────┐
                    │   created    │
                    │  (from UI    │
                    │  or proposal)│
                    └──────┬───────┘


                    ┌──────────────┐
         ┌─────────│     open     │<─────────────────┐
         │         │ (unassigned) │                  │
         │         └──────┬───────┘                  │
         │                │                          │
         │                │ Assign to User/Agent     │ Release
         │                ▼                          │
         │         ┌──────────────┐                  │
         │         │   assigned   │──────────────────┘
         │         │  (assigned)  │
         │         └──────┬───────┘
         │                │
         │                │ Start work
         │                ▼
         │         ┌──────────────┐
         │         │ in_progress  │
         │         │  (executing) │
         │         └──────┬───────┘
         │                │
         │                │ Finish execution
         │                ▼
         │         ┌──────────────┐
         │         │  to_verify   │
         │         │(awaiting     │
         │         │ human verify)│
         │         └──────┬───────┘
         │                │
         │                │ Human verification passed
         │                ▼
         │         ┌──────────────┐
         │         │     done     │
         │         │  (completed) │
         │         └──────────────┘

         │         ┌──────────────┐
         └────────>│    closed    │  (can be closed at any stage)
                   │   (closed)   │
                   └──────────────┘

Assignment Rules:

  • Only the current assignee can update the status
  • Humans can reassign tasks at any status at any time
  • Everyone can comment on tasks at any status
  • Release operation clears the assignee, status returns to open

Assignment Flow (UI):

Click Assign button -> Open Assign modal
    ├── Assign to myself -> Visible to all my Developer Agents
    ├── Assign to specific Agent -> Visible only to that Agent
    ├── Assign to another user -> Visible to that user and their Agents
    └── Release -> Clear assignee, status -> open

Activity Recording: Each assignment/release operation automatically creates an Activity:

  • task_assigned: Task was assigned, payload includes target info
  • task_released: Task was released

7.3 Idea Status Transitions

                    ┌──────────────┐
                    │   created    │
                    │(human created)│
                    └──────┬───────┘


                    ┌──────────────┐
         ┌─────────│     open     │<─────────────────┐
         │         │(awaiting claim)│                  │
         │         └──────┬───────┘                  │
         │                │                          │
         │                │ PM Agent claims          │ Release claim
         │                ▼                          │
         │         ┌──────────────┐                  │
         │         │   assigned   │──────────────────┘
         │         │  (claimed)   │
         │         └──────┬───────┘
         │                │
         │                │ Start processing
         │                ▼
         │         ┌──────────────┐
         │         │ in_progress  │
         │         │(producing    │
         │         │ Proposal)    │
         │         └──────┬───────┘
         │                │
         │                │ Submit Proposal
         │                ▼
         │         ┌──────────────┐
         │         │pending_review│
         │         │(awaiting     │
         │         │human approval)│
         │         └──────┬───────┘
         │                │
         │                │ Proposal approved
         │                ▼
         │         ┌──────────────┐
         │         │  completed   │
         │         │  (completed) │
         │         └──────────────┘

         │         ┌──────────────┐
         └────────>│    closed    │  (can be closed at any stage)
                   │   (closed)   │
                   └──────────────┘

Claim Rules:

  • Only Ideas in open status can be claimed
  • Only the claimant (assignee) can update the status
  • Humans can force reassign Ideas at any status
  • Everyone can comment on Ideas at any status

7.4 Proposal Approval Workflow

                           ┌──────────────────────────────────────┐
                           │        Determined by outputType      │
                           │                                      │
┌──────────────┐     ┌─────▼──────┐     ┌──────────────────────┐  │
│   pending    │────>│  approved  │────>│  outputType=document │──┼──> Create Document
└──────────────┘     └────────────┘     └──────────────────────┘  │
       │                                ┌──────────────────────┐  │
       │                                │  outputType=task     │──┼──> Create Tasks
       │                                └──────────────────────┘  │
       │                                                          │
       ▼                                                          │
┌──────────────┐                                                  │
│   rejected   │                                                  │
└──────────────┘                                                  │
       │                                                          │
       ▼                                                          │
┌──────────────┐                                                  │
│   revised    │─────────────────────────────────────────────────>┘
└──────────────┘    (resubmit after revision)

Approval Results:

  • approved + outputType=document -> Create Document, record proposalId
  • approved + outputType=task -> Batch create Tasks, record proposalId
  • rejected -> End, can re-propose
  • revised -> Re-approve after revision

8. Deployment Architecture

8.1 Local Development

# docker-compose.yml
version: '3.8'

services:
  chorus:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8637:8637"
    environment:
      - DATABASE_URL=postgres://chorus:chorus@db:5432/chorus
      - NEXTAUTH_URL=http://localhost:8637
      - NEXTAUTH_SECRET=${NEXTAUTH_SECRET}
      - OIDC_ISSUER=${OIDC_ISSUER}
      - OIDC_CLIENT_ID=${OIDC_CLIENT_ID}
      - OIDC_CLIENT_SECRET=${OIDC_CLIENT_SECRET}
    depends_on:
      db:
        condition: service_healthy
    volumes:
      - ./src:/app/src
      - ./prisma:/app/prisma

  db:
    image: postgres:16-alpine
    environment:
      - POSTGRES_USER=chorus
      - POSTGRES_PASSWORD=chorus
      - POSTGRES_DB=chorus
    ports:
      - "5432:5432"
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U chorus"]
      interval: 5s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

8.2 Production Deployment (AWS CDK)

┌─────────────────────────────────────────────────────────────────┐
│                    ALB (Application Load Balancer)               │
│                    HTTPS + ACM Certificate                       │
└─────────────────────────────────────────────────────────────────┘

              ┌───────────────┼───────────────┐
              ▼               ▼               ▼
       ┌──────────┐    ┌──────────┐    ┌──────────┐
       │  ECS     │    │  ECS     │    │  ECS     │
       │  Fargate │    │  Fargate │    │  Fargate │
       │  Task    │    │  Task    │    │  Task    │
       └──────────┘    └──────────┘    └──────────┘
              │               │               │
              └───────┬───────┴───────┬───────┘
                      │               │
            ┌─────────▼─────┐  ┌──────▼──────────────┐
            │  Aurora        │  │  ElastiCache         │
            │  Serverless v2 │  │  Serverless Redis 7  │
            │  (PostgreSQL)  │  │  (Pub/Sub + RBAC)    │
            └───────────────┘  └──────────────────────┘

CDK Infrastructure (packages/chorus-cdk/):

ConstructFileResources
Networknetwork.tsVPC (2 AZs), public/private subnets, NAT Gateway, Security Groups
Databasedatabase.tsAurora Serverless v2, Secrets Manager (DB creds + app config)
Cachecache.tsElastiCache Serverless Redis 7, RBAC user + password in Secrets Manager
Serviceservice.tsECS Fargate cluster, ALB, Task Definition, ECR image build

Redis Authentication: RBAC with password user (chorus), default user disabled. Password auto-generated and stored in Secrets Manager, injected into ECS container as REDIS_PASSWORD secret.


9. Security Considerations

9.1 API Key Security

  • API Keys are stored as SHA-256 hashes
  • Plaintext is only returned at creation time and cannot be recovered afterward
  • Supports expiration time and manual revocation
  • Records last usage time

9.2 Data Isolation

  • All queries include companyUuid filtering (UUID-based multi-tenant isolation)
  • Service layer enforces tenant ownership checks

9.3 Input Validation

  • Uses Zod for request body validation
  • Prevents SQL injection (Prisma parameterized queries)
  • Prevents XSS (React automatic escaping)

9.4 Rate Limiting

  • API request throttling
  • Prevents brute-force API Key attacks

10. Extensibility Considerations

10.1 Future Features

FeatureDescriptionStatus
Task DAGDependency modeling + cycle detection + visualizationImplemented
Session ObservabilityAgent Session + Checkin + Kanban integrationImplemented
Chorus PluginClaude Code plugin, automating Session lifecycleImplemented
Task Auto-Scheduling Querychorus_get_unblocked_tasks MCP toolImplemented
Notification SystemIn-app notifications + SSE push + Redis Pub/SubImplemented
Global SearchUnified search across 6 entity types, Cmd+K UI, MCP toolImplemented
Execution MetricsAgent Hours, velocity statisticsTo be developed (P1)
Git IntegrationAssociate commits and PRsTo be developed
Semantic Searchpgvector knowledge base searchTo be developed

10.2 Technical Reserves

  • pgvector: PostgreSQL natively supports it, can be added seamlessly later
  • Redis: ElastiCache Serverless for Pub/Sub event delivery; can be extended for caching or queues later

Appendix

A. Environment Variables

# Database
DATABASE_URL=postgres://chorus:chorus@localhost:5432/chorus

# NextAuth
NEXTAUTH_URL=http://localhost:8637
NEXTAUTH_SECRET=your-secret-key

# Super Admin (system startup config, manages Companies and global settings)
SUPER_ADMIN_EMAIL=admin@example.com
SUPER_ADMIN_PASSWORD_HASH=\$2b\$10$...  # bcrypt hash

# Redis (optional — falls back to in-memory EventBus when unset)
# Local dev: redis://default:chorus-redis@localhost:6379
# CDK: assembled from REDIS_HOST + REDIS_PORT + REDIS_USERNAME + REDIS_PASSWORD
REDIS_URL=redis://default:chorus-redis@localhost:6379

# Note: OIDC configuration has been moved to the database (Company table),
# each Company is independently configured
# PKCE only, no Client Secret required

B. Reference Documentation