Skip to content

scheidydude/orchid

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

248 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Orchid

Orchid

"Orchid is a symbiotic ecosystem of specialized AI agents, cultivated and orchestrated to transform ideas into reliable software systems."

Description

Orchid is an AI agent orchestration framework you install once and run against any project.

Who is it for: Developers and small teams who want AI agents doing real work — writing code, running tests, summarizing research, calling APIs — not just answering questions. Solo devs get a tireless pair programmer. Teams get a shared agentic OS where every user has isolated credentials, budgets, and tool access.

What it does:

  • Turns a plain repo into an agentic workspace: discuss requirements with an AI product manager, generate architecture docs and a task board, then execute tasks with specialized agents (developer, tester, reviewer, researcher)
  • Runs tasks in parallel with dependency-aware scheduling, provider fallback, and cost tracking
  • Simulates an async coworker bench via scheduled tasks — set up recurring agent prompts, MCP tool calls, or shell commands on a cron schedule, and wake up to completed work: daily standup summaries, nightly test runs, automated research briefs
  • Serves a web UI, Telegram/Slack bots, and a REST API from a single orchid serve process

Multi-user mode (V3): Deploy as a shared agentic OS. Each user gets an encrypted credential vault, per-user MCP server access, and LLM/CPU spend limits — managed through two React SPAs (User Portal /app/, Admin Console /admin/).

Interface options: CLI · REST API · React web UI · Telegram · Slack · cron scheduler · remote worker nodes

See CHANGELOG.md for full version history.

~/orchid/              ← install here
~/projects/webtron/    ← your project (any git repo)
~/projects/blog/       ← another project

Install

git clone git@github.com:scheidydude/orchid.git ~/LocalAI/orchid
cd ~/LocalAI/orchid
uv tool install .

# Config goes in ~/.config/orchid/.env (XDG standard, chmod 600)
bash scripts/setup-config.sh
# Then edit ~/.config/orchid/.env and set ANTHROPIC_API_KEY
# llama.cpp is expected at http://localhost:8080/v1 (set LLAMA_BASE_URL to override)

Quick start

# Create a new project with the guided wizard
orchid new "2D space shooter web game" --name webtron

# Or scaffold into an existing repo
orchid init ~/projects/webtron --name webtron --description "2D space shooter"

# Check lifecycle phase and task status
orchid --project ~/projects/webtron --status
orchid --project ~/projects/webtron --phase

# Run the planning workflow (discuss → requirements → tasks)
orchid --project ~/projects/webtron --discuss
orchid --project ~/projects/webtron --approve   # advance to next phase

# Run all pending tasks autonomously
orchid --project ~/projects/webtron --mode auto

# Run a single specific task
orchid --project ~/projects/webtron --run-task T015

# Interactive chat with an agent
orchid --project ~/projects/webtron --mode interactive

# Persistent tmux session
./scripts/start_session.sh ~/projects/webtron

V3 Multi-User OS

Orchid V3 turns a single-user orchestrator into a multi-tenant agentic OS where each user gets isolated credentials, project access, MCP servers, and LLM spend limits — all managed through two React SPAs served by the same orchid serve process.

Architecture

