Repository navigation
Description
cc @dcramer
Some code changes need an extra step. For example, a UI change needs visual QA. Today the agent often skips this step, and a user must ask for it in a follow-up message. Prompt steering does not make this reliable. Loops that re-check every turn cost too many tokens.
Current behavior
- Repo rules live in AGENTS.md and skills. They are prompt text, and the agent can miss them.
- Junior has no check that runs when a Turn changes code or creates a Code change.
- Junior memory already holds many "when X changes, also do Y" rules. Some of them are saved many times. This shows the gap repeats.
Related
- Add hooks for follow-up actions using a fast model #529 covers follow-up hooks after an action or an external Event, such as a push. It does not cover checks inside the Turn before the final Reply. It also does not cover rules that live in a Repository.
- feat(github): plugin-defined reactive behavior hooks #435 covers reactive GitHub behaviors that plugins declare.
Proposal (from the discussion with @dcramer and BYK)
Add change checks: small rule files in the target Repository, for example .agents/triggers/<name>.md. People write them. Junior can also generate them. Each check has:
on: harness points, such asgithub_createPullRequest, a push, or the final Reply when the Sandbox diff is not empty.paths: a free filter on changed files. Most Turns stop here and use no model.when(optional): one cheap-model question about the diff, such as "Can a user see this change?". Use it only when paths are too broad.action: a prompt, with an optional skill, such asvisual-web-qa.mode:obligationadds one Conversation note: "run this, or say why you skip it".subagentstarts a child agent and tells the parent.footershows the gap in the Reply.satisfied_by: a check on Conversation events that uses no model. An example is "the skill loaded and an image was sent after the latest diff". If it passes, nothing happens.- A record of fires, one per (Conversation, check, diff hash). A check fires again only when the diff changes, so no loop can occur.
The agent stays free. It can skip a check if it gives a reason. The worst cost is one extra model call for each diff.
Generated checks. This follows Lore:
- Lore finds repeated user instructions across sessions (
instruction-detect.ts). It finds repeated behavior, such as "the user always asks for tests after the change" (pattern-echo.ts). It sends these to a curator model. It does not create rules directly. - Lore stores rules in a file in the Repository (
.lore.md). Each rule has a stable id. Manual edits are imported back. - For Junior: a periodic job looks for follow-up messages after a Code change, such as "can you run visual QA?". It groups repeats and opens a PR that adds a check. Each generated check has
source: generatedand lists its evidence. Junior never changes a check withsource: human.
Lore parts to reuse
- A cheap-first gate (
semantic-lint/check.ts): free changed-file gate, then embedding filter, then a capped model judge. It never fails a build, so the false-positive rate can be measured first. - Save every verdict, including "no", so the same input is never judged twice (
contradiction.ts). - Add mid-conversation context as framed user-role blocks at the end of the conversation (
pipeline.ts). This keeps the prompt cache. Conversation notes already do something similar in Junior.
Existing Junior parts
- Conversation notes (
packages/junior/src/chat/conversations/notes.ts) can deliver the obligation. beforeToolExecutewithctx.decision.redirectcan gategithub_createPullRequest.- The plugin
heartbeathook withagent.dispatchcan run the generator job. - Missing: a way for that job to read across Conversations. Today
searchConversationEventsreads one Conversation at a time.
Example drafts from memory
I generated these from Junior memory. I checked the getsentry/junior paths against main. One memory was stale: it named a test file that no longer exists.
- Visual QA for
getsentry/sentrystatic/app/**and forpackages/junior-dashboard/src/client/**. These are the only two drafts that usewhen. - Schema change in
packages/*/src/db/schema/**: the diff must add a generated migration, andscripts/check-drizzle-migrations.mjsmust run. - Tests run before a push (any Repository).
- The cloudflared pin must change in both
evals-behavioral.ymlandevals-integration.yml. getsentry/sentry: split frontend and backend changes into separate PRs. Land the backend PR first.
Suggested rollout: start in shadow mode. Log what would fire, and inject nothing. Compare the result with real follow-up messages to measure misses and false fires.
via BYK.
--
Activity
sentry-junior commentedon Oct 28, 2026
This replaces my earlier comment. Junior cannot edit existing issue comments, so I posted a new one. The earlier comment can be deleted.
Example change checks that Junior generated from its saved memories.
These drafts were generated on 2026-10-28 from Junior's long-term memory and checked against
getsentry/juniormain.The
getsentry/sentrypaths come from memory only; I did not check them against that repo (.oxlintrc*is a guess).Each draft lists the memory ids it came from in
evidence. Keep a draft only when at least two separate memories agree, or when one memory is confirmed by repo docs or code.Proposed home:
.agents/triggers/<name>.mdin the target repo, next to AGENTS.md. The triggers then move with the repo's paths and are reviewed by its owners.Generated drafts and human-written triggers use the same format.
source: generateddrafts reach the repo only through a PR. Junior never changes a trigger that hassource: human.Fields:
on: harness events.pr.create=github_createPullRequest.push= a git push in the sandbox.turn.end= final reply while the sandbox diff is not empty.paths: stage-0 gate on changed files. Free.when: optional cheap-model question about the diff. Leave it out when the path gate is precise enough.mode:obligation= inject one note: "run it or say why you skipped it".subagent= start a child agent and tell the parent.footer= report the gap in the reply.satisfied_by: a check on the conversation events, with no model. When it passes, nothing fires.(conversation, trigger, diff hash).visual-qa-sentry-frontend
visual-qa-junior-dashboard
junior-schema-migration
tests-before-push
cloudflared-pin-pair
sentry-frontend-backend-split
sentry-oxlint-suppressions
Rejected or needs a human
pnpm gen:embed-widgets". I could not confirm the source path, so the path gate is missing. A human must addpaths.