Team rules and decisions
A decision made in one person’s agent session lives in that session. The next session, or a colleague’s agent, never hears of it. Team rules give the decisions a home every agent reads: one repository for the organisation, one file per repository, and a read tool.
It is a convention plus a tool. There is nothing to switch on.
Where rules live
| Where | File | Applies to |
|---|---|---|
<org>/.rules (a repository in your organisation) | RULES.md, DECISIONS.md | Every repository of the organisation |
| Any repository | .forgejo/RULES.md, .forgejo/DECISIONS.md | That repository |
Files are read from each repository’s default branch, so a rule changes through a pull request like any other change, with review and history.
Every ## heading starts one rule (or decision); the heading names it. Text before the first ## heading is kept as a preamble.
# Rules
## Migrations
Every migration is expand-only. A column is dropped in a later release, never in the one that stops using it.
## Review
One approval from someone who did not write the change.How org and repository rules combine
The organisation’s rules and the repository’s are merged by heading. A repository rule with the same heading as an organisation rule replaces it (letter case and punctuation do not matter), so a repository can tighten or relax one rule without copying the rest. Repository-only rules come after the organisation’s.
<!-- web/.forgejo/RULES.md -->
## Review
Two approvals: this repository takes payments.get_team_rules: the tool agents call
| MCP tool | REST |
|---|---|
get_team_rules | GET /_app/api/v1/orgs/<org>/repos/<repo>/team-rules |
It returns every rule with its kind (rules or decisions), title, body, whether it came from the organisation or the repository (scope), whether it replaced an organisation rule (overrides_org), and its source (repository, path and branch). files lists every file looked for and whether it was found. digest changes whenever the merged rules change.
It needs findings:read (or orgs:read) and read access to the repository. Someone who can read the repository but not the .rules repository gets the repository’s rules only; the organisation’s files are then listed as not_readable.
Rules are instructions to your agents, with the same standing as the repository’s AGENTS.md, and anyone with write access to the .rules repository or the repository can change them. Give write access to .rules accordingly. A rule never grants a permission.
Put the rules in AGENTS.md or CLAUDE.md
Agents that read an AGENTS.md or CLAUDE.md file but do not call tools can carry the rules in that file. ?format=agents-md returns a Markdown block between two markers:
<!-- fremforge:team-rules:start -->
...
<!-- fremforge:team-rules:end -->The block is the same bytes for the same rules. To add it, or bring it up to date, run this from the repository root (it replaces only the block, and appends it the first time):
ORG=<org>; REPO=<repo>; FILE=AGENTS.md
curl -fsS -H "Authorization: Bearer $FREMFORGE_TOKEN" \
"https://frem.sh/_app/api/v1/orgs/$ORG/repos/$REPO/team-rules?format=agents-md" > .team-rules.md
touch "$FILE"
python3 - "$FILE" .team-rules.md <<'PY'
import sys
path, block = sys.argv[1], open(sys.argv[2]).read().strip()
cur = open(path).read()
s, e = '<!-- fremforge:team-rules:start -->', '<!-- fremforge:team-rules:end -->'
if s in cur and e in cur:
new = cur[:cur.index(s)] + block + cur[cur.index(e) + len(e):]
else:
new = cur.rstrip() + ('\n\n' if cur.strip() else '') + block + '\n'
open(path, 'w').write(new)
PY
rm .team-rules.mdCommit the result in a pull request. To check in CI that the copy is current, compare the digest: in the file’s block with the X-Fremforge-Rules-Digest header of the same request.
If you would rather not copy the rules into the file, one line is enough for agents that can call tools:
At the start of a task, call get_team_rules for this repository and follow the rules it returns.Decisions
DECISIONS.md takes the same shape as RULES.md: one ## heading per decision. Write the decision and the date, and why, so an agent that meets the question again does not decide it a second time:
## Kysely, not Drizzle (2026-10-01)
New database code uses Kysely. Drizzle stays only in the frozen migrations.