Norn: a provider-neutral multi-agent development harness
Project description
Norn
Provider-neutral multi-agent development harness for local subscription CLIs. The name evokes the Norns who shape fate: the harness coordinates independent agents toward a bounded outcome.
Status: beta. The
norn-clidistribution is published to PyPI; the source repository remains private during the beta period.
Norn provides configuration, CLI, run-state, event journal, recovery, human-readable escalation reports, provider-neutral adapters, and an executable multi-role delivery pipeline for Codex, Claude Code, Grok Build, and future command-based subscription CLIs.
norn init
norn --version
norn doctor
norn validate-config
norn providers
norn plan "add an audit log"
norn run "add an audit log"
Install the bundled explicit-invocation skill for both Codex and Claude Code:
norn install-skill --target all --scope project --project /path/to/repository
This writes .agents/skills/norn for Codex and .claude/skills/norn for Claude Code. Use
$norn in Codex or /norn in Claude Code. Install a single user-level copy with
--target codex|claude --scope user; use --force only to replace an existing
copy intentionally. Loading the skill never starts a model-consuming lifecycle
unless the user explicitly requests plan or run.
See Installation for isolated uv tool, pipx, and
virtual-environment installs from PyPI.
plan runs orchestration, planning, and plan review. run continues through
implementation, language-aware local tests/lint, tester roles, triage, and final
gate. UI changes require validated E2E execution. A repository-owned managed
runner is the recommended path; the legacy policy can instead require each tester
to execute E2E independently. Every required result includes the executed argv,
exit code, a concise result summary, and at least one non-empty artifact in its
assigned evidence directory. The harness rejects missing, stale, symlinked, or
out-of-directory evidence and archives accepted files under
.norf/runs/<run-id>/artifacts/e2e/.
To implement a previously approved plan without invoking orchestration, planning,
or plan review again, pass the successful plan run ID with the identical request:
norn plan "add an audit log"
norn run "add an audit log" --from-plan <plan-run-id>
Norn verifies the source run mode and status, project root, request text, and saved artifact schemas before creating the implementation worktree.
For Codex tester roles, the adapter keeps workspace-write filesystem isolation
and enables command networking through a loopback-only proxy allowlist
(localhost and 127.0.0.1). Planning and implementation roles retain their
normal network-off policy.
Docker-backed tests additionally need access to the host Docker API socket. Grant that access explicitly for one run:
norn run "task" --allow-tester-host-services
The flag resolves the local Docker Unix socket from DOCKER_HOST, the active
Docker context, or a detected rootless, Docker Desktop, or system-default socket,
then adds only that socket as a writable root to Codex TEST invocations. Remote
TCP Docker endpoints are not added as filesystem roots. The flag does not remove
the workspace sandbox or promise that raw TCP clients can reach services bound
to the host's loopback interface. Docker socket access effectively delegates host
control, so use the flag only with a trusted repository; the resolved path and
opt-in are recorded in the run manifest and reports.
Configure one shared loopback URL for parallel browser testers. For a project-local development server, also provide its shell-free command:
e2e:
base_url: http://127.0.0.1:4173
server:
command: [pnpm, --filter, "@trato/marchant-admin", dev, --host, "127.0.0.1", --port, "4173", --strictPort]
startup_timeout_seconds: 30
tcp_forwards:
- {listen: 4173, to: "127.0.0.1:4173"}
Norn starts the command after implementation, waits for the URL, directs all tester
roles to reuse it without starting Vite, and always stops it afterward. Omit
server.command to reuse an already-running Docker or external Web server; external
servers are checked once and are not stopped by Norn. tcp_forwards is an explicit
allowlist for services bound to the Norn host's loopback interface. For every E2E
command, Norn supplies a Python argv wrapper that listens only inside that command's
sandbox and exchanges byte streams through a run-scoped file spool with the host
relay. The host accepts only strictly named, regular-file requests for declared
ports; duplicate listen ports, non-loopback targets, and undeclared host ports are
rejected. Runtime files are contained in a mode 0700 temporary directory and are
removed when testing ends.
For distributable projects, prefer a reviewed, repository-owned E2E command over granting a model browser or host permissions:
e2e:
require_all_testers: false
base_url: http://127.0.0.1:4173
server:
command: [pnpm, dev, --host, "127.0.0.1", --port, "4173", --strictPort]
runner:
command: [pnpm, test:e2e]
Norn runs the four static tester roles in parallel, starts or checks the one shared
server, then executes runner.command exactly once as a shell-free argv list.
The implementer may add or update E2E specs but is explicitly told not to launch a
browser, start the server, or execute this runner itself.
The command receives NORN_E2E_BASE_URL, NORN_E2E_EVIDENCE_DIR, NORN_RUN_ID,
and NORN_IMPLEMENTATION_ROUND. It must write at least one non-empty log,
screenshot, trace, or result file below NORN_E2E_EVIDENCE_DIR; Norn's own stdout
and stderr logs do not count as evidence. Norn owns the authoritative command,
timeout, exit code, and evidence validation, then gives those results to
e2e_tester for a read-only review with no command tools. The reviewer cannot
change a failed run into a pass. Managed runners cannot be combined with
e2e.tcp_forwards or --allow-e2e-danger-full-access because execution occurs in
Norn's process, outside a tester provider sandbox.
Heavy repositories receive bounded but generous defaults: model roles and project test commands each have 60 minutes, while managed or delegated E2E has 40 minutes. Override them per project when needed:
limits:
timeouts:
role_seconds: 3600
test_seconds: 3600
e2e_seconds: 2400
These are timeout ceilings, not delays. Existing project configurations that explicitly contain older values keep those values until updated.
The default require_all_testers: true keeps independent browser evidence from
spec, clean, security, and chaos testers. Projects where parallel quality commands
interfere with a watched development server can opt into one dedicated browser role:
e2e:
require_all_testers: false
roles:
e2e_tester: {profile: e2e-browser}
Norn first runs the four non-browser tester roles in parallel, then starts or checks
the shared server and runs e2e_tester alone. The dedicated role is still required
to produce valid browser evidence. Its profile is independently configurable, so a
non-Codex provider can be selected when Chromium is incompatible with the Codex TEST
sandbox. In this mode the four static roles are explicitly prohibited from starting a
browser or development server, connecting to the E2E URL, or rerunning shared project-wide
lint, test, coverage, and build commands already owned by Norn's deterministic gate.
The shipped e2e-browser profile uses Sonnet at medium effort, with Grok 4.5 at
medium effort as its fallback. It intentionally has no Codex or Fable candidate.
When no managed runner is configured and the provider's non-interactive permission
policy still blocks browser or loopback access, a trusted repository may opt in at run time with
--allow-e2e-danger-full-access. This bypass applies only to the dedicated
e2e_tester; the four static testers keep their normal TEST sandbox.
Profiles keep a stable logical model name while optionally mapping it to a provider-specific identifier. For example, Codex Sol is configured as:
candidates:
- provider: codex-cli
model: sol
model_id: gpt-5.6-sol
effort: high
Round limits are configured under limits or overridden per invocation:
norn run "task" --max-rounds 5 --max-total-rounds 12 \
--max-planner-retries 2 --max-final-replans 2
Known Python, Node, Rust, and Go gates are auto-detected. Projects can define additional commands as shell-free argv lists:
gates:
enabled: true
auto_detect: true
commands:
- name: integration
kind: test
command: [python3, -m, pytest, tests/integration]
Each run records role responses, model selections, attempts, findings, test
results, and round history below .norf/runs/<run-id>/. Any non-successful stop
also produces escalation-report.md and escalation.json for human judgment.
By default, run requires a clean Git repository and implements inside a
detached .norf/worktrees/<run-id>/ worktree. Set git.worktree: false only when
direct changes to the selected project directory are intentional.
Fresh Git worktrees contain tracked files only. Projects whose tests require ignored dependencies, local environment files, or a prepared database can provision the worktree before any provider role starts:
git:
worktree: true
worktree_setup:
- name: php-dependencies
copy: laravel/vendor
- name: node-dependencies
copy: node_modules
optional: true
- name: test-database
command: [scripts/setup-norn-database, --template]
timeout_seconds: 900
Each step has exactly one action. copy materializes a project-relative path
from the source checkout; command is a shell-free argv list executed in the
new worktree. Commands receive NORN_SOURCE_ROOT, NORN_WORKTREE, and
NORN_RUN_ID. A failed required step stops the run before model invocation.
Development and packaging
Install the local development tools and run the same checks as CI:
python3 -m pip install --editable ".[dev]"
ruff format --check .
ruff check .
python3 -m unittest discover -v
python3 -m build
python3 scripts/package_smoke.py dist/norn_cli-0.7.3-py3-none-any.whl
CI runs the quality suite on Python 3.12, 3.13, and 3.14, then installs the built wheel in a clean virtual environment and verifies both Codex and Claude skill copies. It uploads the distributions as a short-lived workflow artifact. Publishing a GitHub Release runs the same deterministic gates before sending the verified artifact to PyPI through OIDC Trusted Publishing; neither workflow invokes subscription models.
Release acceptance is intentionally split in two: CI performs deterministic package checks, while local acceptance invokes the authenticated subscription CLIs. Follow Release acceptance before promoting a release. See CHANGELOG for version history and Security for private vulnerability reporting.
The canonical command is norn. The legacy norf-harness executable remains an alias during
the pre-1.0 migration; new automation should use norn.
License
Copyright 2026 uruca-kk. Licensed under the Apache License 2.0.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file norn_cli-0.8.0.tar.gz.
File metadata
- Download URL: norn_cli-0.8.0.tar.gz
- Upload date:
- Size: 88.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
af9ab7899349d6dd68f08da680426acbb8a4f05339252460c9a33877ff706e00
|
|
| MD5 |
f31a8be61bed152f8776c68f29efce09
|
|
| BLAKE2b-256 |
ff266a0b79e8ec0d2fe86d11006506f8a905ea2f226083a1cebb4d8bb879dafc
|
Provenance
The following attestation bundles were made for norn_cli-0.8.0.tar.gz:
Publisher:
publish.yml on uruca-kk/norn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
norn_cli-0.8.0.tar.gz -
Subject digest:
af9ab7899349d6dd68f08da680426acbb8a4f05339252460c9a33877ff706e00 - Sigstore transparency entry: 2167341556
- Sigstore integration time:
-
Permalink:
uruca-kk/norn@e61e86e34717e67fec9e0b86ae35beab14dea1d1 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/uruca-kk
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e61e86e34717e67fec9e0b86ae35beab14dea1d1 -
Trigger Event:
release
-
Statement type:
File details
Details for the file norn_cli-0.8.0-py3-none-any.whl.
File metadata
- Download URL: norn_cli-0.8.0-py3-none-any.whl
- Upload date:
- Size: 78.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a32c2d003167e51aeb1aafbc85dabc7bf51c8f1ce482ae1e0573fc204718c6e
|
|
| MD5 |
5dfca7a3a72f153665dfa5aa8b153507
|
|
| BLAKE2b-256 |
4c324f58aa80d9801fa03336ae9d12e6d635ace6cf63dbd6d649dcdc96f5d48c
|
Provenance
The following attestation bundles were made for norn_cli-0.8.0-py3-none-any.whl:
Publisher:
publish.yml on uruca-kk/norn
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
norn_cli-0.8.0-py3-none-any.whl -
Subject digest:
2a32c2d003167e51aeb1aafbc85dabc7bf51c8f1ce482ae1e0573fc204718c6e - Sigstore transparency entry: 2167341562
- Sigstore integration time:
-
Permalink:
uruca-kk/norn@e61e86e34717e67fec9e0b86ae35beab14dea1d1 -
Branch / Tag:
refs/tags/v0.8.0 - Owner: https://github.com/uruca-kk
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@e61e86e34717e67fec9e0b86ae35beab14dea1d1 -
Trigger Event:
release
-
Statement type: