Contributing to taOS
August 2, 2026 ยท View on GitHub
Welcome - and thanks for your interest in contributing. taOS is a self-hosted AI agent platform for low-power hardware. Before diving in, please read the README for a project overview.
Note: The project is in early development. APIs and interfaces may change. That is fine - contributions of all sizes are welcome.
License & Contributor License Agreement
taOS is open source under the GNU Affero General Public License v3.0 or later (AGPL-3.0-or-later; see LICENSE). A separate commercial license is available from jaylfc for uses the AGPL does not grant, for example embedding taOS in a proprietary product or offering it as a hosted service without releasing your modifications under the AGPL (see COMMERCIAL-LICENSE.md).
To keep this sustainable, all contributors must agree to the Contributor License Agreement (CLA.md) before their contributions are merged. The CLA grants jaylfc the right to include and relicense your contributions under the project's licenses; you keep ownership of your work. You sign once - on your first pull request, comment "I have read the CLA Document and I hereby sign the CLA" and the CLA check turns green; it then covers all your future contributions.
Getting Started for Contributors
git clone https://github.com/jaylfc/taOS.git
cd tinyagentos
uv sync
# Build the desktop SPA - static/desktop/ is gitignored (generated artifact)
cd desktop && npm install && npm run build && cd ..
uv run pytest tests/ --ignore=tests/e2e -n auto
Python 3.10 or later and Node.js 22 or later are required.
For frontend development, use cd desktop && npm run dev - Vite serves with hot reload on port 5173.
Contributing with an AI coding agent
If you work on taOS through a coding agent (Claude Code, opencode, Cursor, or similar), point it at .claude/skills/taos-development-skill/SKILL.md. It ships in the repo, so a clone already has it.
It is the operational companion to this file: repository layout, the architectural patterns worth knowing before touching a subsystem, the local test gate to run before marking work ready, the fork PR and CI specifics, and the pitfalls that have actually cost contributors time here. This file states the conventions; the skill states how to apply them without tripping over the setup.
Agents should read both. The skill deliberately does not restate the conventions in this file, so that the two cannot drift out of sync.
If you are an AI agent reading this: load that skill before making changes, and pay particular attention to "Verifying your work" and "Avoiding collisions with other contributors" below. Most problems here have come from trusting a signal that looked like success rather than from writing bad code.
How to Contribute
Bug Reports
Open a GitHub issue with:
- A clear title describing the problem
- Steps to reproduce
- Expected vs actual behaviour
- Platform and Python version
Feature Requests
Open a GitHub issue describing:
- The use case you are trying to solve
- Why it belongs in the core project rather than a plugin or external tool
Adding Apps to the Catalog
The app catalog is one of the easiest ways to contribute. See Adding an App to the Catalog below.
Code Contributions
- Fork the repository
- Create a branch:
git checkout -b feat/my-feature - Make your changes and add tests
- Run
uv run pytest tests/ --ignore=tests/e2e -n auto- all tests must pass - Open a pull request against
dev, notmaster
Keep pull requests focused. One feature or fix per PR is easier to review.
Branches:
masteris the stable branch that installs track, so it only receives tested changes promoted fromdev. All contributions targetdev. If you open a PR againstmasterby mistake, no problem - we'll retarget it todev(the commits and review carry over).
Documentation
Documentation improvements are always welcome - typo fixes, clarifications, better examples. Open a PR directly.
The taOS agent manual is compiled: edit docs/agent-manual/ and run python3 scripts/build-agent-manual.py to regenerate docs/taos-agent-manual.md.
Adding an App to the Catalog
The catalog lives in app-catalog/. Each app has its own directory containing a manifest.yaml.
Step 1 - Create the directory
app-catalog/
agents/ # agent frameworks
models/ # LLM models
plugins/ # tools and plugins
services/ # background services
Pick the appropriate category and create a directory named after your app's id:
mkdir app-catalog/agents/my-framework
Step 2 - Write manifest.yaml
Use app-catalog/agents/langroid/manifest.yaml as a template:
id: my-framework
name: My Framework
type: agent-framework # agent-framework | model | plugin | service
version: 1.0.0
description: "One-line description of what this does"
homepage: https://github.com/example/my-framework
license: MIT
requires:
ram_mb: 512 # minimum RAM in MB
python: ">=3.10"
install:
method: pip # pip | script | docker
package: my-framework
config_schema:
- name: model
type: model-select
label: LLM Model
required: true
hardware_tiers:
arm-npu-16gb: full # full | limited | unsupported
arm-npu-32gb: full
x86-cuda-12gb: full
x86-vulkan-8gb: full
cpu-only: limited
All fields except config_schema are required. The hardware_tiers block controls which hardware profiles see the app as recommended.
Step 3 - Update catalog.yaml
Add an entry to app-catalog/catalog.yaml under the appropriate section:
- id: my-framework
type: agent-framework
version: 1.0.0
name: My Framework
description: "One-line description matching your manifest"
Step 4 - Open a PR
Submit a pull request. The CI will run the catalog tests automatically. Include a link to the upstream project in your PR description.
Code Style
Python
- Follow the patterns already in the codebase - there is no strict linter, but keep it readable
- One concern per module; avoid cross-importing between route files
- Use
async deffor route handlers; useawaitfor all I/O
Frontend
The UI is a React SPA (desktop/) built with Vite. Static assets are served from static/desktop/ after npm run build. If you are adding a new UI surface:
- Follow existing React patterns in
desktop/src/- no server-rendered templates for new features - ARIA labels are required on interactive elements without visible text labels
- One concern per component; keep API calls in dedicated hooks or service files
Tests
- Use pytest; fixtures live in
tests/conftest.py- use them - Mirror the module structure:
tinyagentos/routes/agents.py->tests/test_agents.py - All PRs must pass CI before merge
Commits
Use conventional commit style:
| Prefix | Use for |
|---|---|
feat: | new feature |
fix: | bug fix |
docs: | documentation only |
refactor: | code change with no behaviour change |
test: | adding or updating tests |
chore: | tooling, deps, CI |
Do not include AI tool attribution in commit messages.
Design laws
Every taOS surface is a live surface: new or changed UI must auto-refresh and
carry motion per docs/design/live-surface-law.md. PRs adding a static screen
get bounced on review.
Testing
Run the full test suite:
uv run pytest tests/ --ignore=tests/e2e -n auto
Run a specific test file:
pytest tests/test_catalog_sync.py -v
The project has ~10,250 tests. CI runs against Python 3.12 and 3.13 on every pull request. Python 3.11 is added on the nightly scheduled run. A PR cannot be merged until all required checks pass.
When adding a feature, add tests that cover the new behaviour. When fixing a bug, add a regression test.
Verifying your work
A passing signal is a claim, not evidence. Most bad merges in this project came from something that looked like success without being one. Before trusting a green tick, ask what would have to be true for it to be lying.
Cases that have actually happened here:
- A PR passed every required check while containing only a version bump. Green proves the suite ran, not that the work happened.
- A test file reported green while every test in it skipped.
pytest.importorskipis the correct way to land tests ahead of the code they cover, and it asserts nothing until that code exists. Once the implementation merges, confirm the skips became real passes. If the import path in the guard does not match what shipped, they skip forever and look identical to passing. cmd | tail && echo OKprints OK whencmdfailed, because&&seestail's exit status. Check the exit code of the command that matters.- Green on a branch with conflicts tested the old base, not the result of merging.
Applied to your own changes:
- If you add a gate, prove it fails. A check only ever observed passing is unproven where it counts. Make it go red deliberately once.
- Verify a review comment against the code before acting on it, including automated ones. Bot findings are often wrong, and confidently phrased.
- Report honestly. If tests fail, say so and include the output. If you skipped a step, name it. "Done" should mean verified, not attempted.
Avoiding collisions with other contributors
- Check whether an open PR already adds the file you are adding. Two PRs that both create the same path cannot both merge, and whichever lands first silently decides the design. This has cost duplicated implementations of the same feature more than once.
- Renaming a CI job orphans its required check. Branch protection matches required checks by name, so a rename means the old name is never reported again and later PRs block on a check that can no longer exist. Keep the old name reported (an aggregate job whose explicit
name:reproduces it) or update protection first. - Stage explicit paths rather than
git add -A, which sweeps in untracked files you did not write.
Documentation gate
A gate blocks PRs that add or remove certain feature code without a matching doc update. It only fires on structural changes (a file added or deleted), never on a plain edit, and only for a small set of conservative rules configured in docs/doc-gate.toml:
| Change | Requires editing one of |
|---|---|
A desktop app under desktop/src/apps/ is added or removed | README.md |
A route module under tinyagentos/routes/ is added or removed | docs/agent-coordination.md |
An installer under tinyagentos/installers/ or scripts/install* is added or removed | README.md |
A manifest under app-catalog/ is added or removed | README.md |
If your PR trips a rule and there is genuinely nothing to document (or you already covered it elsewhere), add a trailer line to a commit message instead of editing a doc:
Docs-Reviewed: no user-facing change, internal refactor only
The trailer must have non-empty text after the colon; a bare Docs-Reviewed: does not count.
Run scripts/install-git-hooks.sh once to enable local hooks (.githooks/pre-commit and .githooks/commit-msg) so the gate runs before you push instead of after you open the PR. Local hooks are a convenience only: .github/workflows/doc-gate.yml is the authoritative check and runs on every PR regardless of local setup or --no-verify.
To add a new rule, edit docs/doc-gate.toml -- rules are data, no code changes needed.
Architecture Overview
tinyagentos/
app.py # FastAPI application factory, lifespan, route registration
config.py # Platform config, hardware detection
routes/ # One module per feature area (86 route modules)
templates/ # Minimal: only agent_debugger.html remains (frontend is a React SPA)
channel_hub/ # Framework-agnostic messaging (6 connectors + message router)
adapters/ # Framework adapters (15 adapters, ~25 lines each)
cluster/ # Distributed compute (worker registration, task routing, optimiser)
worker/ # Cross-platform worker apps (system tray, Android, iOS)
stores/ # Data access layer (SQLite via aiosqlite)
app-catalog/ # YAML manifests for installable apps (108 apps)
tests/ # pytest test suite (~3,590 tests)
Routes are registered in app.py. Route modules access stores via request.app.state (dependency injection set up in the app lifespan) - they do not import stores directly. The frontend is a React SPA; templates/ is minimal and only used for the agent debugger page.
Contact
Questions not suited for a GitHub issue? Email info@taos.my.