orchid serve --port 7842
│
├── /api/auth/*          JWT, OIDC, API keys, audit log
├── /api/user/*          vault, MCP servers, notifications
├── /api/admin/*         MCP catalog, budget reset, runs monitor, config
├── /api/scheduler/*     cron task manager (per-user)
├── /app/*               User Portal SPA   (React, build: orchid/interfaces/portal/)
└── /admin/*             Admin Console SPA (React, build: orchid/interfaces/admin/)

User Portal (/app/)

After login, users land on the User Portal. Pages:

Page What you can do
Dashboard Scheduled tasks (status, next/last run, enable/disable toggle) + projects
Settings → Profile Change username / email / password
Settings → API Keys Create and revoke API keys
Settings → Credentials Per-user encrypted vault — store ANTHROPIC_API_KEY, OPENAI_API_KEY, etc.
Settings → Notifications Email / Telegram / Slack toggles + channel IDs
Settings → MCP Servers View admin-granted servers; add private servers (if enabled)

Credential vault keys matching known provider env vars (ANTHROPIC_API_KEY, OPENAI_API_KEY, …) are automatically injected into scheduled task execution — users supply their own API keys without admin involvement.

Admin Console (/admin/)

Admin-role users see the Admin Console at /admin/. Tabs:

Tab What you can do
Users List, invite (email link), edit role/email/projects, deactivate
MCP Catalog Define shared MCP servers; set scope (shared/private/admin-only); grant/revoke per role or user ID
Audit Log Paginated, filterable by user/action; expandable detail JSON
Quotas Set per-user budget_usd (LLM spend cap) and cpu_budget_seconds (daily wall-clock cap); usage bars; reset spend counter
Task Monitor Live view of all users' scheduled task runs; filter by user/status; expandable output/error
System Config Toggle allow_user_mcp, allow_user_projects; set default quota values for new users

Credential Vault

Each user gets a Fernet-encrypted vault derived from ORCHID_VAULT_KEY:

# Required env var — set in ~/.config/orchid/.env
ORCHID_VAULT_KEY=<random-32-char-secret>

Key derivation: HKDF-SHA256(ORCHID_VAULT_KEY, salt=b"orchid-vault-v1", info=user_id.encode()). Each user has a distinct Fernet key. Compromising one user's key does not expose others. Rotating ORCHID_VAULT_KEY invalidates all vaults — document this in your ops runbook.

MCP Catalog

Admin defines shared MCP servers once. Users see only the servers they're authorised for:

  • scope: shared — all users with matching role
  • scope: admin-only — admins only
  • scope: private — explicit grants only
  • requires_credential: GMAIL_TOKEN — user must have that vault key to connect

Users can also add private MCP server definitions (admin can disable this with web.allow_user_mcp: false).

Budget Enforcement

Set budget_usd per user. Every Anthropic API call in agent_tool tasks accrues cost. If budget_used_usd >= budget_usd, the task run fails with BudgetExceededError. 0.0 = unlimited.

CPU budget (cpu_budget_seconds) caps total wall-clock seconds of task execution per day (UTC midnight reset).

Reset a user's spend counter via the Quotas tab or:

POST /api/admin/users/{user_id}/budget/reset

Building the SPAs

# User Portal
cd orchid/interfaces/portal && npm install && npm run build

# Admin Console
cd orchid/interfaces/admin && npm install && npm run build

Dev servers (hot reload):

# Portal: http://localhost:5174
cd orchid/interfaces/portal && npm run dev

# Admin: http://localhost:5175
cd orchid/interfaces/admin && npm run dev

PostgreSQL Backend

By default Orchid stores users in ~/.config/orchid/users.json. For production multi-node deployments, switch to PostgreSQL:

# Install the extra
uv pip install 'orchid[postgres]'

# Point Orchid at Postgres (add to ~/.config/orchid/.env)
ORCHID_AUTH_STORE_DSN=postgresql://orchid:password@localhost/orchid

# Migrate existing users from JSON → Postgres (idempotent, skips existing rows)
orchid migrate-to-postgres --dsn postgresql://orchid:password@localhost/orchid

# Dry-run first to preview what will be migrated
orchid migrate-to-postgres --dsn ... --dry-run

get_store() automatically uses PostgresUserStore when ORCHID_AUTH_STORE_DSN is set. Tables are created on first connect; schema migrations are idempotent (ALTER TABLE … ADD COLUMN IF NOT EXISTS).

Project Ownership

When web.allow_user_projects is enabled (default: true), non-admin users can create projects. Projects are stored under a user-scoped path:

~/.config/orchid/projects/{user_id}/{project_name}/

Ownership is tracked in ~/.config/orchid/projects/registry.json. Admins see all projects; users see only projects in their User.projects allowlist (empty = unrestricted).

Toggle project creation for non-admins:

PUT /api/admin/config   {"web.allow_user_projects": false}

Bot Notifications

Scheduled task completions (success or failure) send DMs if the user has configured a Telegram chat ID or Slack user ID in their notification settings (Settings → Notifications in the portal).

Requires --telegram / --slack flags on orchid serve:

orchid serve --watch-dir ~/LocalAI --telegram --slack

CLI Auth

The orchid CLI integrates with a running orchid serve instance for authenticated operations. Once logged in, vault credential injection, budget enforcement, and per-user MCP catalog ACLs apply to every CLI run automatically.

# Authenticate — saves session to ~/.config/orchid/cli_session.json (mode 0600)
orchid login --server http://localhost:7842 --username alice

# Show current user, role, and budget/CPU usage
orchid whoami

# Revoke server-side token and delete local session
orchid logout

No session = silent fallback to anonymous single-user mode. Zero breaking change.

User management (admin only):

orchid user list                          # table: all users, roles, budget usage
orchid user invite alice@example.com      # create invite link (email auto-sent if SMTP set)
orchid user invite alice@example.com --role admin
orchid user budget-reset <user_id>        # reset budget_used_usd to 0

Scheduler:

orchid scheduler list                     # list your scheduled tasks
orchid scheduler run <task_id>            # trigger a task immediately

Audit log (admin only):

orchid audit                              # last 50 events
orchid audit --limit 200 --user alice --action LOGIN

Admin Invite Flow

Admins create users via invite — no open registration:

  1. Admin: POST /api/admin/invite or orchid user invite EMAIL → returns invite_url
  2. Admin sends URL to user (email auto-sent if SMTP configured)
  3. User visits URL → sets password → account activated

Role Model

Role Permissions
admin All endpoints; admin console; see all users/tasks/runs
user Own tasks/vault/credentials/MCP servers; portal only
readonly Read-only; no task execution

V2 Planning Workflow

Orchid V2 introduces a lifecycle state machine that guides a project from idea to working software:

NEW → DISCUSSING → REQUIREMENTS → PLANNING → READY → EXECUTING → COMPLETE

Step 1 — Discuss requirements

orchid --project ~/projects/webtron --discuss

Chat with the DiscussionAgent (powered by Claude) to clarify what you're building. The agent asks questions, surfaces ambiguities, and records your answers.

Step 2 — Generate planning artifacts

orchid --project ~/projects/webtron --approve

Advancing past DISCUSSING runs the ProductManagerAgent, which generates:

  • REQUIREMENTS.md — user stories, acceptance criteria
  • ARCHITECTURE.md — recommended stack and component design

Then the ProjectManagerAgent generates:

  • MILESTONES.md — delivery phases with success metrics
  • tasks.md — fully formed task list ready to execute

Step 3 — Execute tasks

orchid --project ~/projects/webtron --mode auto

Orchid picks the next pending task, routes it to the right agent and model, and loops until complete. Gates between phases require --approve to advance.

Lifecycle commands

orchid --project PATH --phase               show current phase
orchid --project PATH --discuss             start/continue discussion
orchid --project PATH --approve [--auto]    approve current gate
orchid --project PATH --artifacts           list planning artifact status
orchid --project PATH --get-result T001     print task result output
orchid --project PATH --run-task T015       run a single specific task

What a project looks like

After orchid init and a planning run:

webtron/
├── CLAUDE.md           ← hot memory: context, decisions, current focus
├── tasks.md            ← task board: parsed by orchid, edited by you
├── REQUIREMENTS.md     ← generated by PM agent
├── ARCHITECTURE.md     ← generated by PM agent
├── MILESTONES.md       ← generated by PM agent
├── .orchid.yaml        ← optional per-project config
└── .orchid/            ← runtime data (gitignored)
    ├── decisions.json
    ├── project.state.json    ← lifecycle phase state
    ├── discussion/
    │   ├── conversation.jsonl
    │   └── context.md
    ├── task_results.json     ← TaskResultStore (--get-result)
    ├── task_metrics.jsonl    ← per-task timing, iterations, action counts
    └── session_logs/

.orchid.yaml

project: webtron
description: "2D space shooter web game"
model_preference: auto     # claude | local | auto

agent_roles:
  - developer
  - researcher
  - reviewer

context_files:             # extra files loaded into every agent prompt
  - README.md
  - docs/architecture.md

# memory:
#   compression_threshold: 8000

# gates:                   # override phase-transition gate behaviour
#   ready_to_executing: auto   # auto | human (default: human)

tasks.md format

- [ ] **T001** Build player ship `type:code_generate` `p1` `agent:developer`
  - src/player.py — movement, shooting, collision
- [ ] **T002** Write unit tests `type:code_generate` `p2`
- [ ] **T003** Review T001 `type:review` `p2` `agent:reviewer` `needs:T001`
- [ ] **T099** Rollup sprint results `type:rollup` `rollup:T001,T002,T003` `output:SPRINT1.md`
- [~] **T004** Skip this task `type:draft` `p3`  # skipped tasks excluded from auto runs

Task types: code_generate draft review summarize search plan critique synthesize rollup verify Priorities: p1 high · p2 normal · p3 low Status: [ ] TODO · [x] DONE · [~] SKIP · [!] BLOCKED

CLI reference

# Core run modes
orchid --project PATH --mode auto          run all pending tasks
orchid --project PATH --mode interactive   chat with agent
orchid --project PATH --status             task board + hot memory (--project defaults to cwd)
orchid --project PATH --add-task "title"   add a task quickly
orchid --project PATH --add-task "title" --type review --priority 1

# Lifecycle (V2)
orchid --project PATH --phase              show current lifecycle phase
orchid --project PATH --discuss            open discussion with AI PM
orchid --project PATH --approve            advance to next phase (human gate)
orchid --project PATH --approve --auto     advance without confirmation
orchid --project PATH --artifacts          list planning artifact existence
orchid --project PATH --get-result T001    print saved task output
orchid --project PATH --run-task T015      run a single specific task

# Task management
orchid task add --title "..." --type code_generate --project PATH
orchid task done --id T001 --project PATH
orchid task block --id T002 --project PATH
orchid task skip --id T015 --project PATH  # mark task as skipped ([~])

# Interfaces
orchid serve --watch-dir ~/LocalAI --port 7842   persistent multi-project server
orchid serve --bots                              start central bot server (Telegram + Slack)
orchid serve --telegram                          start Telegram bot via central server
orchid serve --slack                             start Slack bot via central server
orchid web --project PATH [--port 7842]          single-session web UI

# Deprecated (use `orchid serve --telegram` or `orchid serve --slack` instead)
orchid telegram --project PATH    → DEPRECATED, use `orchid serve --telegram`
orchid slack --project PATH       → DEPRECATED, use `orchid serve --slack`

# Session checkpointing
orchid --project PATH --list-checkpoints        list available checkpoints
orchid --project PATH --rewind CHECKPOINT_ID    restore session to checkpoint (no persist)
orchid --project PATH --resume CHECKPOINT_ID    restore and approve gate (continue from checkpoint)

# Output format
orchid --project PATH --mode auto --output-format stream-json   emit NDJSON event stream

# Hooks
orchid hooks list   [--project PATH] [--section tasks|phases|agent|session] [--event E] [--type shell|http|python]
orchid hooks show   [--project PATH] HOOK_ID
orchid hooks validate [--project PATH]
orchid hooks test   [--project PATH] HOOK_ID
orchid hooks stats  [--project PATH]
orchid hooks add    [--project PATH] --event E --type shell --cmd "..."
orchid hooks remove [--project PATH] HOOK_ID

# MCP servers
orchid mcp ls   [--project PATH]              list tools from all configured MCP servers
orchid mcp call [--project PATH] TOOL [ARGS]  call a specific MCP tool

# Auth (requires orchid serve running)
orchid login [--server URL] [--username U]    authenticate, save session locally
orchid logout                                 revoke token + delete session file
orchid whoami                                 show user, role, email, budget usage

# User management (admin only; requires session)
orchid user list                              list all users with budget/CPU usage
orchid user invite EMAIL [--role R]           create invite link
orchid user budget-reset USER_ID              reset a user's LLM spend counter

# Scheduler (requires session)
orchid scheduler list                         list your scheduled tasks
orchid scheduler run TASK_ID                  trigger a task immediately

# Audit log (admin only; requires session)
orchid audit [--limit N] [--user UID] [--action A]

# Diagnostics
orchid --check-providers                   probe all configured providers
orchid --project PATH --tail               tail the live session log
orchid --project PATH --inject "text"      inject message into running agent
orchid --project PATH --recall "query"     semantic search over memory
orchid --project PATH --search "query"     web search
orchid --project PATH --mode auto --trace  log each ReAct iteration for debugging

Web UI

Orchid ships a React-based web interface for managing projects, planning, tasks, agent runs, vector memory recall, and session history.

Starting the web UI

Persistent server with auto-discovery (recommended):

# Scans ~/LocalAI and ~/Documents/Development for orchid projects automatically
orchid serve --watch-dir ~/LocalAI --watch-dir ~/Documents/Development

# Explicit projects
orchid serve --project ~/myapp --project ~/other/project

Single or multi-project:

orchid web --project ~/projects/webtron
orchid web --project ~/projects/webtron --project ~/projects/blog

Open http://localhost:7842 in your browser.

Health check: GET /health{"status": "ok", "projects": N} — used by Traefik and systemd to confirm the server is up.

Features

  • Login page — checks /api/auth/me on load; shows a centered sign-in form if unauthenticated; logout button in header; no flash of UI before auth resolves
  • PM Dashboard tab — project health at a glance: MilestoneProgress, DependencyGraph (cytoscape.js DAG), SessionBurndown, PhaseTimeline, TaskTiming table from task_metrics.jsonl (deduplicates by task so re-run tasks show current status)
  • Planning tab — V2 lifecycle panel: phase indicator, discussion chat, artifact viewer, gate approval panel, NewProject wizard
  • Discussion history — persistent chat history with AI PM, view previous conversations and context
  • Discussion streaming — real-time WebSocket feed of PM agent responses as they stream
  • Task board — view, create, and update task status; skip tasks with [~] status
  • Agent stream — live WebSocket feed of agent activity during a run
  • Decision log — full history of architectural decisions
  • Session history — browse and replay past agent session logs
  • Vector recall — semantic search over embedded session memory
  • Hot memory — view the project's CLAUDE.md context in real time
  • Run controls — start and stop agent runs from the browser; run single tasks with ▶ button
  • Settings tab — machine profile editor and provider availability status
  • Project Config tab — view and edit .orchid.yaml settings per project
  • Mobile-responsive UI — full PWA support: hamburger drawer navigation, touch-adapted controls, swipe gestures, safe-area insets, offline-capable via service worker manifest
  • Active/Inactive project grouping — projects grouped by activity status in sidebar
  • Auto-discovery — projects in watched directories appear automatically; adding .orchid.yaml to a directory (via orchid init) registers it within seconds without restarting the server
  • Project switcher — sidebar shows all projects with task counts, filesystem path, last session timestamp, and live status indicator

Project Config Tab

The Project Config tab provides a dedicated interface for managing per-project settings:

  • View and edit .orchid.yaml configuration
  • Set model preferences (claude/local/auto)
  • Configure agent roles
  • Manage context files loaded into agent prompts
  • Override gate behaviour (auto vs human approval)
  • Shell mode settings (blocklist/allowlist)

Changes are saved immediately and reflected in subsequent agent runs.

Active/Inactive Project Grouping

Projects in the sidebar are automatically grouped by activity status:

  • Active projects — those with recent sessions or pending tasks
  • Inactive projects — projects with no recent activity

This grouping helps you quickly focus on projects that need attention while keeping completed or dormant projects accessible but visually separated.

Running as a systemd service

A ready-to-install service file is provided:

# Install and start
sudo cp scripts/orchid-serve.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now orchid-serve

# Or use the install script
bash scripts/install-orchid-serve.sh

# Logs
sudo journalctl -u orchid-serve -f

The service watches /home/dave/LocalAI and /home/dave/Documents/Development by default. Edit scripts/orchid-serve.service to change watch directories.

Building the frontend

Orchid ships three separate React SPAs. Build all three for a complete deployment:

# Power-user project SPA (existing web UI)
cd orchid/interfaces/web_ui
npm install && npm run build   # outputs to web_ui/dist/

# User Portal (/app/)
cd orchid/interfaces/portal
npm install && npm run build   # outputs to portal/dist/

# Admin Console (/admin/)
cd orchid/interfaces/admin
npm install && npm run build   # outputs to admin/dist/

When installing via uv tool install, build first:

cd orchid/interfaces/web_ui && npm run build
cd orchid/interfaces/portal && npm run build
cd orchid/interfaces/admin  && npm run build
cd ~/LocalAI/orchid && uv tool install . --force

Development mode

Run the Vite dev server alongside the FastAPI backend for hot-reload:

# Terminal 1 — backend
orchid web --project ~/projects/webtron --dev

# Terminal 2 — frontend (proxies API calls to :7842)
cd orchid/interfaces/web_ui
npm run dev
# Open http://localhost:5173

Frontend stack

Frameworks and core libraries

Library Version Role
React 18 UI component frameworks
React DOM 18 Browser rendering

Build tooling

Tool Version Role
Vite 5 Dev server, bundler, HMR

Vite plugins and add-ons

Plugin Version Role
@vitejs/plugin-react 4 JSX transform and React Fast Refresh

Runtime: none beyond React — no routing library, no state management library, no CSS framework. Styles are a single hand-written index.css using CSS custom properties (variables) for the dark theme.

Backend serving: FastAPI StaticFiles serves web_ui/dist/ in production. The SPA catch-all route returns index.html for all non-API paths.

V2.1 Central Bot Architecture

Orchid V2.1 introduces a central bot server that unifies Telegram and Slack bot management under a single orchid serve command. This replaces the legacy per-project bot commands with a scalable multi-project architecture.

Starting the Central Bot Server

# Start the central server with both Telegram and Slack bots
orchid serve --bots --port 7842

# Start only Telegram bot
orchid serve --telegram --port 7842

# Start only Slack bot
orchid serve --slack --port 7842

# Combine with project watching
orchid serve --bots --watch-dir ~/LocalAI --watch-dir ~/Documents/Development

Environment Variables

Configure bot tokens in ~/.config/orchid/.env:

# Telegram bot token (required for --telegram or --bots)
TELEGRAM_BOT_TOKEN=123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11

# Slack bot token (required for --slack or --bots)
SLACK_BOT_TOKEN=xoxb-1234567890-1234567890123-AbCdEfGhIjKlMnOpQrStUvWx

Deprecation Notice

The legacy bot commands have been deprecated:

Old Command New Command
orchid telegram --project PATH orchid serve --telegram
orchid slack --project PATH orchid serve --slack

The old commands launched bot instances tied to a single project. The new central server manages multiple projects simultaneously and routes commands based on channel-to-project mappings.

Telegram Commands (Underscore Style)

Telegram bot commands use underscores and support project context:

Command Description
/orchid_start Start a new orchid session in the current project
/orchid_status Show task board and current phase
/orchid_projects List all available projects
/orchid_switch <project> Switch to a different project
/orchid_phase Show current lifecycle phase
/orchid_tasks List pending tasks
/orchid_approve Approve current lifecycle gate
/orchid_discuss Start discussion with AI PM
/orchid_help Show available commands

Slack Commands (Hyphen Style)

Slack bot commands use hyphens and follow Slack's command conventions:

Command Description
/orchid-status Show task board and current phase
/orchid-projects List all available projects
/orchid-switch <project> Switch to a different project
/orchid-phase Show current lifecycle phase
/orchid-tasks List pending tasks
/orchid-approve Approve current lifecycle gate
/orchid-discuss Start discussion with AI PM
/orchid-help Show available commands

Channel Routing

The central bot supports channel-to-project mapping for team workflows:

# Each channel can be bound to a specific project
# Commands in that channel automatically target the bound project

Channel: #webtron-dev → Project: ~/projects/webtron
Channel: #blog-team → Project: ~/projects/blog
Channel: #general → Project: (user-selected via /orchid_switch)

How routing works:

  1. When a command is received, the bot looks up the channel ID
  2. If the channel is mapped to a project, that project is used
  3. If not mapped, the user's last-selected project is used
  4. Users can override with /orchid_switch <project> or /orchid_switch <project>

State Files

The central bot maintains state in two JSON files (stored in ~/.config/orchid/):

slack-channels.json

Maps Slack channel IDs to projects:

{
  "channels": {
    "C0123456789": {
      "project_path": "/home/user/projects/webtron",
      "bound_at": "2026-03-22T10:00:00Z",
      "bound_by": "user123"
    },
    "C9876543210": {
      "project_path": "/home/user/projects/blog",
      "bound_at": "2026-03-22T11:30:00Z",
      "bound_by": "user456"
    }
  }
}

telegram-state.json

Tracks Telegram user sessions and project selections:

{
  "users": {
    "123456789": {
      "username": "john_doe",
      "current_project": "/home/user/projects/webtron",
      "last_active": "2026-03-22T12:00:00Z"
    }
  },
  "projects": {
    "/home/user/projects/webtron": {
      "active_sessions": 2,
      "last_command": "2026-03-22T12:05:00Z"
    }
  }
}

Multi-Project Support

The central server can manage unlimited projects simultaneously:

# Watch multiple directories for projects
orchid serve --bots \
  --watch-dir ~/LocalAI \
  --watch-dir ~/Documents/Development \
  --watch-dir ~/projects

# Explicitly register projects
orchid serve --bots \
  --project ~/projects/webtron \
  --project ~/projects/blog \
  --project ~/projects/api-service

Projects are auto-discovered by scanning for .orchid.yaml files in watched directories. Adding a new project (via orchid init) registers it within seconds without restarting the server.

Systemd Service for Central Bot

Install the central bot as a service:

# Copy the service template (includes --bots flag)
sudo cp scripts/orchid-serve.service /etc/systemd/system/

# Edit to configure environment variables
sudo nano /etc/systemd/system/orchid-serve.service

# Enable and start
sudo systemctl daemon-reload
sudo systemctl enable --now orchid-serve

# View logs
sudo journalctl -u orchid-serve -f

The service template includes documentation for TELEGRAM_BOT_TOKEN and SLACK_BOT_TOKEN environment variables.

Hook System

Orchid fires lifecycle events at key points in the agent loop. Hooks let you run shell commands, HTTP requests, or Python callables in response — without modifying core code.

Hook events

Section Events
Session session.start, session.end
Phase phase.transition
Task task.start, task.complete, task.blocked, task.skipped
Agent agent.thought, agent.tool_use, agent.tool_result

Configuring hooks in .orchid.yaml

hooks:
  tasks:
    - event: task.complete
      type: shell
      cmd: "notify-send 'Orchid' 'Task {{ task_id }} done'"
      blocking: false

    - event: task.blocked
      type: http
      url: "https://hooks.slack.com/services/..."
      method: POST
      body: '{"text": "Task {{ task_id }} blocked: {{ reason }}"}'

  session:
    - event: session.end
      type: python
      module: "my_project.hooks"
      function: "on_session_end"
      blocking: true

Shell hooks are sandboxed by the existing shell allowlist. HTTP hooks respect a configurable timeout. Hook errors are logged but never crash the agent loop.

Hook CLI

orchid hooks list --project ~/projects/webtron
orchid hooks validate --project ~/projects/webtron
orchid hooks test --project ~/projects/webtron HOOK_ID
orchid hooks stats --project ~/projects/webtron

Security

Circuit breaker

HTTP hooks use a per-event-type circuit breaker. After 5 consecutive failures the breaker opens and blocks further requests for a configurable window. This prevents a failing webhook from stalling the agent loop.

hooks:
  tasks:
    - event: task.complete
      type: http
      url: "https://hooks.slack.com/..."
      circuit_breaker:
        enabled: true
        failure_threshold: 5
        recovery_timeout_s: 60

Audit logging

Every shell and HTTP hook execution is written to .orchid/audit_log.jsonl:

{"timestamp": "...", "event": "task.complete", "hook_type": "shell",
 "cmd": "notify-send ...", "exit_code": 0, "duration_ms": 12}

Per-agent tool restrictions

Agents are granted only the tools appropriate for their role:

Agent Restrictions
TesterAgent Read tools + bash; no write_file, no git commit/push
ReviewerAgent Read tools only; no write_file, no bash, no git
ResearcherAgent Read tools + search; no write_file
DeveloperAgent Unrestricted (intentional)

Configure overrides in .orchid.yaml:

agents:
  allowed_tools:
    researcher: ["read_file", "list_dir", "bash", "search"]

Native Git Tools

Agents can read and write git state directly through built-in tool functions. Enable in .orchid.yaml:

agents:
  git_tools_enabled: true

Available tools injected into the agent ReAct loop:

Tool Description
git_status Working tree status
git_diff Staged and unstaged diffs
git_log Commit history with optional path filter
git_commit Stage and commit files
git_push Push to remote
git_pull Pull from remote
git_branch_list List branches
git_branch_create Create branch
git_branch_delete Delete branch
git_checkout Switch branch
git_stash Stash working changes
git_stash_pop Pop stash

Git tools are available to DeveloperAgent by default. TesterAgent and ReviewerAgent have restricted tool sets and cannot commit or push.

Parallel Task Execution

Dependency-aware scheduling

Orchid builds a dependency graph from needs: annotations and dispatches tasks in parallel groups:

T001 (no deps)  ─┐
T002 (no deps)  ─┤─ group 1 (parallel)
T003 (no deps)  ─┘

T004 needs:T001,T002  ─┐
T005 needs:T003       ─┘─ group 2 (parallel, runs after group 1)

T006 needs:T004,T005  ─── group 3 (sequential)

Tasks within a group run concurrently via ThreadPoolExecutor. Provider semaphores cap concurrent API calls:

# orchid.defaults.yaml — override in .orchid.yaml
runner:
  max_parallel: 4
  provider_concurrency:
    claude: 3
    openrouter: 3
    openai: 3

Worktree isolation

Sub-tasks delegated by agents can run in isolated git worktrees:

# .orchid.yaml
worktree:
  enabled: true          # false by default
  max_worktrees: 10
  auto_cleanup: true

Each delegated task gets its own checkout at .orchid/worktrees/<task_id>/. Worktrees are cleaned up after the task completes. Task IDs are path-sanitized to prevent directory traversal.

Dynamic Task Spawning

Agents can create new tasks at runtime using the spawn_task tool:

Action: spawn_task
Action Input: {"title": "Write integration test for auth module", "type": "code_generate", "priority": 2}

New tasks are injected into tasks.md with a unique ID, recorded in the session log, and picked up in the next dispatch cycle. The tool uses thread-local session references so parallel tasks can each spawn children independently.

DeveloperAgent's system prompt documents spawn_task with usage rules (what to spawn, when to defer instead).

Agent Pool

Pre-instantiated agents are cached and reused across tasks via an LRU pool. This eliminates repeated model initialization overhead on long runs.

# .orchid.yaml
agent_pool:
  enabled: true          # false by default
  max_size: 8
  idle_ttl_seconds: 300

Pool is keyed by (agent_type, model_key). LRU eviction removes least-recently-used agents when the pool is full. An idle-eviction thread removes agents that have been unused longer than idle_ttl_seconds. Falls back to direct instantiation if the pool is disabled or errors.

Cost Tracking and Budget Scheduling

Cost ledger

Token usage and estimated cost are recorded per task in .orchid/cost_ledger.jsonl:

# Daily spend summary is logged at session end
# Fields: task_id, model, input_tokens, output_tokens, cache_read_tokens,
#         cache_write_tokens, cost_usd, timestamp

Budget enforcement

# .orchid.yaml
cost:
  budget_usd: 10.00         # daily budget cap (0 = no limit)
  budget_warn_pct: 0.80     # warn at 80% of budget
  budget_stop: true         # halt if budget exceeded

  enforce_budget: false     # gate: enable cost-aware routing
  prefer_local_under_pressure: false  # prefer local model when rate-limited

When enforce_budget: true, tasks that would exceed the daily budget raise BudgetBlockedError and halt execution. When prefer_local_under_pressure: true, 429 rate-limit responses trigger automatic fallback to the local model for subsequent tasks.

429 detection

HTTP 429 responses from any provider are detected and recorded. The cost scheduler tracks rate pressure per provider and can automatically select a cheaper alternative.

Provider Fallback Chain

When a provider returns a retriable error (429, 502, 503, timeout), Orchid tries the next provider in an ordered fallback chain before marking the task BLOCKED.

Configure per task type in .orchid.yaml:

providers:
  task_types:
    code_generate:
      name: claude
      fallback: [openrouter, local]   # tried in order on 429/502/503

Global settings in orchid.defaults.yaml (override in project config):

providers:
  fallback_on_errors: [429, 503, 502]   # HTTP codes that trigger fallback
  max_fallback_attempts: 3              # max providers tried per task

Failed providers are marked rate-pressured so CostAwareScheduler skips them for subsequent tasks in the same run. Successful fallback is logged at WARNING level.

MCP Adapter Layer

Orchid can connect to any Model Context Protocol server and expose its tools to the agent ReAct loop automatically.

Transport types

Transport Use case
stdio Local process (subprocess.Popen over stdin/stdout)
http Remote or sidecar MCP server (httpx sync)

Configuration

# .orchid.yaml
mcp_servers:
  - name: filesystem
    transport: stdio
    cmd: ["npx", "@modelcontextprotocol/server-filesystem", "/tmp"]

  - name: remote-tools
    transport: http
    url: "http://localhost:9000/mcp"

MCP tools are discovered at session start and injected alongside built-in tools. The MCPManager connects all configured servers before task execution and tears down on session end.

MCP CLI

orchid mcp ls --project ~/projects/webtron        # list available tools
orchid mcp call --project ~/projects/webtron echo '{"msg": "hello"}'

Session Checkpointing

Orchid captures a checkpoint of session state before executing each task. Checkpoints let you rewind to any prior task boundary and re-run from there.

# List checkpoints for a project
orchid --project ~/projects/webtron --list-checkpoints

# Rewind session state to a checkpoint (does not persist — run --mode auto to continue)
orchid --project ~/projects/webtron --rewind CP-20260504-T012

# Rewind and approve the gate (resume execution from checkpoint)
orchid --project ~/projects/webtron --resume CP-20260504-T012

Orchid keeps the 5 most recent checkpoints per project and prunes older ones at session end. Checkpoints are stored in .orchid/checkpoints/.

Stream-JSON Output

Pass --output-format stream-json to emit a newline-delimited JSON (NDJSON) event stream instead of the default Rich terminal output. Each line is a typed event:

orchid --project ~/projects/webtron --mode auto --output-format stream-json | jq .
{"type": "session_start", "session_id": "...", "timestamp": "..."}
{"type": "task_start", "task_id": "T015", "title": "Build login form", ...}
{"type": "agent_thought", "task_id": "T015", "thought": "I need to...", ...}
{"type": "tool_use", "task_id": "T015", "tool": "write_file", "args": {...}}
{"type": "tool_result", "task_id": "T015", "result": "ok", ...}
{"type": "task_complete", "task_id": "T015", "duration_s": 42.1, ...}
{"type": "session_end", "tasks_done": 3, ...}

The web server also exposes an NDJSON streaming endpoint at GET /api/projects/{id}/stream.

Agentic OS Runtime

Orchid V2.2–V2.4 close a full set of OS-grade reliability, isolation, and preemption gaps, making it safe to run in production and viable for team and enterprise deployments.

Graceful Shutdown (SIGTERM)

A process-wide shutdown.py event propagates from SIGTERM → uvicorn lifespan → BackgroundRunner.graceful_shutdown() → every running agent's ReAct iteration. On shutdown signal, each agent saves a final ReAct checkpoint before exiting, allowing restart recovery to resume from the saved point.

# orchid.defaults.yaml
runner:
  shutdown_timeout: 30    # seconds to wait for tasks to finish before force-kill

The systemd service uses KillMode=mixed + TimeoutStopSec=35 so workers get the full 30 s grace period.

Restart Persistence & Orphan Recovery

Tasks left IN_PROGRESS by a crash are detected on next startup via a per-project .orchid/running marker file. For each orphaned task:

  • ReAct checkpoint ≤ 24 h old → resumed from the saved iteration (conversation history restored, loop continues at iteration + 1).
  • No checkpoint or too old → reset to TODO and re-run from scratch.

The orchestrator wires the checkpoint onto the agent via agent._resume_checkpoint before calling agent.run(). Manual recovery:

orchid --project PATH --recover

Process Isolation and Cancellation

Every task now runs in an isolated child process by default (isolation.subprocess_enabled: true). A pre-forked WorkerPool eliminates the ~0.8 s Python interpreter startup cost:

isolation:
  subprocess_enabled: true      # default: on
  subprocess_workers: 4         # pre-forked pool size (0 = one-shot per task)
  max_task_seconds: 0           # wall-clock timeout; 0 = no limit
  resource_limits:
    max_as_gb: 4                # child address space cap
    max_cpu_s: 600              # child CPU seconds cap
    max_files: 256              # child open file descriptor limit

Workers are replaced automatically on death. The OS-level RLIMIT_AS, RLIMIT_CPU, and RLIMIT_NOFILE limits are applied in every child via preexec_fn. On timeout, the child receives SIGTERM (5 s grace) before SIGKILL.

An in-process threading.Event cancellation path is also available for non-subprocess mode.

Stuck-Task Watchdog

TaskWatchdog runs as a background daemon thread. If a task remains IN_PROGRESS for longer than the configured threshold with no iteration progress, it fires a task.stuck hook event and marks the task BLOCKED.

watchdog:
  enabled: true
  stall_threshold_minutes: 30

Dependency Cycle Detection

DependencyGraph.has_cycle() is checked after every spawn_task() call at runtime. A dynamically-injected task that would create a cycle raises CycleError immediately, preventing silent scheduler deadlock.

File Advisory Locks

FileLockRegistry (orchid/locks.py) serializes parallel agents writing to the same file. write_file and append_file acquire a per-path threading.Lock before writing and release it on completion. Last-write-wins corruption in parallel task groups is eliminated.

Preemption: Pause / Resume / Priority Dispatch

Any running task can be paused. In-process agents pause at the next ReAct iteration boundary, save a checkpoint, and park on a threading.Event; resume() unparks without loss of conversation history. Subprocess pool workers receive SIGSTOP (freezes mid-instruction) and SIGCONT to unfreeze.

Web UI: Task Board shows a ⏸ button on the running task and a ▶ Resume button when paused.

API:

POST /api/projects/{id}/tasks/{task_id}/suspend
POST /api/projects/{id}/tasks/{task_id}/resume

Priority dispatch: _priority_score(task) = p1→30 / p2→20 / p3→10 + age bonus from task ID. The scheduler uses this score (descending) for both topological ordering and parallel group dispatch, so higher-priority tasks always head the queue.

runner:
  preemption_enabled: false         # opt-in (default off)
  preemption_min_runtime_s: 30      # don't preempt a task that started < 30s ago

WebSocket Backpressure

Every ws.send_json() is wrapped in asyncio.wait_for(timeout=5s). Slow or dead clients are evicted from the connection pool on timeout instead of stalling the broadcast loop. A 30 s heartbeat ping detects silent disconnects.

web:
  ws_send_timeout: 5.0    # seconds before a slow client is disconnected
  ws_heartbeat_s: 30.0    # ping interval

CPU & Latency Budgets

Per-iteration latency: if agents.max_iteration_seconds is set, the agent tracks consecutive slow iterations and cancels (with checkpoint) after 3 strikes.

agents:
  max_iteration_seconds: 120    # 0 = disabled

CPU accounting: subprocess mode measures RUSAGE_CHILDREN delta before/after each child. cpu_seconds is stored in TokenRecord, persisted to cost_ledger.jsonl, and shown in the PM Dashboard task timing table.

Per-user CPU quotas: set via admin API:

curl -X PUT /api/auth/users/alice \
  -d '{"cpu_budget_seconds": 3600}'   # 1 hour/day cap

CostScheduler.check_cpu_budget() raises BudgetBlockedError when the daily limit is exhausted.

Mid-Task ReAct Checkpointing

BaseAgent saves a ReActCheckpoint every 5 iterations to .orchid/checkpoints/mid-<task_id>.json. On cancellation, shutdown, or latency budget breach the agent saves a final checkpoint immediately before stopping. The orchestrator loads this checkpoint on the next run and resumes the conversation from the saved iteration.

Agent Mailbox (IPC)

Each agent instance has an AgentMailbox — a thread-safe message queue. Agents can send and receive structured messages via send_message / receive_message ReAct tools, enabling synchronous inter-agent coordination without spawning new tasks.

Max-Iterations Hard Cap

BaseAgent.run() enforces a per-agent-type max_iterations limit defined in orchid.defaults.yaml (and overridable in .orchid.yaml). Exceeding the cap raises MaxIterationsError, caught by the orchestrator and recorded as a blocked task. This bounds runaway agents regardless of token spend.

agents:
  max_iterations:
    developer: 50
    researcher: 30
    reviewer: 20

Capability Registry

CAPABILITY_REGISTRY (orchid/capability.py) declares the required tools, memory access, and network permissions for each agent type as AgentCapability dataclasses. The orchestrator validates agent capabilities at spawn time against the project's tool registry.

Remote Worker Protocol

Orchid can dispatch tasks to remote worker nodes via HTTP. The RemoteDispatcher selects an available node from a pool, sends a RemoteTaskRequest, streams back typed events, and merges the remote worker's cost ledger into the local ledger.

# .orchid.yaml
remote:
  workers:
    - url: "http://worker-1:8001"
      max_parallel: 2
    - url: "http://worker-2:8001"
      max_parallel: 2

Start a worker node:

# On the remote machine
orchid worker --port 8001

Worker nodes expose /health, /task (POST), and /ledger (GET) endpoints via FastAPI (orchid/remote/worker_server.py).

Auth & Multi-User Support (V2.3)

orchid serve ships a complete JWT auth stack. Set JWT_SECRET in ~/.config/orchid/.env to activate it.

Token flow: POST /api/auth/login → HttpOnly orchid_access cookie (JWT, 8 h) + orchid_refresh cookie (30 days). On page load the portal silently calls POST /api/auth/refresh if the access token has expired, so users are not prompted to log in again during a normal working day. The middleware accepts either cookie or Authorization: Bearer header, and recognises API keys by the ok_ prefix.

Roles: user (default), admin, readonly. Admins can update any user's role, project list, or active status via PUT /api/auth/users/{id}. Deactivated users lose all sessions; records are preserved for audit.

API keys (POST /api/auth/apikeys): scoped bearer tokens for CI/scripts. Secret shown once. Use require_scope("tasks:run") on any endpoint to enforce scope. JWT sessions (interactive logins) always bypass scope checks.

OAuth / SSO: Google, Microsoft Entra ID, or any OIDC provider. Configure in ~/.config/orchid/config.yaml:

auth:
  providers:
    - type: google
      client_id: "${GOOGLE_CLIENT_ID}"
      client_secret: "${GOOGLE_CLIENT_SECRET}"
      redirect_uri: "https://your-host/api/auth/oauth/google/callback"
    - type: entra
      tenant_id: "${AZURE_TENANT_ID}"
      client_id: "${AZURE_CLIENT_ID}"
      client_secret: "${AZURE_CLIENT_SECRET}"
      redirect_uri: "https://your-host/api/auth/oauth/entra/callback"

Mobile / PKCE: GET /api/auth/oauth/{p}/start?code_challenge=… stores the S256 challenge; POST /api/auth/oauth/{p}/token requires code_verifier and returns JSON tokens (no cookies).

Audit log: every login, logout, token refresh, API key create/revoke, OAuth login, task run, and admin action is appended to ~/.config/orchid/audit/audit-YYYY-MM-DD.jsonl. Files are rotated daily and never deleted. GET /api/audit (admin-only, paginated) exposes the log.

Per-user project scoping: set User.projects via PUT /api/auth/users/{id} to restrict which projects a user can run tasks against. Empty list = unrestricted. Admins bypass all restrictions.

Web UI login: the React frontend checks /api/auth/me on load and shows a sign-in form for unauthenticated users. A logout button in the header clears the session.

Pluggable auth storage: auth data defaults to ~/.config/orchid/users.json (zero deps). For multi-node or enterprise deployments, set ORCHID_AUTH_STORE_DSN to a PostgreSQL DSN and install the driver:

uv pip install 'orchid[postgres]'
# ~/.config/orchid/.env
ORCHID_AUTH_STORE_DSN=postgresql://user:pass@host:5432/orchid

Tables (orchid_users, orchid_refresh_tokens, orchid_api_keys, orchid_oauth_accounts) are created automatically on first start. See docs/auth-store-backends.md for migration, connection pool tuning, and rollback.

Scheduled Task Manager (D0061)

Orchid V2.5 adds a cron-based scheduler so any user can run recurring agent prompts, MCP tool calls, or shell commands on a schedule — without a separate cron daemon.

Task types

task_type What runs Required config keys
agent_prompt Sends a prompt to an LLM provider (Claude / local) prompt (required), system, provider, mcp_servers
mcp_tool Calls one tool on a configured MCP server server, tool, args
shell Runs a shell command via the existing allowlist command, timeout_sec (default 60)

REST API

All endpoints require authentication. Non-admin users see only their own tasks.

GET    /api/scheduler/tasks              list tasks
POST   /api/scheduler/tasks             create task (returns 201)
GET    /api/scheduler/tasks/{id}        get task
PUT    /api/scheduler/tasks/{id}        update task
DELETE /api/scheduler/tasks/{id}        delete task
POST   /api/scheduler/tasks/{id}/run    trigger immediate run (async, returns queued:true)
GET    /api/scheduler/tasks/{id}/runs   run history for one task
GET    /api/scheduler/runs              run history (admin: all users; user: own)

Creating a scheduled task

curl -X POST /api/scheduler/tasks \
  -H "Authorization: Bearer $TOKEN" \
  -d '{
    "name": "Daily standup summary",
    "schedule": "0 9 * * 1-5",
    "task_type": "agent_prompt",
    "config": {
      "prompt": "Summarise yesterday'\''s git log for the webtron project.",
      "provider": "claude"
    },
    "notify_on_failure": true,
    "notify_on_success": false
  }'

Schedule format

Standard five-field cron: minute hour day-of-month month day-of-week. All times UTC.

"0 9 * * *"        daily at 09:00 UTC
"*/15 * * * *"     every 15 minutes
"0 2 * * 0"        weekly, Sunday at 02:00 UTC

