* perf(workflow): scope resize CSS-var writes to the container, not :root Resizing the editor panel, terminal, output panel, and sidebar was still laggy on large workflows even after the drag handles stopped re-rendering React per frame. A CDP trace showed the cost was style recalculation, not JS or layout: ~3.9s of Document::recalcStyle over a 50-move panel drag. Root cause: each drag frame wrote its CSS variable to document.documentElement. On a large document (~42k elements) any inline custom-property write on the <html> root recalculates style for the whole tree — measured at ~77ms per write, independent of what actually reads the variable (an unused var costs the same). Writing the same variable to the element that consumes it recalcs only that subtree (~0.5ms) — a ~150x reduction. useDragResize now writes the variable to a caller-provided target element during the drag and reconciles to :root once on release (so on-demand readers and the pre-hydration script are unchanged): panel -> .panel-container, terminal -> .terminal-container, output panel -> .terminal-container (both consumers inherit it), sidebar -> .sidebar-shell-outer. The terminal's expanded-threshold sync writes store state directly instead of setTerminalHeight so it no longer touches :root mid-drag. Measured (near-empty canvas, 50-move drag): panel style+layout 3891ms -> 56ms, frame p95 91.6ms -> 8.4ms, main-thread blocking 4566ms -> 0ms; terminal recalc 3899ms -> 16ms, blocking 5558ms -> 0ms; sidebar recalc ~77ms/frame -> ~1ms/frame. * perf(workflow): keep float boundary-sync live during scoped resize drags The resize hooks now write their CSS variable to the consuming container element during a drag (not :root), so use-float-boundary-sync's MutationObserver on :root no longer fired mid-drag and open floats (chat, search-replace, variables) only re-clamped on release. Read each boundary dimension from its scoped element with a :root fallback, and observe the container elements as well as :root, so an open float tracks the drag live exactly as before. * fix(workflow): finalize drag on unmount + clamp reads scoped output width - useDragResize: unmounting mid-drag now runs endDrag (commit + drop the scoped override) instead of a bare cleanup, so navigating away can neither lose the resize nor strand an inline override on a surviving target element. - terminal output-panel clamp: read the live --output-panel-width from the terminal element first (where a drag writes its scoped override), then :root, then the store, so a mid-drag terminal/window resize can't clamp against a stale value. * fix(workflow): robust drag finalize — last-applied value, sidebar unmount, clamp skip Addresses three review findings on the scoped-resize change: - useDragResize: track the last applied value and commit that on release; only recompute from the pointer event while the target is still connected. On an unmount the target's rect can be detached, so a layout-reading compute (e.g. the output panel's) would return a degenerate MIN — committing the last shown value avoids persisting the wrong width, and keeps flick-safety on a normal release. - use-sidebar-resize: finalize on unmount (run endDrag, not bare cleanup) so a drag interrupted by unmount persists the width and drops the scoped override. This matters more than for the panel/terminal because .sidebar-shell-outer lives in the workspace chrome and outlives the sidebar, so a stranded override would win over :root. - terminal output-panel clamp: skip while an output-panel drag is active (its scoped inline override is present). That drag's own compute clamps every frame against the live terminal rect, and a store-driven :root write here would be masked by the inline override anyway. * fix(workflow): sidebar flick-safety + clamp reads committed :root width - use-sidebar-resize: compute the clamped width synchronously on each pointer move (storing lastWidth) and defer only the DOM write to rAF, so a fast flick released before the frame runs still commits the final pointer position instead of a stale frame or nothing. - terminal output-panel clamp: when not mid-drag, read the committed --output-panel-width from :root (written synchronously by the store setter) rather than the React store value, which lags a render behind the commit; fall back to the store before any commit. Comment corrected to match.
A workspace to build, deploy and manage AI agents and workflows.
Quickstart
Cloud-hosted: sim.ai
Self-hosted
npx simstudio
Docker must be installed and running. Use -p, --port <port> to run Sim on a different port, or --no-pull to skip pulling the latest Docker images.
Capabilities
- Connect 1,000+ integrations and every major LLM
- Add Slack, Notion, HubSpot, Salesforce, databases, and more
- Build agents visually, conversationally, or with code
- Ingest files, knowledge bases, and structured table data
- Monitor runs, logs, schedules, and workflow activity
One workspace, every surface
Chat and workflows are just the start — tables, files, knowledge, and scheduled tasks all live in the same workspace.
Tables — a database, built in |
Files — one store for your team and every agent |
Knowledge — your agents' memory |
Scheduled tasks — runs on your schedule |
Self-hosting
Docker Compose
git clone https://github.com/simstudioai/sim.git && cd sim
docker compose -f docker-compose.prod.yml up -d
Sim also supports local models via Ollama and vLLM. See the Docker self-hosting docs for setup details.
Manual Setup
Requirements: Bun, Node.js v20+, PostgreSQL 12+ with pgvector
- Clone and install:
git clone https://github.com/simstudioai/sim.git
cd sim
bun install
bun run prepare # Set up pre-commit hooks
- Set up PostgreSQL with pgvector:
docker run --name simstudio-db -e POSTGRES_PASSWORD=your_password -e POSTGRES_DB=simstudio -p 5432:5432 -d pgvector/pgvector:pg17
Or install manually via the pgvector guide.
- Configure environment:
cp apps/sim/.env.example apps/sim/.env
# Create your secrets
perl -i -pe "s/your_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_internal_api_secret/$(openssl rand -hex 32)/" apps/sim/.env
perl -i -pe "s/your_api_encryption_key/$(openssl rand -hex 32)/" apps/sim/.env
# DB configs for migration
cp packages/db/.env.example packages/db/.env
# Edit both .env files to set DATABASE_URL="postgresql://postgres:your_password@localhost:5432/simstudio"
- Run migrations:
cd packages/db && bun run db:migrate
- Start development servers:
bun run dev:full # Starts Next.js app and realtime socket server
Or run separately: bun run dev (Next.js) and cd apps/sim && bun run dev:sockets (realtime).
Chat API Keys
Chat is a Sim-managed service. To use Chat on a self-hosted instance:
- Go to https://sim.ai → Settings → Chat keys and generate a Chat API key
- Set
COPILOT_API_KEYenvironment variable in your self-hosted apps/sim/.env file to that value
Environment Variables
See the environment variables reference for the full list, or apps/sim/.env.example for defaults.
Tech Stack
Next.js · Bun · PostgreSQL · Drizzle · Better Auth · Tailwind — and the rest of the stack
- Framework: Next.js (App Router)
- Runtime: Bun
- Database: PostgreSQL with Drizzle ORM
- Authentication: Better Auth
- Schema Validation: Zod
- UI: Shadcn, Tailwind CSS
- Streaming Markdown: Streamdown
- State Management: Zustand, TanStack Query
- Flow Editor: ReactFlow
- Docs: Fumadocs
- Monorepo: Turborepo
- Realtime: Socket.io
- Background Jobs: Trigger.dev
- Remote Code Execution: E2B
- Isolated Code Execution: isolated-vm
Contributing
We welcome contributions! Please see our Contributing Guide for details.
License
This project is licensed under the Apache License 2.0 - see the LICENSE file for details.





