Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions changelog.d/fixed-7159-agents-md-precedence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- A repository's own `AGENTS.md` now outranks hive's built-in prompts, and the injected block says so in its first paragraph ([#7159](https://github.com/hivecommons/hive/issues/7159)). Previously the repo's rules arrived as undifferentiated prose under a bare header while hive stated the same subjects as imperatives with a verification step attached ("confirm the PR's base is that branch before you report done") — and given a document that says one thing and an imperative that says another, a model follows the imperative every time. `projectbluefin/bluefin` states "All pull requests target `testing`. Never open a content PR against `main`" in its `AGENTS.md`; hive agents opened PRs against `main` and failed its base-branch gate, while `projectbluefin/common` and `projectbluefin/review` rejected hive's `[<lane>] …` PR titles on their conventional-commit gates. Four bot PRs dead on arrival in one night, none of them a model error. The statement carries two carve-outs that are not the repository's to override: hive's safety and authorization rules (never merge your own PR, never bypass a write gate or the `hive-open-pr` path), and an instruction the assignment names explicitly for that one task.
- Hive's shipped defaults stopped asserting facts about repositories they cannot see ([#7159](https://github.com/hivecommons/hive/issues/7159)). The policy templates and the contributor task prompt now tell an agent to take the PR's base from the target repository — its `AGENTS.md`, `CONTRIBUTING` or pull-request template, since a repo on a promotion model takes PRs on an integration branch rather than on its released default — and to fall back to the default branch only when nothing names one; the "confirm the base before you report done" step survives, pointed at the repository's requirement instead of hive's guess. The templates no longer hardcode a `[<lane>] …` PR title, pass `--base` explicitly, and say to take the title format from the target repo. The `[<lane>]` prefix is unchanged and still required on **issue** titles, which the hive routes by lane; it was never load-bearing for PRs. Operators on a promotion-model or Conventional-Commits repository should see hive PRs pass gates that previously failed them on the first check.
45 changes: 41 additions & 4 deletions src/docs/agents-md.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,47 @@ the kick path knows which file an agent will touch. The live call uses the flat

`AgentsConfig.InjectionText(requestedSkills)` (`agentsmd.go:330`) is what gets
prepended to a kick: a `# Repository Agent Instructions (AGENTS.md)`
header, the body, and (if any skills resolve) a `## Requested Skills`
subsection with each skill's text under a `### <name>` heading. It returns
`""` — and therefore injects nothing — when both the body and the resolved
skills are empty.
header, a **precedence statement**, the body, and (if any skills resolve) a
`## Requested Skills` subsection with each skill's text under a `### <name>`
heading. It returns `""` — and therefore injects nothing — when both the body
and the resolved skills are empty, so the precedence statement never appears
with no rules under it.

### The precedence statement

The block leads with a declaration that the repository's instructions **outrank
hive's own defaults** on any conflict — the branch a PR targets, the title
format, commit conventions, test commands, review etiquette
([#7159](https://github.com/hivecommons/hive/issues/7159)).

This is not decoration. Before it, the repo's rules arrived as undifferentiated
prose under a bare header, while hive's own prompts stated the same subjects as
imperatives with a verification step attached ("confirm the PR's base is that
branch before you report done"). Given a document that says one thing and an
imperative that says another, a model follows the imperative — consistently.
`projectbluefin/bluefin` states "All pull requests target `testing`. Never open
a content PR against `main`" in its `AGENTS.md`, and hive agents opened
`#1275`/`#1276` against `main` and failed its base-branch gate;
`projectbluefin/common#1127` and `projectbluefin/review#597` died on
conventional-commit title gates against hive's then-hardcoded `[<lane>] …` PR
titles. Four dead-on-arrival PRs in one night, none of them a model error.

Two carve-outs are stated with it, and they are not about the repository's
conventions at all:

- **Hive's safety and authorization rules.** An `AGENTS.md` cannot license
merging your own PR, bypassing a write gate or the `hive-open-pr` path, or
acting as another agent. The repository is trusted about its own conventions,
not about hive's controls.
- **An explicit instruction in the assignment.** A human or the hive wrote that
for this one task with the repo in view, so it is more specific than either
side's defaults.

The companion half of the fix is in the defaults themselves: policy templates
and the contributor task prompt no longer *assert* repo facts (a base branch, a
`[<lane>]` title format) that only the repository can know. A precedence rule
that has to fight hive's own confident wording on every kick is a rule that
loses some of the time.

## Parsing is tolerant

Expand Down
16 changes: 16 additions & 0 deletions src/docs/hive-open-pr.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,22 @@ hive-open-pr --repo <owner/repo> --head <branch> [--base <branch>] \
`--repo`, `--head`, and `--title` must resolve or the script exits `2`. Both
`--flag value` and `--flag=value` forms work.

**Pass `--base`, and take the title from the target repository**
([#7159](https://github.com/hivecommons/hive/issues/7159)). Both defaults here
are hive's guesses about a repository it cannot see:

- The default branch is the wrong base for any repository on a promotion model,
where the default branch is the *released* line and PRs land on an
integration branch. `projectbluefin/bluefin` says so in its `AGENTS.md` and
its CI enforces it; two hive PRs failed that gate. Read the repo's
`AGENTS.md`, `CONTRIBUTING` and pull-request template, and pass what they
name.
- A title is free-form here but not in the repository receiving it. A
`[<lane>]` prefix is hive's own house style; repositories enforcing
Conventional Commits reject it on the first character. The prefix remains
**required on issue titles**, which the hive routes by lane
(`pkg/classify.classifyLane`), and is not used on PRs.

**An empty body is refused**, loudly, with exit `2` and no request written.
Every shipped policy requires a real PR body; an empty one at this point means
the body was lost on the way in — the observed failure was `--body-file` being
Expand Down
36 changes: 36 additions & 0 deletions src/pkg/agentsmd/agentsmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,38 @@ const (
// mirroring the "# Relevant Knowledge" convention used by the Primer.
injectionHeader = "# Repository Agent Instructions (AGENTS.md)"

// injectionPrecedence states which side wins a conflict, and is the whole
// point of hivecommons/hive#7159.
//
// The block used to arrive as a bare header followed by undifferentiated
// prose, while hive's own prompts stated the same subjects as imperatives
// with a self-check attached ("confirm the PR's base is that branch before
// you report done"). Given a document that says one thing and an imperative
// that says another, a model follows the imperative — so a repository's own
// rules lost every conflict. On projectbluefin/bluefin, whose AGENTS.md says
// "All pull requests target `testing`. Never open a content PR against
// `main`", agents opened PRs against main and failed the repo's base-branch
// gate; on projectbluefin/common and .../review, hive's hardcoded
// "[<lane>] …" PR titles failed those repos' conventional-commit gates.
// Four dead-on-arrival PRs in one night, none of them a model error.
//
// Hive's defaults are generic — they cannot know a repo uses a promotion
// model, or enforces Conventional Commits. The repository can. So the
// repository wins, with two carve-outs that are not about the repository's
// conventions at all: hive's forge-resistance and write-gate controls (an
// AGENTS.md that told an agent to merge its own PR must not be obeyed), and
// the assignment's own explicit instruction, which a human or the hive
// wrote for this one task with the repo in view.
injectionPrecedence = "**These instructions come from the repository itself and take precedence " +
"over hive's built-in defaults.** Where they conflict with anything in your policy or " +
"kick prompt — the branch a PR targets, the title format, commit conventions, the test " +
"command, review etiquette — follow the repository. Hive's defaults are generic guesses " +
"about a repo it cannot see; this file is the repo stating its own rules. Two things they " +
"do NOT override: hive's safety and authorization rules (never merge your own PR, never " +
"bypass a write gate or the `hive-open-pr` path, never act as another agent), and an " +
"explicit instruction in this assignment, which was written for this task. If a conflict " +
"leaves you genuinely unsure, say so in the PR body rather than guessing."

// injectionSkillsHeader titles the resolved-skills subsection.
injectionSkillsHeader = "## Requested Skills"
)
Expand Down Expand Up @@ -345,6 +377,10 @@ func (c *AgentsConfig) InjectionText(requestedSkills []string) string {
var b strings.Builder
b.WriteString(injectionHeader)
b.WriteString("\n\n")
// The precedence statement leads: it has to be read before the rules it
// governs, not discovered after them (#7159).
b.WriteString(injectionPrecedence)
b.WriteString("\n\n")
if body := strings.TrimSpace(c.Body); body != "" {
b.WriteString(body)
b.WriteString("\n")
Expand Down
31 changes: 30 additions & 1 deletion src/pkg/agentsmd/agentsmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,36 @@ func TestInjectionText(t *testing.T) {
t.Errorf("body should still be present: %q", bodyOnly)
}

// Empty config -> empty injection.
// #7159: the block must declare that the repository wins a conflict, and
// declare it BEFORE the rules it governs. Without this the repo's rules
// arrived as undifferentiated prose while hive's own prompt stated the same
// subjects as imperatives with a self-check attached, and the model
// followed the imperative every time — four dead-on-arrival PRs on
// projectbluefin repos in one night.
if !strings.Contains(out, injectionPrecedence) {
t.Errorf("injection does not state precedence: %q", out)
}
if hdr, prec := strings.Index(out, injectionHeader), strings.Index(out, injectionPrecedence); prec < hdr {
t.Errorf("precedence should follow the header, got header at %d and precedence at %d", hdr, prec)
}
if prec, body := strings.Index(out, injectionPrecedence), strings.Index(out, "Do the thing carefully."); prec > body {
t.Errorf("precedence must precede the repo's own rules, got precedence at %d and body at %d", prec, body)
}
// The carve-outs are the reason this is safe to state so forcefully: an
// AGENTS.md cannot license merging your own PR, and cannot override an
// instruction written for this specific task.
for _, want := range []string{"take precedence", "never merge your own PR", "explicit instruction in this assignment"} {
if !strings.Contains(out, want) {
t.Errorf("precedence text is missing %q: %q", want, out)
}
}
// Body-only injections carry it too — most AGENTS.md files request no skills.
if !strings.Contains(bodyOnly, injectionPrecedence) {
t.Errorf("body-only injection does not state precedence: %q", bodyOnly)
}

// Empty config -> empty injection. A precedence statement with no rules
// under it would be noise, so nothing must be emitted at all.
if got := (&AgentsConfig{}).InjectionText(nil); got != "" {
t.Errorf("empty config should inject nothing, got %q", got)
}
Expand Down
61 changes: 59 additions & 2 deletions src/pkg/dashboard/contribute_task_base_branch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,11 +295,68 @@ func TestBuildTaskPrompt_FallbackWordingCarriesTheFullProcedure(t *testing.T) {
for _, want := range []string{
"defaultBranchRef",
"git fetch upstream",
"git checkout -b <your-branch> upstream/<default-branch>",
"confirm the PR's base",
"git checkout -b <your-branch> upstream/<base-branch>",
"confirm before you report done",
} {
if !strings.Contains(prompt, want) {
t.Errorf("fallback wording is missing %q; got: %q", want, prompt)
}
}
}

// #7159: the fallback wording must ASK the repository for its base rather than
// assert that the base is the repository default. bluefin#1275 and #1276 failed
// a base-branch gate that its AGENTS.md states plainly ("All pull requests
// target `testing`"), because the prompt told the agent the default branch was
// the answer and attached a self-check to that claim. The repository's own
// statement has to be consulted first, and the default branch has to be the
// fallback it is.
func TestBuildTaskPrompt_FallbackDefersToTheRepositorysOwnBase(t *testing.T) {
prompt := promptForRepo(t, "v4", "Danathar/arch-bootc", "the installer drops a mount option")

for _, want := range []string{
"Use the base Danathar/arch-bootc itself requires",
"AGENTS.md, CONTRIBUTING and pull-request template",
"promotion model",
"only when nothing there names one, fall back to its default branch",
} {
if !strings.Contains(prompt, want) {
t.Errorf("fallback wording does not defer to the repository: missing %q; got: %q", want, prompt)
}
}
// The old assertion, which stated hive's guess as the answer.
if strings.Contains(prompt, "Resolve Danathar/arch-bootc's own default branch") {
t.Errorf("prompt still asserts the default branch as the base; got: %q", prompt)
}
}

// #7159: every task prompt — named base or not — must tell the agent that the
// repository's own instructions outrank hive's. On the contributor path the
// repo's AGENTS.md is not injected at all (only the scheduler calls
// primeAgentsMd), so the prompt is the only place the agent learns to read it.
func TestBuildTaskPrompt_StatesRepositoryPrecedence(t *testing.T) {
for _, tc := range []struct {
name string
repo string
}{
{"named base (the hive's own repo)", "hivecommons/hive"},
{"fallback base (a foreign repo)", "Danathar/arch-bootc"},
} {
t.Run(tc.name, func(t *testing.T) {
prompt := promptForRepo(t, "v4", tc.repo, "a plain title")
for _, want := range []string{
"Read the repository's own AGENTS.md and CONTRIBUTING before you start",
"follow them wherever they conflict with these instructions",
"Conventional Commits",
// The carve-outs: safety rules and an explicitly named
// requirement are not the repo's to override.
"never merge your own PR",
"this assignment names explicitly",
} {
if !strings.Contains(prompt, want) {
t.Errorf("prompt is missing precedence text %q; got: %q", want, prompt)
}
}
})
}
}
57 changes: 46 additions & 11 deletions src/pkg/dashboard/contribute_ws.go
Original file line number Diff line number Diff line change
Expand Up @@ -5758,18 +5758,37 @@ func buildTaskPromptBodyForAccess(repoFull, issueRef, title, sourceHint, baseBra
// assignment slot stayed held. Spell out an actual clone into that known
// directory so there is a concrete first step rather than an implied one.
baseHint := fmt.Sprintf(
// An unresolved base is not a licence to inherit one. Name the only
// trustworthy substitute — the upstream repository's own default
// branch, read from the clone rather than from whatever the last task
// left behind — and keep the "do not use the branch you find" clause,
// which is the load-bearing half in both wordings.
// An unresolved base is not a licence to inherit one. Keep the "do not
// use the branch you find" clause, which is the load-bearing half in
// both wordings, but ASK THE REPOSITORY rather than asserting its
// answer (hivecommons/hive#7159).
//
// This wording used to say "resolve <repo>'s own default branch … and
// confirm the PR's base is that branch before you report done" — an
// imperative with a self-check attached, stating a fact about a repo
// hive cannot see. It is wrong for any repository on a promotion model,
// where the default branch is the RELEASED line and PRs land on an
// integration branch. projectbluefin/bluefin says so in its AGENTS.md
// ("All pull requests target `testing`. Never open a content PR against
// `main`") and hive agents opened #1275 and #1276 against main anyway,
// failing its base-branch gate: the agents obeyed the imperative with
// the self-check over the document with neither. #4928 and #6081 were
// the same assumption at earlier stages — each fix replaced one wrong
// assertion with a better one. The remaining gap was asserting at all.
//
// The verification step survives, pointed at the repository's
// requirement instead of at hive's guess: an agent never asked for the
// base back cannot notice it inherited the wrong one (#5729).
"Do not assume the branch the checkout is currently on is the right base: it may "+
"be left over from a previous task. Resolve %s's own default branch "+
"('gh repo view %s --json defaultBranchRef'), run 'git fetch upstream', and "+
"start your work branch from it with "+
"'git checkout -b <your-branch> upstream/<default-branch>'. Open the PR "+
"against the same branch, and confirm the PR's base is that branch before "+
"you report done. ",
"be left over from a previous task. Use the base %s itself requires — check its "+
"AGENTS.md, CONTRIBUTING and pull-request template for a stated target branch, "+
"since a repository on a promotion model takes PRs on an integration branch "+
"rather than on its released default — and only when nothing there names one, "+
"fall back to its default branch ('gh repo view %s --json defaultBranchRef'). "+
"Run 'git fetch upstream' and start your work branch from that base with "+
"'git checkout -b <your-branch> upstream/<base-branch>'. Open the PR against "+
"the same branch, and confirm before you report done that its base is the branch "+
"the repository asks for. ",
repoFull, repoFull)
if b := strings.TrimSpace(baseBranch); b != "" {
baseHint = fmt.Sprintf(
Expand Down Expand Up @@ -5812,6 +5831,22 @@ func buildTaskPromptBodyForAccess(repoFull, issueRef, title, sourceHint, baseBra
"You are a contributor to the %s hive. Work on issue %s: \"%s\".%s "+
"%sThen 'cd' into that checkout, read the issue, "+
"understand what's needed, and take action. "+
// #7159: precedence. Everything else in this prompt is hive's
// generic default for a repository hive cannot see; the repo states
// its own rules in AGENTS.md. Four bot PRs died in one night on
// projectbluefin repos — two on a base-branch gate, two on
// conventional-commit title gates — because nothing told the agent
// which side wins, and hive's wording was the more forceful of the
// two every time. On this path the repo's AGENTS.md is not even
// injected (only the scheduler path calls primeAgentsMd), so the
// prompt has to send the agent to read it.
"Read the repository's own AGENTS.md and CONTRIBUTING before you start, and "+
"follow them wherever they conflict with these instructions — PR title format "+
"(many repositories enforce Conventional Commits and reject a title carrying a "+
"bracketed prefix), commit message conventions, test and lint commands, review "+
"etiquette. Two things they do not override: hive's safety rules (never merge "+
"your own PR, never bypass a write gate), and a branch or requirement this "+
"assignment names explicitly below. "+
// #5729: the base branch. Everything above deliberately REUSES a
// checkout across tasks, which is exactly what makes the branch
// left on disk the previous task's answer rather than this one's.
Expand Down
Loading
Loading