# 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 — self-host or use our cloud

## Architecture

### Monorepo Structure

Giselle uses a **Turborepo monorepo** with pnpm workspaces, organized into four main directories:

```
/workspace
├── apps/                    # Deployable applications
│   ├── studio.giselles.ai/  # Giselle Cloud (production)
│   └── 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 Cloud

# Build
pnpm build-sdk               # Build SDK packages
pnpm -F studio.giselles.ai build  # Build Giselle Cloud

# 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
6. Update `.continuity/` per-branch ledger — Reflect the change immediately

### 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.<name>` using `giselle.<name>.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<typeof Workspace>;
  ```
- 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<boolean>({
  key: "my-new-feature",
  async decide() {
    if (process.env.NODE_ENV === "development") {
      return takeLocalEnv("MY_NEW_FEATURE_FLAG");
    }
    const edgeConfig = await get(`flag__${this.key}`);
    if (edgeConfig === undefined) {
      return false;
    }
    return edgeConfig === true || edgeConfig === "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
<FeatureFlagContext
  value={{
    // ...existing flags
    myNewFeature: featureFlag?.myNewFeature ?? false,
  }}
>
```

**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 <NewFeatureUI />;
}
```

**Local development**: Set the environment variable (e.g., `MY_NEW_FEATURE_FLAG=true`) in `.env.local`.

**Production**: Configure via Vercel Edge Config with key `flag__my-new-feature`.

## Continuity (per-branch ledgers + batched summary)
Keep “human intent” and session context in-repo for review **without frequent merge conflicts** by using a two-layer model:
- `CONTINUITY.md`: a **batched snapshot** (low churn), updated occasionally.
- `.continuity/`: **per-branch ledgers** (high churn), updated on every request / during work.

### Agent behavior spec
- Locate ledger (every user request):
  - Determine current git branch name: `git rev-parse --abbrev-ref HEAD`.
  - Sanitize branch by replacing `/` with `__`.
- Find ledger file:
  - In `.continuity/`, find files whose filename ends with `-<sanitizedBranch>.md` (suffix match).
  - If multiple match, pick the latest by lexicographically greatest datetime prefix `YYYYMMDD-HHMMSS`.
- Reuse / create:
  - If one exists: read it first and update it as needed.
  - If none exists: create `YYYYMMDD-HHMMSS-<sanitizedBranch>.md` initialized from `.continuity/template.md` and the current user request.

### Notes on the two-layer model
- Read **both** `CONTINUITY.md` and the current `.continuity/` branch ledger to understand context.
- Write high-churn notes only to `.continuity/` (what changed, why, tradeoffs, open questions, working set).
- Periodically batch-summarize `.continuity/*` into `CONTINUITY.md` (“as of <date>”).

### `functions.update_plan` vs the Ledger
- `functions.update_plan` is for short-term execution scaffolding while you work (a small 3–7 step plan with pending/in_progress/completed).
- `CONTINUITY.md` is a batched summary; per-branch ledgers live in `.continuity/`.
- Keep them consistent: summarize `.continuity/` into `CONTINUITY.md` periodically (not every micro-step).

### In replies
- Begin with a brief “Ledger Snapshot” based on the current per-branch ledger (Goal + Now/Next + Open Questions). Print the full ledger only when it materially changes or when the user asks.

### `CONTINUITY.md` format (keep headings)
- Goal (incl. success criteria):
- Constraints/Assumptions:
- Key decisions:
- State:
- Done:
- Now:
- Next:
- Open questions (UNCONFIRMED if needed):
- Working set (files/ids/commands):

<!-- opensrc:start -->

## 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 <package>           # npm package (e.g., npx opensrc zod)
npx opensrc pypi:<package>      # Python package (e.g., npx opensrc pypi:requests)
npx opensrc crates:<package>    # Rust crate (e.g., npx opensrc crates:serde)
npx opensrc <owner>/<repo>      # GitHub repo (e.g., npx opensrc vercel/ai)
```

<!-- opensrc:end -->