Run history

Each run is appended to ~/.config/orchid/cron/runs.jsonl. Runs older than 30 days are pruned automatically on startup. The API returns runs newest-first.

{"run_id": "run_a1b2c3d4", "task_id": "stask_00ff1234", "owner_id": "u1",
 "task_name": "Daily standup summary", "task_type": "agent_prompt",
 "started_at": "2026-05-25T09:00:01Z", "finished_at": "2026-05-25T09:00:08Z",
 "status": "success", "output": "Yesterday: 3 commits…", "error": ""}

Audit events

Successful runs write scheduled_task_run to the audit log; failures write scheduled_task_failed (with truncated error detail).

Portal UI helpers

The User Portal task form (/app/) includes several helpers to reduce manual JSON editing:

Schedule builder — click "🗓 Schedule builder" to open a visual picker with frequency (every N minutes / hourly / daily / weekly / monthly), time-of-day dropdowns in your local timezone (auto-detected), day-of-week toggles, and a live preview showing both the local time and the UTC cron expression that will be stored.

Enable / disable toggle — each task row in the dashboard has a checkbox that flips enabled on/off with a single click. Disabled tasks remain visible in the list; the cron engine skips them at their scheduled time.

Export / Import — the modal header has "↓ Export" (downloads taskname.orchid-task.json) and "↑ Import" (reads a JSON file and populates all form fields). Use this to share task definitions between users or back up configs.

