Skip to content

feat(discovery): add snapshot-backed OpenClaw discovery adapter - #201

Open
prasith-jobleap wants to merge 13 commits into
kunchenguid:mainfrom
prasith-jobleap:openclaw-adapter
Open

prasith-jobleap wants to merge 13 commits into
kunchenguid:mainfrom
prasith-jobleap:openclaw-adapter

Conversation

@prasith-jobleap

@prasith-jobleap prasith-jobleap commented Oct 1, 2026 •

Copy link
Copy Markdown

What Changed

  • Added src/discovery/adapters/openclaw.js, a new local-only, SQLite-backed harness adapter that reads an online backup snapshot (openclaw backup sqlite create, or BACKPASS_OPENCLAW_DB) rather than the live database, covers live branches plus deleted/reset archives (zstd-aware), assigns order-independent generation identity from each session's anchor, strips injected context/system scaffolding, excludes probe/eval/test namespaces and self-sentinel sessions, and removes its private temp snapshot via a new cleanup hook invoked from src/cli.js.
  • Registered openclaw in ALL_HARNESSES, the ADAPTERS map, the --harness help text, and transcriptSource (provenance keyed by agent, not the moving snapshot path); typed OpenClaw run/cron/subagent/heartbeat/acp/hook sessions classify as non-interactive in src/interaction.js.
  • Discovery now caps any association built on an adapter-inferred cwd (cwdInferred) at tier 3 with confidence: "inferred" (capInferred in src/discovery/index.js), user-scope project normalization skips those rows, and collectHosts is skipped entirely when no selected harness can be collected remotely; README and tests (test/openclaw-adapter.test.js, test/fixtures/openclaw/build.js, test/remote-discovery.test.js, test/config.test.js) cover the new harness and these rules.

🤖 Generated with Claude Code

Risk Assessment

✅ Low: The final fix round is a one-condition change to capInferred that matches the recorded user decision exactly; I traced the tier-1, tier-3-on-its-own, strict, project-scope, and user-scope cache paths and the inferred label now survives normalizeProjects in every case, with a regression test that fails on the prior code, and no other sibling relabel site exists.

Testing

