Thank you for your interest in contributing to FormicOS. This guide covers everything you need to get started, from environment setup through submitting a pull request.
Before contributing, please read:
- CODE_OF_CONDUCT.md — community expectations
- GOVERNANCE.md — decision-making process and CLA requirements
- SECURITY.md — how to report vulnerabilities (do not open public issues for security bugs)
Before contributing, also review CLA.md for the contributor license terms, commercial relicensing grant, and revenue-share program.
- Check existing issues or open a new one describing the change
- Fork the repository and create a feature branch
- Sign the CLA when prompted (see CLA.md)
- Make your changes following the architecture rules below
- Run the full CI pipeline locally (lint, typecheck, layer-check, tests, frontend build)
- Submit a pull request using the PR template
- Address review feedback from maintainers
Look for issues labeled good-first-issue for tasks suitable for new
contributors. These are scoped to avoid touching architectural seams.
FormicOS requires a signed CLA for external code contributions. The CLA is what allows Intradyne to keep the AGPLv3 distribution and also offer commercial licenses for the same codebase. Contributors who sign the CLA also become eligible for the revenue-share program described in the CLA.
If your employer owns the IP in your contributions, your employer can sign the Corporate CLA to blanket-authorize all employees. This avoids each contributor needing individual employer authorization.
DCO sign-off may be used as a supplementary provenance signal, but it does not replace the CLA because it does not grant commercial relicensing rights.
# Python 3.12+ required
uv sync --devThis installs all runtime and development dependencies including pytest, pyright, and ruff.
# Node 22+ required
cd frontend
npm ci# Terminal 1: backend
python -m formicos
# Terminal 2: frontend dev server (optional -- for HMR during UI work)
cd frontend && npm run devThe backend serves the built frontend from frontend/dist/ at port 8080. The
dev server proxies to the backend for WebSocket connections.
You'll need a configured LLM path: local llama.cpp / OpenAI-compatible
inference, or cloud credentials such as ANTHROPIC_API_KEY / GEMINI_API_KEY
in .env.
See docs/DEPLOYMENT.md for the full Docker Compose deployment guide, including GPU setup, persistence rules, and security posture.
# Full CI pipeline (run all of these before submitting)
uv run ruff check src/
uv run pyright src/
python scripts/lint_imports.py
python -m pytest -q
# Or as one command
uv run ruff check src/ && uv run pyright src/ && python scripts/lint_imports.py && python -m pytest -q
# Frontend build check
cd frontend && npm run buildThe browser smoke tests use Playwright and verify core operator-facing behavior.
# Prerequisites: the backend must be running (python -m formicos)
# Install Playwright browsers (first time only)
cd frontend && npx playwright install chromium
# Run smoke tests
cd frontend && npm run smokeThe smoke spec lives at tests/browser/smoke.spec.ts (repo root). The
npm run smoke script in frontend/package.json resolves the relative
path automatically.
The test suite includes:
- Unit tests for core types, events, adapters, engine, and surface modules
- Feature tests (pytest-bdd) for the executable specifications under
docs/specs/ - Contract parity tests verifying Python and TypeScript type alignment
- Layer boundary test (AST-based import analysis)
The most successful contributions to FormicOS are:
- focused on one concern at a time
- grounded in the current code, tests, and documented contracts
- accompanied by the tests and docs needed to explain the change
- explicit about any architectural or protocol implications
When possible:
- keep refactors separate from feature changes
- avoid mixing unrelated fixes into the same PR
- update affected docs when user-visible behavior changes
- call out any follow-up work that remains
Read these before making changes:
-
4-layer dependency rule. Core imports nothing. Engine imports only Core. Adapters import only Core. Surface imports all.
scripts/lint_imports.pyenforces this via AST analysis. Backward imports fail the build. -
Event sourcing. Every state change is an event. No shadow databases, no second stores, no CRUD updates. State is derived by replaying events into projections. See ADR-001.
-
Pydantic v2 only. All serialized types use
pydantic.BaseModel. No msgspec, no dataclasses for events. See ADR-002. -
Closed event union. The event types in
core/events.pyform a closed union. Adding a new event type requires maintainer approval because it extends every consumer and adapter. See the process below. -
20K LOC soft ceiling on
core/+engine/+adapters/+surface/combined. Exceeding it requires justification; it is guidance, not a hard CI gate. -
No print() statements. Use
structlogfor all logging. -
Frozen contracts. Files in
docs/contracts/are the integration seams. Do not modify them without maintainer approval. Code against them.
This is intentionally difficult because it affects every layer:
- Get maintainer approval. Event types are a closed union and affect every consumer, projection, and contract mirror.
- Add the event class to
src/formicos/core/events.pyextendingEventEnvelope. - Add it to the
FormicOSEventunion type and__all__. - Update
docs/contracts/events.py(the frozen contract mirror). - Add TypeScript mirror to
docs/contracts/types.tsandfrontend/src/types.ts. - Add a handler to
surface/projections.py_HANDLERSdict. - Add serialization to
surface/view_state.pyif it affects the browser or API snapshot shape. - Write a
.featurescenario indocs/specs/that exercises the event. - Run the full CI pipeline. The contract parity test will catch mismatches.
- Add the tool function in
src/formicos/surface/mcp_server.pyusing the@mcp.tool()decorator. - If the tool mutates state, route it through
handle_command()insurface/commands.pyso it emits proper events. - Add the corresponding WebSocket command type to
docs/contracts/types.tsif it should be callable from the browser UI. - Update the protocol status tool count in
surface/view_state.py. - Write a feature scenario exercising the tool.
- Create
frontend/src/components/your-component.ts. - Use Lit Web Components (
LitElement,html,css). - Import shared styles from
../styles/shared.js. - Keep components under 200 LOC (soft limit; the app shell is an exception).
- Import the component in the parent that uses it.
- Types go in
frontend/src/types.ts. - Run
cd frontend && npm run buildto verify.
- Python: ruff (rules: E, F, W, I, UP, B, SIM, TCH), line length 100
- Python typing: pyright strict mode,
from __future__ import annotations - Logging: structlog with descriptive event names (
module.action) - Frontend: TypeScript, Lit decorators (
@customElement,@state,@property) - Serialization: Pydantic v2
BaseModelwithConfigDict(frozen=True)for events
Before making an architectural choice, check docs/decisions/ for existing ADRs:
| ADR | Decision |
|---|---|
| 001 | Event sourcing as sole persistence |
| 002 | Pydantic v2 as sole serialization |
| 003 | Lit Web Components for frontend |
| 004 | typing.Protocol for port interfaces |
| 005 | MCP as sole programmatic API |
| 006 | Trunk-based development with feature flags |
See docs/decisions/INDEX.md for the full list (51 ADRs covering event sourcing, knowledge metabolism, federation, parallel planning, and more).
If your change contradicts an ADR, stop and flag the conflict.
These are the most important files and directories to understand:
| Path | Purpose |
|---|---|
src/formicos/core/events.py |
Closed event union — source of truth for all state changes |
src/formicos/core/types.py |
Shared domain types (ColonyContext, etc.) |
src/formicos/core/ports.py |
Port interfaces (typing.Protocol) |
src/formicos/engine/runner.py |
Colony round execution loop |
src/formicos/surface/projections.py |
Event replay into in-memory read models |
src/formicos/surface/queen_runtime.py |
Queen orchestration and tool dispatch |
src/formicos/surface/knowledge_catalog.py |
Federated knowledge retrieval with 7-signal scoring |
docs/contracts/ |
Frozen integration seams — do not modify without maintainer approval |
docs/decisions/ |
Architecture Decision Records (ADR files present in the repo) |
docs/specs/ |
Executable specifications (pytest-bdd scenarios) |
frontend/src/types.ts |
TypeScript type mirrors of Python contracts |
- Keep PRs focused: one concern per PR when possible
- Include a clear description of what changed and why
- Reference related issues
- Ensure all CI checks pass before requesting review
- If your change touches overlap files shared between teams, note it in the PR description
Some project infrastructure requires repository administrator action and cannot be configured through code alone. See docs/GITHUB_ADMIN_SETUP.md for the complete checklist including branch protection, CLA enforcement, labels, and security scanning configuration.