# AGENTS.md - Giselle Development Guide ## Development Philosophy ### Core Principle: **Less is more** Keep every implementation as small and obvious as possible. ### Guidelines - **Simplicity first** – Prefer the simplest data structures and APIs that work - **Avoid needless abstractions** – Refactor only when duplication hurts - **Remove dead code early** – `pnpm tidy` scans for unused files/deps and lets you delete them in one command - **Minimize dependencies** – Before adding a dependency, ask "Can we do this with what we already have?" - **Consistency wins** – Follow existing naming and file-layout patterns; if you must diverge, document why - **Explicit over implicit** – Favor clear, descriptive names and type annotations over clever tricks - **Fail fast** – Validate inputs, throw early, and surface actionable errors - **Let the code speak** – If you need a multi-paragraph comment, refactor until intent is obvious ## Project Overview Giselle is built to design and run AI workflows beyond prompt chains. Not a chat. Not a chain. A system you can run. ### Key Features: - Visual editor - Instant execution - No infra headaches - Open source — focused on local use and self-hosting ## Architecture ### Monorepo Structure Giselle uses a **Turborepo monorepo** with pnpm workspaces, organized into four main directories: ``` /workspace ├── apps/ # Deployable applications │ ├── studio.giselles.ai/ # Giselle Studio application │ └── ui.giselles.ai/ # UI component showcase ├── packages/ # Published SDK packages (@giselles-ai/*) ├── internal-packages/ # Internal shared packages (@giselle-internal/*) └── tools/ # Development utilities ``` ### Package Layers **SDK Packages (`packages/@giselles-ai/*`):** - `protocol` — Core domain types and schemas (Workspace, Node, Task, Generation) - `giselle` — Engine implementation (tasks, generations, triggers, integrations) - `react` — React hooks and components for client integration - `nextjs` — Next.js integration with route handlers - `language-model` — Language model abstractions and cost calculations - `language-model-registry` — Provider-specific model implementations - `rag` — RAG pipeline (chunking, embedding, querying) - `github-tool` — GitHub integration utilities **Internal Packages (`internal-packages/@giselle-internal/*`):** - `workflow-designer-ui` — Visual workflow editor (React Flow-based) - `ui` — Shared UI components (Radix-based) ### Technology Stack | Layer | Technology | |-------|------------| | Runtime | Node.js 22+ | | Package Manager | pnpm 10+ | | Build System | Turborepo | | Web Framework | Next.js 16 (App Router) | | UI Library | React 19 | | Styling | Tailwind CSS 4 | | State Management | Zustand (editor), SWR (data fetching) | | Validation | Zod v4 | | Database | PostgreSQL with Drizzle ORM | | Vector Store | pgvector | | Formatting/Linting | Biome | | Testing | Vitest | | AI SDK | Vercel AI SDK | ### Data Flow ``` Workspace (JSON) → Protocol Types → Giselle Engine → Task Execution → Generation Output ↓ Language Model Registry → AI Provider APIs ``` ### Key Domain Concepts - **Workspace** — A visual workflow containing nodes and connections - **Node** — Either an OperationNode (actions, text generation, triggers) or VariableNode (text, files, vector stores) - **Task** — An executable instance of a workflow with sequences of generations - **Generation** — A single step execution (created → queued → running → completed/failed) - **App** — A published workflow entry point with parameters ## Development Workflow TBD ### Initial Setup ```sh pnpm install # Install all dependencies pnpm build-sdk # Build SDK packages (required before running apps) ``` ### Development Commands ```sh # Development pnpm dev:studio.giselles.ai # Start Giselle Studio # Build pnpm build-sdk # Build SDK packages pnpm -F studio.giselles.ai build # Build Giselle Studio # Quality Checks pnpm format # Format code with Biome pnpm check-types # Type-check all packages pnpm test # Run all tests pnpm tidy # Find unused files/dependencies pnpm tidy --fix # Remove unused files/dependencies ``` ### After Every Code Change Run these commands in order: 1. `pnpm format` — Format code 2. `pnpm build-sdk` — Rebuild SDK packages 3. `pnpm check-types` — Verify types 4. `pnpm tidy` — Check for unused code 5. `pnpm test` — Run tests ### API addition rule (Giselle ↔ HTTP) When adding a new **public API** to `packages/giselle/src/giselle.ts`, also add the corresponding routing entry to `packages/http/src/router.ts` (typically `jsonRoutes.` using `giselle..inputSchema`) so the API is reachable through the HTTP layer (e.g., via `NextGiselle`). ### Testing ```sh pnpm test # Run all tests pnpm -F @giselles-ai/giselle test # Run tests for a specific package cd packages/giselle && vitest # Run tests in watch mode vitest run src/tasks/run-task.test.ts # Run a specific test file ``` Test files follow the `*.test.ts` naming pattern and use Vitest. ### Pull Request Guidelines - Create PRs in **meaningful minimum units** — even 1 commit or ~20 lines is fine - Feature flags protect unreleased features, so submit PRs for any meaningful work - **~500 lines**: Consider wrapping up for a PR - **1000 lines**: Maximum threshold — avoid exceeding this ## Key Conventions ### Naming TBD **File Names: kebab-case** ``` ✅ user-profile.ts ✅ api-client.tsx ✅ text-generation.ts ❌ UserProfile.ts ❌ apiClient.tsx ``` **Components: PascalCase** ``` ✅ UserProfile ✅ TextGenerationNode ❌ userProfile ``` **Variables and Functions: camelCase** ``` ✅ userEmail ✅ calculateTotalPrice() ✅ validateUserInput() ❌ user_email ``` **Booleans: Prefix with `is`, `has`, `can`, `should`** ``` ✅ isEnabled, hasPermission, canEdit, shouldRetry ✅ isCompletedGeneration(), hasActiveSubscription() ❌ enabled, permission, completed ``` **ID Types: Prefixed strings with branded types** ```typescript // packages/protocol/src/node/base.ts export const NodeId = createIdGenerator("nd"); // "nd_xxx" export const InputId = createIdGenerator("inp"); // "inp_xxx" export const OutputId = createIdGenerator("otp"); // "otp_xxx" ``` ### Code Style TBD **Formatting (Biome)** - Tab indentation - Double quotes for strings - Organized imports (auto-sorted) **TypeScript** - Prefer explicit types over `any` - Use Zod schemas for runtime validation and type inference: ```typescript export const Workspace = z.object({ id: WorkspaceId.schema, nodes: z.array(NodeLike), connections: z.array(Connection), }); export type Workspace = z.infer; ``` - Use discriminated unions for variant types: ```typescript export const Node = z.discriminatedUnion("type", [ OperationNode, VariableNode, ]); ``` **React** - Functional components with hooks - Zustand for complex state (editor store) - SWR for server data fetching - Selective subscriptions to minimize re-renders: ```typescript // Good: Subscribe only to needed data const node = useEditorStore((s) => s.nodesById[nodeId]); // Bad: Subscribe to entire state const state = useEditorStore((s) => s); ``` **Async/Await** - Prefer async/await over raw promises - Use try/catch for error handling ### Error Handling TBD **Custom Error Classes with Symbol Markers** For cross-package error identification, use Symbol-based instance checking: ```typescript const marker = "giselle.react.error.APICallError"; const symbol = Symbol.for(marker); export class APICallError extends ReactError { private readonly [symbol] = true; static isInstance(error: unknown): error is APICallError { return ReactError.hasMarker(error, marker); } } ``` **Validation Errors** Handle Zod validation errors at API boundaries: ```typescript try { return await jsonRoutes[routerPath](giselle)({ input }); } catch (e) { if (e instanceof ZodError) { return new Response("Invalid request body", { status: 400 }); } return new Response("Internal Server Error", { status: 500 }); } ``` **Exhaustive Type Checking** Use `never` type for exhaustive switch statements: ```typescript switch (generation.context.operationNode.content.type) { case "action": // handle action break; case "textGeneration": // handle text generation break; default: { const _exhaustiveCheck: never = generation.context.operationNode.content.type; throw new Error(`Unhandled type: ${_exhaustiveCheck}`); } } ``` **Fail Fast** Validate inputs early and throw with actionable messages: ```typescript if (!generation) { throw new Error(`Generation(id: ${generationId}) is not found`); } if (!config.vectorStoreQueryService) { throw new Error("No vector store query service provided"); } ``` ### Feature Flags Feature flags protect unreleased features, allowing safe merges to main and production deploys. **Step 1: Define the flag in `apps/studio.giselles.ai/flags.ts`** ```typescript export const myNewFeatureFlag = flag({ key: "my-new-feature", decide() { return process.env.MY_NEW_FEATURE_FLAG === "true"; }, description: "Enable my new feature", options: [ { value: false, label: "disable" }, { value: true, label: "Enable" }, ], defaultValue: false, }); ``` **Step 2: Use on server (Next.js server components, data loaders)** ```typescript // apps/studio.giselles.ai/app/workspaces/[workspaceId]/data-loader.ts const myNewFeature = await myNewFeatureFlag(); return { // ... featureFlags: { // ...existing flags myNewFeature, }, }; ``` **Step 3: Expose to React components** Add the flag to the `FeatureFlagContextValue` interface: ```typescript // packages/react/src/feature-flags/context.ts export interface FeatureFlagContextValue { // ...existing flags myNewFeature: boolean; } ``` Add to `WorkspaceProvider` defaults: ```typescript // packages/react/src/workspace/provider.tsx ``` **Step 4: Use in React components** ```typescript import { useFeatureFlag } from "@giselles-ai/react"; function MyComponent() { const { myNewFeature } = useFeatureFlag(); if (!myNewFeature) { return null; // or fallback UI } return ; } ``` **Local development**: Set the environment variable (e.g., `MY_NEW_FEATURE_FLAG=true`) in `.env.local`. **Production**: Set the same environment variable on the server. Only `"true"` enables the flag. Restart the server after changing configuration; rebuild when a flag is evaluated at build time. ## Source Code Reference Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details. See `opensrc/sources.json` for the list of available packages and their versions. Use this source code when you need to understand how a package works internally, not just its types/interface. ### Fetching Additional Source Code To fetch source code for a package or repository you need to understand, run: ```bash npx opensrc # npm package (e.g., npx opensrc zod) npx opensrc pypi: # Python package (e.g., npx opensrc pypi:requests) npx opensrc crates: # Rust crate (e.g., npx opensrc crates:serde) npx opensrc / # GitHub repo (e.g., npx opensrc vercel/ai) ```