Save & Test / Test — a button always visible in the modal footer:

  • New / duplicate task — labelled "▶ Save & Test": saves the task first (creating it in the scheduler), then triggers an immediate run. The modal stays open during the run. On success a toast fires and the modal auto-closes after 3 s. On failure the modal stays open with an inline error so you can edit and retry.
  • Existing task — labelled "▶ Test": triggers an immediate run without saving (use "Save changes" first if you made edits).
  • A failed Save & Test stores the new task ID so clicking Test again reruns that task rather than creating a duplicate.

MCP tool browser — for mcp_tool and agent_tool task types a "🔌 Browse MCP" button appears next to the type selector:

  • mcp_tool mode: two-panel server → tool list; selecting a tool shows its description and argument schema (name, type, required badge, description); "Use tool_name" fills the config JSON with {server, tool, args} pre-populated from the schema.
  • agent_tool mode: checkbox list of available servers (pre-ticked if already in config); Apply merges the servers array while preserving prompt and system fields.

The modal is 900 px wide with a 12-row resizable textarea, giving enough room to edit complex JSON configs directly.

Task Wizard — available on new tasks via the "✨ Wizard" pill in the modal header. A chat overlay interviews the user in plain language, then calls POST /api/scheduler/wizard which runs two LLM passes via the system-configured provider: a conversational turn that gathers requirements and signals TASK_READY, followed by a structured extraction pass that outputs the complete task JSON (name, description, schedule, task_type, config with real values, MCP server list when needed). Clicking "Apply to form →" on the returned config card populates all form fields without overwriting them with blank templates.

