Skip to content

OpenCode Repository Memory: A Dumb PR Review Workflow

The general memory model is capture, distill, and approve. This page turns that model into a small repository-level OpenCode setup. It intentionally starts with a dumb proof: plain files, shell scripts, and explicit prompts. There is no automatic rule promotion and no database.

Research scope: This is a general workflow pattern documented by myblog. The .opencode/, scripts/, and .agent-memory/ paths below are illustrative files for a separate target repository or workspace. They are not part of myblog's own agent harness.

What the proof should demonstrate

The workflow has separate processes with separate responsibilities:

  1. Collect raw OpenCode events and PR review notes without interpreting them.
  2. Categorize an inbox item into a small, human-selected category.
  3. Propose a short candidate rule without editing the repository harness.
  4. Integrate each candidate interactively into AGENTS.md, a skill, or another instruction file.

The first version can be run by hand. Once the file flow is trustworthy, a hook can automate only the collection step.

Repository layout

.opencode/
  commands/review-memory.md
  plugins/review-capture.ts
  skills/pr-review-memory/SKILL.md
scripts/
  review-memory/
    collect-event.sh
    collect-review.sh
    categorize.sh
    integrate.sh
.agent-memory/
  reviews/
    inbox/
    categories/
    proposals/

Keep .agent-memory/reviews/inbox/ and generated session material ignored. Keep approved changes in normal repository files so they can be reviewed in the same PR as any other change.

Step 1: prove collection with local files

Start without OpenCode. Export or paste a PR review into a Markdown file, then copy it into the inbox:

gh pr view 123 --comments > /tmp/pr-123-review.md
./scripts/review-memory/collect-review.sh /tmp/pr-123-review.md

The collector should do nothing except copy the source and add a timestamp:

#!/usr/bin/env bash
set -euo pipefail

source_file="${1:?usage: collect-review.sh REVIEW_FILE}"
root="$(git rev-parse --show-toplevel)"
stamp="$(date -u +%Y%m%dT%H%M%SZ)"
out="$root/.agent-memory/reviews/inbox/pr-review-$stamp.md"

mkdir -p "$(dirname "$out")"
cp -- "$source_file" "$out"
printf 'Collected %s\n' "$out"

At this stage, a human can inspect the copied file and delete bad or sensitive input before any later process sees it.

Step 2: add a repository-level OpenCode hook

OpenCode loads JavaScript or TypeScript plugins from .opencode/plugins/. A minimal project plugin can record that a session became idle and delegate the actual file write to the same shell-based collector:

import type { Plugin } from "@opencode-ai/plugin";

export const ReviewCapture: Plugin = async ({ $, worktree }) => ({
  "session.idle": async () => {
    await $`${worktree}/scripts/review-memory/collect-event.sh opencode session.idle`;
  },
});

The event collector remains intentionally boring:

#!/usr/bin/env bash
set -euo pipefail

source_name="${1:?usage: collect-event.sh SOURCE EVENT}"
event_name="${2:?usage: collect-event.sh SOURCE EVENT}"
root="$(git rev-parse --show-toplevel)"
out="$root/.agent-memory/reviews/inbox/events.jsonl"

mkdir -p "$(dirname "$out")"
printf '{"source":"%s","event":"%s","at":"%s"}\n' \
  "$source_name" "$event_name" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$out"

This hook does not summarize conversations or edit instructions. It only leaves an auditable marker that a separate process can inspect later. Verify the hook against the installed OpenCode version because plugin event payloads and available event names can change.

Step 3: categorize in a separate process

Do not classify while collecting. Use a small interactive script after the inbox has been inspected:

#!/usr/bin/env bash
set -euo pipefail

input="${1:?usage: categorize.sh INBOX_FILE}"
root="$(git rev-parse --show-toplevel)"
name="$(basename "$input")"

PS3="Choose a category: "
select category in rules workflow testing security docs other; do
  if [[ -n "$category" ]]; then
    mkdir -p "$root/.agent-memory/reviews/categories/$category"
    cp -- "$input" \
      "$root/.agent-memory/reviews/categories/$category/$name"
    printf 'Categorized as %s\n' "$category"
    break
  fi
done

Run it explicitly:

./scripts/review-memory/categorize.sh \
  .agent-memory/reviews/inbox/pr-review-20260728T040000Z.md

The category list is deliberately small. Add a category only when repeated reviews show that the current list is insufficient.

Step 4: write a proposal, not a harness edit

A proposal can be written by a person or by an OpenCode session. The output is just a short Markdown file in proposals/:

# Candidate rule

- Run `make build` before deploying documentation changes.

Evidence: PR #123, repeated reviewer comment about missing build validation.
Suggested target: AGENTS.md

An optional project command can constrain OpenCode to this behavior:

---
description: Propose repository memory from a reviewed PR note
---

Read the supplied PR review note. Write only one or more candidate rules to
.agent-memory/reviews/proposals/. Do not edit AGENTS.md, skills, or any other
harness file. Include the evidence and a suggested target for each rule.

This command is a proposal step, not an approval step.

Step 5: integrate with a human in the loop

The integration script shows each candidate, asks whether it should be kept, and asks where it belongs. It refuses to silently choose a target:

#!/usr/bin/env bash
set -euo pipefail

proposal="${1:?usage: integrate.sh PROPOSAL_FILE}"
root="$(git rev-parse --show-toplevel)"

while IFS= read -r line; do
  [[ -z "$line" || "$line" == \#* || "$line" != "- "* ]] && continue

  printf '\nCandidate:\n%s\n' "$line"
  read -r -p "Add this candidate? [y/N] " approve
  [[ "$approve" =~ ^[Yy]$ ]] || continue

  PS3="Choose a harness target: "
  select target in "AGENTS.md" \
      ".opencode/skills/pr-review-memory/SKILL.md" \
      ".github/copilot-instructions.md" "skip"; do
    [[ -n "$target" ]] || continue
    [[ "$target" == "skip" ]] && break

    if [[ ! -f "$root/$target" ]]; then
      printf 'Target does not exist; skipping: %s\n' "$target"
      break
    fi

    printf '%s\n' "$line" >> "$root/$target"
    printf 'Added to %s\n' "$target"
    break
  done
done < "$proposal"

The resulting edits are ordinary Git changes:

git diff -- AGENTS.md .opencode/skills .github/copilot-instructions.md

A maintainer can still reject, rewrite, or move a rule in the PR. The script is only a mechanical prompt around that decision; it is not a policy engine.

A minimal skill target

If the integration target is an OpenCode skill, create and review its normal frontmatter before allowing the script to append rules:

---
name: pr-review-memory
description: Apply approved project rules learned from PR review
---

## Approved rules

Project skills live under .opencode/skills/<skill-name>/SKILL.md. Keep the skill focused on repeatable procedure; keep project facts and stable commands in AGENTS.md when they should be loaded for every task.

Guardrails for the dumb version

  • Raw inbox files are input, not instructions.
  • The collector never categorizes or summarizes.
  • The proposal process never edits harness files.
  • The integration process asks about every candidate and target.
  • Approved rules are reviewed as normal Git diffs.
  • Redact credentials, customer data, and prompt-injected text before proposal.

Only after this proof is useful should a team consider automatic PR retrieval, semantic search, or a hosted memory service.

References