Built an isolated sandbox (throwaway git repo, isolated HOME and XDG config, synthetic schema-23 OpenClaw snapshots built with the repo's fixture builder, and fake openclaw/ssh/acpx binaries), then drove backpass scan and backpass analyze end-to-end: project-scope discovery with correct namespace exclusion and interactive/non-interactive kinds, inferred-cwd sessions labelled tier 3 and dropped by --strict in project and user scope (including a deleted-subdirectory workspace), session identity unchanged across archival and across colliding-archive processing order, --since using archive activity time, self-session exclusion for bare and closed-wrapper sentinels with a human quote kept, injected scaffolding absent from the trace the analysis agent actually receives while human words after an unclosed wrapper survive, silent empty harness when the openclaw binary is absent, the backup-snapshot path through a fake openclaw with cleanup of the private temp directory and refusal of an artifact outside it, and no ssh contact for configured hosts under --harness openclaw with a control run proving the host is contacted otherwise. All passed. One early run accidentally used a PATH containing the operator's real openclaw binary; its backup command failed (exit 1) under the sandbox HOME, wrote nothing to real data, and the scenario was re-driven with the binary absent.

  • Live validation: ✅ go - 11 of 11 scenarios driven live against the product
Scenario Result Live Evidence
User runs scan in a repo with OpenClaw sessions: sessions attributed to the repo, probe/eval/test namespaces excluded, zstd archive decoded, human channels interactive and cron/subagent/heartbeat/acp/… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s1-scan-project.txt and s1-scan-project.json
Session with no recorded cwd falls back to the configured workspace and is shown as tier 3 'inferred cwd (no recorded cwd)'; --strict drops it while recorded-cwd sessions stay ✅ pass live s1-scan-project.txt (fallback/compressed rows t3) and ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s2-scan-strict.txt (20 matched, tier3 0)
Session identity survives archival (dated and undated), a live session keeps its id when a distinct colliding archive appears, and ids are the same whether the colliding archive was created earlier or… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s3-identity.txt
--since 30d excludes an archive whose events are from 2025 even though it was deleted today ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s10-since-30d.txt
Backpass's own sessions are excluded (bare sentinel, sentinel after a closed wrapper, and sentinel after an unclosed wrapper via the .backpass cwd check) while a human who quotes the sentinel after an… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s4-self-session.txt
Analysis agent receives a trace with injected context, conversation-info block, closed memory wrapper, timestamp prefix, system and provenance-injected messages removed, while human words after an unc… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s5-analyze-trace.txt
openclaw binary absent and no snapshot supplied: scan completes with exit 0 and an empty openclaw row, no error ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s6-missing-binary.txt
openclaw binary present: backpass runs openclaw backup sqlite create --agent main --repository <private tmp> --json, reads the returned snapshot, and removes the private temp directory on exit ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s7-backup-binary.txt
Adversarial: backup returns an artifact outside its private repository, or the backup command fails: harness is skipped with a named warning, exit 0, no leftover temp dir ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s7b-artifact-escape.txt and s7c-backup-fails.txt
Configured SSH hosts with --harness openclaw: no ssh process is spawned; control run with claude,openclaw contacts the host ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s8-hosts-openclaw-only.txt
User scope: an inferred-cwd session stays tier 3 'inferred' (not relabelled git) when the workspace is a git toplevel or a deleted subdirectory of a repo, keeps its project, and --strict drops it ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s9-user-scope.txt
Evidence: Project-scope scan table (snapshot A)
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  23       22       1     0
  SELF = backpass's own loss / gradient-descent sessions, excluded from the corpus

22 transcript(s) associated with this repo · tier1 20 (exact) · tier1.5 0 (sibling clone) · tier2 0 (remote) · tier3 2 (best-effort) · interactive 17 · non-interactive 5
  re-run with --strict to exclude the best-effort tier

HOST   HARNESS   SESSION       KIND             WHEN      SIZE  TIER  HOW
local  openclaw  compressed:b  interactive      4d ago    -     t3    inferred cwd (no recorded cwd): cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  dashboard:e6  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  fallback:1be  interactive      4d ago    -     t3    inferred cwd (no recorded cwd): cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  slack:998beb  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  discord:998b  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  telegram:998  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  webchat:998b  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  signal:direc  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  cron:job:run  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  subagent:chi  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  heartbeat-ti  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  acp:child:99  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  hook:gmail:9  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  retrieval:99  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  evaluation:9  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  slack:channe  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  compressed-l  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  quoter:22e4e  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  livex:acd934  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  undatedlive:  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  archive:dc0e  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  oldarchive:a  interactive      639d ago  -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
Evidence: Project-scope scan JSON (snapshot A)
{
  "repo": "repo",
  "scope": "project",
  "perHarness": {
    "openclaw": {
      "scanned": 23,
      "matched": 22,
      "cached": 0,
      "skipped": 0,
      "self": 1,
      "error": null
    }
  },
  "perHost": [],
  "mix": {
    "interactive": 17,
    "nonInteractive": 5,
    "total": 22
  },
  "transcripts": [
    {
      "harness": "openclaw",
      "id": "openclaw-compressed:b0cb7863b9ba03727f3d74e43ad7e7430f518f6dde2d346c738482abe61674a9",
      "nativeId": "compressed:b0cb7863b9ba03727f3d74e43ad7e7430f518f6dde2d346c738482abe61674a9",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550002000,
      "mtimeMs": 1790550002000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 3,
        "confidence": "inferred",
        "reason": "inferred cwd (no recorded cwd): cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "compressed",
        "generation": "generation-1",
        "sessionKey": "agent:main:dashboard:archived",
        "agent": "main",
        "cwdSource": "configured-workspace"
      },
      "interactionSignals": {
        "source": "dashboard"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "62833b78f32a1035d62d51ae72c70d4a5595f76b633ccc868f4396ca05fe0a65",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-dashboard:e6bf3d53ad2a1a0b7d9f49eb273ae0686035fc87c66c9811b9b4c4552b723c92",
      "nativeId": "dashboard:e6bf3d53ad2a1a0b7d9f49eb273ae0686035fc87c66c9811b9b4c4552b723c92",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "dashboard",
        "generation": null,
        "sessionKey": "agent:main:dashboard:demo",
        "agent": "main",
        "cwdSource": "session-metadata"
      },
      "interactionSignals": {
        "source": "dashboard"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "3eb28e0386d9a3a46582b80bd1d8aaad80306d12090ebbd7cae71eef46fe4646",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-fallback:1be8d0c179ba1f411ca40aad6e15186fafdb5406ad6d6e7b79b9a8dafa400ee3",
      "nativeId": "fallback:1be8d0c179ba1f411ca40aad6e15186fafdb5406ad6d6e7b79b9a8dafa400ee3",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 3,
        "confidence": "inferred",
        "reason": "inferred cwd (no recorded cwd): cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "fallback",
        "generation": null,
        "sessionKey": "agent:main:main",
        "agent": "main",
        "cwdSource": "configured-workspace"
      },
      "interactionSignals": {
        "source": "main"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "15134e8fa63e1180aaed578e4714a2598ae1578a0af6f5e1b82234d148bf241f",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-slack:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "nativeId": "slack:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "slack",
        "generation": null,
        "sessionKey": "agent:main:slack",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "slack"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "5e3d9513ecd072c4ebc829974e063aa885ed11e15a575c4bc506df4341267ca5",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-discord:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "nativeId": "discord:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "discord",
        "generation": null,
        "sessionKey": "agent:main:discord",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "discord"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "65717f68f6d03bb11601cf6531baf07e807211ce51913d69c4b844ec49b8974d",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-telegram:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "nativeId": "telegram:998beb26a73ae867b1e82f3313d5088e577ace41c629a2888ff13aeaf2214cc9",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extr

... [14685 bytes truncated] ...

"compressed-live:bce88a45923861a808df27e3e94b80ae0175cdf4e1346bf0861f1ec798f4ca80",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "compressed-live",
        "generation": null,
        "sessionKey": "agent:main:dashboard:compressed",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "dashboard"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "bd3ad330c93694f6bd6f1c30938cc03e0f2b431b80ba6ae1db9e01ca47d26abc",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-quoter:22e4ec5129cbbdc191c8da0cfd3d487ecca2b3becdf4bc344e7b5e8a441753bc",
      "nativeId": "quoter:22e4ec5129cbbdc191c8da0cfd3d487ecca2b3becdf4bc344e7b5e8a441753bc",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "quoter",
        "generation": null,
        "sessionKey": "agent:main:slack:quoter",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "slack"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "a15bac1e587cc851eb6f833e1bc915e333806c16b54e0ba90261bd1efa21aa2d",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14",
      "nativeId": "livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "livex",
        "generation": null,
        "sessionKey": "agent:main:slack:livex",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "slack"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "e23440a2b11f1d24b52819ab1a7f8d59155f397b61e78e17bbad26c006fc873d",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-undatedlive:ec84c9e30e0e48e126c3477728de02da08d5b6134fb7f773fa9e01181cf647e6",
      "nativeId": "undatedlive:ec84c9e30e0e48e126c3477728de02da08d5b6134fb7f773fa9e01181cf647e6",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550001000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "undatedlive",
        "generation": null,
        "sessionKey": "agent:main:slack:undated",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "slack"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "2bfe520f3a4bc2a53147f5e4a5f0a3223793583d27213cf61706d56e44cedd03",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-archive:dc0e1cf8fd0c8ef3ea51181058cf4eb34742e3f34a886aa3b6e911fc0d68f47a",
      "nativeId": "archive:dc0e1cf8fd0c8ef3ea51181058cf4eb34742e3f34a886aa3b6e911fc0d68f47a",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1790550000000,
      "mtimeMs": 1790550000000,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "archive",
        "generation": "generation-1",
        "sessionKey": "agent:main:dashboard:archived",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "dashboard"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "8db76a383d003264d3910aaf4a1b35c1f142120b007caec6372f4d08a89d5966",
      "interaction": "interactive"
    },
    {
      "harness": "openclaw",
      "id": "openclaw-oldarchive:a3d60a22d5fb5ea1ee5517903a017f4964a906c3375d55228c8f789d4ea31a8d",
      "nativeId": "oldarchive:a3d60a22d5fb5ea1ee5517903a017f4964a906c3375d55228c8f789d4ea31a8d",
      "path": "/private/tmp/backpass-oc-live-8896/snaps/A.sqlite",
      "cwd": "/private/tmp/backpass-oc-live-8896/repo",
      "gitBranch": null,
      "title": null,
      "model": null,
      "startedAt": 1735689600000,
      "mtimeMs": 1735689600001,
      "bytes": 0,
      "contentSignature": null,
      "experimental": false,
      "association": {
        "tier": 1,
        "confidence": "exact",
        "reason": "cwd is worktree /private/tmp/backpass-oc-live-8896/repo",
        "project": "/private/tmp/backpass-oc-live-8896/repo",
        "projectRoot": "/private/tmp/backpass-oc-live-8896/repo"
      },
      "extra": {
        "sessionId": "oldarchive",
        "generation": "g1",
        "sessionKey": "agent:main:slack:old",
        "agent": "main",
        "cwdSource": "event-header"
      },
      "interactionSignals": {
        "source": "slack"
      },
      "project": "/private/tmp/backpass-oc-live-8896/repo",
      "projectRoot": "/private/tmp/backpass-oc-live-8896/repo",
      "host": null,
      "identity": "b8ecbf89df0d4fe0b86949393bc00b11a5862594a9657bf0a7a2bb962e0cfa1d",
      "interaction": "interactive"
    }
  ]
}
Evidence: Strict scan drops inferred-cwd sessions
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  23       20       1     0
  SELF = backpass's own loss / gradient-descent sessions, excluded from the corpus

20 transcript(s) associated with this repo · tier1 20 (exact) · tier1.5 0 (sibling clone) · tier2 0 (remote) · tier3 0 (best-effort) · interactive 15 · non-interactive 5

HOST   HARNESS   SESSION       KIND             WHEN      SIZE  TIER  HOW
local  openclaw  dashboard:e6  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  slack:998beb  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  discord:998b  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  telegram:998  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  webchat:998b  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  signal:direc  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  cron:job:run  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  subagent:chi  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  heartbeat-ti  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  acp:child:99  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  hook:gmail:9  non-interactive  4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  retrieval:99  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  evaluation:9  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  slack:channe  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  compressed-l  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  quoter:22e4e  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  livex:acd934  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  undatedlive:  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  archive:dc0e  interactive      4d ago    -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
local  openclaw  oldarchive:a  interactive      639d ago  -     t1    cwd is worktree /private/tmp/backpass-oc-live-8896/repo
Evidence: Identity across archival and archive order

A livex gen=live id=livex:acd93426bb9fda… B livex gen=gen-7 id=livex:acd93426bb9fda… C livex gen=live id=livex:acd93426bb9fda… / gen-3 id=livex:bbbc2145991280… D livex gen=live id=livex:acd93426bb9fda… / gen-3 id=livex:bbbc2145991280…

A livex gen=live id=livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14
A undatedlive gen=live id=undatedlive:ec84c9e30e0e48e126c3477728de02da08d5b6134fb7f773fa9e01181cf647e6
B undatedlive gen=gen-1 id=undatedlive:ec84c9e30e0e48e126c3477728de02da08d5b6134fb7f773fa9e01181cf647e6
B livex gen=gen-7 id=livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14
C livex gen=live id=livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14
C livex gen=gen-3 id=livex:bbbc21459912805aae1585ac202f35dca729cd9f20485ba0de38e87383fe544e
D livex gen=live id=livex:acd93426bb9fdafbd284a783ca7a67c40ca68679c7c0642732240c4fba831a14
D livex gen=gen-3 id=livex:bbbc21459912805aae1585ac202f35dca729cd9f20485ba0de38e87383fe544e
Evidence: Self-session exclusion
self count reported by scan: 1
selfbare      EXCLUDED  (first user message = bare sentinel)
selfclosed    EXCLUDED  (closed <active_memory_plugin> wrapper then sentinel)
selfunclosed  EXCLUDED  (unclosed wrapper then sentinel, cwd under <repo>/.backpass/synthesis)
quoter        KEPT      (human quotes the sentinel after an unclosed wrapper)
Evidence: Trace delivered to the analysis agent (scaffolding stripped)
analyze summary: {"total":1,"cached":0,"analyzed":1,"skipped":0,"failed":0,"usage":[{"agent":"pi","usage":null}],"staleMemoryHash":0,"quotesNotInTrace":0}
exit=0
--- raw events file handed to the agent: /private/tmp/backpass-oc-live-8896/repo/.backpass/raw/93b56526-3a17-4d3e-a118-dada2b1778a8.jsonl | still exists after the call: false
header: {"harness":"openclaw","session":"richscaffold:c87730ac301d331e927f89d43b08fb610eda564878eb785501440a153ba2388d","model":null}
{"kind":"message","role":"user","text":"Keep my actual words: please update the release notes."}
{"kind":"tool","name":"read","input":{"path":"CHANGELOG.md"},"result":"changelog body","status":"completed"}
{"kind":"message","role":"assistant","text":"Updating release notes."}
{"kind":"message","role":"user","text":"unclosed memory block\nSecond human turn: also bump the version."}
{"kind":"message","role":"assistant","text":"Bumped."}
{"kind":"message","role":"user","text":"Third human turn: run the tests."}
{"kind":"message","role":"assistant","text":"Tests pass."}
--- distilled trace as delivered in the analysis prompt:
878eb785501440a153ba2388d
harness: openclaw
date: 2026-09-27T23:01:40.000Z
cwd: /private/tmp/backpass-oc-live-8896/repo
association: tier 1 (exact)

\### turn 1 · user
Keep my actual words: please update the release notes.

tool: read "CHANGELOG.md" -> changelog body
\### turn 2 · assistant
Updating release notes.

\### turn 3 · user
unclosed memory block
Second human turn: also bump the version.

\### turn 4 · assistant
Bumped.

\### turn 5 · user
Third human turn: run the tests.

\### turn 6 · assistant
Tests pass.

---
raw transcript: /private/tmp/backpass-oc-live-8896/repo/.backpass/raw/93b56526-3a17-4d3e-a118-dada2b1778a8.jsonl
Tool calls above are one-li
prompt contains "private generated text": false
prompt contains "generated memory": false
prompt contains "Injected bootstrap": false
prompt contains "bootstrap instructions": false
prompt contains "Conversation info": false
prompt contains "[Sun 2026-09-27": false
prompt contains "Keep my actual words": true
prompt contains "Second human turn": true
prompt contains "Third human turn": true
prompt contains "<active_memory_plugin>": false
Evidence: Missing openclaw binary
(openclaw not on PATH)
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  0        0        0     0

0 transcript(s) associated with this repo · tier1 0 (exact) · tier1.5 0 (sibling clone) · tier2 0 (remote) · tier3 0 (best-effort) · interactive 0 · non-interactive 0

HOST  HARNESS  SESSION  KIND  WHEN  SIZE  TIER  HOW
exit=0
Evidence: Backup snapshot path and temp cleanup
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  23       22       1     0
exit=0
openclaw backup sqlite create --agent main --repository /private/tmp/backpass-oc-live-8896/tmp/backpass-openclaw-hAEFJa --json
(end of listing)
Evidence: Backup artifact escape refused
warn openclaw: online backup artifact is outside its private repository - harness skipped
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  0        0        0     0
exit=0
Evidence: Backup failure warning
warn openclaw: online backup failed (1); set BACKPASS_OPENCLAW_DB to a snapshot - harness skipped
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  0        0        0     0
exit=0
Evidence: Hosts skipped for openclaw-only run
repo · 1 worktree(s) · since all

HOST   HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local  openclaw  23       22       1     0
  SELF = backpass's own loss / gradient-descent sessions, excluded from the corpus
ssh invocations: 0
ssh fake-remote connecting
warn fake-remote: failed to start ssh control master: ssh fake-remote exited 255 - host skipped, run continues
repo · 1 worktree(s) · since all

HOST         HARNESS   SCANNED  MATCHED  SELF  CACHED  NOTE
local        claude    0        0        0     0
local        openclaw  23       22       1     0
ssh invocations: 2
ssh -o BatchMode=yes -o ConnectTimeout=10 -o ServerAliveInterval=15 -o LogLevel=ERROR -o ControlMaster=auto -o ControlPath=/tmp/bp-9951-23cc638bbd87-%C -o ControlPersist=60 -O check -T -- fake-remote
ssh -o BatchMode=yes -o ConnectTimeout=10 -o ServerAliveInterval=15 -o LogLevel=ERROR -o ControlMaster=auto -o ControlPath=/tmp/bp-9951-23cc638bbd87-%C -o ControlPersist=no -M -N -T -- fake-remote
Evidence: User-scope inferred label preserved
scope user matched 23
dashboard | t1 | git | cwd is in /private/tmp/backpass-oc-live-8896/repo | project=/private/tmp/backpass-oc-live-8896/repo
fallback | t3 | inferred | inferred cwd (no recorded cwd): cwd is in /private/tmp/backpass-oc-live-8896/repo | project=/private/tmp/backpass-oc-live-8896/repo
slack | t1 | git | cwd is in /private/tmp/backpass-oc-live-8896/repo | project=/private/tmp/backpass-oc-live-8896/repo
strict matched 20 fallback present: false dashboard present: true
dashboard | t1 | git | cwd is in /private/tmp/backpass-oc-live-8896/repo | cwd=/private/tmp/backpass-oc-live-8896/repo
fallback | t3 | inferred | inferred cwd (no recorded cwd): cwd /private/tmp/backpass-oc-live-8896/repo/agent-workspace | cwd=/private/tmp/backpass-oc-live-8896/repo/agent-workspace
Evidence: --since uses archive activity time
scanned 22 oldarchive present: false livex present: true

Pipeline

Updates from git push no-mistakes

⏭️ **intent** - skipped

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (4) ✅
  • ℹ️ src/discovery/adapters/openclaw.js:384 - For a transcript whose entries carry no timestamps (a case the adapter explicitly supports: startedAt = times[0] ?? row.created_at at line 370 and the P1-3 test's undated archive), the anchor's time component is the row's own created_at. That value differs between the live session_windows row (window creation) and the session_transcript_archives row (archive creation), so an undated session's id changes when it is archived even though its content is unchanged. This contradicts the header comment (lines 18-21) and the README sentence 'identity survives archival', and the gap ledger would count the same session as two sightings across runs. Sibling site: the mtime fallback at line 374 uses the same undated path but is harmless there. Smallest remedy: when times is empty, build the anchor from the first event alone (no time component) or note the exception in the header and README; the anchor formula was specified by the user in an earlier decision, so the change needs sign-off rather than a silent fix.

🔧 Fix applied.
1 warning still open:

  • ⚠️ src/discovery/index.js:295 - capInferred (added in fix commit 373dbde, the starting head) returns a fresh { tier, confidence, reason } object and so discards the project and projectRoot fields that every scope's associate function puts on the association: project scope sets both to repo.root (src/scope.js:205-213) and user scope sets them from the git toplevel, remote, or cwd key (src/scope.js:69-104). Every CLI path reaches this through scan.js:12, which always passes scope. Concrete sequence: the repo under analysis is the configured OpenClaw workspace (the usual place for an OpenClaw AGENTS.md), an OpenClaw session has no recorded cwd (the fixture's fallback shape), associate returns tier 1 with project = repo.root, capInferred rewrites it to tier 3 with project undefined, and toTranscript (index.js:421) stores project: null. Downstream that session is silently omitted from analyzedByProject, sourceProjects, and sessionsByProject in src/fold.js:73,123,133, from the gap ledger's project field (src/gap-ledger.js:243), and from the user-scope minGapProjects gate (src/proposal.js:612-624), so its evidence can never satisfy the project floor and the relevance-by-project table undercounts it. Nothing errors. The intended cap is only on tier/confidence/reason; the smallest remedy is to spread the original association ({ ...association, tier: 3, confidence: &#34;inferred&#34;, reason }) so project/projectRoot survive, and to extend the unit test at test/openclaw-adapter.test.js:534-544 with an association carrying project/projectRoot so the test cannot pass while the keys are dropped. No sibling sites: capInferred is called only at index.js:308, and the remote path never sees OpenClaw rows because the adapter is localOnly.

🔧 Fix applied.
2 warnings still open:

  • ⚠️ src/discovery/index.js:295 - capInferred (added in fix commit 373dbde, the starting head) returns a fresh { tier, confidence, reason } object and so discards the project and projectRoot fields that every scope's associate function puts on the association: project scope sets both to repo.root (src/scope.js:205-213) and user scope sets them from the git toplevel, remote, or cwd key (src/scope.js:69-104). Every CLI path reaches this through scan.js:12, which always passes scope. Concrete sequence: the repo under analysis is the configured OpenClaw workspace (the usual place for an OpenClaw AGENTS.md), an OpenClaw session has no recorded cwd (the fixture's fallback shape), associate returns tier 1 with project = repo.root, capInferred rewrites it to tier 3 with project undefined, and toTranscript (index.js:421) stores project: null. Downstream that session is silently omitted from analyzedByProject, sourceProjects, and sessionsByProject in src/fold.js:73,123,133, from the gap ledger's project field (src/gap-ledger.js:243), and from the user-scope minGapProjects gate (src/proposal.js:612-624), so its evidence can never satisfy the project floor and the relevance-by-project table undercounts it. Nothing errors. The intended cap is only on tier/confidence/reason; the smallest remedy is to spread the original association ({ ...association, tier: 3, confidence: &#34;inferred&#34;, reason }) so project/projectRoot survive, and to extend the unit test at test/openclaw-adapter.test.js:534-544 with an association carrying project/projectRoot so the test cannot pass while the keys are dropped. No sibling sites: capInferred is called only at index.js:308, and the remote path never sees OpenClaw rows because the adapter is localOnly.
  • ⚠️ src/discovery/index.js:295 - The "inferred" label that fix round 373dbde introduced (and round 47498ce preserved alongside project fields) is overwritten in user scope by the pre-existing normalizeProjects in src/scope.js:257-268, which treats every tier-3 association with a local cwd as a bare-cwd association. Concrete sequence: backpass scan --scope user (openclaw is enabled by default), an OpenClaw session with no recorded cwd falls back to the configured workspace, that workspace is a git toplevel, so associateUser returns tier 1 confidence: &#34;git&#34; and indexes its worktrees into knownWorktrees (the cached association is the uncapped one, src/scope.js:245-251); capInferred returns a fresh { tier: 3, confidence: &#34;inferred&#34;, reason: &#34;inferred cwd (no recorded cwd): ...&#34; }; then scope.normalizeProjects(transcripts) (src/discovery/index.js:171) matches the cwd against the indexed root (listWorktrees always includes the root, src/repo.js:54-66) and sets association.confidence = &#34;git&#34; and association.reason = &#34;cwd is in registered worktree ...&#34; (src/scope.js:267-268). Tier stays 3 and --strict still drops the session, but the distill header (association: tier 3 (git), src/distill.js:136) now presents a guessed cwd as a recorded git location, which is the exact mislabel the cap was meant to prevent. Nothing errors. No sibling sites: project scope has no normalizer and the remote path never sees OpenClaw rows. Smallest remedy: in normalizeProjects skip associations already labelled inferred (if (transcript.association?.confidence === &#34;inferred&#34;) continue;), with a user-scope test asserting the capped association keeps confidence: &#34;inferred&#34; after discoverTranscripts.

