Your AI agent keeps breaking rules you have already given it. You tell it to stop writing wrapper functions that do nothing but forward to another call; it agrees, and twenty minutes later it writes:
def get_user(user_id: int) -> User:
return fetch_user(user_id)
So you add a line to your ever-growing AGENTS.md in the hope it fixes it. It doesn't.
byoris the sheepdog for your flock of coding agents: you set the rules and it reins in any agent that attempts to stray in real time.
byor can do this reliably because the rules it creates are real executable checks,
not markdown prompts. You rarely write one of these rules by hand. If you tell your
agent to create a rule, or even give it critical feedback about code it has written,
it will use byor's skill to create the best automated system to keep your agent in
check.
The inner loop
Working with a coding agent is a loop: you set a goal and the agent runs until
it gets there. Almost everything that keeps code quality honest sits outside
that loop. You review the diff at the end, CI complains after the push, or you
run a cleanup prompt once the feature works. byor moves enforcement inside
the loop: a post-edit hook checks each edit as the agent makes it, and the
agent fixes the violation while it still has the context.
Where the feedback lands changes what happens to it:
- You review the change, not a cleanup. The code already follows your rules when you first read it, so review time goes to what the change does.
- One violation now gets fixed; a thousand at the end get triaged. An outer pass that hands the agent a long report invites it to fix the easy half and stop. The same feedback delivered one edit at a time just gets applied.
- Bad decisions get caught before they are load-bearing. When the agent reaches for a library you banned, the correction at the first import is one line. The same correction at review time, with a day of work built on top of that library, is a rewrite.
Three examples
Linters keep absorbing the rules general enough for everyone to agree on. The
rules worth writing yourself are the ones that name your choices, and byor
gives them the same enforcement a linter has. They come in three sizes.
A pattern. Suppose this codebase uses httpx. No general linter can know
that. A short ast-grep rule enforces it with no
holes, because import statements are a choke point: every use of a library
starts with one. Its agent_prompt tells the agent what to do instead of
leaving it to guess:
# .byor/rules/project/no-requests.yml
id: no-requests
language: Python
severity: error
message: This codebase uses httpx, not requests.
rule:
any:
- kind: dotted_name
regex: ^requests(\.|$)
inside: { stopBy: end, kind: import_statement }
- pattern: from requests import $$$NAMES
- pattern: from requests.$SUB import $$$NAMES
metadata:
byor:
agent_prompt: >
Use httpx instead. For simple calls the API is the same
(httpx.get, httpx.post); for anything repeated, use an httpx.Client
or httpx.AsyncClient with an explicit timeout. Do not add requests
to the dependencies.
The first clause matches the module name node inside any plain import, so
the aliased, submodule, and even comma-combined (import os, requests) forms
are all covered; the two patterns handle from-imports.
A shape. There is no string to grep for in the wrapper at the top of this
page. What makes it a violation is structure: a function whose body is a single
call to something else, in any form (return, await, a bare call,
yield from, with or without a docstring). ast-grep matches structure, so a
rule can say exactly that:
# .byor/rules/project/no-routing-functions.yml
id: no-routing-functions
language: Python
severity: warning
message: Do not create functions whose only behavior is routing to another call.
rule:
all:
- any:
- pattern: return $CALLEE($$$ARGS)
- pattern: return await $CALLEE($$$ARGS)
- pattern:
context: $CALLEE($$$ARGS)
selector: expression_statement
- pattern:
context: await $CALLEE($$$ARGS)
selector: expression_statement
- pattern:
context: yield from $CALLEE($$$ARGS)
selector: expression_statement
- any:
- all:
- nthChild: 1
- nthChild:
position: 1
reverse: true
- all:
- nthChild: 2
- nthChild:
position: 1
reverse: true
- follows:
kind: expression_statement
has:
kind: string
- inside:
kind: block
inside:
kind: function_definition
field: body
metadata:
byor:
agent_prompt: >
Remove this routing function and call the underlying implementation
directly. If the function must exist as a public API or integration
boundary, add real boundary behavior such as validation, translation,
authorization, retry policy, error handling, or instrumentation. Do not
preserve a wrapper whose only effect is changing argument order, defaults,
or names.
A script. Some rules are not about the text of the code at all. This check fails whenever the dependency list differs from the last commit, so an agent must stop and ask before adding a package:
# .byor/config.yml
checks:
- name: dependency-gate
extensions: [toml]
run: .byor/scripts/dependency-gate.sh
gate: false
#!/bin/sh
# The `dependencies = [...]` block, from its opening line to the first `]`.
deps() { awk '/^dependencies = \[/ { open = 1 } open { print } open && /\]/ { exit }'; }
[ -f pyproject.toml ] || exit 0
git rev-parse --verify --quiet HEAD >/dev/null 2>&1 || exit 0 # no commits yet: nothing to compare
committed=$(git show HEAD:pyproject.toml 2>/dev/null | deps)
current=$(deps <pyproject.toml)
[ "$committed" = "$current" ] && exit 0
echo "The dependency list in pyproject.toml differs from the last commit."
echo "If you added or removed a package without being asked to, revert it and ask the user first."
exit 1
gate: false marks a check that polices the agent rather than the code: the
post-edit hook runs it, but the pre-commit and CI gates byor generates leave
it out, where it would block a person adding a dependency on purpose. A rule
like this only makes sense inside the loop. examples/ has a second
one, five lines of shell that reject hand-edits to uv.lock; it never fires on
uv add run in a terminal, because the hook only sees the agent's own file
edits.
Everything above is real and exercised in CI: the rules against valid and invalid samples, the scripts in both directions. See examples/ for the annotated versions.
The first two are ordinary ast-grep rules, and they follow you everywhere you read code:
- IDE — set up your IDE with
ast-grep lspto seemessageas a diagnostic. - AI agent — a post-edit hook hands over the
agent_prompt, scoped to the lines it changed, so the agent fixes the violation before moving on. - Terminal —
ast-grep scanshows themessage.
ast-grep rules are byor's built-in kind; it also runs any linter, type checker, or
script you already use and folds their output into the same agent feedback.
Install
uv tool install byor && byor install # install the CLI, then set up the skill + agent hooks (once)
byor init # optional — only for repo-scoped or shared rules (see below)
byor bundles ast-grep, so Python 3.11+ is all you need to run it — the rules
themselves work in any language ast-grep supports (TypeScript, Go, Rust, and
more), not just Python. byor install registers your editor and agent
integrations machine-wide. byor init is optional: run it only when you want
rules or checks scoped to a repository, or shared with contributors — your
personal global rules and checks already work in every repo without it.
On a repo the team has not adopted byor for, byor init --private keeps the
whole footprint out of git (nothing tracked, ignored via .git/info/exclude);
see docs/sync-model.md.
docs/ai-agents.md covers what each step writes.
After that one-time bootstrap, let your AI coding agent handle the rest: open it
in the repo and say "set up byor". The skill verifies the install, runs
byor init if you want repo or team rules, and offers to import the preferences
you already wrote in your CLAUDE.md / AGENTS.md as enforced rules.
Terminal and editor
A rule under .byor/rules/ is an ordinary ast-grep rule, so the ordinary tools
read it:
ast-grep scan # lint the repo
ast-grep scan src/ # ...or a path
For live in-editor diagnostics, point your editor's ast-grep integration at
ast-grep lsp: rules light up as you type and reload when you edit them.
(editor setup.)
Rule scopes
The same rule format lives at three scopes:
| Scope | Lives in | Shared with |
|---|---|---|
project |
.byor/rules/project/ |
Your team (committed) |
local |
.byor/rules/personal/local/ |
You, this repo only |
global |
~/.config/byor/rules/ |
You, in every repo |
Global rules are your personal standards; byor makes them apply in every repo. Project and local rules override a global rule with the same ID, so a team policy or a local experiment takes precedence. See docs/rules.md for the rule workflow and docs/sync-model.md for how byor copies global rules into each repo.
Tags in metadata.byor.tags are arbitrary labels you own. byor uses them for
listing, profile setup, and repo-local exclusions; it does not reserve any tag
names. Use byor list --tags to see the vocabulary already present in a repo.
Profiles are named templates in your global config that apply private
repo-local exclusions at init time, or later with byor profile add. They are
useful when a repo should opt out of broad groups of global rules or checks
without deleting those personal standards everywhere:
profiles:
existing:
description: Low-friction defaults for mature repositories.
rules:
excluded_tags:
- legacy-risk
checks:
excluded_tags:
- strict
Packages are the opposite of a global rule: a named bundle of rules (and
optional checks) under ~/.config/byor/packages/ that a repo opts into
rather than getting everywhere automatically. byor package add <name> installs
one for you in a repo (personally, like a local rule — not committed); promote
its rules or checks with byor promote to share them with the team. Reach for a
package when a rule set is reusable but too situational to force on every repo.
See docs/rules.md.
With AI coding agents
Agents can both obey your rules and write new ones:
- Feedback. A post-edit hook runs
byor agent-checkafter the agent edits a file and feeds the diagnostics back into its context, scoped to the lines it changed, so it fixes violations before moving on. - Capture. A bundled skill turns durable feedback ("never do this", "always
do that") into an ast-grep rule: the agent drafts it, confirms once, and runs
byor add. When a linter or type checker fits better, the skill offers that instead. - Setup. The same skill onboards you: say "set up byor" and it checks the install, optionally inits the repo, and imports the mechanically checkable preferences from your existing CLAUDE.md / AGENTS.md as rules — and can clean up an existing repo on a throwaway branch so you start without a wall of warnings.
byor install wires up the agents you pick (once, machine-wide); byor hook
adds or drops one later.
byor install --agents claude-code,codex
byor hook install --agent copilot # add an agent later
byor hook uninstall --agent copilot # or remove one (--agent skill removes the skill)
byor supports five harnesses:
| Harness | Skill | Real hook | Diagnostic precision |
|---|---|---|---|
| Claude Code | yes | PostToolUse |
the edited lines |
| Codex | yes | PostToolUse |
the edited lines |
| Copilot CLI | yes | postToolUse |
the edited lines |
| OpenCode | yes | tool.execute.after plugin |
the changed file |
| Pi | yes | tool_result extension |
the changed file |
Cursor and Antigravity are not supported: neither exposes a post-edit hook that byor can reliably integrate with, so byor omits them until that changes.
A checks: section in .byor/config.yml (or your global config) runs extra
command-line tools (a linter, a type checker, anything) on the changed files and
folds their output into the same feedback. See
docs/ai-agents.md.
Continuous integration
Project rules are committed files that work with ast-grep, so CI doesn't need
byor: a fresh clone already has everything ast-grep scan reads. Scan with
--error so warnings fail the build (a plain scan exits 0 on warnings):
- uses: astral-sh/setup-uv@v6
- run: uvx --from ast-grep-cli ast-grep scan --error
byor init --gate generates this workflow and a matching .pre-commit-config.yaml
for you — promoting your effective rules and checks into committed config first,
so the gate stays byor-free but also covers your checks. Checks marked
gate: false stay out of both files: they police the agent inside the loop,
not the humans at the gate. See docs/sync-model.md.
Commands
Setup. You run these once to get going.
byor install Register byor's AI integrations (machine-wide)
byor init Initialize byor in a repository
byor init --private Keep byor to yourself; commit nothing (git info/exclude)
byor init --gate Distribute a byor-free pre-commit + CI gate to the team
byor hook Add or remove an agent integration
byor doctor Check that everything is wired up
byor profile List or apply configured profiles
byor package List or install opt-in rule/check packages
Rules. Your agent runs these as it captures and manages rules for you.
byor add Create a rule in a scope
byor list Show rules and where they come from
byor edit Open a rule in $EDITOR
byor remove Delete a rule
byor promote Move a personal/package rule or a check into shared config
byor exclude Disable a global rule in this repository
byor include Re-enable an excluded global rule
Automatic. byor runs these itself: the post-edit hook and self-heal.
byor agent-check Render diagnostics for your agent
byor sync Mirror global rules into the repo
Every command takes --help, and repo-operating commands take --repo PATH
(default: search upward from the current directory).
What's next
Today byor's inner loop has one mechanism: a deterministic post-edit hook. That already covers anything a rule, a linter, a type checker, or a script can express.
The harder strays are behavioral, not textual: an agent drifting off the plan you agreed on, stopping a loop early, editing files outside the scope you set. Teaching the sheepdog to herd those too is where byor is headed. If there is a rule you wish it could enforce, open an issue.
Documentation
- docs/rules.md — rule format, scopes, and the rule workflow
- docs/ai-agents.md — AI agent integration and
agent-check - docs/sync-model.md — copies, self-healing, and git hooks
- examples/ — reference rules (simple → advanced) and config setups
Release files for byor 0.3.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| byor-0.3.2.tar.gz | 95.4 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| byor-0.3.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 216.8 kB
Release files / byor-0.3.2.tar.gz
| Download URL | byor-0.3.2.tar.gz |
|---|---|
| Size | 95.4 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
72ed78325282d52af19acd010c2c4135d9bcdaa808ce4a0bd18e5d55c3b76307
|
|
BLAKE2b-256 checksum How to use checksums |
d94b767c506802c102427973d59c4da3b1b66d2fc13a5cecb54553fd1aba9f2e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.
Transparency logRelease files / byor-0.3.2-py3-none-any.whl
| Download URL | byor-0.3.2-py3-none-any.whl |
|---|---|
| Size | 121.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
67935cc6509fa6396c398d549dbdc9a599e78bddd5416a221a1f0738d9229801
|
|
BLAKE2b-256 checksum How to use checksums |
9c835a6561beb1164998b722397791c9a603f31147c73cebde0dc922c589d799
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Jul 10, 2026.
Transparency log