This document describes both the target provider facade and the staged migration
history used by the stacked PRs. On the provider-explicit-registry stack tip,
the provider registry no longer has a legacy fallback factory, concrete
parse-capable providers are expected to be provider-authoritative, and
Claude.ai / ChatGPT are explicitly import-only.
The earlier legacy-only -> shadow-compare -> provider-authoritative flow is
historical guidance for the lower migration branches in the stack. The final tip
must not reintroduce ProviderMigrationLegacyOnly; the literal "legacy-only"
value is rejected as an invalid migration mode. ProviderMigrationShadowCompare
has also been removed from the current stack tip: future staged migrations need
to add an explicit temporary comparison mode on the branch that needs it rather
than relying on a dormant runtime path. Tip runtime code requires every
parse-capable, non-import provider to be authoritative.
provider-authoritative currently makes the provider registry, migration
manifest, sync-engine processFile dispatch, full-sync source discovery, and
source lookup authoritative for parse-capable providers. Kata issue n489 is
closed at the stack tip: the dead legacy parser wrappers and old AgentDef
source callback surface have been removed, and the tip no longer falls back to
the provider-by-provider legacy processFile switch.
agentsview supports many agent formats, but parser integration is currently
spread across parser.AgentDef, parser-specific discovery functions, parser
function signatures, and a large sync.Engine switch. Adding a provider often
means touching several unrelated areas and relying on convention for optional
features such as tool calls, usage events, termination status, source mtime, and
incremental parsing.
This design adds a shared provider facade so adding or migrating a provider
means implementing one contract. The facade keeps provider source shape
internal, while the sync engine consumes normalized source identities,
fingerprints, and ParseResult values.
- Keep
ParsedSession,ParsedMessage,ParsedToolCall,ParsedToolResult,ParsedUsageEvent, andParseResultas the normalized output contract. - Keep every parse-capable provider except explicit import-only providers in
provider-authoritativemode. - Keep
sync.Engine.processFileprovider-owned; the provider-by-provider legacy dispatch switch and runtime shadow hook must not be restored at the tip. - Make source discovery, source lookup, watch planning, fingerprinting, parsing, and optional incremental parsing provider-owned.
- Keep provider-authoritative
AgentDefentries free ofDiscoverFuncandFindSourceFunc. Runtime callers should reach provider discovery and lookup through the provider registry, not through per-agent callback shims. - Disallow exported provider-specific facade functions named like
Discover*,Find*,Parse*Session(s),Parse*DB,Process*, orClassify*. Package-local parser mechanics and provider-neutral path parsers such asParseVirtualSourcePath,ParseAiderVirtualPath, andParseCursorTranscriptRelPathare allowed because they are not runtime dispatch facades. - Provide reusable provider helpers for common source layouts, especially JSONL file discovery.
- Make optional parsed features auditable through a concrete
Capabilitiesstruct. - Preserve current SQLite schema, parse-diff semantics, skip-cache behavior, and parser output parity.
These goals describe how the stacked PRs got to the tip and remain useful when a future provider migration starts on a lower branch:
- Migrate every existing provider to the facade, not only future providers.
- Keep both runtime shapes available at the root of the stack so legacy parsing stays authoritative while provider parsing is shadow-compared provider by provider.
- Make every provider PR an actual migration step: adding a provider implementation must also opt that provider into the shared migration manifest and provider-vs-legacy parity tests.
- Remove the provider-by-provider
sync.Engine.processFiledispatch switch only at the tip of the stack, after every parse-capable provider has passed the shadow comparison path.
- Rewrite individual provider parsers from scratch.
- Change the persistent database schema as part of this refactor.
- Move DB writes into providers.
- Make source storage shape a global engine concern.
- Turn all providers into JSONL providers. JSONL helpers are shared utilities, not the abstraction boundary.
The provider facade must respect these constraints:
- Source shape belongs to the provider. The engine must not know whether a source is a JSONL file, SQLite row, sidecar, trace folder, import archive, or multiple files.
- Providers embed a base facade with zero-value no-op implementations for optional source behavior.
- Providers must implement
Parse; the base facade must not provide a fake parse implementation. - Capabilities use a concrete struct. The zero value of every capability field is unsupported.
- Capability enum string and JSON methods should be generated with
dmarkham/enumer, because it supports generatedString, JSON, and text marshal methods from one enum definition. - All existing providers migrate to the new layer before the old sync dispatch is considered removed.
- On lower migration branches, legacy dispatch remains the writer. Provider dispatch is run through the same root-level harness for opted-in providers and compared against legacy output, but it must not mutate persisted session state until the stack tip switches authority.
- A provider branch is incomplete if it only adds provider code. It must also move the provider's migration status out of legacy-only mode and include the dual-run test coverage that proves the new shape is exercised.
The provider contract should live near the parser boundary, for example in
internal/parser/provider.go, because it works with parser-owned normalized
types and agent metadata.
type ProviderFactory interface {
Definition() AgentDef
Capabilities() Capabilities
NewProvider(ProviderConfig) Provider
}
type ProviderConfig struct {
Roots []string
Machine string
}
func (cfg ProviderConfig) Clone() ProviderConfig {
cfg.Roots = append([]string(nil), cfg.Roots...)
return cfg
}
func (cfg ProviderConfig) RootsCopy() []string {
return append([]string(nil), cfg.Roots...)
}
type Provider interface {
Definition() AgentDef
Capabilities() Capabilities
Discover(context.Context) ([]SourceRef, error)
WatchPlan(context.Context) (WatchPlan, error)
SourcesForChangedPath(context.Context, ChangedPathRequest) ([]SourceRef, error)
FindSource(context.Context, FindSourceRequest) (SourceRef, bool, error)
Fingerprint(context.Context, SourceRef) (SourceFingerprint, error)
Parse(context.Context, ParseRequest) (ParseOutcome, error)
ParseIncremental(
context.Context,
IncrementalRequest,
) (IncrementalOutcome, IncrementalStatus, error)
}ProviderFactory is the registry surface. Provider is a config-bound instance
created by NewProvider for one engine, with that engine's configured roots and
machine. NewProvider implementations must clone ProviderConfig before
storing it. Every retained owner of root slices must get its own copy: one for
ProviderBase.Config, separate copies for source helpers, and separate copies
inside helper constructors that retain roots. This keeps later caller mutation
and helper-local normalization from changing another component's view of roots.
If ProviderConfig later gains map, slice, or pointer fields, Clone must be
updated to preserve the same snapshot invariant. This keeps changed-path
classification root-aware without requiring mutable singleton providers or
passing raw roots through every engine call.
ProviderBase implements every optional source method with safe zero-value
no-op behavior. It does not implement Parse, so a concrete provider cannot
satisfy Provider without a real parser entry point.
type ProviderBase struct {
Def AgentDef
Caps Capabilities
Config ProviderConfig
}
var ErrUnsupportedProviderFeature = errors.New("unsupported provider feature")
type UnsupportedProviderFeatureError struct {
Provider AgentType
Feature string
}
func (err UnsupportedProviderFeatureError) Error() string {
return string(err.Provider) + ": unsupported provider feature " + err.Feature
}
func (err UnsupportedProviderFeatureError) Unwrap() error {
return ErrUnsupportedProviderFeature
}ProviderBase provides Definition, Capabilities, empty discovery, empty
watch plans, no changed-path classification, no source lookup, unsupported
fingerprints, and (IncrementalOutcome{}, IncrementalUnsupported, nil) for
incremental parsing. Unsupported methods that report an error use
ErrUnsupportedProviderFeature, so callers can distinguish "feature is absent"
from I/O, database, or parser failures with errors.Is. That keeps the engine
call surface uniform: every provider can be called through the full Provider
interface without feature-specific nil checks.
Unsupported defaults are still contract checks, not fallback policy. Any
provider that returns a SourceRef from discovery, changed-path classification,
lookup, or another provider source path must implement Fingerprint for that
reference. The engine treats unsupported fingerprinting for a returned source as
a provider contract failure; it must not silently use a zero fingerprint, mark
the source clean, or downgrade to an unspecified full parse path.
Reusable source helpers must not be generic indirection interfaces or provider
base classes. They are plain source-set structs such as JSONLSourceSet,
DirectoryJSONLSourceSet, or SQLiteFanoutSourceSet. A provider keeps a helper
as a named field and forwards the source methods it supports. That forwarding is
deliberate: it makes the provider's optional behavior visible at the concrete
type without adding another abstraction layer.
Every provider should include a compile-time assertion:
var _ Provider = (*CodexProvider)(nil)Embedding and delegation examples must be compile-tested as part of the provider harness, so the documented pattern cannot drift into impossible Go.
NewProvider returns a config-bound provider instance for one sync engine.
Provider instances are long-lived enough to serve full sync, live watch sync,
source lookup, diagnostics, export, and parse-diff calls for that engine.
Provider methods must be safe for concurrent calls. Implementations should keep
configuration and source helper fields immutable after construction. Any cache,
lazy initialization, database handle, or source index stored on the provider
must use normal Go synchronization or be confined to one method call. Providers
must honor context.Context cancellation for filesystem, database, and parser
work that can block.
The engine may pass SourceRef values between goroutines and queue them for
later work against the same provider instance. SourceRef.Opaque therefore must
be immutable, safe to read concurrently, and valid for the life of the provider
instance. It must not contain unguarded mutable state or open handles that
require engine cleanup. When a source needs a handle, the provider should store
stable keys in Opaque and open or manage the handle inside provider methods.
The intended implementation pattern is: embed ProviderBase, keep source
helpers as named fields, and implement explicit forwarding methods for the
source behaviors the provider supports. ProviderBase keeps every optional
method callable; provider methods override only the useful defaults.
type CodexProvider struct {
ProviderBase
sources SiblingMetadataSourceSet
}
func NewCodexProvider(cfg ProviderConfig) *CodexProvider {
config := cfg.Clone()
sourceRoots := config.RootsCopy()
return &CodexProvider{
ProviderBase: ProviderBase{
Def: codexAgentDef(),
Caps: codexCapabilities(),
Config: config,
},
sources: SiblingMetadataSourceSet{
Base: JSONLSourceSet{
Agent: AgentCodex,
Roots: sourceRoots,
Extensions: []string{".jsonl"},
Recursive: true,
},
MetadataFiles: []string{CodexSessionIndexFilename},
},
}
}
func (p *CodexProvider) Discover(
ctx context.Context,
) ([]SourceRef, error) {
return p.sources.Discover(ctx)
}
func (p *CodexProvider) WatchPlan(
ctx context.Context,
) (WatchPlan, error) {
return p.sources.WatchPlan(ctx)
}
func (p *CodexProvider) SourcesForChangedPath(
ctx context.Context,
req ChangedPathRequest,
) ([]SourceRef, error) {
return p.sources.SourcesForChangedPath(ctx, req)
}
func (p *CodexProvider) FindSource(
ctx context.Context,
req FindSourceRequest,
) (SourceRef, bool, error) {
return p.sources.FindSource(ctx, req)
}
func (p *CodexProvider) Fingerprint(
ctx context.Context,
source SourceRef,
) (SourceFingerprint, error) {
return p.sources.Fingerprint(ctx, source)
}
func (p *CodexProvider) Parse(
ctx context.Context,
req ParseRequest,
) (ParseOutcome, error) {
sourcePath, ok := p.sources.pathFromSource(req.Source)
if !ok {
return ParseOutcome{}, fmt.Errorf(
"codex source missing file path: %s",
req.Source.Key,
)
}
sess, msgs, err := ParseCodexSession(
sourcePath,
req.Machine,
false,
)
if err != nil || sess == nil {
return ParseOutcome{}, err
}
return ParseOutcome{
Results: []ParseResultOutcome{{
Result: ParseResult{Session: *sess, Messages: msgs},
DataVersion: DataVersionCurrent,
}},
ResultSetComplete: true,
}, nil
}For a simple JSONL provider, the same pattern uses a different source-set field:
type QwenProvider struct {
ProviderBase
sources DirectoryJSONLSourceSet
}
func NewQwenProvider(cfg ProviderConfig) *QwenProvider {
config := cfg.Clone()
sourceRoots := config.RootsCopy()
return &QwenProvider{
ProviderBase: ProviderBase{
Def: qwenAgentDef(),
Caps: qwenCapabilities(),
Config: config,
},
sources: DirectoryJSONLSourceSet{
JSONLSourceSet: JSONLSourceSet{
Agent: AgentQwen,
Roots: sourceRoots,
Extensions: []string{".jsonl"},
Recursive: true,
},
ProjectFromPath: qwenProjectFromPath,
},
}
}Providers that need one-off source behavior still embed ProviderBase and write
only the concrete methods they need:
type VisualStudioCopilotProvider struct {
ProviderBase
traces VisualStudioTraceSourceSet
}
func (p *VisualStudioCopilotProvider) Fingerprint(
ctx context.Context,
source SourceRef,
) (SourceFingerprint, error) {
return p.traces.StrictFingerprint(ctx, source)
}The rule is: embed ProviderBase once, compose source helpers as named fields,
and forward intentionally. Do not embed source helpers beside ProviderBase
when they define the same optional methods as the base; same-depth promoted
selectors will not give the intended override. Source behavior stays explicit:
there is no generic source-behavior interface and no runtime method table hidden
in the base.
SourceRef is the engine-visible handle for provider-owned source data.
type SourceRef struct {
Provider AgentType
Key string
DisplayPath string
FingerprintKey string
ProjectHint string
// Provider-owned payload. The sync engine passes this back to the
// same provider and must not inspect it.
Opaque any
}Rules:
Keyis stable within the provider and suitable for logs and dedupe.DisplayPathis human-readable and may be a virtual path.FingerprintKeyis the DB lookup key used for skip/data-version checks. With the no-schema-change migration, this is the authoritative persisted source identity and is written throughParsedSession.File.Path/sessions.file_path.SourceFingerprint.Keymust either equal the selected persisted identity or be empty so the engine falls back toSourceRef.FingerprintKeyandSourceRef.Key; there is no separate fingerprint-key column.ProjectHintis advisory and can be empty.Opaqueis internal provider state. The engine treats it as an opaque token.Opaqueis never persisted or logged, must be immutable for engine callers, and must remain usable whenSourceRefis queued across goroutines for the lifetime of the provider instance.- When
FindSourcereceives a raw/full session ID for a source file that can contain multiple logical sessions, it must return aSourceRefscoped to that logical session. For example, Visual Studio Copilot must return the<traceFile>#<conversationID>virtual path instead of the physical trace file so raw export and single-session sync cannot accidentally process unrelated conversations.
Backwards compatibility:
- Migrated providers should keep
FingerprintKeycompatible with the source key or storedfile_pathvalues already written by the legacy sync path whenever practical. Existing fingerprint and data-version metadata should continue to short-circuit unchanged sources after the facade migration. - If a provider must change its lookup key or fingerprint identity, that provider migration must explicitly document the expected full resync or metadata transition. The facade migration itself must not silently force all providers through a full resync.
- Diagnostics, parse errors, and logs should surface stable fields such as
Provider,Key,DisplayPath, andFingerprintKey.Opaqueis never persisted or logged because it may contain provider-internal implementation details that are not stable across releases.
Watch and changed-path classification use provider-owned root metadata:
type WatchPlan struct {
Roots []WatchRoot
}
type StoredSourcePathHintMode string
const (
StoredSourcePathsNone StoredSourcePathHintMode = ""
StoredSourcePathsProviderRoot StoredSourcePathHintMode = "provider-root"
)
type WatchRoot struct {
Path string
Recursive bool
IncludeGlobs []string
ExcludeGlobs []string
DebounceKey string
StoredSourcePathHints StoredSourcePathHintMode
}
type ChangedPathRequest struct {
Path string
EventKind string
WatchRoot string
StoredSourcePaths []string
}WatchRoot.Path is the actual filesystem root the engine should watch.
Recursive controls watcher depth. Include and exclude globs are advisory
provider filters that allow broad OS watch roots without parsing every changed
file. DebounceKey groups related paths such as sibling metadata files and a
transcript. ChangedPathRequest.WatchRoot is the matched watch root, so the
provider can classify changes relative to the configured root that produced
them. StoredSourcePaths is the persisted sessions.file_path hint set scoped
to the matched watch root and provider. Providers with virtual, database-backed,
or multi-file sources use it to classify deletion and tombstone events that no
longer have a regular source file on disk.
StoredSourcePathHints is zero-valued for providers that do not need persisted
source-path hints. SQLite fan-out providers that need tombstone recovery declare
StoredSourcePathsProviderRoot on the narrow watch root that owns the shared
source. That declaration is the engine's only signal to load stored source
paths; it must not infer the need from provider names, virtual path syntax, or
capability combinations. A provider must not request stored-source hints from a
broad recursive root unless the root is already narrow enough for an indexed
provider/root lookup.
StoredSourcePaths contains optional, already-persisted session file_path
values for the matched provider and watch root. It exists for providers that
model a shared physical source as virtual per-session sources, such as SQLite
fan-out providers. The engine supplies these paths from a session-store API that
queries by exact provider plus watched-root path prefix, using an index on
(agent, file_path) or equivalent. The lookup must not scan all sessions, and
the returned list must be sorted and de-duplicated before it is passed to the
provider. Provider-only migration slices may cover the behavior with explicit
provider tests until that caller exists. Providers use the hints only to recover
tombstone sources for rows or DB files that can no longer be rediscovered from
current metadata. Providers must still filter by the concrete changed DB/path
before returning any source.
For this lookup, the provider key is the persisted sessions.agent value and
must equal string(SourceRef.Provider) / string(AgentDef.Type) for every
migrated provider. The watched-root match is an exact root-or-descendant check
against persisted sessions.file_path values after cleaning absolute paths and
normalizing path separators with the platform filepath rules already used by
the sync engine. Implementations must use filepath.Rel or an equivalent
boundary-aware check; raw string prefix matching is not valid because /data/db
must not match /data/db2 or /data/db-backup. The lookup does not resolve
symlinks or inspect provider virtual suffixes. Today the DB-backed virtual shape
is <clean-db-path>#<session-id>, so a watch root that is the DB directory
matches those paths by ordinary descendant semantics; if a future provider
persists a virtual format that does not preserve a cleaned root prefix, it
cannot use StoredSourcePathsProviderRoot until it also adds a different
explicit hint mode and store query contract. Duplicate suppression happens after
the same cleaning step used for root matching. The API must not silently cap
results; if an implementation needs batching, it must stream or accumulate all
matching paths before classification, or return an error and let the caller fall
back to the provider's full discovery path.
Persisted source paths are compatibility keys. Providers that request stored hints must continue to understand their previously persisted virtual path formats, or require a documented full resync for a format change. Malformed or obsolete stored paths are ignored for tombstone recovery and may be logged as debug provider diagnostics; they are not parse diagnostics, are not user-visible session errors, and must not make the whole changed-path classification fail.
The provider owns the final changed-path decision. The engine may use
IncludeGlobs and ExcludeGlobs as coarse prefilters because the provider
supplied them, but SourcesForChangedPath must still tolerate unfiltered events
and apply authoritative provider-specific classification. Diagnostics should
report whether the provider accepted, ignored, or rejected a changed path.
FindSource replaces the current FindSourceFunc fallback model. It must cover
file-backed and database-backed providers because FindSourceFile,
SourceMtime, token usage commands, session watch, and export flows all need
provider-specific source lookup today.
type FindSourceRequest struct {
RawSessionID string
FullSessionID string
StoredFilePath string
FingerprintKey string
RequireFreshSource bool
}Stored DB file_path values are advisory compatibility keys. The engine passes
them through FindSourceRequest, but the provider decides whether they are real
paths, virtual paths, row keys, or obsolete source hints. The engine must not
stat, split, glob, or otherwise interpret a stored path before asking the
provider to resolve it.
RequireFreshSource means the caller needs a source reference the provider has
verified against current source state. The provider may use stored metadata as a
hint, but it must confirm the current file, database row, import record, or
virtual source can still be read or fingerprinted. Filesystem providers usually
check the current path; database and virtual providers can satisfy this by
resolving the current logical record. If the source no longer exists, the
provider returns (SourceRef{}, false, nil). If the source might exist but
cannot be checked because of an I/O or database failure, it returns an error.
When RequireFreshSource is false, the provider may return the best
compatibility match for display/export flows that can tolerate stale source
hints.
The provider owns fingerprint calculation because source freshness can depend on composite state:
- transcript files plus sibling metadata;
- SQLite database file mtimes;
- virtual paths for one logical session inside a database;
- sidecar files that supersede encrypted or summary sources;
- trace folders containing related files.
type SourceFingerprint struct {
Key string
Size int64
MTimeNS int64
Inode uint64
Device uint64
Hash string
}The engine uses fingerprints for generic skip/data-version checks and stores the same normalized source file metadata it stores today. Hashes remain optional where they are expensive or not meaningful.
Fingerprinting must stay cheap enough for the sync hot path. Acceptance tests or benchmarks should use representative large roots and composite sources with concrete pass criteria:
- no full-root content hashing during unchanged sync;
- no recursive directory walk for every source when discovery already produced the source list;
- file-backed fingerprints are bounded by the source plus declared sibling metadata files;
- database fan-out fingerprints reuse database-level metadata plus row/session identifiers instead of scanning unrelated rows;
- any provider that requires a full content hash documents why mtime, size, inode/device, row metadata, or sidecar metadata are insufficient and includes a benchmark budget for that provider.
type ParseRequest struct {
Source SourceRef
Fingerprint SourceFingerprint
Machine string
ForceParse bool
}
type ParseOutcome struct {
Results []ParseResultOutcome
ExcludedSessionIDs []string
SourceErrors []SourceError
ResultSetComplete bool
ForceReplace bool
SkipReason SkipReason
}
type ParseResultOutcome struct {
Result ParseResult
DataVersion DataVersionState
RetryReason string
}
type SourceError struct {
SourceKey string
DisplayPath string
SessionID string
Err error
Retryable bool
}
type DataVersionState uint8
const (
DataVersionUnspecified DataVersionState = iota
DataVersionCurrent
DataVersionNeedsRetry
)
type SkipReason uint8
const (
SkipNone SkipReason = iota
SkipNoSession
SkipUnsupportedSource
SkipNonInteractive
SkipShadowedBySidecar
)Runtime behavior:
- Whole-source parse failures return
error. - Multi-session providers return one
ParseResultOutcomeper successfully parsed session andSourceErrorsfor per-session failures, so good sessions can still be ingested. - All session IDs in parse outcomes use the persisted normalized/full session ID
namespace. That includes
ParseResultOutcome.Result.Session.ID,ExcludedSessionIDs, andSourceError.SessionID. Raw upstream IDs may appear in provider internals, diagnostics, or lookup requests, but the engine compares outcome IDs only against persisted full session IDs. SourceError.SessionIDis required for per-session failures from multi-session providers.SourceKeyandDisplayPathare diagnostic source identifiers, not substitutes for persisted session identity. If the provider cannot isolate a failure to a persisted full session ID, it must return a whole-sourceerrorinstead of aSourceError.Retryabledecides whether a failure can be cached by mtime.ForceReplaceis the generic signal for full parses that must rewrite existing ordinals.DataVersionCurrentmeans the corresponding successful result represents the current parser data version for that session.DataVersionNeedsRetrymeans successful fallback results may be written, but the session remains eligible for a future parse at the current data version. The engine must not persist a current data-version marker for that session.RetryReasonrecords why, for example an Antigravity-style lower-resolution fallback.DataVersionUnspecifiedis allowed only during migration adapters; provider harness tests should require new providers to set eitherDataVersionCurrentorDataVersionNeedsRetryfor every returned result.- Mixed data-version states are valid for multi-session sources. One result can
be current while another result from the same source needs retry, and a
retryable
SourceErroraffects only the failed session unless the provider reports a whole-sourceerror. - Data-version writes are per result, but clean skip-cache persistence remains
source/fingerprint scoped.
ResultSetCompletemeans the provider has accounted for the complete logical session set represented by theSourceRef/FingerprintKey: returned results, explicit exclusions, and clean replacements cover every retained session for that source. The engine may write a clean skip-cache entry only whenResultSetCompleteis true, every returned result isDataVersionCurrent, there are noSourceErrors, and any previously persisted rows for thatFingerprintKeyare either returned, listed inExcludedSessionIDs, or covered by a cleanForceReplace. - Any
DataVersionNeedsRetryresult, retryable per-session error, non-retryable per-session error, or incomplete result set suppresses the clean skip-cache entry for the wholeFingerprintKey. Non-retryable errors may be recorded as diagnostics or failure-cache entries, but they do not prove the source is clean because a future parser version or source change may still need to revisit the same logical session set. - During a partial multi-session parse, existing persisted rows that are absent
from
Resultsare retained unless their IDs are listed inExcludedSessionIDsor the provider completes a cleanForceReplaceparse for the owning logical source. A retryableSourceErrorleaves that session's existing row stale and eligible for a future retry instead of deleting it or marking it current. SkipReasonreplaces implicit "nil session means skip" behavior. Skips are explicit outcomes and should not be conflated with retryable parse failures.- Provider cache identity and
ResultSetCompletesemantics are root hook invariants, not per-provider policy. Provider branches may add source-family coverage, but they must not redefine cache keys, omission/deletion behavior, or retry-state persistence. - When a migrated provider's fingerprint key differs from the legacy
file.Path, the provider path reads, writes, and clears only the provider fingerprint key. Old legacy skip-cache entries may remain in the persisted archive as inert compatibility leftovers; they must not be consulted for a provider-authoritative source once its provider key is known. - Providers do not write to the DB.
- Providers do not mutate, delete, or repair source files.
Skip reason semantics:
SkipNone: the provider did not skip the source.SkipNoSession: the source is valid for the provider but does not contain a session after parsing.SkipUnsupportedSource: the source was discovered by broad matching but is not a supported source shape for this provider.SkipNonInteractive: the source is intentionally excluded because it is not a user-facing session.SkipShadowedBySidecar: a sibling or replacement source supersedes this source. The replacement write path should useForceReplacewhen existing rows need to be rewritten.
Unchanged-source skips remain engine skip-cache decisions based on provider
fingerprints. They are not returned as ParseOutcome values.
Incremental parsing is optional provider behavior.
type IncrementalRequest struct {
Source SourceRef
Fingerprint SourceFingerprint
SessionID string
Offset int64
StartOrdinal int
Machine string
}
type IncrementalOutcome struct {
SessionID string
Messages []ParsedMessage
EndedAt time.Time
ConsumedBytes int64
MessageCount int
UserMessageCount int
TotalOutputTokens int
PeakContextTokens int
HasTotalOutputTokens bool
HasPeakContextTokens bool
ForceReplace bool
}
type IncrementalStatus uint8
const (
IncrementalUnsupported IncrementalStatus = iota
IncrementalNoNewData
IncrementalApplied
IncrementalNeedsFullParse
)ProviderBase.ParseIncremental returns
(IncrementalOutcome{}, IncrementalUnsupported, nil). Providers that support
append-only incremental parsing set the relevant source capability and implement
the method.
Incremental status semantics:
IncrementalUnsupported: this provider does not implement incremental append for the requested source; the engine should run the normal full parse path.IncrementalNoNewData: the source is valid and no append was needed; the engine may update freshness metadata without writing messages.IncrementalApplied:IncrementalOutcomecontains appended messages and counters that should be written.IncrementalNeedsFullParse: the provider inspected the source but cannot safely append; the engine must run the normal full parse path.
Errors from ParseIncremental are real failures for the attempted incremental
operation. The engine uses IncrementalStatus, not provider-specific error
strings or a bare boolean, to decide whether to fall back to full parsing.
IncrementalRequest.SessionID and IncrementalOutcome.SessionID use the same
persisted full session ID namespace as normal parse outcomes.
Capabilities use a concrete struct and an iota enum. The zero value maps to unsupported.
//go:generate go run github.com/dmarkham/enumer -type=CapabilitySupport -json -text -transform=snake -trimprefix=Capability -output=capabilitysupport_enumer.go
type CapabilitySupport uint8
const (
CapabilityUnsupported CapabilitySupport = iota
CapabilitySupported
CapabilityNotApplicable
)enumer is preferred over plain stringer here because it can generate
String, JSON marshal/unmarshal, text marshal/unmarshal, value listing, and
validation helpers from the same enum definition.
The struct should group source mechanics and parsed-content features:
type Capabilities struct {
Source SourceCapabilities
Content ContentCapabilities
}
type SourceCapabilities struct {
DiscoverSources CapabilitySupport
WatchSources CapabilitySupport
ClassifyChangedPath CapabilitySupport
FindSource CapabilitySupport
CompositeFingerprint CapabilitySupport
IncrementalAppend CapabilitySupport
MultiSessionSource CapabilitySupport
PerSessionErrors CapabilitySupport
ExcludedSessions CapabilitySupport
ForceReplaceOnParse CapabilitySupport
}
type ContentCapabilities struct {
FirstMessage CapabilitySupport
SessionName CapabilitySupport
Cwd CapabilitySupport
GitBranch CapabilitySupport
Relationships CapabilitySupport
Subagents CapabilitySupport
Thinking CapabilitySupport
ToolCalls CapabilitySupport
ToolResults CapabilitySupport
ToolResultEvents CapabilitySupport
PerMessageTokenUsage CapabilitySupport
AggregateUsageEvents CapabilitySupport
TerminationStatus CapabilitySupport
MalformedLineCount CapabilitySupport
TruncationStatus CapabilitySupport
Model CapabilitySupport
StopReason CapabilitySupport
}Providers set supported or not-applicable values explicitly. Missing fields stay unsupported. Capability tests ensure a provider does not emit normalized fields that contradict unsupported declarations.
Capability semantics are intentionally strict:
CapabilityUnsupportedmeans this provider does not currently emit or implement the feature. Tests should fail if normalized output contains that feature.CapabilitySupportedmeans the feature is implemented and covered by provider fixtures or source-behavior tests.CapabilityNotApplicablemeans the upstream source format cannot represent the feature. It is not a placeholder for unfinished implementation work.
Capability conformance uses meaningful presence predicates rather than raw Go zero-value checks. Unsupported string fields such as cwd, git branch, model, stop reason, and session name must remain empty; unsupported counts such as malformed lines must remain zero; unsupported booleans such as truncation must remain false; unsupported repeated data such as relationships, subagents, tool calls, tool results, and usage events must remain empty. A zero value is therefore absence unless the provider declares support for the corresponding feature and fixtures prove a source can intentionally emit it.
Generated enum tooling should be reproducible:
- keep a
tools.gofile with atoolsbuild tag and a blank import forgithub.com/dmarkham/enumer; - pin the enumer module in
go.modand commitgo.sum; - commit the generated
capabilitysupport_enumer.gofile; - include a generator check, for example
go generate ./internal/parser && git diff --exit-code -- internal/parser/capabilitysupport_enumer.go.
The facade should include helper types for common provider patterns. These
helpers live below the provider abstraction; the engine still talks only to
Provider.
Helpers should be plain source-set utilities. A helper stores the source-layout
state for one pattern and exposes methods with the same signatures as provider
source methods. Providers use helpers through named fields and choose which
methods to forward. Unforwarded methods fall back to ProviderBase no-ops.
Embedded default implementation for optional provider methods:
- empty discovery;
- empty watch plan;
- no changed-path classification;
- no source lookup;
- unsupported fingerprinting;
- no incremental parse.
ProviderBase carries metadata and capabilities but does not implement Parse.
A reusable JSONL source lister/fingerprinter for the common pattern of session
transcripts stored as .jsonl files. It does not implement Provider by
itself; concrete providers hold it as a field and forward discovery, watch
planning, changed-path classification, source lookup, and fingerprinting when
those behaviors apply.
Expected options:
- root directories;
- recursive or shallow traversal;
- extension set, defaulting to
.jsonl; - path filters;
- project extraction from path;
- source key derivation from path;
- stable sorting;
- optional symlink directory handling to match current discovery behavior.
This helper should cover simple JSONL providers and serve as the base for more specific source sets.
Specialized JSONL helper for layouts where project or workspace names come from
directory structure, such as <project>/<session>.jsonl or nested
projects/<encoded-project>/chats/<id>.jsonl.
Wraps another source-set implementation and folds sibling files into watch plans and effective fingerprints. This covers patterns like transcript plus metadata/title/index files.
Creates one or many SourceRef values from a shared SQLite source while keeping
table and row details provider-owned. It supports providers where one database
file represents many logical sessions. SQLite fan-out providers must define
their deletion contract explicitly: live metadata produces normal virtual
sources, while persisted source-path hints allow row or DB deletion events to
emit tombstone sources that parse as SkipNoSession.
Providers that expose one logical session inside a shared source can continue to return virtual display paths, but the virtual path format should be provider owned and resolved through provider methods rather than hard-coded in sync.
Historically, the root of the migration stack supported both execution shapes:
- the legacy
DiscoveredFileandprocessFileshape, which remains authoritative on lower migration branches; - the provider
SourceRefshape, which can process the same logical source without writing session state while a lower branch is in shadow-compare mode.
On the current stack tip, the legacy runtime path and shadow recorder have been
removed from processFile; this section documents lower-stack migration
semantics and the completed provider-authoritative target. The root-level
migration harness owns the comparison on lower branches. It exposes an explicit
provider runtime manifest keyed by parser.AgentType. Family helpers may build
entries for related providers, but tests must expand the family to every
concrete AgentType; a family-level entry is not enough to mark an individual
parse-capable provider migrated.
- legacy-only: historical lower-stack mode where only the existing sync path runs and writes. This is the normal mode for legacy adapter providers. A concrete provider may move back to this mode only as a documented rollback with an open follow-up task. The stack tip removes this mode for parse-capable providers and rejects it as invalid.
- shadow-compare: historical transitional lower-stack mode where legacy runs and writes; provider runs through the new generic path and produces normalized in-memory planned effects. Tests compare those effects against the legacy outcome. Runtime mismatches are developer diagnostics only; they must not persist user-visible parse diagnostics or change SSE-visible state. This mode is not a live runtime option on the current stack tip.
- provider-authoritative: the provider registry and migration manifest are authoritative for provider construction. On the current stack tip, provider dispatch also writes, returns the caller result, and the old provider-specific legacy path is absent.
- import-only: the provider exists for non-filesystem import/export metadata and is intentionally excluded from parse shadow comparison.
Only the stack-tip cleanup may use provider-authoritative mode for existing
providers. Lower provider PRs migrate by moving each affected concrete
AgentType entry from legacy-only to shadow-compare in the manifest and adding
the required parity coverage. A concrete parse-capable provider that is not
import-only and is not listed in the migration manifest is a test failure; this
makes a PR that merely reimplements a parser without wiring the migration easy
to spot.
The target provider-only engine flow becomes:
- Load provider factories from the provider registry.
- Create config-bound providers with
ProviderConfig{Roots: roots, Machine: machine}. - Ask each provider to discover
SourceRefvalues for configured roots. - Dedupe source refs by provider and key.
- Ask each provider for
SourceFingerprint. - Run generic skip/data-version checks using
FingerprintKeyand fingerprint fields. - Attempt incremental parsing when the provider declares and implements it, and
interpret the result through
IncrementalStatus. - Call provider
Parsefor full parses. - Apply existing normalization and DB write paths to each
ParseResultOutcome.Resultvalue. - Persist source metadata, skip cache, excluded IDs, usage events, and parse diagnostics using the existing storage model.
Changed-path live sync becomes:
- The watcher reports a changed path.
- The engine finds providers whose
WatchPlanroots match the changed path. - If the matched
WatchRootdeclaresStoredSourcePathHints, the engine loads persisted source paths through the scoped session-store API for that provider and root. - Each matched provider classifies it through
SourcesForChangedPathwith aChangedPathRequestthat includes the changed path, event kind, matched watch root, and any scoped persisted source paths. - The engine processes the returned
SourceRefvalues generically.
Source lookup becomes:
- The engine loads the owning provider and stored session metadata.
- The engine passes the raw session ID, full session ID, stored
file_path, and stored fingerprint key toFindSourceRequest. - The provider returns a
SourceRef, not just a string path. - The engine can ask the provider for a fingerprint/source mtime from that reference.
The engine must treat stored file_path as an advisory compatibility key. It
must not check that path first or assume it is a filesystem path, because some
providers expose virtual paths or logical sessions inside a shared source.
FindSourceRequest.RawSessionID is lookup input only; returned parse outcomes,
source errors, and exclusions still use persisted full session IDs.
This section documents the lower-stack migration harness. It is not a supported
runtime path on the provider-explicit-registry stack tip, where provider
processing is authoritative and the runtime shadow recorder/hook has been
removed.
The root harness has two comparison surfaces:
- source-level parity, available as soon as the root harness lands, compares one legacy-discovered source or fixture set against the provider runner. Provider implementation PRs must use this surface before they can move their manifest entry to shadow-compare.
- caller-level dual-run, added by the later sync, lookup/watch, and diagnostics
tasks, wraps production callers such as full sync, changed-path sync,
SyncSingleSession, source lookup, source mtime, and parse-diff.
This ordering makes shadow-compare real before caller migrations exist: provider PRs prove source-level parity first, then caller tasks prove the same provider runner behaves correctly when invoked through production sync surfaces.
During migration, both comparison surfaces use the provider-only execution helpers but keep side effects isolated:
- Run the legacy caller normally and keep its
processResultas the value that drives DB writes, skip-cache persistence, data-version updates, source metadata, SSE emissions, and diagnostics. - Run the provider caller for the same agent through the generic provider
helper. Depending on the caller, this may come from provider discovery,
SourcesForChangedPath, orFindSource; the comparison layer must not teach the engine provider-specific path formats. - Normalize both outputs into the same comparison shape for the surface being
exercised. The root
processFilecomparison covers full session IDs, parsed message/tool/usage content, excluded IDs, retry state, data-version state, source metadata, and per-session errors. Source-level provider tests and later caller tasks ownSkipReasonparity until the legacy side exposes a comparable skip-reason projection. - Represent provider-side effects as in-memory planned effects, not live DB mutations. Planned effects include source metadata writes, data-version writes, skip-cache updates, and diagnostics. Integration tests may additionally run against disposable stores, but shadow mode never receives the live writer. SSE/event scope parity is deferred until the provider-authoritative caller owns live event emission.
- Report mismatches as test failures in the migration harness. Runtime diagnostics are opt-in developer diagnostics only; they must not create user-visible parse diagnostics or SSE-visible state. The provider side must not mutate persisted session state while in shadow-compare mode.
ProviderPlannedEffects must match the legacy engine's observable write model,
not an abstract parser-local model:
- source metadata keys use the provider fingerprint key when present, then
SourceRef.FingerprintKey, thenSourceRef.Key; - skip-cache keys follow the same key order the legacy engine uses before a persisted skip decision;
- data-version entries represent process-result-level write intent for the
concrete session rows the engine would stamp after successful writes,
including current versus
DataVersionNeedsRetrystate; retry-reason text is provider-local until the legacy process result exposes equivalent detail; - diagnostics mirror the legacy parse diagnostic fields, including display path, source key, session ID, error, and retryability, but are never written to the live diagnostics table in shadow mode;
- legacy skip, incremental, and whole-source error states are recorded as
non-comparable in the root
processFilehook. ProviderSkipReasonparity is handled by provider-local/source-level tests until a later caller task defines a legacy skip-reason projection.
Provider output must be namespaced before it can produce planned effects and
before any remote machine prefix is applied. ParseResult.Session.Agent must
equal the provider AgentType. Persisted session IDs in the result graph must
use the provider's AgentDef.IDPrefix when one exists; this includes result
IDs, parent IDs, usage-event session IDs, subagent links, exclusions, and
diagnostic session IDs. ParsedSession.SourceSessionID is excluded from this
check because current parsers use it for upstream/raw source IDs, not persisted
session IDs. Diagnostic SourceError.SourceKey values are required and must be
one of the observed source identities or a derived virtual key: the provider
fingerprint key, SourceRef.FingerprintKey, SourceRef.Key, or one of those
values followed by #, ::, or |. Cross-provider sessions are not legal in
shadow mode because they make parity false positives indistinguishable from real
legacy behavior.
Fingerprint failures and parse failures are compared separately. A fingerprint
failure means no provider parse was attempted and the mismatch report records a
fingerprint failure. A parse failure after a successful fingerprint records the
fingerprint key and parse error. Neither failure may block the legacy write path
while the mode is shadow-compare.
Mismatch reports must include provider, migration mode, source key, fingerprint key, comparison field path, a bounded legacy summary, a bounded provider summary, and whether the mismatch came from discovery, fingerprint, parse output, planned effects, or runtime failure. Runtime reporting is developer-only logging or debug output until a later task defines persistence.
Shadow comparison can double-parse large sources while a provider migrates.
Provider PRs that touch large roots, shared SQLite sources, or composite sources
need fixture coverage or benchmarks that show fingerprinting and shadow parse
overhead are acceptable before promotion. The rollback rule is to move the
manifest entry back to legacy-only, leave the legacy path authoritative, and
keep the blocking kata/review item open.
Provider branches must exercise this transition with shared tests rather than only provider-local unit tests. The branch is considered migrated only when the manifest entry and parity tests are present. Deferred parity items such as provider-only retry-reason text, SSE scopes, and caller-specific skip-reason mapping are promotion gates for provider-authoritative mode; they cannot remain open at the stack tip where legacy dispatch is removed.
Before any concrete provider changes to provider-authoritative, that branch
must prove the generic hook contract for its source shape:
FindSourcehonors global and file-scoped force-parse by allowing stale stored source hints when requested.- provider not-found in authoritative mode is an explicit error, not an implicit legacy fallback.
- multi-result sources preserve
ParseResultOutcome.DataVersionper session, so retry-needed fallback rows do not mark unrelated current rows stale. - skip-cache lookup and persistence use the provider fingerprint key selected
for the source, with tests for virtual or composite paths where
file.Pathdiffers fromSourceRef.FingerprintKey. ResultSetComplete, excluded IDs, diagnostics, and source errors have parity tests for the provider's source family before the old legacy dispatch for that provider is removed.
If a provider that moved to shadow-compare proves flaky on a lower migration branch, its manifest entry can return to that branch's legacy-only mode with a reason and an open kata task or review note. After the explicit-registry tip, legacy-only is no longer a valid code state; regressions should instead be handled by reverting or repairing the affected provider branch before the stack is resubmitted. The tip cleanup cannot proceed while any parse-capable provider has known shadow mismatches.
parser.Registry remains the stable metadata surface during migration, but the
source of truth shifts to providers.
Target API:
func ProviderFactories() []ProviderFactory
func ProviderFactoryByType(AgentType) (ProviderFactory, bool)
func NewProvider(AgentType, ProviderConfig) (Provider, bool)
func AgentByType(AgentType) (AgentDef, bool)
func AgentByPrefix(string) (AgentDef, bool)AgentByType and AgentByPrefix can continue to return AgentDef for config,
settings, display, and export code. AgentDef source callbacks become legacy
compatibility fields during migration and are removed or deprecated once every
consumer uses providers.
The implementation should migrate all providers through a stacked dual-run sequence:
- Add provider core types,
ProviderBasedefaults, contract invariants, and compile-tested embedding examples. - Add capability enum generation, pinned
enumertooling, and generated-file verification. - Add provider factory registry tests while preserving current
parser.Registry. - Add the root-level dual-run migration harness before migrating provider
branches. It must contain the shared provider execution helper, comparison
normalizer, source-level parity surface, planned-effect comparison model,
caller-level
processFileshadow comparison for full sync, explicit per-AgentTypemigration manifest, and tests that fail when a concrete parse-capable provider exists without a migration-mode entry. - Add JSONL source-set helpers and tests for simple file-backed JSONL providers.
- Use
git-spiceto restack the provider branches on the root harness branch after that lower branch changes when the user has explicitly authorized branch changes for the session. The stack must be verified withgs log shortand conflicts resolved provider by provider. Pushing, submitting, or updating PRs is a separate network operation and requires separate explicit authorization. - Migrate simple JSONL providers with acceptance tests for discovery,
fingerprint, parse output, skip-cache metadata, and data-version behavior.
Each provider PR must move its affected concrete
AgentTypeentries from legacy-only to shadow-compare and run the shared source-level provider-vs-legacy parity harness. Legacy sync remains authoritative. - Add and migrate sibling/composite source providers with acceptance tests for watch planning, composite fingerprints, sidecar/title refreshes, and changed path classification. Each PR must opt its provider into shadow-compare and pass the shared source-level parity harness while legacy sync remains authoritative.
- Add and migrate virtual-path and SQLite fan-out providers with acceptance
tests for stored advisory paths, tombstone recovery via
StoredSourcePaths, logical session lookup, per-session errors, and source mtime behavior. Each PR must opt its provider into shadow-compare and pass the shared parity harness while legacy sync remains authoritative. - Add and migrate non-file import/database providers with acceptance tests for
FindSource, fingerprinting, and unsupported source mechanics. Import-only providers are explicitly marked import-only rather than shadow-compared. - Add the session-store API for persisted source-path hint lookup before
changed-path shadow comparison depends on providers. It must query by
provider and watched root, use the
(agent, file_path)index shape, return stable de-duplicated paths, and include tests for provider/root filtering, path normalization, sibling-prefix false positives such as/root/dbversus/root/db2, dedupe, batching/no-truncation behavior, and large unrelated session tables. - Add provider compatibility tests for stored hint interpretation before changed-path shadow comparison depends on providers. SQLite fan-out providers must cover malformed or obsolete virtual paths, debug-only diagnostics, DB row deletion, DB file deletion, and stale hints that belong to a different physical DB under the same watch root.
- Move the remaining source-processing caller semantics into the caller-level
dual-run harness: changed-path sync and
SyncSingleSession. The root harness already shadows the sharedprocessFilepath, so these tasks must add caller-specific source selection, stored-source hints, and acceptance coverage rather than adding a second shadow hook. During migration these callers compare provider output against legacy for parsed output, skip-cache, data-version, source metadata, diagnostics, excluded IDs, and retry state; only the legacy result writes. Changed-path comparison must populateStoredSourcePathsfrom scoped persisted session metadata and include DB row deletion and DB file deletion integration tests. - Move lookup/watch callers into the caller-level dual-run harness: session watch flows, export/source lookup, source mtime, and token-usage raw source probing. During migration these callers compare lookup freshness, virtual path, source mtime, and raw probing behavior against legacy.
- Move diagnostic and comparison callers into the caller-level dual-run harness: parse-diff and parse diagnostics. During migration these callers compare report shape and source-error behavior against legacy.
- At the tip of the stack only, switch all shadow-compared providers to
provider-authoritative dispatch, remove the provider-by-provider
processFileswitch, and remove or deprecate oldAgentDefsource callback fields after all callers stop using them.
Migration should keep existing parser unit tests. Provider-level tests become the required integration surface for future providers.
The migration is tracked under parent kata issue terh. The dual-run correction
adds these blocking tasks:
5jrz: add the root-level dual-run provider migration harness.y8wg: restack the provider PRs on the dual-run root with git-spice.0cfe: migrate simple JSONL providers by shadow-comparing each provider.jwav: migrate composite and sibling-file providers by shadow-comparing each provider.aghm: migrate virtual-path and database-backed providers by shadow-comparing each provider.1xcx: add provider/root-scoped stored source-path hint lookup.9dee: add stored-hint provider compatibility tests.kj1f: move source-processing callers into the dual-run harness.djyy: move lookup, watch, export, and usage callers into the dual-run harness.cff5: move parse-diff and diagnostics into the dual-run harness.n489: completed stack-tip legacy cleanup. TheprocessFiledispatch, deadprocessXwrappers, oldAgentDefsource callbacks, and stale runtime fallback paths are removed at the current tip.
Required tests:
- Provider registry completeness: every
AgentTypehas exactly one provider. - Prefix uniqueness and metadata parity with current registry behavior.
- Provider factory instantiation: configured roots and machine are copied into config-bound providers and do not mutate singleton registry state.
ProviderBasecontract tests: zero-value optional methods are callable and return the documented no-op or typed unsupported results.- Unsupported-feature tests proving the engine distinguishes
ErrUnsupportedProviderFeaturefrom I/O and parse errors, and treats unsupported fingerprinting for returned sources as a contract failure. - Provider concurrency tests or race tests for shared provider instances and
immutable
SourceRef.Opaquepayloads. - Compile-tested embedding examples for
ProviderBase, named JSONL source-set fields, sibling metadata source-set fields, and explicit concrete method overrides. - Capability enum generation, JSON representation, and zero-value behavior.
- Capability conformance: unsupported fields should not be emitted in parsed output.
- Capability semantics:
not_applicableis accepted only for fields impossible in the upstream source format, not for unimplemented work. - JSONL source helper discovery, sorting, filtering, project extraction, and fingerprint tests.
- Sibling metadata fingerprint tests.
- Watch-plan and changed-path classification tests for recursive roots, non-recursive roots, include/exclude filters, and sibling metadata debounce groups.
- Stored advisory path tests proving
FindSourceRequest.StoredFilePathis interpreted only by the provider. - Outcome ID namespace tests proving
ParseResultsession IDs,ExcludedSessionIDs, andSourceError.SessionIDuse persisted full session IDs even when lookup starts with a raw upstream ID. - SQLite fan-out source key, virtual path, and per-session error tests.
- Data-version tests for current, skipped, retry-needed, and mixed per-session parse outcomes from one source.
- Skip-cache tests for complete clean multi-session parses, incomplete
multi-session parses, retry-needed results, retryable
SourceErrors, and non-retryableSourceErrors. - Fingerprint performance tests or benchmarks for large roots and composite sources, with the pass criteria from the fingerprint section.
- Provider harness tests for discovery, fingerprint, parse, source lookup, and optional incremental parsing.
- Incremental parsing tests for
IncrementalUnsupported,IncrementalNoNewData,IncrementalApplied,IncrementalNeedsFullParse, and real incremental errors. - Parse diagnostic tests proving stable source fields are reported and opaque payloads are not serialized or logged.
- Source-level migration parity tests comparing provider output to current parser/process output before each provider group opts into shadow-compare, including skip-cache, data-version writes, persisted source metadata, diagnostics, excluded IDs, and retry-needed behavior.
- Caller-level migration parity tests proving full sync, changed-path sync,
SyncSingleSession, lookup/watch flows, export/source lookup, source mtime, token usage raw-source probing, parse-diff, and diagnostics invoke the same provider runner without changing legacy-authoritative writes during the migration stack. - Migration manifest tests that fail when a concrete parse-capable
AgentTypeis registered without an explicit mode. Provider implementation PRs must include the manifest change to shadow-compare; import-only providers must be marked explicitly so they are not mistaken for missed migrations. Family helpers must expand to concrete agent entries in tests. - Dual-run isolation tests proving provider shadow comparison cannot write sessions, messages, source metadata, data-version rows, skip-cache entries, diagnostics, or SSE-visible state while legacy remains authoritative. These tests compare in-memory planned effects or disposable-store observations, not live production mutations.
- Sync integration tests for incremental Claude/Codex, multi-session sources, parse-diff, source mtime, source lookup, skip cache, usage events, sidecars, virtual paths, and title/metadata refreshes.
- Caller migration tests for full sync, changed-path sync,
SyncSingleSession, session watch flows, export/source lookup, token usage raw-source probing, source mtime, and parse diagnostics. Changed-path caller tests must include provider/root-scopedStoredSourcePathscollection, index-backed lookup with unrelated large tables, batching/no-truncation behavior, path normalization, sibling-prefix exclusion, malformed stale source-path tolerance in provider tests, plus DB row deletion and DB file deletion tombstone flows for SQLite fan-out providers. - Generated tooling check for
enumeroutput. - Adding-provider checklist test that fails until registry, capabilities, fixtures, source behavior, migration-mode wiring, parity coverage, and docs are present.
Providers return structured errors and outcomes. The engine makes generic decisions from those structures:
- whole-source failure: returned
error; - per-session failure:
SourceErrors, while successful sessions from the same source are still written; - retryable failure: do not cache skip by unchanged mtime and do not mark the affected source/session current for the parser data version;
- non-retryable per-session failure: eligible for failure-cache persistence, but not for a data-version-blind clean source skip;
- full parse fallback from incremental:
IncrementalNeedsFullParse; - unsupported optional provider feature:
ErrUnsupportedProviderFeature; - successful lower-resolution fallback: per-result
DataVersionNeedsRetryplusRetryReason; - skipped non-session source: explicit
SkipReason; - existing-row rewrite required:
ForceReplace.
Parse-diff treats provider SourceErrors as reportable parse errors, preserving
today's behavior for shared database sources.
Implementation should update developer-facing docs to describe how to add a provider:
- add an
AgentType; - implement a provider embedding
ProviderBase; - select source helpers or implement provider-specific source methods;
- implement
Parse; - set capabilities;
- add fixtures and provider harness tests;
- update README/config docs for default directories and environment variables.
- All current providers are registered through the provider facade.
- Before the stack tip, all parse-capable current providers are explicitly listed as shadow-compared in the migration manifest and legacy dispatch remains authoritative.
- At the stack tip,
sync.Engineno longer has a provider-by-provider parse dispatch switch. - Source shape is not inspected by the engine.
- Capability reports serialize to readable JSON names.
- Capability enum generation is pinned and reproducible.
- Configured roots are provider-instance state, not mutable singleton state.
ProviderBasezero-value optional methods are callable by the engine.- Stored
file_pathvalues are provider-owned advisory lookup keys. - Retry-needed outcomes preserve future parse eligibility instead of marking data versions current.
- Existing parser and sync tests pass after migration.
- Parse-diff continues to use the same provider path as normal sync.
- Adding a provider requires implementing the provider contract and fails tests until capabilities, source behavior, fixtures, migration-mode wiring, parity coverage, and docs are present.
- The provider-facade kata tasks listed in this spec are implemented and closed with evidence as their corresponding stack slices are verified.
- The final stack has no legacy provider/parser dispatch surface left for migrated providers.