🔧 Fix applied.
3 warnings still open:

  • ⚠️ src/discovery/index.js:295 - capInferred (added in fix commit 373dbde, the starting head) returns a fresh { tier, confidence, reason } object and so discards the project and projectRoot fields that every scope's associate function puts on the association: project scope sets both to repo.root (src/scope.js:205-213) and user scope sets them from the git toplevel, remote, or cwd key (src/scope.js:69-104). Every CLI path reaches this through scan.js:12, which always passes scope. Concrete sequence: the repo under analysis is the configured OpenClaw workspace (the usual place for an OpenClaw AGENTS.md), an OpenClaw session has no recorded cwd (the fixture's fallback shape), associate returns tier 1 with project = repo.root, capInferred rewrites it to tier 3 with project undefined, and toTranscript (index.js:421) stores project: null. Downstream that session is silently omitted from analyzedByProject, sourceProjects, and sessionsByProject in src/fold.js:73,123,133, from the gap ledger's project field (src/gap-ledger.js:243), and from the user-scope minGapProjects gate (src/proposal.js:612-624), so its evidence can never satisfy the project floor and the relevance-by-project table undercounts it. Nothing errors. The intended cap is only on tier/confidence/reason; the smallest remedy is to spread the original association ({ ...association, tier: 3, confidence: &#34;inferred&#34;, reason }) so project/projectRoot survive, and to extend the unit test at test/openclaw-adapter.test.js:534-544 with an association carrying project/projectRoot so the test cannot pass while the keys are dropped. No sibling sites: capInferred is called only at index.js:308, and the remote path never sees OpenClaw rows because the adapter is localOnly.
  • ⚠️ src/discovery/index.js:295 - The "inferred" label that fix round 373dbde introduced (and round 47498ce preserved alongside project fields) is overwritten in user scope by the pre-existing normalizeProjects in src/scope.js:257-268, which treats every tier-3 association with a local cwd as a bare-cwd association. Concrete sequence: backpass scan --scope user (openclaw is enabled by default), an OpenClaw session with no recorded cwd falls back to the configured workspace, that workspace is a git toplevel, so associateUser returns tier 1 confidence: &#34;git&#34; and indexes its worktrees into knownWorktrees (the cached association is the uncapped one, src/scope.js:245-251); capInferred returns a fresh { tier: 3, confidence: &#34;inferred&#34;, reason: &#34;inferred cwd (no recorded cwd): ...&#34; }; then scope.normalizeProjects(transcripts) (src/discovery/index.js:171) matches the cwd against the indexed root (listWorktrees always includes the root, src/repo.js:54-66) and sets association.confidence = &#34;git&#34; and association.reason = &#34;cwd is in registered worktree ...&#34; (src/scope.js:267-268). Tier stays 3 and --strict still drops the session, but the distill header (association: tier 3 (git), src/distill.js:136) now presents a guessed cwd as a recorded git location, which is the exact mislabel the cap was meant to prevent. Nothing errors. No sibling sites: project scope has no normalizer and the remote path never sees OpenClaw rows. Smallest remedy: in normalizeProjects skip associations already labelled inferred (if (transcript.association?.confidence === &#34;inferred&#34;) continue;), with a user-scope test asserting the capped association keeps confidence: &#34;inferred&#34; after discoverTranscripts.
  • ⚠️ src/discovery/index.js:294 - Sibling site of the invariant fix rounds 373dbde and 2d9bc53 addressed ("an inferred cwd is labelled inferred and never relabelled"), left behind rather than introduced by 2d9bc53. capInferred returns the association untouched when association.tier &gt;= 3, so an inferred cwd that only reaches tier 3 on its own never receives confidence: &#34;inferred&#34;, and the new guard in normalizeProjects (src/scope.js:261) does not recognise it. Concrete user-scope sequence: backpass scan --scope user (openclaw on by default); an OpenClaw session has no recorded cwd and falls back to the configured workspace &lt;repo&gt;/agent-workspace, a subdirectory that has since been deleted while &lt;repo&gt; is a live git repo. gitToplevel returns null for a missing path (src/repo.js:46), so associateUser yields tier 3 confidence: &#34;cwd&#34; (src/scope.js:96-102); capInferred returns it unchanged because tier is already 3; any other session recorded in &lt;repo&gt; indexes its worktrees into knownWorktrees; normalizeProjects then matches &lt;repo&gt;/agent-workspace under &lt;repo&gt; and sets confidence = &#34;git&#34;, reason = &#34;cwd is in registered worktree &lt;repo&gt;&#34; (src/scope.js:267-268). The distill header (src/distill.js:136) and scan reason column (src/commands/scan.js:149) now present a guessed, non-existent path as a registered git location, the same mislabel r3-1 fixed for the tier-1 case. Nothing errors; tier stays 3 and --strict still drops it, so this is a labelling defect only. Project-scope sibling: the same early return leaves a dead-path tier 3 (confidence: &#34;path&#34;/&#34;glob&#34;, src/discovery/association.js:114-118) without the inferred label, so the row's extra.cwdSource: &#34;configured-workspace&#34; is the only hint the cwd was guessed. Smallest remedy: drop the association.tier &gt;= 3 early return so every cwdInferred row is stamped { ...association, tier: 3, confidence: &#34;inferred&#34;, reason: &#34;inferred cwd (no recorded cwd): ...&#34; }, which also makes the normalizeProjects guard cover it. This flips the asserted outcome of the pipeline-authored unit test at test/openclaw-adapter.test.js:586-587 (capInferred(tier3, { cwdInferred: true }) currently expected to return the input unchanged) and changes the cap's designed contract, so the remedy, not the defect, needs sign-off.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 11 of 11 scenarios driven live against the product
