Repo Memory
Repo memory provides persistent file storage via Git branches with unlimited retention. The compiler auto-configures branch cloning/creation, file access at /tmp/gh-aw/repo-memory-{id}/, commits/pushes, and merge conflict resolution (your changes win).
Enabling Repo Memory
Section titled “Enabling Repo Memory”---tools: repo-memory: true---Creates branch memory/default at /tmp/gh-aw/repo-memory-default/. Files are stored within the branch at the branch name path (memory/default/). Files auto-commit/push after workflow completion.
Work-queue workers
Section titled “Work-queue workers”Workers using tools.work-queue.worker: true cannot use standalone
tools.repo-memory persistence: its ordinary push job is not attributed to a
Claim. Removing this restriction or adding a declaration alongside that push
job does not make the writes Claim-scoped.
For persistent snapshots, use a matching safe-outputs.scripts or custom
preparation job and safe-outputs.claim-adapters with effect-type: git_tree.
Pin an existing native base commit, a fixed target repository and a separate
branch prefix. Credential-free preparation emits declared file content; the
trusted handler creates an immutable Claim branch and independently verifies
its commit, complete tree and blobs. The installed Work must freeze a positive
numeric repository target and permit the output, intersecting the approved
profile, Subject and every worker-created ancestor. Preparation and restored
history are not authority or delivery proof.
The repository’s
ESLint Refiner workflow
preserves its legacy memory/eslint-refiner JSON/JSONL history and publishes new
eslint-refiner.json snapshots under memory/eslint-refiner-runs/claims/.
A read-only setup step restores both histories from exact native commit and
blob identities. It rejects truncated trees, foreign repositories, malformed
refs, altered bytes and exceeded bounds rather than silently dropping history.
Its limits are 128 snapshots, 1 MiB per file and 16 MiB total; archive older
history through an approved operator before exceeding them.
This migration preserves memory continuity but does not update the old
canonical branch. The Claim adapter cannot overwrite an existing Claim branch,
and memory/eslint-refiner-runs avoids conflicting with the existing
memory/eslint-refiner ref. Do not retain the ordinary unscoped memory push job
or use a moving branch name as the configured base revision.
Advanced Configuration
Section titled “Advanced Configuration”---tools: repo-memory: branch-name: memory/custom-agent-for-aw branch-prefix: tracking # Custom prefix instead of "memory" description: "Long-term insights" file-glob: ["*.md", "*.json"] max-file-size: 1048576 # 1MB (default 100KB) max-file-count: 50 # default 100 max-patch-size: 1048576 # 1MB max (default 10KB) target-repo: "owner/repository" create-orphan: true # default allowed-extensions: [".json", ".txt", ".md"] # Restrict file types (default: empty/all files allowed) format-json: true # Pretty-print .json files (default: false) validation: timeout-minutes: 1 json-schemas: - file: state.json schema: type: object required: [version, items] additionalProperties: false properties: version: { enum: [1] } items: type: array items: { type: string } script: | const data = JSON.parse(fs.readFileSync(path.join(memoryRoot, "state.json"), "utf8")); if (!Array.isArray(data.items)) throw new Error("state.json must contain an items array");---branch-prefix changes the default memory prefix and must be 4-32 alphanumeric, hyphen, or underscore characters; it cannot be copilot. allowed-extensions limits which file types can be stored, format-json: true pretty-prints .json files before commit, validation.json-schemas declares mandatory per-file JSON/JSONL validation, validation.script runs an optional custom JavaScript domain validator, and max-patch-size caps the total diff size for one push (default 10KB, max 1MB) to prevent oversized updates.
Structured Ledger
Section titled “Structured Ledger”Ledgers use tools.ledger, independently of repo-memory file storage. The legacy
tools.repo-memory.ledger declaration is no longer supported. For built-in
state models, declare log, set, map, table, counter, or notes:
---tools: ledger: findings: type: table key: id schema: type: object required: [id, status] properties: id: { type: string } status: { type: string }---Each ledger persists immutable records on its own ledgers/<name> Git branch.
Trusted preparation reconstructs a disposable, read-only SQLite database at
/tmp/gh-aw/ledgers/<name>/ledger.db. Query state for the built-in current
state and records for immutable history. Schemas validate operation values,
not operation envelopes; table requires a string primary-key field.
Configure bounded storage limits and maintenance cadence per ledger:
tools: ledger: findings: type: log schema: .github/schemas/ledger.schema.json max-segment-kb: 100 # default 100 KiB max-record-kb: 32 # default 32 KiB max-patch-kb: 10 # default 10 KiB compaction: schedule: weekly min-segments: 32 max-segments: 128Agents submit mutations only through the configured ledger safe-output tools.
Map ledgers expose ledger_map_put and ledger_map_delete; notes expose
ledger_note_add and ledger_note_vote. Other built-in types use typed
ledger_append operations. Writes are deferred: the immediate
response confirms queuing, not durability. The trusted push_ledger_changes
job validates and reconciles accepted requests against the latest branch state
before pushing. Queued writes are not immediately visible in the run’s projection.
Ledger compaction never runs in the agent or persistence job. Standalone tools.ledger ledgers are compacted by Agentic Maintenance through an untrusted plan job and a trusted apply job; see Ledger compaction.
Custom replay.script, replay.config, and compaction.script settings are
rejected. Use schemas for domain-specific values and the built-in compaction
policy for segment selection. Ordinary repo-memory validation.script
remains supported; it is a separate file-storage validator.
Existing raw-record ledgers can retain their generic projection by omitting
type and querying records.payload with SQLite JSON functions. Adding a
built-in type does not convert historical raw payloads into operations.
See Ledger replay projections for type
semantics and migration guidance.
File Glob Matching Rules:
- Patterns are matched against the relative path within the artifact directory — do not include the branch name.
- Slashless patterns (no
/in the pattern, e.g.*.json,*.md) are matched against the full relative path, so a single*only matches files at the artifact root (depth 0). They do not match files inside subfolders (depth 1+) — use a pattern containing/(e.g.**/*.json) for that. - Patterns containing
/(e.g.metrics/**,data/*.csv) are matched against the full relative path from the artifact root and work as standard glob expressions. - Absolute paths (patterns starting with
/) are not supported and are rejected at compile time and runtime.
Example: with the default filter ["*.json", "*.md"], the root-level file processed-discussions.json is persisted (depth 0 ✓), but discussion-task-miner/processed-discussions.json (depth 1) is not — use ["**/*.json", "**/*.md"] to also match files nested in subfolders.
Multiple Configurations
Section titled “Multiple Configurations”---tools: repo-memory: - id: insights branch-prefix: daily # Creates daily/insights branch file-glob: ["*.md"] - id: state file-glob: ["*.json"] max-file-size: 524288 # 512KB---Mounts at /tmp/gh-aw/repo-memory-{id}/ during workflow execution. The required id determines the folder name, and branch-name defaults to {branch-prefix}/{id} with memory as the default prefix. Files are stored inside the branch under that branch-name path. File globs match paths relative to the memory directory, so never include the branch name. Slashless patterns such as *.json match files at the memory root only. Use **/*.json to match nested JSON files; combine it with *.json to match both root and nested files. Patterns containing /, such as metrics/*.json, match the full relative path; * does not cross directories, while ** does. Files excluded by the glob or extension filters produce warnings listing their paths.
Behavior
Section titled “Behavior”Branches auto-create as orphans by default, or clone with --depth 1. After validating file-glob, max-file-size, and max-file-count, gh-aw auto-commits and pushes when changes are present and threat detection passes.
On a shared memory branch, file-glob and allowed-extensions scope normalization, validation, and persistence to eligible files. Existing files outside that scope are preserved, including files managed by other workflows.
Custom validation
Section titled “Custom validation”Use validation.json-schemas for required structural checks. Each declaration has an inline schema and may have a relative file path or glob (* matches within a path segment; ** spans segments). If file is omitted, the schema applies to every eligible .json and .jsonl file in the memory directory, excluding .git; omit format too so the format is inferred for each file. Otherwise, paths must match the configured file-glob and allowed-extensions policies and exist in the candidate; globs must match at least one file, and every match is validated. A json file must contain one non-empty valid JSON document. A jsonl file is checked record by record: LF and CRLF are accepted, a final newline is optional, an existing empty file has zero records, and blank physical lines or malformed records fail with a line number. Schema checks do not coerce, default, strip, or rewrite values. They run after configured filtering and format-json normalization, before an optional validation.script, and are repeated before upload/commit and in the repo-memory push job.
The file format is inferred from each matched file’s .json or .jsonl extension (case-insensitive), so a glob may match both formats. Optional format: json or format: jsonl overrides inference and is required for other extensions.
The supported schema vocabulary is deliberately limited to type (including type lists and null), primitive enum, required, nested properties, additionalProperties: false, object-schema items, and standalone oneOf or anyOf. Schemas are limited to 32 levels; alternatives cannot have sibling constraints. Numeric enum integers must be exactly representable by JavaScript, and values checked against numeric enums must not lose precision during JSON parsing. This is not full JSON Schema support: keywords such as $ref, const, format, pattern, numeric/string/array bounds, and uniqueItems are rejected at compile time. Use a script for cross-file rules, uniqueness, positive-ID checks, timestamps, or other unsupported constraints.
Use validation.script when generic storage and schema checks are not enough. The script is a JavaScript body executed with Node.js over the complete configured memory directory after format-json normalization and before artifact upload or branch commit. It runs in the agent job and is re-run in the repo-memory push job as defense in depth.
The script also checks the cloned baseline before the agent starts, without formatting or filtering the checkout. A baseline failure is shown to the agent with the validator’s output so it can repair memory and call push_repo_memory to verify the change. If an invalid baseline remains unchanged, the agent job warns and skips its memory artifact instead of failing after the agent exits. Any changed memory still requires successful validation before upload and again before the push.
Available globals are Node.js fs and path, plus memoryRoot/memoryDir, memoryId, and memoryKind ("repo"). These paths and the working directory point to a temporary copy of the eligible memory files, excluding the checkout’s .git directory. Changes to this copy are not persisted and do not modify the original memory directory. This copy is not a sandbox: validators remain trusted code with the runner’s filesystem permissions.
Environment variables available to the validator are intentionally limited to basic runner paths plus GH_AW_MEMORY_ROOT, GH_AW_MEMORY_DIR, GH_AW_MEMORY_ID, and GH_AW_MEMORY_KIND; GitHub tokens and write credentials are not passed to the validator subprocess. Network access follows the workflow runner’s normal network policy. The default timeout is 1 minute and may be set with validation.timeout-minutes (1-5 minutes).
Throw an exception, return false, time out, exit nonzero, or modify a memory file to reject persistence. Validator stdout and stderr are reported separately from built-in storage validation output so agents can distinguish domain-schema validation from size/count checks.
In the repo-memory push job, validation failures set validation_failed to true and include both non-empty output streams in validation_error and the Repo-memory push failed job summary. Push failures are also logged with their underlying error. Configured GH_TOKEN, GITHUB_TOKEN, and GITHUB_APP_TOKEN values and HTTP authorization credentials are redacted from these diagnostics; validators should never print secrets.
Commits use the GitHub GraphQL createCommitOnBranch mutation, so they are automatically Verified with GitHub’s GPG key and satisfy rulesets that require signed commits.
Comparison with Cache Memory
Section titled “Comparison with Cache Memory”| Feature | Cache Memory | Repo Memory |
|---|---|---|
| Storage | GitHub Actions Cache | Git Branches |
| Retention | 7 days | Unlimited |
| Size Limit | 10GB/repo | Repository limits |
| Version Control | No | Yes |
| Performance | Fast | Slower |
| Best For | Temporary/sessions | Long-term/history |
For fast 7-day caching without version control, see Cache Memory.
Troubleshooting
Section titled “Troubleshooting”- Branch not created: Ensure
create-orphan: trueis enabled, or create the branch manually. - Validation or patch-size failures: Keep changes within
file-glob,max-file-size(100KB default),max-file-count(100 default), andmax-patch-size(10KB default). - Changes not persisting: Confirm the directory path, let the workflow finish, and check the push job’s Repo-memory push failed summary and logs for the underlying error. Custom validation failures include both stdout and stderr; updates rejected by validation are not committed or pushed.
- Merge conflicts: Concurrent pushes are replayed onto the latest remote state, so your file changes win.
.jsonlfiles are merged withmerge=union, so rows written by concurrent runs are all kept; other file types keep the local version. - Many runs finishing at once: Pushes are not serialized. Each run commits with a compare-and-swap on the remote branch head and, if another run wins the race, re-reads the head, rebases its change with JSONL union merging, and retries with exponential backoff. To avoid conflicts entirely, have each run write its own file instead of all runs editing the same file, for example
runs/${{ matrix.worker }}.jsonlortargets/${{ matrix.target }}.jsonl. - GH013 — Commits must have verified signatures: This usually means the artifact included a symlink, executable file, or submodule entry, which forced a fallback to plain
git push. Remove the unsupported file type and re-run.
Security
Section titled “Security”Do not store sensitive data in repo memory. It follows repository permissions, so use private repositories when appropriate, avoid secrets, set constraints such as file-glob, max-file-size, max-file-count, and max-patch-size, consider branch protection, and use target-repo when you want isolation.
Examples
Section titled “Examples”See Deep Report and Daily Firewall Report for long-term insights and historical data tracking.
Learn More
Section titled “Learn More”See Cache Memory for 7-day cache storage, Frontmatter for full configuration details, and Safe Outputs for output automation.