CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to Omnigent
Thanks for your interest in improving Omnigent. Issues and pull requests are
welcome. For larger changes, open an issue first so we can discuss the approach.
Please don't include secrets, internal URLs, customer data, or private
configuration in issues, tests, examples, or logs.
## Issue prioritization
We rank open community issues so maintainers see the most important work first.
The ranking is a triage aid, not a delivery promise or roadmap commitment.
An LLM reads the issue title, body, and labels and classifies its type, severity,
and affected areas. It does not assign the final priority directly. Priority
comes from deterministic arithmetic:
The issue template provides an initial type, but automation may correct the
`Bug`, `Feature`, or `Docs` label when the issue content indicates a different
type. For example, a feature request that describes broken existing behavior is
reclassified as a bug and follows the bug-report evidence requirements below.
```text
score = severity points × component weight + community-demand points
```
| Signal | Current treatment |
| --- | --- |
| Severity | S0=100, S1=60, S2=30, S3=10. It captures impact and reach. |
| Component | The highest matching area weight, currently 0.9–1.4. |
| Community demand | GitHub `+1` reactions add up to 15 points, capped at 12 reactions. |
| Needs information | An issue labeled `needs-info` scores zero until the missing information arrives. |
Scores map to priority labels as follows:
| Priority | Score |
| --- | ---: |
| `P0-critical` | 100 or higher |
| `P1-high` | 60–99.99 |
| `P2-medium` | 25–59.99 |
| `P3-low` | Below 25 |
Age, readiness, and duplicate-count adjustments are not currently enabled.
Component importance is a separate signal, so severity is not raised merely
because an issue affects a particular harness or subsystem.
Maintainers can correct severity, component, or priority labels when context is
missing from the model. Automation preserves those overrides and does not
replace a maintainer-set priority with its own proposal. The queue is rerun as
issues change, while unchanged LLM classifications are reused.
For bugs, include the observed impact, reproduction evidence, Omnigent version,
platform, and affected harness or authentication mode. Direct steps are best,
but a clear intermittent observation, controlled test, diagnostics, or concrete
analysis of the failing code path can also give maintainers enough to
investigate. For feature requests, describe the user problem and expected
reach. Use a `+1` reaction when an existing issue matters to you; ordinary
comments are not counted as votes.
The scoring configuration and component map are public in
[`default_scoring.json`](.github/triage_v2/src/issue_prioritization/default_scoring.json)
and [`areas.json`](.github/areas.json).
## Response times and inactive issues
Priority determines the order in which maintainers consider work; it does not
set a response, review, or resolution deadline. We are working toward published
response targets, but they are not yet an SLA. For now, we do not guarantee a
specific response time for issues or pull requests.
Issues with no activity for 30 days are marked `stale` and close after another
14 days without activity. New activity restarts that inactivity window. Issues
labeled `pinned`, `security`, or `bug` are exempt.
Pull requests with no activity for 60 days are marked `stale` and close after
another 14 days without activity. New activity clears the label and restarts
the inactivity window. This applies to both contributor and maintainer PRs,
including drafts. PRs labeled `pinned` or `security` are exempt. Source branches
are kept, and authors can comment `/reopen` to resume work after an automated
closure. This policy also applies to older PRs that predate the review-state
labels below.
If a bug is labeled `needs-info`, the automation asks its author for the
specific missing information and gives them 7 days to respond. An author reply
triggers another triage pass; the label clears only when the new information is
actionable. A reply that still does not provide enough evidence does not reset
the original deadline.
If the requested information is still missing when the deadline passes, the
issue is automatically closed as not planned. A later author reply reopens the
issue and sends it through triage again. This lifecycle is separate from the
normal stale policy and applies to bug reports that would otherwise be exempt
from stale closure.
Pull requests waiting for an author response follow the separate 7-day policy
described under [Review state labels](#review-state-labels).
## Development setup
This is a Python package with an optional frontend under `web/`. Use
[`uv`](https://docs.astral.sh/uv/) for local development:
**Supported dev OS: macOS or Linux.** Native Windows is not supported for
development — some test dependencies are POSIX-only (`pexpect`/`pyte` are
excluded on Windows), a few modules import POSIX stdlib or call `os.getuid()`
at import time, and the `pre-commit` hooks assume the Unix `.venv/bin/` layout,
so `pytest` and `pre-commit` cannot pass natively. On Windows, use
**WSL2 (Ubuntu)** and clone into the **Linux** filesystem (`~/…`, not `/mnt/c`);
this matches CI. Git Bash is not sufficient — it runs native-Windows Python.
Install local prerequisites first:
- [`uv`](https://docs.astral.sh/uv/getting-started/installation/) for Python
environments and dependency management.
- `tmux`, required for native Claude/Codex terminals launched by the local host
(`brew install tmux` on macOS, or `apt install tmux` on Debian/Ubuntu).
- `bubblewrap` (`bwrap`), **Linux only**, used to OS-sandbox those native
Claude/Codex/Pi terminals (`apt install bubblewrap` on Debian/Ubuntu). macOS
uses the built-in `seatbelt` sandbox and needs nothing extra.
- Node.js 22 LTS or newer with `pnpm` (install via `corepack enable` or
`npm install -g pnpm`) when working on `web/`.
- A Rust toolchain for the recommended `omnidev` local development supervisor.
```bash
git clone https://github.com/omnigent-ai/omnigent.git
cd omnigent
uv python install
uv venv --python "$(cat .python-version)"
uv sync --extra all --group dev
source .venv/bin/activate # or prefix commands with `uv run`
```
Repository-only dependencies use PEP 735 groups: `lint` for static checks and
code generation, `test` for pytest, and `dev` for both. Product capabilities
remain installable extras. Plain `uv sync` installs neither group by default.
Package mirrors that embargo new releases, such as the Databricks PyPI proxy
used for internal builds, install omnigent from the requirements declared in
`pyproject.toml`, not from `uv.lock`. When you raise a dependency floor or add a
dependency, keep the floor at a release those mirrors already serve and let
`uv.lock` pin anything newer. With access to the mirror, run
`just check-internal-mirror` before merging; it resolves through the proxy
without writing `uv.lock`. The `young-dependency-floor` custom lint fails when a
floor needs a release inside the uv cooldown. An urgent bump, such as a security
fix, may keep that floor once the mirror serves the release: suppress the lint
on the requirement with a `custom-lint: disable-next` comment that says why.
Adding or changing that waiver keeps the required Maintainer Approval check red
until a maintainer, the author included, runs `just check-internal-mirror` and
adds the `dependency-floor-approved` label.
Install the git hooks once per clone so the checks below also run on every
commit and push (`just ensure` does this as part of setup):
```bash
just hooks # or: uv run --no-sync pre-commit install
```
Lockfiles: `uv.lock` follows the lock format of the uv version CI pins
(`.github/actions/setup-uv`). The scheduled lockfile job migrates it when that
pin moves, and Dependabot PRs get their lockfiles repaired automatically; a
maintainer can still comment `/regen` on any same-repo PR to force it.
Common checks:
Pyrefly is the canonical Python type checker for the repository.
```bash
uv run --no-sync pytest # Python tests (e2e/live skipped by default)
uv run --no-sync ruff check . && uv run --no-sync ruff format --check .
uv run --no-sync pyrefly check # Python type checking (core and client SDK)
uv run --no-sync pre-commit run --all-files
```
The `session-list-visibility` custom rule checks Python, JavaScript/TypeScript,
and shell clients for explicit visibility on session-list requests. It follows
local query builders and same-file helpers; direct server tests and mock
fixtures are excluded so they can exercise the API's compatibility default.
Install web dependencies (`pnpm install --frozen-lockfile --filter web`) before
running the full custom lint, then use
`uv run --no-sync python -m dev.lint.custom_lint`. A request built outside the
checker's supported patterns needs a `custom-lint: disable=session-list-visibility
-- <reason>` comment on its request line (or `disable-next` on the preceding
line), after verifying that its query builder sends visibility.
When touching `web/`:
```bash
cd web && pnpm install && pnpm run lint && pnpm run type-check && pnpm run build
```
When touching `editors/vscode/`:
```bash
cd editors/vscode && pnpm install && pnpm run type-check && pnpm run test && pnpm run build
```
## Running locally
Start with the smallest relevant automated test described in [Tests](#tests).
For full-stack manual testing, use `omnidev`.
### Recommended: worktree-safe testing with `omnidev`
`omnidev` runs the current checkout's server, host, and Vite frontend in one
terminal. Each checkout path, including each worktree, gets isolated state,
configuration, database, artifacts, logs, and automatically allocated ports,
so it can run alongside your normal Omnigent installation and other worktrees.
Install the supervisor once from an up-to-date checkout:
```bash
cargo install --path dev/omnidev --force
```
Then run it from anywhere inside the branch checkout or worktree you want to
test. A fresh worktree needs its own Python environment first:
```bash
cd /path/to/omnigent-worktree
uv sync --extra all --group dev
omnidev
```
Open the exact `ui` URL displayed in the header; do not assume the Vite port is
`5173`. Python changes under `omnigent/` reload the server and host, while
frontend changes use Vite HMR.
Run CLI commands against the development pod through the passthrough so they
use that checkout and its isolated state instead of a globally installed
`omnigent`:
```bash
omnidev omnigent config show
omnidev omnigent agent list
```
Keep `omnidev` in the foreground and quit with `q` or `Ctrl-C` so it tears down
all three processes. An interactive terminal inside an existing Omnigent
session also works; use `git rev-parse --show-toplevel` to confirm that its
current checkout is the one you intend to test.
See [`dev/omnidev/README.md`](dev/omnidev/README.md) for log controls,
clean-state testing, backend-only and LAN modes, and other options.
### Session-list refresh settings
The web and embedded clients share defaults in `web/src/appConfig.ts`.
`sidebar.mineRefreshMs` defaults to 60,000 ms and `sidebar.sharedRefreshMs`
to 180,000 ms. Set `VITE_MINE_REFRESH_MS` and `VITE_SHARED_REFRESH_MS` before
starting Vite or building the frontend to override them with positive integer
milliseconds, for example:
```bash
VITE_MINE_REFRESH_MS=120000 VITE_SHARED_REFRESH_MS=300000 pnpm --dir web dev
```
Each refresh requests the loaded window, capped by `sidebar.maxRefreshSessions`
(default 200). Up to that cap, the response replaces the window. Beyond it, the
newest 200 rows replace the refreshed portion and older loaded rows remain.
Agent discovery reads the first 30 Mine sessions from that cache, adding no
session-list requests. Agents found only in older or shared sessions are not
discovered; the built-in `/v1/agents` catalog is still loaded separately.
Pagination still adds 30 rows per request. Mine and Shared results are checked
against the viewer's ownership metadata in the frontend as well.
If Load more is clicked during a refresh, the click waits for that refresh,
then fetches the next 30 using the updated cursor and appends them to the cache.
Archived refreshes each time it is opened, without scheduled polling.
### Manual three-terminal fallback
Use the manual flow when you need to run or debug each component separately.
Unlike `omnidev`, it does not isolate state or allocate ports. These commands
assume the default ports are free:
```bash
# Terminal 1: local server on :6767
uv run omnigent server
# Terminal 2: register your machine as a host
uv run omnigent host --server http://localhost:6767
# Terminal 3: frontend dev server
cd web
pnpm run dev
```
Open the Vite URL from the frontend dev server, usually
`http://localhost:5173/`. The host registration is what lets the web UI browse
your filesystem and start new sessions on your machine — without it, the web UI
is read/continue-only.
`omni` is an alias for `omnigent`, so `omni host --server ...` works too.
The host URL can also be passed positionally (`omnigent host
http://localhost:6767`). See the [README](README.md) for more on hosts,
harnesses, and credentials.
### Disposable backend-only validation
Use this when you want to validate the Python backend and local API server from
a source checkout without building the web UI, configuring provider
credentials, creating sessions, or running agents -- a quick server/API smoke
check on your working copy or current `main`.
[`scripts/backend-smoke.sh`](scripts/backend-smoke.sh) automates it:
```bash
scripts/backend-smoke.sh # boots on port 18080
PORT=18090 scripts/backend-smoke.sh # override the port if 18080 is busy
```
It installs `uv` into a throwaway toolchain venv, runs `uv sync --frozen`,
starts the server in API-only mode (`OMNIGENT_SKIP_WEB_UI=true`), waits for
`/health`, and smoke-tests `/`, `/health`, `/docs`, `/v1/agents`, and
`/v1/sessions` -- expecting HTTP `200` from all five. It exits non-zero if any
check fails.
Notes:
- **Requires `bash` or `zsh`** (the script's `#!/usr/bin/env bash` shebang
guarantees this); it is not POSIX-`sh` portable. **Also needs** Python 3.12+
as `python3`, `git`, `curl`, and network access to PyPI. No provider
credentials are needed. **Works on Linux and macOS.**
- **Fully isolated, disposable:** every artifact -- the toolchain and project
venvs, config, data, the SQLite database, artifacts, logs, and `pip`/`uv`
caches -- lives under one `mktemp -d` runtime directory removed on exit, so
the run never touches your real `~/.omnigent`, `~/.config` / `~/Library`, or
package caches. `HOME` is the primary isolation lever (it redirects
`~/.config` on Linux and `~/Library` on macOS); the explicit `UV_*` / `PIP_*`
/ `OMNIGENT_*` overrides pin the toolchain and app state regardless of OS,
and `XDG_*` are set so an `XDG_*` already exported in your shell cannot
redirect state back to your real home.
- **What it does not cover:** the web UI, mobile access, human-in-the-loop
approval flows, provider-backed sessions, or agent execution. Use the full
local development flow above when working on those areas.
## Tests
A change that alters behaviour under `omnigent/` should ship with a test, and a
bug fix should add a test that fails before the fix. Pure refactors, renames,
type-only changes, dependency bumps, and edits with no observable behaviour
change don't need a new test.
Prefer the smallest test that covers the change. A fast, focused **unit test**
in the area suite is the default and what most changes need. Reach for
`tests/integration/` only when behaviour genuinely spans components, and for
`tests/e2e/` only for full-stack flows that a unit test can't capture — these
are slower and (for e2e) gateway-bound, so don't use them where a unit test
would do.
Put the test in the suite that matches the area you changed — most backend
areas mirror their source directory under `tests/`:
| Area changed (`omnigent/…`) | Test suite (`tests/…`) |
| --- | --- |
| `server/` | `server/` |
| `runner/` | `runner/` |
| `runtime/` | `runtime/` |
| `tools/` | `tools/` |
| `inner/` | `inner/` |
| `llms/` | `llms/` |
| `db/` | `db/` (a schema migration especially warrants one) |
| `policies/` | `policies/` |
| `repl/` | `repl/` |
| `entities/` | `entities/` |
| `stores/` | `stores/` |
| `host/` | `host/` |
| `spec/` | `spec/` |
Two cross-cutting suites sit on top of these:
- `tests/integration/` — behaviour that spans several components (e.g. server +
runtime) and isn't captured by any single area's unit test.
- `tests/e2e/` — full-stack flows driven against a live LLM (sessions, the
runtime, sub-agent dispatch, client-tool tunneling, transports, native
harness bridges, steering/cancellation). These are slow and gateway-bound, so
reserve them for genuine end-to-end behaviour — but a PR that adds new
user-facing functionality **must** include at least one e2e happy-path test
(see `.github/copilot-instructions.md`).
Frontend-only changes follow the guidance below instead.
### Frontend (`web/`)
Frontend changes follow the same expectation with a different toolchain:
- Prefer a **colocated Vitest test** — a `*.test.ts`/`*.test.tsx` file next to
the component or module you changed — and run it with `pnpm test`.
Existing tests count when they already cover the changed behaviour; add or
update tests for concrete gaps.
- Use Playwright tests under `tests/e2e_ui/` for browser behaviour or full user
flows that unit or component tests cannot adequately cover. Extend an existing
test where practical, and explain what the browser test uniquely verifies.
UI changes do not automatically require a new or modified E2E test. The
existing E2E UI suite continues to run in CI.
- Styling/formatting-only changes, copy tweaks with no flow change, and
refactors with no behaviour change are exempt, same as the backend.
## Developer Certificate of Origin
To contribute to this repository, you must sign off your commits to certify
that you have the right to contribute the code and that it complies with the
open source license. If you can certify the contents of the [DCO](DCO), add a
`Signed-off-by` line to each commit message:
```
Signed-off-by: Joe Smith <joe.smith@email.com>
```
Please use your real name — pseudonymous/anonymous contributions are not
accepted. If your `user.name` and `user.email` git configs are set, `git
commit -s` adds the sign-off automatically. The DCO check on every pull
request enforces this, so unsigned commits will block merging.
## Pull requests
- Branch from `main`, keep changes focused, and include tests or docs when relevant.
- Sign off your commits with `git commit -s` (see
[Developer Certificate of Origin](#developer-certificate-of-origin) above).
- **Reference an issue** (see below).
- Fill in the PR template. Bug fixes and features should include reproducible
evidence in the Demo or Test Plan. For **UI / frontend changes**, check the
"UI / frontend change" box and attach a **video or images** in the `Demo`
section showing the new behaviour, so reviewers can see it without checking
out the branch.
- Under **Release notes**, choose exactly one Yes/No checkbox and keep both
rows. Choose Yes only for outstanding user-facing features, bug fixes, UX
changes, and breaking changes; write one line for users in **Changelog**.
Breaking changes must choose Yes and describe the compatibility impact.
Features behind a feature flag are eligible only once the flag is enabled for
users. Choose No for small fixes or improvements, features behind disabled
flags, and internal changes with no user impact, and delete the Changelog
section. The complete changelog still credits the PR using its title. This
records your recommendation; maintainers curate the release notes.
### CI freshness
Passing CI is valid for 24 hours from each workflow's original creation time.
`Merge Ready` shows the earliest expiry across the required CI checks. Changes
to `main` during that window do not invalidate a passing result.
After expiry, update your PR branch from `main` and let fresh CI finish. If the
branch is already current, push a new commit to trigger fresh PR workflows.
Re-running an existing workflow, including with `/rerun`, keeps its original
age and does not renew the window. DCO and intentionally skipped checks do not
expire.
A sweep scheduled every 15 minutes rechecks expired green statuses on open
PRs, including those without the `automerge` label. GitHub can delay scheduled
runs, so idle PRs become blocked when the sweep and gate evaluation complete.
Each sweep reevaluates at most 50 PRs, prioritizing known expiries over older
statuses that have no expiry recorded yet. Large backlogs take multiple sweeps.
Known expiries use a lightweight job that rereads the current status under the
same per-head lock as full gate evaluations, then marks it red only if it is
still expired. This skips check enumeration and dependency-report evaluation;
legacy statuses without a valid expiry still need a full evaluation. Status
writers queue without replacing pending evaluations (up to 100 pending jobs
per head; GitHub cancels additional arrivals). Normal gate evaluations also
enforce expiry, and only a full evaluation can restore green.
GitHub's normal merge button, native auto-merge, and the separate Dependabot
auto-merge workflow rely on the published `Merge Ready` status. They can merge
after the recorded deadline until reevaluation revokes green; they do not
independently enforce the timestamp. Before asking GitHub to merge a green
gate, the `Merge Ready` workflow itself checks the timestamp again.
A `/merge` command from a user with write access records the request with the
`automerge` label and posts an acknowledgement. Every subsequent gate evaluation
can act on that request after checking CI freshness, so overlapping CI events
cannot discard it. Adding the label directly uses the same path.
To cancel the request, remove the `automerge` label and disable GitHub auto-merge
if it is already enabled.
### Database migration reviews
Follow the [database best practices](docs/DATABASE_BEST_PRACTICES.md) when
changing schemas, queries, transactions, or storage code. Polly uses this
reference in its reviews and treats violations of mandatory requirements as
blocking findings.
Changes under `omnigent/db/migrations/`, to `omnigent/db/alembic.ini`, or to
[`.github/CODEOWNERS`](.github/CODEOWNERS) require a GitHub approval from at
least one of Edwin He (`@Edwinhe03`), Aravind Segu (`@aravind-segu`), Corey
Zumar (`@dbczumar`), or Bryan Qiu (`@bbqiu`). The reviewer must be someone
other than the PR author, including for maintainer-authored PRs.
GitHub requests these reviews automatically. The `main-no-force-push` ruleset
requires code-owner approval and dismisses stale approvals after changes are
pushed. This requirement is enforced by GitHub alongside the CI checks.
<a name="every-pr-needs-an-issue"></a>
### When a PR needs an issue
We require an issue for contributor-authored pull requests, with the exceptions
below. Issues are how work gets prioritized, so a PR without one arrives
unsorted and waits longer.
**Maintainer-authored pull requests are exempt, regardless of the type of
change.** Maintainers may link an existing issue when useful, but do not need
to create one solely to satisfy this requirement.
Reference it in the description. Which keyword you use depends on whether the PR
finishes the issue:
| Your PR | Write | Effect |
| --- | --- | --- |
| Finishes the issue | `Closes #123` (or `Fixes` / `Resolves`) | GitHub links the PR and closes the issue on merge |
| Is one step towards it | `Part of #123` (or `Related to` / `Towards` / `Refs`) | The issue stays open |
`Closes` is preferred when it applies, because GitHub records a real link and
closes the issue for you. For a partial change, do not claim `Closes`: use one of
the second-row keywords instead, so the issue is not closed before the work is
done. You can also link a closing issue from the **Development** section of the
sidebar, which counts the same as a `Closes` keyword.
A bare `#123` is not enough on its own. It creates a cross-reference rather than
saying anything about this PR, so pair it with one of the keywords above. The
reference also has to point at an **issue**: naming another pull request does not
count, since a PR is not a tracking record.
**No issue for your change yet?** If your PR requires one, open it first, then
reference it. This lets a maintainer confirm the approach before you write code.
Contributor-authored PRs are also exempt for changes with no user-visible
behaviour: pure **Refactor / chore**, **Docs**, or **Test / CI** work. If that
describes your PR, check that box under *Type of change* and no issue is needed.
Other contributor-authored PRs that fix a bug, add a feature, or change the UI
need an issue, even when they also touch docs or tests.
A bot comments once on non-exempt PRs that reference no issue and labels them
`needs-issue`.
Reference an issue and the label clears automatically. A PR still labeled
`needs-issue` after **7 days** is closed, the same way and for the same reason as
`waiting-on-author` below: to keep the review queue readable, not as a judgement
on the change. It is reversible, so comment `/reopen` once you have added the
reference.
### Review state labels
Two labels track whose turn it is. Both are managed by automation, so you do not
need to apply them.
| Label | Meaning |
| --- | --- |
| `waiting-on-author` | A maintainer has left feedback. The PR is in your court. |
| `waiting-for-review` | You have responded. It is back in the reviewer's queue. |
A third label, `needs-issue`, is separate from these two: it says the PR
references no issue, not that anyone is waiting on a reply. See [When a PR needs
an issue](#when-a-pr-needs-an-issue).
A maintainer reviewing or commenting on your PR sets `waiting-on-author`. When
you push a commit, comment, or reply to a review, that clears automatically and
`waiting-for-review` goes on instead, which also re-pings your reviewer. You do
not need to ask for a re-review.
A PR left in `waiting-on-author` for **7 days** with no reply or new commit is
closed to keep the review queue readable. That is not a judgement on the change,
and it is reversible: comment `/reopen` (see below).
**As of 5 August 2026** maintainers follow this process for new pull requests.
PRs opened before then are being worked through separately, so an older PR may
not carry these labels yet; that does not mean it has been forgotten. The
issue-link rule also applies only to PRs opened on or after that date, so you
will not be asked to retrofit an issue onto an older PR.
### Reopening a closed PR
If automation closed your PR (as a duplicate, for example) and you think that
was wrong, comment `/reopen` on it and a bot will reopen it for you. GitHub only
lets maintainers press the Reopen button, so this command is how you do it
yourself. You can also use it on a PR you closed by hand.
Only the PR author can use it, and it won't override a maintainer who closed
your PR deliberately; ask them in a comment instead. It also needs your source
branch to still exist. If you deleted it, push it again and open a fresh PR
linking the old one.