Scenario Result Live Evidence
User runs scan in a repo with OpenClaw sessions: sessions attributed to the repo, probe/eval/test namespaces excluded, zstd archive decoded, human channels interactive and cron/subagent/heartbeat/acp/… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s1-scan-project.txt and s1-scan-project.json
Session with no recorded cwd falls back to the configured workspace and is shown as tier 3 'inferred cwd (no recorded cwd)'; --strict drops it while recorded-cwd sessions stay ✅ pass live s1-scan-project.txt (fallback/compressed rows t3) and ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s2-scan-strict.txt (20 matched, tier3 0)
Session identity survives archival (dated and undated), a live session keeps its id when a distinct colliding archive appears, and ids are the same whether the colliding archive was created earlier or… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s3-identity.txt
--since 30d excludes an archive whose events are from 2025 even though it was deleted today ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s10-since-30d.txt
Backpass's own sessions are excluded (bare sentinel, sentinel after a closed wrapper, and sentinel after an unclosed wrapper via the .backpass cwd check) while a human who quotes the sentinel after an… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s4-self-session.txt
Analysis agent receives a trace with injected context, conversation-info block, closed memory wrapper, timestamp prefix, system and provenance-injected messages removed, while human words after an unc… ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s5-analyze-trace.txt
openclaw binary absent and no snapshot supplied: scan completes with exit 0 and an empty openclaw row, no error ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s6-missing-binary.txt
openclaw binary present: backpass runs openclaw backup sqlite create --agent main --repository &lt;private tmp&gt; --json, reads the returned snapshot, and removes the private temp directory on exit ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s7-backup-binary.txt
Adversarial: backup returns an artifact outside its private repository, or the backup command fails: harness is skipped with a named warning, exit 0, no leftover temp dir ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s7b-artifact-escape.txt and s7c-backup-fails.txt
Configured SSH hosts with --harness openclaw: no ssh process is spawned; control run with claude,openclaw contacts the host ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s8-hosts-openclaw-only.txt
User scope: an inferred-cwd session stays tier 3 'inferred' (not relabelled git) when the workspace is a git toplevel or a deleted subdirectory of a repo, keeps its project, and --strict drops it ✅ pass live ~/.no-mistakes/evidence/01M3Z8A8Y924DG0XS9CAQAP73D/s9-user-scope.txt
  • backpass scan --harness openclaw --since all (plain and --json) in project scope against snapshot A with BACKPASS_OPENCLAW_DB set
  • backpass scan --harness openclaw --since all --strict (tier-3 inferred sessions dropped)
  • backpass scan --harness openclaw --since 30d --json (old-activity archive created today excluded)
  • backpass scan --json across snapshots A/B/C/D comparing nativeId for a live session, its unchanged archive, an undated session, and a colliding earlier/later archive
  • backpass scan --harness openclaw with openclaw absent from PATH and no BACKPASS_OPENCLAW_DB
  • backpass scan --harness openclaw with a fake openclaw backup sqlite create binary on PATH and TMPDIR isolated; checked temp dir removed after exit
  • Adversarial fake openclaw returning an artifact outside its repository, and one exiting 1
  • backpass scan --harness openclaw with discovery.hosts configured and BACKPASS_SSH_BIN pointing at a logging fake ssh; control run with --harness claude,openclaw
  • backpass scan --scope user --harness openclaw --since all --json (and --strict) with the configured workspace as a git toplevel, and with OPENCLAW_STATE_DIR pointing at a config whose workspace is a deleted subdirectory
  • backpass analyze --harness openclaw --analysis-agent pi --jobs 1 --limit 1 --json with BACKPASS_ACPX_BIN set to a recording fake acpx; inspected the raw events file and distilled prompt it received
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 3/5

[Medium risk] Adds OpenClaw transcript adapter to session discovery.

The PR does not appear safe to merge until live-session identity remains stable and the missing-binary collection failure is addressed.

Reviews (5) · Last reviewed commit: "no-mistakes(document): docs: cover infer..."

Comment thread src/discovery/adapters/openclaw.js Outdated
Comment thread src/discovery/adapters/openclaw.js Outdated
Comment thread src/discovery/index.js Outdated
Comment thread src/discovery/adapters/openclaw.js Outdated
Comment thread src/discovery/adapters/openclaw.js
Comment thread src/discovery/adapters/openclaw.js Outdated
Comment on lines +90 to +93
if (result.spawnError?.code === "ENOENT") {
fs.rmSync(snapshotDir, { recursive: true, force: true });
snapshotDir = null;
return null;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Missing binary is silent When openclaw is not installed, this branch returns no sessions without a warning. Because OpenClaw is enabled by default, a run can appear to have found no history when it could not collect that history. The repository requires missing capabilities to be named along with how to fix them, rather than silently degraded.

Context Used: If there is a VISION.md file at the root of the repo, the PR must not conflict / diverge / drift from it. If the PR description has an "Intent" section, respect that as the accepted user intent. - Do make comments if anything in the implementation ... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@prasith-jobleap

Copy link
Copy Markdown
Author

On the latest Greptile note ("Missing binary is silent"): this is deliberate. A missing openclaw binary is treated as no store, the same way hermes and the other adapters handle an absent store (openReadOnly -> null -> []). An earlier review round asked for exactly this, so that the default-on harness doesn't print a warning on every run for people who don't use OpenClaw. If BACKPASS_OPENCLAW_DB is set explicitly, the warning still fires. Happy to switch openclaw to opt-in if you'd rather.

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate.

Diff reviewed on 01d7a19e62f44f44d9cf02d90c6019b67dfee1c3 (fork PR from @prasith-jobleap). After review I approved first-time fork CI, Guard generated files, and Require no-mistakes. All three are green on this HEAD (ubuntu + macos build-and-test, Guard, no-mistakes attestation bound to this SHA). Greptile FAILURE is present but not a gate (repo does not use Greptile). MERGEABLE; mergeStateStatus UNSTABLE is Greptile-only. Not a security risk: online backup snapshot only (never live DB), private 0700 temp dir, artifact path confined under the snapshot repository, localOnly so SSH probes exclude it, ENOENT → silent empty like other absent stores, cleanup on CLI finally + process.exit. Pinned fixture builder + test/openclaw-adapter.test.js. No workflow-file changes.

Contract-class: new-default. Unconfigured discovery gains openclaw in ALL_HARNESSES (every default scan attempts collection). Separately, sessions with no recorded cwd still get cwd = configured workspace (extra.cwdSource: "configured-workspace") and then associate through ordinary path tiers — so a config fallback can land as tier 1 rather than best-effort/opt-in. VISION: "An association that cannot be made deterministically is labelled best-effort and stays opt-in, because a wrong attribution is worse evidence than none." Hermes skips sessions with no recoverable cwd; this path does not. Residual documented identity move when a live member of a colliding-anchor group is archived (or a lone archive gains a colliding sibling) is honest in the adapter header / README but still touches gap-ledger / rejection keys.

VISION per-rule (live VISION.md @ main a1bd0af vs tip of this PR)

  • Evidence is the only currency: aligns when cwd comes from session metadata/header (real turns from snapshot/archives). cannot tell / tension for configured-workspace → tier-1 (may attribute another project's sessions).
  • The human owns the weights: aligns (discovery only; no write / review-gate change).
  • Nothing the model says is taken on faith: aligns (pinned fixture, measured store, fail-soft drift warnings).
  • The budget is the constraint: aligns (no always-loaded growth).
  • It reads what you already have and owns nothing: aligns in kind (local snapshot, no upload, no key, localOnly). More harnesses welcome if they cannot destabilise others — fail-soft + localOnly look sound. does not align on the deterministic-association / opt-in bar for the workspace fallback.
  • Failure is loud and named: aligns (backup failure / schema / codec named and skipped; binary absent silent empty).
  • Scope: aligns (discovery adapter + distill lifecycle only).

Not waiting on the author for CI/no-mistakes — those are green. Waiting on a captain decision before merge:

  1. Keep openclaw in the default ALL_HARNESSES list, or move it to opt-in (OPT_IN_HARNESSES / --harness openclaw only)?
  2. For cwd-less sessions: skip (Hermes-style), or keep configured-workspace but force best-effort / --strict-excluded association instead of path tier 1?

I will not squash-merge until that call. No competing PR from me.

…apter PR. ci-1: every generation (live and archived) now gets `${session_id}:${sha256(startedAt\nJSON.stringify(events[0]))}`; the "first-seen keeps the bare session_id" branch in discover() was removed and the generation-key collision fallback kept; adapter header comment and README identity sentence updated. ci-2: the <active_memory_plugin> strip pattern now requires its closing tag, so an unclosed block leaves following human words intact (sibling wrapper patterns already required terminators). ci-3: discoverTranscripts computes the remote-capable harness list once and skips collectHosts entirely when it is empty (single call site). Tests added/updated: live id stable beside a later distinct archive and across its own archival; id independent of archive processing order (created_at swap); unclosed memory block preserved; configured hosts + harnesses ["openclaw"] produces no ssh calls and empty perHost/remoteMasters; bare-id assertions switched to the digest form. Verified the six new/updated tests fail on the pre-fix source and pass after. TMPDIR=/private/tmp pnpm run check: lint, format:check, typecheck OK; 885 tests pass, 0 fail. Changes left uncommitted in the worktree (README.md, src/discovery/adapters/openclaw.js, src/discovery/index.js, test/openclaw-adapter.test.js, test/remote-discovery.test.js)
… PR per the user's instructions. ci-1 (archive order changes identity). Invariant: a generation's id depends only on its own anchor, class (live/archived), generation key, and whether its anchor group holds other distinct content, never on processing order. discover() now runs two passes: the per-row loop reads every generation, dedupes by content digest per session_id (live wins over content-equal archives, duplicate archives keep the first read) and stores anchor + record; then `identify(sessionId, generations)` groups by anchor and assigns `${session_id}:${sha256(anchor)}` to every member of a single-digest group, while in a multi-digest group the live member keeps the anchor id and every archive gets `${session_id}:${sha256(anchor + "\n" + String(generation))}`. The order-dependent `generationId`/`seen` fallback was removed. The --since cutoff is still applied after identity. Header comment updated; it documents the remaining identity moves honestly: a live member of a collision group being archived, and also a lone archive gaining its first colliding sibling (that second move is inherent to the specified design, since a lone archive holds the plain anchor id until a sibling appears; noted here because the instruction asked to document only the first). README identity sentence updated accordingly. ci-2 (unclosed block bypasses self exclusion). Invariant: when any injected wrapper, closed or unclosed, precedes Backpass's sentinel, the first user message after stripping starts with the sentinel. stripScaffolding now, after removing complete wrappers, detects a leading unterminated opener (internal context, conversation info, or active_memory_plugin); if the self sentinel appears anywhere it drops everything before it, otherwise it drops only the opening `<active_memory_plugin>` tag and keeps the rest. The discover() self check (`startsWith(SELF_SESSION_SENTINEL)` after stripping) therefore sees the sentinel for all three openers; read() shares the same function so distill gets the same text. Tests (test/openclaw-adapter.test.js): twin collision group ids unchanged when archive processing order is reversed via created_at; a live member keeps its anchor id when colliding archives appear, and adding an earlier colliding archive leaves every existing id unchanged; unclosed memory block drops only its opening tag; unclosed wrapper of each kind before the sentinel yields text starting with the sentinel (and text before the sentinel is kept when no wrapper is unterminated); discover() excludes a session whose first user message is a closed or unclosed wrapper followed by the sentinel. Five tests fail on the pre-fix adapter (verified by swapping in HEAD's file) and pass after. Verification: TMPDIR=/private/tmp pnpm run check: eslint clean, prettier clean, tsc clean, 887 tests pass / 0 fail. Changes left uncommitted in the worktree: README.md, src/discovery/adapters/openclaw.js, test/openclaw-adapter.test.js
…sions) on the OpenClaw adapter per the user's instructions; ci-1 untouched as directed. Invariant: stripping never discards human text, and the self sentinel excludes a session only when it is the first non-wrapper content of the first user message (at the very start or right after a closed leading wrapper). `stripScaffolding` is the single boundary shared by discover()'s self check and read()/distill, so fixing it there covers both consumers. src/discovery/adapters/openclaw.js: removed the `UNTERMINATED_WRAPPER` regex and the branch that sliced text up to a quoted `SELF_SESSION_SENTINEL`. For an unterminated `<active_memory_plugin>` block only the opening tag is dropped and every remaining character is kept (unterminated internal-context and conversation-info openers are left verbatim as before). The discover() self check is unchanged (`startsWith(SELF_SESSION_SENTINEL)` after stripping) and so now fires only when the sentinel leads. The header comment notes the tradeoff: a Backpass prompt behind a truncated wrapper is not recognised by the sentinel and relies on the cwd/state-dir check in src/discovery/self.js, which Backpass-spawned sessions (cwd inside `<repo>/.backpass/`) always hit. test/openclaw-adapter.test.js: (1) the adapter-level exclusion test now asserts the bare sentinel and a closed wrapper + sentinel are excluded, while a human quote, an unclosed memory block, and an unclosed internal-context opener followed by the sentinel are all kept by discover(); (2) replaced the old "unclosed wrapper before the sentinel is dropped" test with one asserting every preceding character survives for each unclosed opener (memory block loses only its tag), and only a closed leading wrapper leaves the sentinel first; (3) new end-to-end test through discoverTranscripts with config.state.root set: a Backpass session whose first user message is an unclosed memory block followed by the sentinel, with workspace cwd under `.backpass/synthesis`, is excluded (perHarness.openclaw.self === 1), while a human session in the repo quoting the sentinel after the same unclosed wrapper is kept. All three tests fail on the pre-fix adapter (verified by swapping in HEAD's file) and pass after. Verification: TMPDIR=/private/tmp pnpm run check: eslint clean, prettier clean, tsc clean, 888 tests pass / 0 fail. README needed no change (it does not describe sentinel handling). Changes left uncommitted in the worktree: src/discovery/adapters/openclaw.js, test/openclaw-adapter.test.js
Sessions with no recorded cwd fall back to the configured OpenClaw workspace.
That path is a guess, so it no longer reaches the deterministic path tiers:
discovery caps it at tier 3 (labelled best-effort, excluded by --strict),
per VISION's deterministic-association rule. Recorded cwds (session metadata
or event headers) keep their normal tiers. Also drops internal ticket ids
from test names.
@prasith-jobleap prasith-jobleap changed the title feat(discovery): add snapshot-backed OpenClaw transcript adapter feat(discovery): add snapshot-backed OpenClaw discovery adapter Oct 2, 2026
? Math.max(...times)
: Math.max(row.updated_at || row.created_at, ...times);
const { events, model } = normalized(entries);
const anchor = [...(times.length ? [String(times[0])] : []), String(JSON.stringify(events[0]))].join("\n");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Live session identity changes If a live OpenClaw session starts with undated entries and later receives its first dated message, this anchor changes from the first event alone to a timestamp plus that same event. The session gets a new identity even though it is still the same session, so the gap ledger can count it as another sighting and identity-based suppression can stop applying.

Knowledge Base Used: Transcript distillation and sampling

@kunchenguid

Copy link
Copy Markdown
Owner

Speaking as Kun's firstmate.

Re-triage after substantive author pushes since the prior waiting-on-captain stamp on 01d7a19 (now HEAD 03be93752578b821b8798b68072cb292d5d7bc0c). Fork CI, Guard generated files, and Require no-mistakes were action_required on this SHA; after diff review I approved them. All three are green on this HEAD (ubuntu + macos build-and-test, Guard, no-mistakes attestation). Greptile FAILURE is present but not a gate. MERGEABLE; mergeStateStatus UNSTABLE is Greptile-only. No workflow-file changes. Not a security risk: online backup snapshot only (never live DB), private 0700 temp dir, artifact path confined under the snapshot repository, localOnly (SSH host contact skipped when only local-only harnesses selected), ENOENT → silent empty, cleanup on CLI finally + process.exit.

Author addressed captain item #2 (cwd-less association): cwdInferred when neither metadata nor header cwd is present; discovery capInferred forces tier 3 / confidence: "inferred" / reason prefix; --strict drops it; user-scope normalizeProjects skips inferred rows so they cannot be relabelled to git. This matches VISION's best-effort / opt-in bar for non-deterministic association (Hermes-style skip was the other option; this is the labelled best-effort path). Residual documented identity moves on colliding-anchor archive transitions remain honest in the adapter header.

Contract-class: still new-default. Unconfigured discovery still gains openclaw in ALL_HARNESSES (every default scan attempts collection). Contributor risk text does not decide the class. The tier-3 cap does not flip this to restore or opt-in.

VISION per-rule (live VISION.md @ main 0268201c vs tip 03be937)

  • Evidence is the only currency: aligns for real session quotes from snapshot/archives; inferred cwd no longer reaches path tier 1.
  • The human owns the weights: aligns (discovery only; no write / review-gate change).
  • Nothing the model says is taken on faith: aligns (pinned fixture, measured store, fail-soft drift warnings).
  • The budget is the constraint: aligns (no always-loaded growth).
  • It reads what you already have and owns nothing: aligns in kind (local snapshot, no upload, no key, localOnly, fail-soft). Deterministic-association bar for the workspace fallback: now aligns via tier-3 / --strict. Default-on harness addition remains a product call, not a VISION block by itself ("More harnesses is always welcome…").
  • Failure is loud and named: aligns (backup / schema / codec named and skipped; binary absent silent empty like other absent stores).
  • Scope: aligns (discovery adapter + distill lifecycle only).

Not waiting on the author for CI/no-mistakes — those are green. Still waiting on a captain decision before merge (only remaining open item):

  1. Keep openclaw in the default ALL_HARNESSES list, or move it to opt-in (OPT_IN_HARNESSES / --harness openclaw only)?

Item #2 from the prior stamp is cleared by the author's tier-3 cap. I will not squash-merge until the default-harness call. No competing PR from me.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants