Thanks for considering a contribution! This doc is the contribution process guide — how to make a change and get it merged. It has two parts:
- Dev environment & reference (setup, repo layout, testing, code style) — the quick version lives here, but the canonical, in-depth environment setup guide is Getting started (prerequisites, per-platform installs, local run, OAuth config). This doc does not duplicate it — it points to it and adds only the repo conventions a contributor needs.
- Contribution workflow (Making a change, Continuous integration, Opening a pull request, Commit messages, AI agent contributions) — how a change actually lands. For the release/versioning process, see RELEASING.md.
See Prerequisites in the getting started guide for the tools you'll need (git, Node.js 22+, .NET 10 SDK, and Azure CLI if you're touching the provisioning/deployment scripts) and per-platform install instructions (winget/brew/apt-get).
git clone https://github.com/sabbour/agentweaver.git
cd agentweaver
npm run setup # checks/prepares local prerequisites (git, dotnet, node)
npm run dev # starts the API (http://localhost:5000) and Web UI (http://localhost:5173)See the Getting started guide for the
full walkthrough, including configuring a GitHub OAuth App for local sign-in (the callback
URL for local dev is http://localhost:5000/auth/github/callback — the API's own origin,
no /api prefix, since that endpoint is mapped at the root, not under /api).
| Path | What it is |
|---|---|
apps/Agentweaver.Api |
The .NET API (coordinator, runs, memory, auth, sandbox orchestration) |
apps/Agentweaver.Mcp |
The MCP server surface |
apps/web |
The React/Vite frontend |
docs/ |
VitePress documentation site (published at sabbour.me/agentweaver) |
packages/ |
Shared .NET libraries (agent runtime, squad model, etc.) |
scripts/azure |
The Node.js provisioning/deployment/release toolchain (no bash/PowerShell) |
tests/Agentweaver.Tests |
.NET test suite |
tests/e2e |
End-to-end tests |
k8s/ |
Kustomize-based Kubernetes manifests (AKS deployment): k8s/base/ (generic manifests), k8s/overlays/production/ (image tags, ConfigMap/replacements patches), k8s/reference/ (non-deployed examples/one-off jobs) |
- Use a short-lived branch and PR for every change.
devis the default, protected integration branch. Branch from currentorigin/devwith a descriptive conventional prefix, e.g.fix/123-short-desc,feat/short-desc, ordocs/short-desc.- Direct pushes to
devare not allowed, including tiny and docs-only changes. - Before merge, the branch must be current with
devand all blocking CI must rerun successfully. GitHub enforces this through “require branches to be up to date before merging.” - Squash-merge so
devkeeps one commit per logical change. GitHub automatically deletes the source branch after merge. mainis stable/published-only. Do not open ordinary PRs into it; it receives a soaked release promotion or an audited emergency hotfix only.- Do not create a long-lived local
integration/stagingbranch as a private promotion pipeline. A disposable merge-test branch or worktree is fine, but delete it after validation.
- Direct pushes to
- Keep changes focused. Scope one change to one concern — it's easier to review and to revert if something goes wrong.
- Add or update tests for any behavior change (see Testing below).
- Update docs if you changed user-facing behavior, npm scripts, or configuration —
README.mdanddocs/guide/are the two places most likely to need updates. - Run the relevant test suite(s) locally before you push. CI re-runs the full suite
on every pull request and push to
devormain, but running the affected suite locally first keeps the feedback loop short and avoids red PRs. - Add a changeset for shipped user-facing behavior before opening the PR: run
npm run changeset, usepatchfor compatible fixes andminorfor features or breaking changes while Agentweaver is at0.x, and write prose for users rather than restating a commit title. Do not editVERSION, package versions, orCHANGELOG.md. Docs/tests/CI-only changes normally need no changeset; use thechangeset:not-requiredlabel only with aChangeset exemption:rationale in the PR body. Infrastructure and local deployment commands never consume changesets;release:publishconsumes only metadata already prepared byrelease:prepare. TheChangeset advisoryCI job blocks merge if it detects a release-relevant change with no changeset and no exemption, so this can't be silently skipped. For the full playbook, see the changelog skill. - Verify live for anything with runtime/deploy impact, not just via unit tests.
Good: “Add exportable workflow bundles so operators can move a workflow between installations.”
Bad: “feat: add export.” It repeats a commit title without explaining the user impact.
The active topology is dev → release/vX.Y.Z → main:
devis the default, protected integration branch. Normal PRs target it and use required PRs, blocking CI, up-to-date-before-merge, squash merge, and automatic source branch deletion.release/vX.Y.Zis an ephemeral release-candidate/soak branch cut from a greendevSHA. Stabilization fixes land there by PR and are immediately forward-ported todev.mainis stable/published-only. It receives only a promotion PR from a soaked release branch or an audited emergency hotfix, which must be forward-ported todev. Release tags are cut from the exact resultingmainpromotion SHA.
The complete operating flow is in RELEASING.md.
Prepare dependencies once per worktree. Every package root keeps a physical, private
node_modules; developer worktrees share only a fingerprinted npm download cache under
Git's common directory. A new worktree still runs reproducible npm ci, while an
unchanged local tree is verified and reused. CI, npm workspaces/local links, and
authenticated npm configuration use isolated npm ci:
npm run deps:ensureFor an ordinary branch or a layer in a stacked change, run only the affected areas:
npm run validate:layer
# .NET changes require a focused VSTest filter at layer level.
npm run validate:layer -- --area dotnet \
--dotnet-filter "FullyQualifiedName~Agentweaver.Tests.Coordinator"The layer profile detects Node toolchain, web, docs, and .NET paths relative to
origin/dev. It runs web tests and lint after one dependency setup. A .NET layer must
name a focused filter. The layer check is advisory; each PR still receives required CI.
NuGet packages are shared by the user cache, while locked restore, build output, and
tests remain worktree-local.
At the top of a completed stack, run the full profile against the exact integrated
tree. PR 1 must independently pass the full suite against its current dev merge
candidate before merge; a green stack top is not a substitute:
npm run validate:fullThe underlying area commands remain available for focused troubleshooting:
# .NET API / packages: locked restore, one build, then test exact outputs
dotnet restore tests/Agentweaver.Tests/Agentweaver.Tests.csproj --locked-mode -p:CopilotSkipCliDownload=true
dotnet build tests/Agentweaver.Tests/Agentweaver.Tests.csproj --no-restore -p:CopilotSkipCliDownload=true
dotnet test tests/Agentweaver.Tests/Agentweaver.Tests.csproj --no-build --no-restore -p:CopilotSkipCliDownload=true
# Node.js provisioning/deployment/release toolchain and CI contracts
node --test scripts/azure/tests/*.test.mjs scripts/changesets/tests/*.test.mjs scripts/ci/tests/*.test.mjs
# UI harness fixture/regression suite
node scripts/ci/shared-deps.mjs ensure --project scripts/ui-harness
npm --prefix scripts/ui-harness test
# Web frontend (Vitest)
npm --prefix apps/web run test
# Web frontend lint
npm --prefix apps/web run lint
# Docs site build (only if you changed docs/)
npm run docs:buildSee Validation workflow for cache keys, invalidation, fallback behavior, and timing output.
Pull requests and pushes to dev and main are verified by the
CI workflow. It runs the same commands documented under
Testing above, split into one job per area so each gets a dedicated runner
(several .NET and web tests are timing-sensitive and flake under CPU contention if
crowded onto a single runner). A changes job classifies each diff by path first, and
every suite job below except Changeset advisory only runs when its path group
actually changed (any edit to .github/workflows/ci.yml itself always runs
everything, so the pipeline is always fully verified when it changes; edits to
other workflow files don't run these suites, since they don't drive them); a
job that's skipped this way still counts as passing for required-status-checks
purposes:
| Job | What it runs | Gating | Runs when |
|---|---|---|---|
Seven .NET test shard (…) jobs |
Stable namespace shards plus isolated PostgreSQL/Testcontainers, process-global environment, and Kata runtime gates; every shard writes TRX results | Blocking — must pass | Every PR targeting dev, so all seven ruleset-required contexts are emitted even for metadata-only PRs |
Node toolchain tests |
Full Node toolchain/CI-helper tests plus npm --prefix scripts/ui-harness test |
Blocking — must pass | Node toolchain paths or UI harness/shared harness paths changed |
Web tests |
Web tests and lint after one isolated npm ci |
Blocking — must pass | apps/web/** changed |
Docs build |
npm run docs:build |
Blocking — must pass | docs/** changed |
Changeset advisory |
npm run version:check && npm run changeset:check |
Blocking — must pass | Always, on every PR |
The repository policy requires the seven named .NET shard jobs plus the Node toolchain,
web, docs, and changeset jobs on a branch that is up to date with dev. Path-conditional
non-.NET jobs count as passing when skipped; the named .NET shard jobs intentionally run
on every dev PR so GitHub emits each required context. The GitHub ruleset described in
.github/dev-branch-protection.md is active, so
admission is mechanical: direct pushes to dev are rejected and merges are blocked until
the branch is current and the required checks are green.
Changeset advisory now fails the build (not just a warning) when a release-relevant
change has no changeset and no changeset:not-required exemption.
CI is the full-suite authority and deliberately keeps job filesystems isolated. Its npm
steps call the same dependency helper with --isolated, preserving npm ci
reproducibility. Local concurrent worktrees share only npm download content; writable
dependency trees and build/test outputs never cross worktree boundaries.
The Publish images workflow builds the four
Agentweaver container images and publishes them to GitHub's container/artifact registry
(ghcr.io/<owner>/agentweaver-{api,frontend,mcp,agent-host}). It never deploys
anything — Azure/AKS deployment stays with the npm run azure:* toolchain.
The image list is not restated in the workflow: the plan job derives its build matrix
from scripts/azure/image-spec.mjs (the deploy
toolchain's single source of truth) through
scripts/ci/ghcr-plan.mjs, which is unit-tested by the
Node toolchain tests job. Triggers map to the branch topology above:
- push to
dev→:sha-<short>and:dev - push to
release/vX.Y.Z→:sha-<short>and:rc-X.Y.Z - push to
main→:sha-<short>and:main - a published GitHub Release →
:sha-<short>,:X.Y.Z,:vX.Y.Z, and:latest(:latestis skipped for prereleases) - manual
workflow_dispatchon any ref (an arbitrary commit) →:sha-<short>only, with an optional build-only dry run that skips the push
Every build publishes the immutable sha-<short> tag, so any image is addressable by
the exact commit it was built from — the same identifier model
npm run azure:deploy-from-local and azure:deploy-from-commit use.
- Keep the PR scoped to one concern and give it a conventional-commit-style title (see Commit messages) — the title is what shows up in the generated changelog and the GitHub Release notes.
- Describe what changed and why, and how you verified it (which suite(s) you ran, and any live/deploy verification for runtime changes).
- Make sure the blocking CI jobs are green and that you have not introduced new lint findings before asking for review.
- Update, retest, then squash-merge. If another PR reaches
devfirst, GitHub marks yours out of date. Update fromorigin/dev, resolve conflicts, rerun relevant tests/CI, and merge only after all required checks are green on the updated branch.
Fork the repository on GitHub, clone your fork, add the canonical repository as
the upstream remote, and create your short-lived branch from an up-to-date
upstream/dev. Open the PR from that branch to dev; it follows the same CI,
up-to-date, review, and squash-merge rules as every other contribution.
Fork PRs do not receive repository secrets: CI uses the pull_request trigger (not
pull_request_target) and its jobs do not use secrets.*. CODEOWNERS and a required
approval for non-owner PRs are not active today. On the first real external fork
PR, audit the fork workflow again, then add/activate those controls as a checklist
item; do not assume they already exist.
.github/labels.json is the canonical taxonomy for new and
relabeled issues. Use one type:*, priority:*, go:*, and release:* label where
applicable; use squad:{member} for ownership and an optional area:* label for product
scope. sync-squad-labels.yml reads that manifest for static labels and generates squad
member labels from the roster. The legacy bug, enhancement, and workstream:* labels
are deprecated in favor of type:bug, type:feature, and the smaller area:* vocabulary;
existing issues are not being mass-relabelled.
Changesets generates new CHANGELOG.md sections from reviewed release-note fragments. Please use a
conventional-commit-style prefix:
feat: ...— new functionalityfix: ...— bug fixesdocs: ...— documentation-only changeschore: .../refactor: ...— internal changes with no user-facing behavior changetest: ...— test-only changes
- .NET: follow the existing conventions in the file/module you're editing. Don't introduce a new formatting style into an existing file.
- .NET dependencies: every project restores with
RestorePackagesWithLockFile=true(seeDirectory.Build.props) and commits apackages.lock.json, and CI restores withRestoreLockedMode=trueso an unreviewed dependency version change fails the build instead of silently resolving a newer package. Use exactPackageReferenceversions (no floating1.*or open ranges) for new/changed dependencies. After adding, removing, or changing a package version, regenerate the affected lock file(s) withdotnet restore --force-evaluateand commit the updatedpackages.lock.jsonalongside the.csprojchange. - Node.js (
scripts/azure/): ESM (.mjs), no bash/PowerShell — this toolchain is intentionally 100% cross-platform Node.js (it fully replaced the earlier bash/PowerShell scripts). Read the module header comment at the top of the relevantscripts/azure/*.mjsfile before changing behavior — several non-obvious ordering/timing decisions are documented there specifically to avoid reintroducing past bugs. - Web: TypeScript + React, FluentUI components. Run
npm --prefix apps/web run lintbefore submitting.
- Do not hand-edit generated
CHANGELOG.mdrelease sections; add or correct the source changeset instead. - Do not add build/deploy logic outside
scripts/azure/— bash/PowerShell scripts were fully removed in favor of the Node.js toolchain; don't reintroduce a second toolchain. - Do not commit secrets (API keys, GitHub OAuth client secrets, connection strings) —
appsettings.Development.jsonis git-ignored for local secrets; use .NET user-secrets or environment variables instead. - Do not weaken auth/security checks (or otherwise take shortcuts) just to make a test or a manual verification pass — fix the real blocker instead.
Some contributions to this repo are made by AI agents rather than people. This project is
developed with Squad, a team of named agents (Trinity, Tank, Morpheus, Smith, Link,
Seraph, Scribe, Ralph, Rai, and others), and can optionally route work to GitHub's
@copilot coding agent when it is on the roster. This section documents how that
agent-driven flow works. It does not replace the human workflow above — human
contributors follow the same branch → up-to-date PR → squash-merge path in
Making a change and can skip this section.
Issue-driven lifecycle. Agent work is anchored to a GitHub issue and follows
issue -> branch -> PR -> review -> merge. The label-based automation in
.github/workflows/ drives it:
sync-squad-labels.ymlkeeps thesquad:{member}labels in sync with the roster in.squad/team.md.squad-triage.ymlreacts to thesquadlabel: the Lead agent routes the issue to a member (or to@copilotwhen it is a good fit), applies thesquad:{member}label, and adds a defaultgo:needs-researchverdict.squad-issue-assign.ymlreacts to asquad:{member}label by acknowledging the assignment (and, forsquad:copilot, handing the issue to the@copilotcoding agent).squad-label-enforce.ymlenforces mutual exclusivity within thego:,type:,priority:, andrelease:label namespaces.
Feature and bug issue templates add the squad label by default, so the triage workflow
routes them when filed. For issues filed outside those templates, add squad manually
to request Squad routing. Triage is a lightweight operating norm rather than a hard SLA:
handle P0 reports the same business day and route other new Squad issues within a few
business days.
The assigned agent branches as squad/{issue-number}-{slug}, commits with a
conventional-commit message that references the issue (Closes #{number}, including the
Co-authored-by: Copilot trailer), pushes, and opens a PR with gh pr create against
dev. The full lifecycle, spawn context, and merge commands live in
.squad/templates/issue-lifecycle.md; the
orchestration rules live in .github/agents/squad.agent.md.
Agent PRs are gated by the same CI as everyone else's.
Branches vs. worktrees. A locally run Squad agent (including a Copilot CLI agent)
must use one dedicated git worktree per issue under .worktrees/, reusing it
when collaborating on that issue. This prevents concurrent local agents from sharing a
working tree or index. A hosted agent (such as GitHub's @copilot coding agent) uses the
platform's isolated branch and environment instead — no local worktree applies. Human
contributors may use a worktree as a convenience, but a plain short-lived branch in the
main checkout is supported. The creation, reuse, dependency, team-root, and cleanup
mechanics live in .squad/templates/worktree-reference.md;
do not duplicate them here.
New feature workflow. Proposing a new feature or capability (agent or human):
- Open a GitHub issue first — no un-tracked feature work. Describe the user story/problem it solves.
- Add or update a spec under
specs/before or alongside the code. Specs are area-grouped, one file per user story, each linking its GitHub issue number — follow the existing files' format exactly (title;**Issue:**+**Area:**header;## User story,## Context / problem,## Scope(In/Out),## Acceptance criteria,## Notable edge cases), and add the story to the matching area section ofspecs/README.md. - Then follow the normal issue → branch → PR → review → merge lifecycle above,
including updating user-facing docs (
docs/guide/,README.md) in the same change as required by the Documenting your work guidance below.
New user-facing functionality that lands without a corresponding specs/ entry should
be flagged in review. This is a convention, not an enforced gate: the
docs-drift.yml nudge watches API/workflow/blueprint/
MCP code paths against docs/** only — it does not cover specs/, so nothing
mechanically blocks a spec-less feature PR. Reviewers are responsible for catching it.
Bug-fix workflow. Fixing a bug (agent or human):
- Open (or reuse) a GitHub issue describing the bug: repro steps and expected vs. actual behavior. Don't file untracked fixes for anything beyond a trivial/obvious one-liner (typo, broken link, obviously-wrong constant) — anything with behavioral nuance or a risk of regression gets an issue.
- Reference the issue in the commit/PR with
Closes #N(see Commit messages) so it auto-closes on merge. - Include a regression test that fails before the fix and passes after, whenever the bug is in code with a test suite — this is the existing "add or update tests for any behavior change" rule applied to fixes, and it is what QA (Smith's charter) means by preventing regressions. A fix with no test should say why one isn't feasible.
- After merge, the same lifecycle applies: CI-gated, and the issue closes
automatically via
Closes #N(or close it manually if the fix only partially addresses the issue).
Peer review and the reviewer-rejection protocol. Changes requested is ordinary
review feedback: the original author may revise the same PR normally, with no lockout.
Lockout occurs only when a Reviewer (Tester, Code Reviewer, Lead, or Rai for Responsible AI)
explicitly declares Rejected / independent rewrite required — for example, with the
exact PR comment marker REJECTED — requires independent rewrite. Then the original author
is locked out of the next revision, a different agent must produce it, and the Reviewer
chooses whether to reassign or escalate. The Coordinator enforces that rule mechanically.
The rejection marker must remain on the PR so the author rotation is auditable on GitHub
without Coordinator session history; a status:locked-out PR label may additionally be
used when the repository creates it. The full rules are in the "Reviewer Rejection Protocol"
section of squad.agent.md.
Rubber-ducking. Before a non-trivial or risky change ships, the Coordinator can invoke a
rubber-duck review pass — a dedicated critical-feedback agent whose only job is to hunt for
bugs, logic errors, and design flaws before anything is committed. It is invoked at the
Coordinator's discretion for higher-risk work, not automatically on every change.
Auditable decisions. Meaningful design decisions are recorded to the decisions inbox
(.squad/decisions/inbox/); Scribe periodically merges routine operational decisions into
the canonical .squad/decisions.md. Cross-cutting architecture or technical decisions that
should survive that ledger's compaction are promoted to numbered
ADRs. This keeps agent-driven changes traceable back
to the reasoning behind them.
Documenting your work. Docs are part of the definition of done, not a follow-up. When a
change affects user-facing behavior — npm scripts, CLI flags, setup/deploy steps, API routes
or config, the OAuth flow — update the relevant docs in the same change: the VitePress
guide under docs/guide/ (the source of truth for
https://sabbour.me/agentweaver, built with npm run docs:build / docs:dev /
docs:preview) and/or README.md (quick overview and links out). CONTRIBUTING.md and
RELEASING.md are the process docs; add inline code comments only where they genuinely
clarify intent (this repo's style avoids over-commenting). Some reference pages under
docs/reference/ are generated by node scripts/gen-docs.mjs — regenerate and commit them,
never hand-edit. The docs-drift.yml workflow backs this
up: it hard-fails a PR when a committed generated reference (e.g.
docs/reference/mcp-tools.md) is stale, and posts a non-blocking reminder when code in
doc-relevant paths (API endpoints, workflows, blueprints, MCP tools) changes without any
docs/** update.
Do not hand-edit generated CHANGELOG.md release sections. Add or correct the source changeset instead; the matching GitHub Release notes project that same section (see Releasing). Finally, keep decision records and docs
distinct: a .squad/decisions/inbox/ entry captures why a choice was made (an internal
audit trail) and never substitutes for updating docs/guide/ / README.md, which tell users
how to use the feature.
Open a GitHub issue or start a discussion — we're happy to help you get oriented.