-
Notifications
You must be signed in to change notification settings - Fork 0
Pipeline Plan 313
Implementation plan written to .claude/pipeline-artifacts/plan.md.
Summary of the plan:
3 files to modify:
-
scripts/lib/ruflo-adapter.sh— add 4 functions:ruflo_prune_memory_export(),ruflo_merge_memory_exports(),ruflo_ci_memory_pull(),ruflo_ci_memory_push() -
.github/workflows/shipwright-pipeline.yml— add 2 steps (restore before pipeline, persist after) -
scripts/sw-ruflo-adapter-test.sh— add 6 unit tests
Key design decisions:
- Uses the same orphan-branch + tmp-dir + commit + push pattern already proven by
shipwright-data(the issue's pseudocodegit pushsyntax was invalid) - Chose separate
refs/ruflo-memorybranch over reusingshipwright-datafor separation of concerns - Merge strategy: union of keys, newer timestamp wins — acceptable that worst-case concurrent conflicts lose 2/3 jobs' learning
- 90-day pruning prevents unbounded growth
- All paths return 0 (fail-open) — pipeline never fails over memory persistence
-
ruflo memory exportmay only capture KV store (not HNSW/Q-weights) — documented as known limitation, infrastructure works regardless
Alternative A: Orphan git branch (chosen)
- Pros: Zero external infrastructure, works with any GitHub repo, GITHUB_TOKEN sufficient, mirrors existing
shipwright-datapattern in the workflow - Cons: Git push conflicts with concurrent jobs, limited merge granularity
- Blast radius: 2 functions added to ruflo-adapter.sh, 2 steps added to workflow
Alternative B: GitHub Actions artifact + download
- Pros: No git conflicts, built-in retention policies
- Cons: Artifacts are per-workflow-run and expire; cross-workflow retrieval requires API calls; not persistent long-term
- Rejected: Artifacts are ephemeral, not suitable for cross-run learning
Alternative C: Dedicated shipwright-data branch (reuse existing)
- Pros: Already exists in workflow; less orphan branch proliferation
- Cons: Mixes concerns (events/costs/budget vs ruflo memory); concurrent push conflicts already a problem there; ruflo memory could be large and noisy
- Rejected: Separation of concerns — ruflo memory has different lifecycle and ownership
| Risk | Impact | Mitigation |
|---|---|---|
| Push conflict with concurrent CI jobs | 2/3 jobs lose memory in worst case | 3-retry loop with jitter; acceptable per issue spec |
ruflo memory export doesn't capture HNSW/Q-weights |
Reduced cross-run learning value | Document as known limitation; KV store still persists |
| Unbounded memory file growth | Slow fetches, large git objects | 90-day pruning before every push |
| Orphan branch pollutes repo | Confusion for contributors | Uses refs/ruflo-memory (not visible in git branch) |
| Merge logic (timestamp union) has bugs | Corrupted memory state | Defensive: if merge fails, push local-only export |
Depends on:
-
scripts/lib/ruflo-adapter.sh— existingruflo_available,ruflo_with_timeout,_ruflo_run,ruflo_export_memory,ruflo_import_memory -
.github/workflows/shipwright-pipeline.yml— the CI workflow to wire into -
jq— already installed in CI (system dependencies step)
Depended on by: Nothing — this is additive, new functions only.
No circular dependency risk.
The issue's pseudocode uses git push origin ".claude-flow/data/memory-export.json:refs/ruflo-memory/memory-export.json" which is not valid git syntax. We need the same tmp-dir + orphan-branch + commit + push pattern already used for shipwright-data (workflow lines 803-852). This is proven infrastructure.
shipwright-pipeline.yml ruflo-adapter.sh
┌─────────────────────┐ ┌──────────────────────────────┐
│ Restore ruflo memory│────────────>│ ruflo_ci_memory_pull() │
│ (before pipeline) │ │ ├─ git fetch refs/ruflo-mem │
└─────────────────────┘ │ ├─ git show memory-export │
┌─────────────────────┐ │ └─ ruflo memory import │
│ Save ruflo memory │────────────>│ ruflo_ci_memory_push() │
│ (after, always) │ │ ├─ ruflo memory export │
└─────────────────────┘ │ ├─ ruflo_prune_memory_export│
│ ├─ ruflo_merge_memory_expor │
│ └─ git commit + push (3x) │
└──────────────────────────────┘
ruflo_ci_memory_pull() → exit 0 (always) ruflo_ci_memory_push() → exit 0 (always) ruflo_prune_memory_export(file, days) → exit 0|1 ruflo_merge_memory_exports(local, remote, out) → exit 0|1
CI Start → ruflo_ci_memory_pull()
1. git fetch origin refs/ruflo-memory:refs/ruflo-memory
2. git show refs/ruflo-memory:memory-export.json > local file
3. ruflo memory import --input <file>
4. Log byte count for observability
CI End → ruflo_ci_memory_push()
1. ruflo memory export --output <file>
2. ruflo_prune_memory_export(<file>, 90)
3. Retry loop (3x):
a. git fetch refs/ruflo-memory
b. git show remote memory-export.json
c. ruflo_merge_memory_exports(local, remote, merged)
d. Create tmp git dir, checkout orphan, copy, commit, push
e. On push fail: sleep jitter, retry
- ruflo_ci_memory_pull: All errors → return 0, log warning
- ruflo_ci_memory_push: All errors → return 0, log warning. Push retries 3x then gives up
-
ruflo_prune_memory_export: Returns 1 on jq failure; caller uses
|| true - ruflo_merge_memory_exports: Returns 1 on failure; caller falls back to local-only
-
Workflow steps: Both use
continue-on-error: true
Decision: Implement persistence regardless. If ruflo memory export only captures the KV store (not HNSW/Q-weights), the KV store alone is valuable. Document as known limitation.
Add 4 functions after ruflo_export_memory() (line 529):
-
ruflo_prune_memory_export()— prune entries older than N days -
ruflo_merge_memory_exports()— union merge with timestamp tiebreaker -
ruflo_ci_memory_pull()— fetch from orphan branch + import -
ruflo_ci_memory_push()— export + prune + merge + push
Add 2 steps in the pipeline job:
- Before "Run Shipwright pipeline": restore ruflo memory from git
- After "Save ruflo memory cache": persist ruflo memory to git
Add tests for the 4 new functions
- Accepts
file_pathandmax_age_daysparameters - Uses
jqto filter entries older thanmax_age_daysusing.timestampor.updated_at - Atomic rewrite via tmp file + mv
- Returns 0 on success, 1 on error; emits event with pruned count
- Accepts
local_file,remote_file,output_file - Uses
jqto merge: union of keys, newer timestamp wins on conflict - Atomic write to output via tmp + mv
- Returns 0 on success, 1 on failure
- Guards:
ruflo_available || return 0,[[ "${CI:-}" == "true" ]] || return 0 - git fetch, git show, ruflo memory import
- Log byte count, emit event
- Same guards as pull
- Export via existing
ruflo_export_memory - Prune with 90-day threshold
- 3-attempt retry loop: fetch, merge, create tmp dir, orphan checkout, commit, push
- On all retries exhausted: warn, return 0
Add two steps to .github/workflows/shipwright-pipeline.yml:
- "Restore ruflo memory from git" before pipeline run
- "Persist ruflo memory to git" after pipeline (always)
- Prune: mixed timestamps, all-fresh entries
- Merge: overlapping keys (newer wins), disjoint keys
- CI pull/push: guard conditions (CI=false → no-op)
- Task 1: Add
ruflo_prune_memory_export()toscripts/lib/ruflo-adapter.sh - Task 2: Add
ruflo_merge_memory_exports()toscripts/lib/ruflo-adapter.sh - Task 3: Add
ruflo_ci_memory_pull()toscripts/lib/ruflo-adapter.sh - Task 4: Add
ruflo_ci_memory_push()toscripts/lib/ruflo-adapter.sh - Task 5: Add "Restore ruflo memory from git" step to workflow
- Task 6: Add "Persist ruflo memory to git" step to workflow
- Task 7: Add unit tests for prune function
- Task 8: Add unit tests for merge function
- Task 9: Add unit tests for CI pull/push guard conditions
- Task 10: Run test suite and verify all tests pass
- Unit tests (6): prune mixed, prune all-fresh, merge overlapping, merge disjoint, CI pull guards, CI push guards
- Integration tests (0): Functions are self-contained shell
- E2E tests (0): Verified by first actual CI pipeline run
- 100% of guard conditions (CI=false, ruflo unavailable)
- Prune: old entries removed, new entries kept
- Merge: union semantics, newer timestamp wins
- Happy path: Export → prune → merge → push succeeds
- Error case 1: No orphan branch (first run) → pull no-ops, push creates branch
- Error case 2: Push conflict → retry fires, succeeds or gives up gracefully
- Edge case 1: Empty memory export → prune/merge handle empty JSON
- Edge case 2: Malformed remote JSON → merge falls back to local-only
-
ruflo_ci_memory_pull()fetches from orphan branch and imports into ruflo -
ruflo_ci_memory_push()exports, prunes, merges, and pushes to orphan branch - 90-day pruning removes old entries before every push
- Pipeline never fails due to memory persistence errors (all paths return 0)
- Concurrent push conflicts handled with 3-retry loop + jitter
- CI workflow has restore/save steps wired correctly
- All new functions have unit tests
- Existing tests still pass
- Code follows project conventions: Bash 3.2 compat, atomic writes, jq --arg
| Risk | What Could Break | Mitigation |
|---|---|---|
| jq not available | Prune/merge fail | Already installed in CI; guard with command -v jq
|
| Wrong ref format | Fetch/push fail silently | Use proven shipwright-data pattern |
| Invalid JSON export | jq errors | Validate before merge; fall back to local-only |
| Missing write permission | Push fails | Already in workflow permissions: contents: write
|
| Concurrent corruption | Stale data | Retry with fetch-before-push; worst case: one run lost |
- Current: ruflo memory lost between CI runs
- After: persists; pull ~2-5s, push ~5-10s
- Pull: < 10s, Push: < 30s (including retries)
- File size: bounded by 90-day pruning (< 1MB typical)