Container Isolation

When Docker is available, tasks can run inside a minimal container image for the strongest isolation boundary.

isolation:
  container: true          # requires Docker on host
  container_image: "python:3.12-slim"

ContainerRunner (orchid/container_runner.py) falls back gracefully to subprocess isolation when Docker is unavailable.

Checkpoint Export

Checkpoints can be exported to a portable JSON file for transfer between machines or archival:

orchid --project PATH --export-checkpoint CP-20260508-T042 > checkpoint.json
orchid --project PATH --import-checkpoint checkpoint.json

Architecture

shutdown.py          process-wide threading.Event for graceful SIGTERM propagation
agent_registry.py    global task_id → agent map for suspend/resume without coupling
orchestrator.py      main loop: pick task → plan (Claude) → dispatch agent
session.py           state lifecycle: load, save, compress hot memory; RLock for parallel safety
config.py            three-layer merge: defaults → .orchid.yaml → CLI flags
lifecycle.py         V2 state machine: NEW → DISCUSSING → … → COMPLETE
gates.py             human|auto gate system for lifecycle transitions
machine_profile.py   developer preferences at ~/.config/orchid/machine-profile.yaml
discovery.py         auto-discovery: scan watch_dirs, watchdog inotify watcher
agent_manager.py     per-project agent loop threads, APScheduler cron support
scheduler.py         DependencyGraph, parallel group detection, topological sort; has_cycle(); _priority_score() with age bonus
agent_pool.py        LRU cache of pre-instantiated agents; idle eviction thread
worktree.py          WorktreeManager: isolated git worktrees per delegated task
subprocess_runner.py SubprocessRunner: WorkerPool (pre-forked N workers) + one-shot fallback; RLIMIT_* via preexec_fn; RUSAGE_CHILDREN CPU accounting; SIGSTOP/SIGCONT suspend/resume per task
worker_protocol.py   TaskContext, WorkerEvent, WorkerResult dataclasses for subprocess protocol
watchdog.py          TaskWatchdog: background thread; fires task.stuck hook on stall
locks.py             FileLockRegistry: per-path threading.Lock for parallel write safety
mailbox.py           AgentMailbox: thread-safe per-agent message queue
capability.py        CAPABILITY_REGISTRY: AgentCapability declarations, get_capability()
container_runner.py  ContainerRunner: Docker-based isolation (opt-in, graceful fallback)

agents/
  base.py            ReAct loop (Reason → Act → Observe), tool dispatch, allowed_tools filter
  discussion_agent.py  elicits requirements via conversation (Claude, cached)
  product_manager.py   generates REQUIREMENTS.md + ARCHITECTURE.md
  project_manager.py   generates MILESTONES.md + tasks.md
  developer.py       code generation/editing; unrestricted tools; git + spawn_task documented
  tester.py          QA verification: runs tests, structured output, no code writes
  researcher.py      search and summarize (local model)
  reviewer.py        critique and quality gate (Claude API)
  delegator.py       delegate sub-tasks to agents; worktree isolation; pool-based acquisition

memory/
  state.py           tasks.md + CLAUDE.md read/write
  decisions.py       append-only JSON Lines decision log
  vector.py          ChromaDB embedded vector store

tools/
  models.py          unified call() for Claude API + llama.cpp
  filesystem.py      read_file, write_file, list_dir, append_file
  shell.py           bash execution with blocklist + optional allowlist + timeout
  git.py             12 git tool functions: status, diff, log, commit, push, pull, branch ops
  task_injection.py  spawn_task() agent tool; threading.local session ref for parallel safety

interfaces/
  cli.py             Typer CLI entry point
  web_server.py      FastAPI REST + WebSocket backend (V2 planning endpoints)
  web_ui/            React + Vite frontend source
  telegram_bot.py    Telegram bot (python-telegram-bot)
  slack_bot.py       Slack bot (slack-bolt, Socket Mode)
  background_runner.py  non-blocking executor shared by Telegram and Slack
  central_bot.py     V2.1 central bot server (Telegram + Slack routing)

