memory
Provider-agnostic durable-memory access for the Shipwright workflow. Use when loading context at phase start or storing distilled memories at phase end via /sw-memory-sync. Routes through configured provider; never calls providers directly.
- License 许可
- MIT
- 最近更新
- 2026-11-03
- Stars
- ★ 2
- Forks
- 0
01 前言 Front Matter
- name
- memory
- metadata
- { "shipwright-capability": { "version": 1, "metadata": { "skill": "memory", "selectionFamily": "memory" }, "triggers": [ { "type": "phase_default", "command": "sw-memory-sync", "selectionFamily": "memory" } ] } }
- description
- Provider-agnostic durable-memory access for the Shipwright workflow. Use when loading context at phase start or storing distilled memories at phase end via /sw-memory-sync. Routes through configured provider; never calls providers directly.
02 资源文件 1
- 参考文档CAPABILITIES.md14.9 KB
03 SKILL.md 正文
memory-preflight
The single entry point every Shipwright command uses to read and write durable memory. It hides the
provider behind the capability spec in CAPABILITIES.md, so swapping providers is a
config change, never a command edit.
Model tier: cheap — resolve via python3 scripts/sw_bootstrap.py resolve-model-tier.py -- --skill memory. When using the Task tool for subagent dispatch, resolve concrete model IDs from models.tiers in config (never semantic tier names in subagent model: frontmatter).
Resolve the provider (first step, always)
- Load workflow config via
shipwright_paths.load_workflow_config(root)(PRD 342 path authority) and readmemory.provider,memory.project,memory.defaultScope. When no config exists, check.cursor/sw-memory.provider(per-repo marker) — if present and the marker text is literallyin-repo, provider isin-repowith project = workspace basename. - Write binding hard-cut (PRD 279 R9–R12 / R14–R17; shared brainstorm numbering with PRD 278):
mutating paths (
/sw-memory-syncstore, ordinarystore/importwhenroot=is set) MUST callscripts/memory_preflight.pywrite assert (assert_write_binding/memory_sync_store_path/resolve_provider --for-write) immediately before adapter dispatch. Binding is either configmemory.provider+ non-emptymemory.project, or marker.cursor/sw-memory.provider=in-repo(basename project only). Unbound writes refuse closed with a typed cause (memory-write-unbound,memory-write-marker-remote-needs-project,memory-write-project-missing,memory-write-global-refused) and an audit line under.cursor/sw-memory-write-audit.jsonl. There is no ambient Recallium write default and no soft-warn write window.__global__is explicit-config only. - Read / display resolve (D2): unbound reads/preflight may resolve for display without authorizing
writes (
resolve_providerwithout--for-write→source=unbound,displayGuidance=in-repo,writeAuthorized=false). Do not ambient-default display resolve torecalliumwhen unbound. - Catalog + validator (before adapter or rule-fetch): resolve the provider id through
scripts/memory_provider_register.pyagainst.sw/memory-provider-catalog.json(emit:core/sw-reference/memory-provider-catalog.json). This runs on config write, startup/preflight, and hook trust — unknown, malformed, or unregistered ids fail closed (no cross-provider fallback). - Load the adapter at
providers/<memory.provider>.mdfrom the plugin only after registration passes. It defines the concrete tool calls and declares capability flags. - Hook / rule-fetch trust: catalog membership alone does not grant hook trust —
capability_trust.pygates andvalidate_registrationmust pass beforerules_script_for_providerresolves the out-of-band rules script. There is no silent swap to another provider's rule-fetcher. - Reachability vs empty: provider unreachable (cannot confirm guardrail state) → degrade per surface
contract; hook
beforeSubmitPromptblocks when opted in. Provider reachable with zero rule-class memories → pass (confirmed empty is normal for greenfield repos).
Pre-work search (mandatory)
Every work-performing command MUST run a scoped memory-preflight search (read mode) before its
first substantive mutation. The enumerated surfaces are: /sw-execute, /sw-debug (rca-core entry),
/sw-prd, /sw-brainstorm, /sw-amend, /sw-review, /sw-stabilize. Route through this skill +
providers/<memory.provider>.md only — never call a provider tool directly (sw-guardrails).
Scoped read recipe
Follow CAPABILITIES.md Read recipe, scoped to the surface being touched:
- Recency OFF (
recentOnly: false) — durable facts are not recent-only. - Run scoped searches, not one broad query:
- file-path search on the paths the command will touch,
- semantic search on the change type / PRD / feature / surface,
- category narrowing when the adapter supports
categoryFilter.
- Search these classes:
rule,decision,learning,code-context,playbook,design. expandonly the handful of ids that look relevant before mutation.
Per-command scope hints remain in the table under Read mode (preflight) below; the obligation and recipe above apply to every enumerated work-performing surface.
Surface and reconcile
Hits MUST be surfaced to the acting agent before mutation and reconciled against found rules/decisions:
- An applicable
ruleor a contradicting priordecisionis a reconcile obligation — record alignment or an explicit conflict + how it is resolved; never silently ignore. - A direct conflict with a frozen decision/rule that cannot be reconciled is a blocker — halt per the invoking command's halt contract (do not proceed with mutation).
- Memory remains an input, not an authority — except
decision-class SoT per Source of truth resolution when memory-SoT is active.
Recording (mechanical)
Record the pre-work search breadcrumb before the first substantive mutation:
python3 scripts/wave.py memory prework record \
--surface sw-execute \
--scope "core/skills/memory/SKILL.md" \
--classes rule,decision,learning,code-context,playbook,design \
[--hit-count N]
The shared recorder (scripts/wave_memory_prework.py) writes a redacted per-surface artifact to
.cursor/hooks/state/memory-prework-search.json and appends an auditable line to
.cursor/sw-deliver-runs/run.log. Outcomes:
| Probe / search | outcome |
Gate behavior |
|---|---|---|
| Provider unreachable (mechanical probe) | memory:offline |
Satisfies degrade-open gate (R6) |
| Search completed, zero relevant hits | memory:none |
Satisfies gate (R7) |
| Search completed with hits | memory:hits |
Satisfies gate; hits surfaced + reconciled (R5) |
Offline is probe-gated — never agent-asserted. Enforcement at the first file-mutating tool call
reuses the PRD 017 preToolUse deny path (Phase 3).
Load-context orientation (R20)
For load-context, read the store's derived index.md before any search or expand. The index
groups memories by canonical category with title/first-line plus id for cheap orientation. When
index.md is absent, run python3 scripts/in-repo-memory-search.py maintain-derived --store <dir>
once, then read the index.
Read mode (preflight)
Run before doing the command's real work. Follow the read recipe in CAPABILITIES.md:
- recency OFF unless the task is explicitly "recent",
- scoped searches (file-path + semantic + optional category), not one broad query,
expandonly the few relevant hits.
Per-command read templates:
| Command | What to retrieve |
|---|---|
/phase-execute |
PRD/task memories; code-context for the target files (file-path search); domain decision/learning. |
/coderabbit |
known false-positives; review patterns; code-context for changed paths. |
/stabilize-pr |
prior CI-failure debug; bot-pattern learning; changed-path context. |
/watch-ci |
lazy — only search on a red run, scoped to the failing job/check. |
/phase-start |
only on a non-routine branch decision (parent ambiguity, prior workflow correction). |
Memory is an input, not an authority: git state, the per-repo stateFile, agentsFile, and repo
doctrine remain the sources of truth — except for the decision doc class when memory-SoT is active
(see Source of truth resolution below).
Source of truth resolution (decision class only — R1–R3)
The decision doc class has a provider-conditional authoritative side. All other classes
(learning, design, debug, etc.) remain distillation-only — the SoT switch does not apply.
Single source for freeze, compound, and audit:
python3 scripts/memory-sot.py resolve --class decision --json
# effective: repo | memory
python3 scripts/memory-sot.py resolve --class learning --json
# effective: distillation (scope guard)
Config: memory.sourceOfTruth — repo | memory | auto (default auto).
| Knob | Provider | Effective SoT |
|---|---|---|
auto |
recallium (external) |
memory |
auto |
in-repo / none |
repo |
repo |
any | repo |
memory |
any | memory |
Default auto + in-repo preserves today's R32/KTD3 behavior (no change for existing repos).
Migration gate (R30 — memory-authoritative providers)
When memory.sourceOfTruth is omitted and the active provider's catalog row has
sourceOfTruthClass: memory-authoritative, Shipwright fail-closed until the operator exports
decision bodies and sets an explicit knob:
python3 scripts/memory-sot.py resolve --class decision --json
python3 scripts/planning-doctor.py --root . # surfaces migration-required classification
Remediation: run /sw-memory-export (or python3 scripts/memory-sot.py migration export command) to
materialize provider decision bodies, then set memory.sourceOfTruth to repo or memory explicitly in
workflow.config.json. Repo-authoritative providers (in-repo) with an omitted key resolve as
implicit-repo-default without migration.
Memory envelope v2 (R29)
Distilled memories use the canonical v2 envelope (scripts/memory_envelope_v2.py,
schemaVersion: 2). Supersession creates a new envelope and marks the prior record superseded —
no in-place mutation. Provider adapters map native storage onto this codec at read/write boundaries;
catalog envelopeFields (native / sideChannel / lossy) records per-provider fidelity.
| Field | Role |
|---|---|
stableId |
Canonical memory id (filename stem for in-repo) |
projectId |
memory.project scope |
category |
Canonical doc class (decision, learning, rule, …) |
status |
active or superseded |
scope |
{repos, paths, tags} binding |
evidenceRefs |
Citations / backlinks |
confidence |
Distillation confidence (0–1) |
observedAt |
ISO timestamp of observation |
lastValidatedAt |
Last validation stamp (nullable) |
validUntil |
Optional expiry (nullable) |
supersedes |
Prior stableId chain |
contentHash |
SHA-256 over semantic body |
schemaVersion |
Always 2 for new records |
sensitivity |
public | internal | private | secret |
appliedRedaction |
Provenance record from the redaction chokepoint |
v1 records upgrade via scripts/memory_envelope_upgrade.py; alias merges persist in
.cursor/sw-memory/alias-ledger/.
Destination-aware redaction (R29/R41 — mandatory before persist/re-inject)
Redaction is destination-tier aware. The --destination argument is required — there is no
default. Valid tiers: local, committed, external, cross-project, logs. Learning-store /
exogenous-routing derivations (PRD 272) must pass the same scripts/memory_redact.py chokepoint before
admission — provenance fields may retain pointers; raw payloads never skip the filter.
python3 scripts/sw_bootstrap.py memory-redact.py --destination external <<'EOF'
<payload>
EOF
| Destination | Residual secrets |
|---|---|
external, cross-project, logs |
Fail-closed — non-zero exit when patterns remain |
local, committed |
Advisory — residuals logged; write may proceed |
Named egress enforcement points (/sw-memory-sync, cross-project copy) call
enforce_egress(..., destination_tier=...) and stamp appliedRedaction via
scripts/memory_redaction_provenance.py. See rules/memory-guardrails.mdc for the full contract.
Portability and audit commands
| Command | Doc | Purpose |
|---|---|---|
/sw-memory-export |
core/commands/sw-memory-export.md |
Neutral JSONL/OKF export for backup or provider swap |
/sw-memory-import |
core/commands/sw-memory-import.md |
Import neutral interchange (human-gated for rule class) |
/sw-memory-sync |
core/commands/sw-memory-sync.md |
Distill transcript deltas into the provider |
/sw-memory-audit |
core/commands/sw-memory-audit.md |
Inspect, propose cleanup, human-gated rule promotion |
Route all four through this skill + providers/<memory.provider>.md — never direct provider tool calls.
Rule promote integrity (PRD 277 D5–D6)
D5 — needs-reconcile on partial failure. scripts/memory_rules_promote.py writes through the
adapter first. Allowlist commit happens only after verdict: ok. A failed or incomplete provider write
records .cursor/sw-memory/needs-reconcile.json and does not append the rule id to the allowlist.
memory-preflight rules-load fail-closes while that file exists (cause=needs-reconcile).
D6 — integrity is id + hash. Audit approval binds ruleId and contentHash (SHA-256 of the body).
A hash or id mismatch refuses the write (rule-write-hash-mismatch / rule-write-id-mismatch); the
operator must re-approve the current body.
Redaction chokepoint (R41 — mandatory before persist/re-inject)
Before any store, transcript distillation (/sw-memory-sync), or compounding write, pipe content
through the executable filter:
python3 scripts/sw_bootstrap.py memory-redact.py --destination committed <<'EOF'
<payload>
EOF
Or: python3 scripts/sw_bootstrap.py memory-redact.py --destination committed -- path/to/file. The filter is deterministic (same input → same output),
runs offline, and scrubs the named corpus in rules/memory-guardrails.mdc (AWS keys, GitHub PATs, JWTs,
Bearer tokens, PEM private keys, emails). Never persist or re-inject unredacted content.
Fail-closed (R10): if memory-redact.py exits non-zero, abort the provider write and any snapshot
write (scripts/memory-decision-snapshot.py write propagates the failure). A provider outage after a
successful snapshot stamp degrades to the committed snapshot with a warning — never block freeze/CI.
Write mode (after substantive work)
Store distilled memories per the write contract in CAPABILITIES.md:
- assert write binding first (PRD 279):
python3 scripts/memory_preflight.py assert-sync-store(orresolve-provider --for-write) — refuse unbound; never ambient-default to Recallium, - pick the canonical category (decision / learning / debug / design / code-context / research / discussion); never a generic catch-all,
- set
relatedFiles+ stable tags (prd-<n>,task-<n>,surface:<cmd>), - run
scripts/memory-redact.pyon the payload first (R41 chokepoint), - search before store;
modifya near-duplicate instead of adding a second, - project scope by default; global only on explicit user direction (
__global__requires explicit config binding), - store the distilled substance, never a raw transcript dump.
- populate
titleanddescriptionfrontmatter at store time from the distilled first line when omitted (memory-preflightwrite path).
For decision-class writes, resolve the inverted pointer recipe first (R6):
python3 scripts/memory-sot.py pointer-recipe --path docs/decisions/<n>-<slug>.md [--memory-id <provider-id>] --json
| Effective SoT | Provider write | Git snapshot |
|---|---|---|
repo |
Pointer only — relatedFiles: [docs/decisions/...]; never the record body |
Authoritative (snapshotRole: authoritative) |
memory |
Content-bearing authoritative record (redacted) | Pointer (snapshotRole: pointer, memoryPointer) |
Exactly one side is authoritative at a time; the recipe JSON names authoritative vs nonAuthoritative.
Store on: a decision with rationale, a non-obvious lesson, a bug root-cause+fix, an architecture choice, a notable review/CI pattern, or a distilled session recap. Do not store routine, recoverable steps.
Capability degradation
Check the adapter's flags and adjust:
- no
semanticSearch→ runscripts/in-repo-memory-search.py(keyword + frontmatter filters) instead of vector search; results feedexpand, - no
tasks→ the phase board uses the local registry fallback, - no
filePathSearch→ semantic search on the path string, - no
categoryFilter→ skip category narrowing / post-filter, - no
export→ provider swap relies on re-distillation from raw transcripts.
In-repo provider specifics (memory.provider: in-repo)
Read: when semanticSearch:false, call:
python3 scripts/in-repo-memory-search.py \
--store .cursor/sw-memory \
--query "<terms>" \
[--category decision] [--tag prd-1] [--file-glob src/auth.ts]
Then expand via python3 scripts/in-repo-memory-search.py expand --store <dir> --ids <id> (full body + backlinks), or read memories/<id>.md directly.
Write:
- Lazy-create store dirs on first write:
mkdir -p .cursor/sw-memory/memories .cursor/sw-memory/rules. - Pipe payload through
scripts/memory-redact.py(R41) before any file write. - Default
commitMode: committed→ write non-rule files under.cursor/sw-memory/memories/. commitMode: local→ non-rule files under.cursor/sw-memory-local/memories/(gitignored).category: rulealways writes to.cursor/sw-memory/rules/— offline hook reads committed rules.- Never auto-seed starter rules; store starts empty.
Planning store memory backend (PRD 034 R11/R23; PRD 057 R21 — 21a local cache + 21b provider round-trip)
When planning.store.backend is memory, unit bodies are cached under a gitignored directory
(.cursor/sw-memory/planning-bodies/<memory.project>/, MemoryLocalCacheBackend in
scripts/planning_store.py) that always passes content through scripts/memory-redact.py on read and
write — body storage only; the memory backend does not alter source-of-truth for any planning class.
This local cache is always written unconditionally regardless of whether a memory provider is configured
(21a — PRD 057 R21), and is never bypassed: it remains the fast-path read and the fallback whenever the
provider round-trip below is unavailable. On top of it, 21b adds a real round-trip through the configured
provider's REST adapter (Recallium only, at present): put() best-effort mirrors the redacted body to a
dedicated /planning-bodies/<unitId> REST resource guarded by the same loopback-only base-URL check as
providers/recallium-rules.py, and get() recovers content through that same adapter when the local cache
is absent (e.g. a fresh checkout on another machine). Each cached body's frontmatter records
configuredProvider (informational — names whichever provider is configured for this skill's other memory
operations), plus providerRoundTrip/providerRoundTripReason (whether this body actually round-tripped,
and why not when it didn't). See core/providers/planning-store/memory.md for the full contract and
scripts/test/fixtures/memory-roundtrip/harness.py for the offline fixture covering both the round-trip and
every fallback path.
Decision-class units under docs/planning/decision/ still follow the PRD-015 committed redacted snapshot +
pointer flow regardless of visibility. The authoritative decision record paths remain
docs/decisions/<n>-<slug>.md (repo-SoT) or the provider record (memory-SoT) per Source of truth
resolution below — the planning-store memory backend never replaces that contract.
Decision records (file-linked deliverables)
Boundary rule (R32 / KTD3 + provider-conditional SoT):
Resolve authority first: python3 scripts/memory-sot.py resolve --class decision --json.
| Effective SoT | Authoritative artifact | Memory role |
|---|---|---|
repo |
Decision record (docs/decisions/<n>-<slug>.md) |
Pointer via relatedFiles only |
memory |
Provider decision record (redacted) |
Content-bearing; git snapshot is pointer |
| Artifact | Role | Mutable | CI freeze |
|---|---|---|---|
Decision record (docs/decisions/<n>-<slug>.md) |
Up-front, reviewed-before-build deliverable | Only via amendment | Yes |
decision-class memory |
Retrospective knowledge distillation | Yes | No |
When repo-SoT is active (default for in-repo):
- Read: load the record from git; memory may point at it via
relatedFilesbut is not authoritative. - Write: store a pointer (
relatedFiles: [docs/decisions/...]), never the record body. - Flag content-bearing
decisionmemories that duplicate an existing record — they should become pointers.
When memory-SoT is active, the provider record is authoritative; the committed git snapshot carries a
forward pointer (memoryPointer in frontmatter — see scripts/memory-decision-snapshot.py write).
Supersede reconciliation (docs/decisions/SUPERSEDED.log):
On record-level supersede, append the superseded path via:
python3 scripts/reconcile.py append-superseded --path docs/decisions/<old>.md --replacement docs/decisions/<new>.md
/sw-memory-sync runs python3 scripts/reconcile.py supersede-reconcile --json after distillation and
best-effort re-points the non-authoritative side per the active SoT (provider relatedFiles under
repo-SoT; git snapshot pointer under memory-SoT). Pointer freshness is auditable, not transactional
(provider out of CI reach).
Issue-store brainstorm distillation (PRD 043 R19)
When planning.store.backend is issue-store and a PRD is frozen via planning_store.py freeze:
- Linked brainstorm content (from
sw-edges/link-brainstorm-prd) is excerpted and piped throughscripts/memory-redact.py— no raw transcript. - Distilled
researchentry is stored via the memory backend's local-only cache (MemoryLocalCacheBackend/planning.store.backend: memorybodies path — 21a, see above). - A
sw-memory-pointercomment on the brainstorm issue links PRD ↔ memory ↔ brainstorm. - Brainstorm issue is closed+linked, never deleted.
Failure at distillation → sw:freeze-incomplete on the PRD issue; deliver halts fail-closed.
Cross-project recall (PRD 046 R90 / PRD 043 R27)
When issue-store is active, rationale and distilled learnings may be recalled across projectKey
boundaries via memory pointers — never by duplicating deliverable bodies into memory.
python3 scripts/planning_cross_project_recall.py recall --payload-json '{"sourceProjectKey":"proj-a","callerProjectKey":"proj-b","query":"rationale","pointers":[...],"authorizedProjects":["proj-a"]}'
- Scope: queries are keyed by source
projectKey+ caller authorization (authorizedProjects). - Redaction: pointer dereference passes through
scripts/memory-redact.pyon read;private/memoryvisibility emits opaque excerpts ({unitId}: [private]) — project B cannot read project A private rationale. - Ranking: deterministic tie-break (
projectKey,unitId,memoryId). - No duplication: deliverable content stays in planning artifacts; memory holds pointers and redacted distillations only.
Route all cross-project recall through this skill + providers/<memory.provider>.md — never direct provider
calls from deliver or planning-graph code.
Boundaries
- Never call a provider tool directly from a command; always go through this skill + the adapter.
- Never write
rule-category memories unless the user explicitly asks ("remember this", "add a rule"). - Never store secrets, credentials, tokens, or raw transcript content.
04 同仓库更多 Skills
wrong-name
Missing use when phrase and wrong directory name for critical lint failure.
demo-skill
Demo workflow pack skill fixture. Use when validating workflow-pack-sdk golden paths in CI.
good-skill
Validates Agent Skills spec conformance for fixture trees. Use when testing skills-spec-guard pass paths across core and dist mirrors. Does not ship production skills.
wrong-name
Fixture with name/dir mismatch for guard failure tests. Use when verifying skills-spec-guard rejects invalid identity. Does not ship production skills.
bad-description
Missing the required when trigger phrase in this fixture skill.
dropin-demo
暂无简介