Hermes can represent a configured root as either individual transcript files or as a state.db archive that fans out into multiple sessions. Moving it behind a concrete provider makes that source choice explicit instead of leaving archive behavior inside the legacy adapter path.\n\nThe provider preserves transcript discovery and lookup while treating state.db as a multi-session, force-replace source. Its fingerprint covers the archive database plus sibling transcripts so transcript-quality changes can refresh the archive source that ParseHermesArchive reads.
fix(parser): preserve hermes archive event coverage
Hermes archive discovery can normalize a configured sessions directory or direct state.db path into a sibling archive source, but the watch plan and changed-path classifier still assumed the configured root was the only event root. That left state.db updates and removed primary files invisible to provider-path sync.
Normalize archive watch roots, map delete and rename-style events syntactically when primary files are gone, and cover archive-parent, sessions-directory, and direct-state roots. This lets Hermes enter shadow comparison as an actual migration branch.
Validation: go test -tags "fts5" ./internal/parser -run 'Test(HermesProvider|ProviderMigrationModes)' -count=1; go test -tags "fts5" ./internal/parser -count=1; go vet ./...; git diff --check
fix(parser): watch hermes archive roots syntactically
Hermes archive configs can point at the archive parent, its sessions directory, or the state.db file before the sibling archive components have been created. Watch planning needs to treat those shapes as archive roots from their paths, not from startup-time existence checks, otherwise late-created metadata or transcripts are invisible until a full sync.
The transcript watch root is now retained for archive-shaped roots even when sessions/ is not present yet, while ordinary transcript-only roots keep their recursive file watch.
Validation: go test -tags "fts5" ./internal/parser -run 'TestHermesProvider' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; git diff --check
fix(parser): feed hermes archive roots to runtime watcher
Hermes provider watch planning now knows how to follow archive-shaped roots, but the actual serve-time watcher still reads registry watch resolvers. Without a matching Hermes resolver there, the default .hermes/sessions config can miss sibling state.db creation or updates in live sync.
Expose Hermes shallow archive-parent watch roots through the registry while keeping transcript roots recursive, and add shadow parity coverage so this branch remains a migration rather than an additive provider implementation.
Validation: go test -tags "fts5" ./cmd/agentsview ./internal/parser ./internal/sync -run 'TestCollectWatchRootsHermesSessionsWatchesStateDBParent|TestHermesProvider|TestParseHermes|TestProviderMigrationModes|TestObserveProviderSourceMatchesHermesLegacyParser' -count=1; go test -tags "fts5" ./cmd/agentsview ./internal/parser ./internal/sync -count=1; go fmt ./...; go vet ./...; ./custom-gcl run --config .golangci.nilaway.yml ./cmd/agentsview/... ./internal/parser/... ./internal/sync/...; git diff --check
fix(sync): classify hermes archive watcher events
Roborev jobs 2715 and 2716 caught that Hermes archive watch roots were subscribed but the legacy SyncPaths classifier still ignored sibling state.db events. That meant live sync could wait for a periodic full sync even though the watcher saw the change.
Map configured Hermes archive roots, state.db events, and direct archive transcript events back to the state.db source that processHermes already parses, while preserving transcript-only root classification for standalone Hermes session files.
Validation: go test -tags "fts5" ./internal/sync -run TestSyncPathsHermesStateDBEventRefreshesArchive -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Test(HermesProvider|ObserveProviderSourceMatchesHermesLegacyParser|SyncPathsHermesStateDBEventRefreshesArchive)' -count=1; go fmt ./...; go vet ./...; git diff --check
fix(sync): include hermes transcripts in archive skips
Roborev job 2803 caught that Hermes transcript watcher events could still be suppressed by state.db-only skip metadata after being routed to the archive source. In mixed state-db/transcript archives, state.db can be unchanged while a sibling transcript is new or updated.
Use archive-effective size and mtime for state.db skip checks by folding direct transcript files from the sibling sessions directory into the snapshot, and add a regression where a transcript event refreshes an already-indexed archive.
Validation: go test -tags "fts5" ./internal/sync -run 'TestSyncPathsHermes(ArchiveTranscriptEventRefreshesArchive|StateDBEventRefreshesArchive)' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync -run 'Test(HermesProvider|ObserveProviderSourceMatchesHermesLegacyParser|SyncPathsHermes)' -count=1; go fmt ./...; go vet ./...; git diff --check
fix(sync): use aggregate hermes archive fingerprints
Hermes archive freshness needs the state.db sync path to compare the same aggregate fingerprint it persists. Discovering through the public Hermes session lister reselected state.db and missed sibling transcripts, so state.db events could avoid real skip-cache parity.\n\nEnumerate direct transcript files for the archive snapshot and stamp archive parse results with the aggregate state.db fingerprint before writing. This keeps unchanged archive syncs comparable while still refreshing when sibling transcripts change.\n\nValidation: go test -tags "fts5" ./internal/parser ./internal/sync; go vet ./...; make nilaway
fix(sync): apply hermes archive fingerprints consistently
Hermes archive refresh paths need to compare and persist the same aggregate fingerprint for state.db plus sibling transcripts. Otherwise cached parse skips and single-session refreshes can fall back to raw state.db metadata and miss transcript-only archive changes.
Use the aggregate archive file info before generic skip-cache checks and share the archive parse-and-stamp helper between full archive processing and single-session refreshes. The regression coverage now persists the metadata, checks unchanged archive skips, and covers transcript discovery/removal behavior.
Validation: go test -tags "fts5" ./internal/sync -run 'TestHermesArchive|TestProcessFileHermes|TestProcessHermesArchive|TestSyncSingleHermesArchive' -count=1; go test -tags "fts5" ./internal/parser ./internal/sync; go vet ./...; make nilaway
refactor(parser): fold hermes into provider
Move Hermes source discovery, lookup, and parse ownership onto the
concrete hermesProvider and delete the package-level
DiscoverHermesSessions, FindHermesSourceFile, ParseHermesArchive, and
ParseHermesSession free functions. Discovery and find-source bodies now
live as provider-owned helpers (discoverHermesSessions,
findHermesSourceFile); parse, archive parse, the state-db reader, and the
transcript-archive fallback become hermesProvider methods (parseSession,
parseArchive, parseStateDB, parseTranscriptArchive).
Reproduce Hermes archive behavior on the provider. The provider's archive
Parse now stamps every state.db session with the state.db path plus the
aggregate (state.db + direct transcripts) size and mtime, replacing the
engine's stampHermesArchiveResults/hermesArchiveEffectiveInfo so a
transcript-only change still refreshes the archive's stored freshness. The
new provider helpers hermesArchiveEffectiveFileInfo and
hermesArchiveTranscriptFiles mirror the legacy engine aggregation (every
.jsonl and session_*.json directly under the sessions directory, no
dedup). The existing composite archive Fingerprint and archive watch/
classify source-set methods already carried the rest.
Make Hermes provider-authoritative and drop its legacy sync dispatch:
remove classifyHermesPath (and its hermesSyncArchivePaths,
hermesSyncDirExists, hermesSyncTranscriptPath helpers), the processFile
hermesArchiveEffectiveInfo stat hook and case arm, processHermes,
parseHermesArchive, stampHermesArchiveResults, hermesArchiveEffectiveInfo,
hermesArchiveTranscriptFiles, hermesArchiveSourcePaths, and the
syncSingleHermesArchive special-case plus its method. Single-session
resync of an archive now falls through to the generic provider path, which
reparses the whole archive (ForceReplace) the same way a full sync does.
Drop the Hermes AgentDef DiscoverFunc/FindSourceFunc hooks (the
provider-owned WatchRootsFunc/ShallowWatchRootsFunc stay), remove
hermes_provider.go from the pending shim scan list, replace the
shadow-baseline test with provider-API coverage plus a guard that the
legacy entrypoints stay gone, and route the package and engine archive
tests through provider methods and the provider-authoritative processFile/
SyncPaths paths.
Add internal/sync/provider_shadow_support_test.go defining the shared
writeProviderShadowSourceFile test helper that the remaining vibe shadow
test still references, which was orphaned by a predecessor commit.
test(sync): drop unused shadow source-file helper
The hermes fold left writeProviderShadowSourceFile in a dedicated test
support file, but every shadow test writes its fixtures inline, so the
helper has no callers and trips the unused linter. Remove the dead
scaffolding.
Hermes now uses a concrete provider for both transcript-file roots and state.db archive roots. Transcript roots remain single-session sources, while state.db roots are modeled as multi-session force-replace sources.
The provider keeps legacy transcript discovery and lookup behavior and gives archive roots a composite fingerprint over state.db plus sibling transcripts, matching the files ParseHermesArchive can read when selecting the richest message stream.