providers/
  registry.py        8-layer provider resolution chain; resolve_chain() returns (primary, [fallbacks])
  base.py            ProviderBase ABC: availability cache, optimize_for_caching(); RetriableProviderError
  anthropic.py       Claude API — explicit prompt caching, session stats, retry
  local.py           llama.cpp OpenAI-compat endpoint — implicit KV cache detection
  ollama.py          Ollama — implicit KV cache, stable-prefix ordering
  openai.py          OpenAI / OpenRouter
  bedrock.py         AWS Bedrock (boto3, lazy import)

hooks/
  events.py          hook event constants (session, phase, task, agent)
  types.py           ShellHook, HTTPHook, PythonHook dataclasses
  registry.py        hook registry: fire events, blocking/non-blocking execution
  loader.py          load hooks from .orchid.yaml; circuit breaker + audit wired here
  schema.py          Pydantic validation for hook config
  circuit_breaker.py CircuitBreakerRegistry: per-event-type breakers, open/half-open/closed states
  audit.py           AuditLogger: thread-safe JSONL writer → .orchid/audit_log.jsonl

mcp/
  types.py           MCPTool, MCPResult dataclasses
  client.py          MCPClient ABC + MCPClientError
  stdio_client.py    subprocess.Popen transport (JSON-RPC 2.0 over stdin/stdout)
  http_client.py     httpx.Client transport
  adapter.py         MCPAdapter: wraps client, caches tool list
  manager.py         MCPManager: multi-server lifecycle, tool injection

