# Contributing to Pipali Thanks for your interest in improving Pipali! To keep the project maintainable, we follow a lightweight process. Reading this first will save everyone time — including you. ## Contribution Process **Work from a triaged issue — don't open a PR against an un-reviewed or declined one.** 1. **Find or open an issue.** Use the [issue templates](https://github.com/khoj-ai/pipali/issues/new/choose) — Bug Report, Feature Request, or Question. For usage and how-to questions, use the **Question** template rather than a bug or feature issue. 2. **Wait for triage.** New issues start with the `triage` label. A maintainer reviews each one and either **accepts it** (removes the `triage` label) or **declines it** (closes the issue). An open issue with no `triage` label is accepted and ready to work on. 3. **Open a pull request** linking the triaged issue (`Closes #123`). First PR wins — *unless the issue is assigned to someone*, in which case it's reserved for them, so please don't open a competing PR. To claim an issue and avoid duplicate effort, comment to say you're working on it. > **Pull requests against an untriaged (`triage`-labelled) or closed issue may be closed without review.** This isn't about gatekeeping — it's about not having you invest hours in a change we can't accept. A two-line issue we can decline in a minute is far kinder than a large PR we have to reject. Good first contributions are labelled [`good first issue`](https://github.com/khoj-ai/pipali/labels/good%20first%20issue) and [`help wanted`](https://github.com/khoj-ai/pipali/labels/help%20wanted) — these are pre-approved, so you can start right away (comment to claim one first). ### What we tend to accept - Bug fixes with a clear reproduction. - Changes that resolve a triaged, accepted issue. - Focused PRs that do one thing well. - Useful, visual explanations like diagrams and screenshots of the issue or feature. ### What tends to get declined - Large unsolicited PRs with no prior discussion. - Sweeping refactors, formatting-only changes, or dependency bumps without a motivating issue. - Changes that expand scope beyond the linked issue. ## Development Setup ### Prerequisites - [Bun](https://bun.sh) v1.2.19+ - A [Pipali](https://pipali.ai) account ### Install and Run ```bash git clone https://github.com/khoj-ai/pipali.git cd pipali bun install bun run dev ``` Open [http://localhost:6464](http://localhost:6464) in your browser. ### Desktop App 1. Setup [Tauri prerequisites](https://v2.tauri.app/start/prerequisites/) 2. [Generate](https://v2.tauri.app/reference/cli/#signer-generate) a signing key and set `TAURI_SIGNING_PRIVATE_KEY` env var in .env file or shell export. See [Tauri signed updates](https://v2.tauri.app/plugin/updater/#signing-updates) 3. Build the Tauri desktop app: ```bash bun run tauri:build:debug ``` ### Environment Variables Copy `.env.example` to `.env`. The app auto-loads `.env` via Bun — no dotenv needed. ### Tests ```bash bun test # Unit tests bun run test:e2e # E2E tests (Playwright) ``` ### Database Pipali uses [PGlite](https://pglite.dev) (embedded Postgres) with [Drizzle ORM](https://orm.drizzle.team/). The database is stored locally at `./pipali.db/`. ```bash bun run db:generate # Generate migration from schema changes bun run db:migrate # Apply migrations ``` ## Development Guidelines - **DRY**: Reuse functions, refactor for maintainability, use standards where available. - **Test**: Update existing tests or add new ones to test important behaviors ## Project Structure ``` src/ ├── client/ # React 19 frontend ├── server/ # Bun + Hono backend │ ├── routes/ # HTTP & WebSocket endpoints │ ├── processor/ # Agent system (director-actor pattern) │ ├── db/ # PGlite database & Drizzle ORM │ ├── automation/ # Scheduled task system │ ├── skills/ # Custom agent behaviors │ └── sandbox/ # Secure command execution src-tauri/ # Tauri desktop app (Rust) tests/ # Unit & E2E tests drizzle/ # Database migrations ``` ## Architecture ### System Overview ``` ┌──────────────────────────────────────────────────────────┐ │ Pipali App (Local) │ │ │ │ ┌────────────┐ WebSocket ┌───────────────────────┐ │ │ │ React │◄──────────────►│ Bun + Hono Server │ │ │ │ Frontend │ (real-time) │ │ │ │ └────────────┘ │ ┌─────────────────┐ │ │ │ │ │ Director │ │ │ │ │ │ (agent loop) │ │ │ │ │ └───────┬─────────┘ │ │ │ │ │ │ │ │ │ ┌───────▼─────────┐ │ │ │ │ │ Actors │ │ │ │ │ │ (tool execution)│ │ │ │ │ └───────┬─────────┘ │ │ │ │ │ │ │ │ │ ┌───────▼─────────┐ │ │ │ │ │ PGlite │ │ │ │ │ │ (local db) │ │ │ │ │ └─────────────────┘ │ │ │ └───────────┬───────────┘ │ └────────────────────────────────────────────┼─────────────┘ │ ┌──────────────────▼─────────────────────┐ │ Pipali Platform (Remote) │ │ │ │ • LLM API (Manage Context, Routing) │ │ • Remote AI Tools (Browse Web) │ │ • Auth (OAuth + token refresh) │ │ • Billing, Usage, Teams │ └────────────────────────────────────────┘ ``` ### Director-Actor Pattern The agent uses a **director-actor pattern** for task orchestration: - **Director** (`src/server/processor/director/`) — An async generator that loops: call LLM → get tool calls → execute tools → feed results back → repeat until the LLM responds without tool calls - **Actors** (`src/server/processor/actor/`) — Individual tool implementations (file ops, shell, web search, MCP tools). Execute in parallel. - **Research Runner** (`src/server/processor/research-runner.ts`) — Manages conversation persistence around the director loop ``` ┌────────────────────────┐ │ Director │ │ (async generator) │ │ │ │ 1. Call LLM │ │ 2. Parse tool calls │ │ 3. Dispatch to actors │ │ 4. Collect results │ │ 5. Loop or finish │ └───────────┬────────────┘ │ ┌─────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ ┌────────────────┐ ┌──────────────┐ ┌──────────────────┐ │ Built-in │ │ MCP Tools │ │ Confirmation │ │ Actors │ │ │ │ Gate │ │ │ │ browser__ │ │ │ │ view_file │ │ fill_form │ │ Dangerous ops │ │ edit_file │ │ │ │ require user │ │ write_file │ │ slack__ │ │ approval │ │ grep_files | │ send_message│ │ │ │ list_files │ │ │ │ Tunable via │ │ read_webpage │ │ (namespaced │ │ preferences │ │ search_web │ │ server__ │ │ │ │ shell_command │ │ toolname) │ │ │ │ ask_user │ │ │ │ │ │ email_user │ │ │ │ │ │ generate_image│ │ │ │ │ └────────────────┘ └──────────────┘ └──────────────────┘ ``` ### Skills System Skills are reusable instruction sets that extend the agent's behavior. Each skill is a directory with a `SKILL.md` file (and optional scripts/references). ``` ~/.pipali/skills// ├── SKILL.md # Frontmatter (name, description) + markdown instructions ├── scripts/ # Optional helper scripts (TypeScript, Python, etc.) ├── references/ # Optional reference files └── package.json # Optional npm dependencies ``` **How skills are loaded:** 1. `loadSkills()` scans `~/.pipali/skills/` and builtin skills at startup 2. The director injects loaded skills into the system prompt as XML 3. The LLM reads skill name + description, and can read the full `SKILL.md` for detailed instructions when relevant Skills are designed for progressive disclosure — the agent sees a summary of all skills but only reads full instructions when a skill is relevant to the current task. **Code:** `src/server/skills/`, builtin skills in `src/server/skills/builtin/` ### Command Sandbox Shell commands run in an OS-enforced sandbox, by default. Sandboxed commands skip user confirmation; commands that need full system access require explicit approval. We use Seatbelt on Mac, Bubblewrap on Linux. Windows does not currently have sandboxing, so all shell commands require user confirmation. ``` ┌─────────────────────────────────────────────────┐ │ shell_command actor │ │ │ │ ┌─────────────────┐ ┌───────────────────┐ │ │ │ Sandbox Mode │ │ Direct Mode │ │ │ │ │ │ │ │ │ │ macOS Seatbelt │ │ Full system │ │ │ │ Linux bwrap │ │ access │ │ │ │ │ │ │ │ │ │ Restricted to: │ │ Requires user │ │ │ │ • allowed paths│ │ confirmation │ │ │ │ • allowed hosts│ │ via WebSocket │ │ │ │ │ │ │ │ │ │ No confirmation│ │ │ │ │ │ needed │ │ │ │ │ └─────────────────┘ └───────────────────┘ │ └─────────────────────────────────────────────────┘ ``` **Sandbox rules** are stored per-user in the database (`SandboxSettings` table): - Read is allowed by default apart from explicitly denied paths. - `deniedReadPaths` — sensitive paths blocked from reading (e.g., `~/.ssh`, `~/.aws`, `.env`) - Write is denied by default apart from explicitly allowed paths. - `allowedWritePaths` — directories the sandbox can write to (default: `/tmp`, `~/.pipali`) - Network access is denied by default apart from explicitly allowed domains - `allowedDomains` — network domains accessible from the sandbox (e.g., npm, pypi, github) **Code:** `src/server/sandbox/`, uses `@anthropic-ai/sandbox-runtime` ### Conversation Storage (ATIF) All conversations are stored in **ATIF** (Agent Trajectory Interchange Format) — a structured JSON format persisted as JSONB in PGlite. See [RFC](https://github.com/laude-institute/harbor/blob/main/docs/rfcs/0001-trajectory-format.md) for details. Each conversation is a sequence of steps: ``` Step 1: source=system │ System prompt Step 2: source=user │ User message Step 3: source=agent │ Thought + tool_calls (e.g., search_web, view_file) Step 4: source=agent │ Observation (tool results) Step 5: source=agent │ Thought + tool_calls (follow-up tools) Step 6: source=agent │ Observation (results) Step 7: source=agent │ Final response (no tool calls = loop ends) ``` Key entry points: - Agent orchestration: `src/server/processor/director/` - Tool implementations: `src/server/processor/actor/` - API routes: `src/server/routes/api.ts` - WebSocket handler: `src/server/routes/ws.ts` - Database schema: `src/server/db/schema.ts`