remote/
  types.py           WorkerNode, RemoteTaskRequest, RemoteTaskResponse dataclasses
  worker_server.py   FastAPI worker node server (/health, /task, /ledger)
  dispatcher.py      RemoteDispatcher: node selection, retry, ledger merge

auth/
  types.py           User, RefreshToken, ApiKey, OAuthAccount, AuditEvent dataclasses
  base.py            BaseUserStore ABC — 27 abstract methods, implemented by both backends
  store.py           FileUserStore (JSON, default) + get_store() singleton factory (auto-selects backend)
  store_postgres.py  PostgresUserStore — ThreadedConnectionPool, auto-schema, UPSERT-safe
  jwt.py             hash_password, verify_password, issue/verify access tokens, issue/verify refresh tokens, issue/verify API keys
  middleware.py      get_current_user, get_optional_user, require_auth(role=), require_scope(scope=)
  audit.py           AuditStore: append-only JSONL daily rotation; AuditAction constants; make_event()
  providers/
    base.py          OIDCProvider ABC; link_or_create_user() with email-match linking
    oidc_generic.py  GenericOIDCProvider: discovery doc fetch, code exchange, userinfo; PKCE S256
    google.py        GoogleOIDCProvider (pre-set discovery URL)
    entra.py         EntraOIDCProvider (tenant-aware discovery URL)
    registry.py      ProviderRegistry: register() + from_config(yaml)

cron/
  types.py           ScheduledTask, TaskRun dataclasses; _new_task_id/run_id/utcnow helpers
  store.py           TaskRunStore: append-only JSONL run history; 30-day pruning; thread-safe
  executor.py        TaskExecutor: dispatches agent_prompt/mcp_tool/shell; always returns TaskRun, never raises
  engine.py          CronEngine: APScheduler BackgroundScheduler wrapper; get_engine() singleton; add/remove/run_now
  api.py             register_routes(): installs all /api/scheduler/* endpoints on FastAPI app

output/
  events.py          typed event dataclasses (SessionStart, TaskStart, AgentThought, …)
  emitter.py         EmitterProtocol + NullEmitter
  ndjson_emitter.py  NDJSONEmitter (stream) + NDJSONBufferEmitter (in-memory)
  ws_emitter.py      WebSocket emitter for web server streaming

checkpoint/
  schema.py          CheckpointMetadata, Checkpoint, CheckpointEntry, ReActCheckpoint dataclasses
  store.py           CheckpointStore: save, load, list, delete, prune; save/load_react_checkpoint
  restore.py         rewind_session(), list_checkpoints(), export_checkpoint(), resume_orphaned_tasks()

cost/
  ledger.py          CostLedger: JSONL-backed token/cost recorder; node_id field; merge_from_file() for remote ledger merge
  scheduler.py       CostScheduler: budget cap enforcement, per-user quotas, 429 rate-limit backoff, provider selection

Model routing

Task type Default model
orchestrate review plan critique synthesize rollup Claude API
draft code_generate summarize search transform verify Local llama.cpp

Override per-project with model_preference: claude in .orchid.yaml, or per-task with the model: tag.

Prompt caching

Orchid automatically minimises API token costs via provider-specific caching strategies:

Anthropic (explicit caching): System prompts ≥ 2048 chars are automatically wrapped with cache_control: {type: ephemeral}. Pass cacheable_prefix=N to cache the first N messages. The DiscussionAgent separates static instructions (always cached) from conversation history. Cache write/read counts are logged at session close.

llama.cpp / Ollama (implicit KV caching): cache_prompt: true is sent with every request so the server retains KV attention state for repeated prompt prefixes. optimize_for_caching() always places stable content before dynamic content to maximise prefix reuse. Cache hits are detected heuristically from response timings (< 1.0 ms/token = cache hit).

Config in .orchid.yaml:

caching:
  enabled: true
  anthropic:
    cache_control: true
    min_cacheable_chars: 2048
  local:
    cache_prompt_hint: true

Development

pytest -m "not network" --ignore=tests/test_integration.py --ignore=tests/test_metrics.py   # 1550+ tests, no API calls required
ruff check orchid/        # 0 errors

Test structure

File What it covers
tests/conftest.py Autouse fixtures reset store singleton, ledger singleton, shutdown event, and agent registry between every test
tests/test_auth.py 134 auth endpoint tests — JWT, refresh tokens, API keys, OAuth/OIDC, PKCE, audit log, user management
tests/test_shutdown.py Global shutdown event; agent raises + saves checkpoint on shutdown signal
tests/test_agent_registry.py Registry register/deregister/get; concurrent access safety
tests/test_graceful_shutdown.py BackgroundRunner.graceful_shutdown() — signals cancel events, waits for futures, timeout returns False; marker file write/remove
tests/test_orphan_recovery.py resume_orphaned_tasks() — fresh checkpoint keeps IN_PROGRESS, stale/missing resets to TODO
tests/test_worker_pool.py WorkerPool submit/shutdown; _apply_resource_limits()
tests/test_suspend_resume.py _priority_score() ordering; scheduler dispatch order; agent suspend/resume threading; runner suspend_task/resume_task; subprocess pool SIGSTOP/SIGCONT
tests/test_cpu_accounting.py WorkerResult.cpu_seconds; ledger daily_cpu_for_user(); check_cpu_budget() raises; 3-strike latency cancel
tests/test_cron_types.py ScheduledTask and TaskRun dataclass defaults, ID format, uniqueness, UTC timestamps
tests/test_cron_store.py TaskRunStore append/get/prune/filter; UserStore scheduled-task CRUD and persistence
tests/test_cron_executor.py TaskExecutor dispatch for all three task types; mocked providers and MCP; never-raises guarantee
tests/test_cron_engine.py CronEngine singleton, start/stop, job registration, invalid cron skip, add/update/remove/run_now
tests/test_cron_api.py /api/scheduler/* endpoint integration tests: CRUD, run-now, access scoping, admin override

Pytest marks

Mark Usage
network Tests requiring real network (excluded by default: -m "not network")
slow Tests taking >5 s (excluded in fast runs: -m "not slow")

CI runs automatically on push/PR via GitHub Actions (.github/workflows/ci.yml).

Languages and Formats

Languages

  • Python — entire backend: orchestrator, agents, providers, CLI, web server, tests
  • JavaScript / JSX — React frontend components and hooks
  • Bash — install scripts and service helpers
  • CSS — web UI dark theme (index.css)
  • HTML — Vite's index.html entry point

Configuration and data formats

  • YAML — orchid.defaults.yaml, .orchid.yaml project configs, Traefik routing, machine-profile.yaml
  • TOML — pyproject.toml (package metadata, build config, tool settings)
  • JSON — package.json, lock files, project.state.json, task_results.json, slack-channels.json, telegram-state.json
  • JSONL — append-only session logs, decisions, discussion conversation history

Other

  • Markdown — README.md, CLAUDE.md, tasks.md, REQUIREMENTS.md, ARCHITECTURE.md, MILESTONES.md, templates
  • systemd unit — orchid-serve.service and service templates

Documentation

See docs/getting-started.md for a full walkthrough with examples.

License

Orchid is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0).

You are free to use, modify, and distribute Orchid under the terms of the AGPL-3.0. If you distribute Orchid or run it as a network service, you must make your modifications available under the same license.

Copyright (c) 2026 David Scheiderman

About

an AI agent orchestration framework designed to run persistently on a bare-metal AI server

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Contributors