You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
I searched existing issues and discussions and this hasn't been proposed yet.
What problem does this solve?
The Roadmap names Phase 2's provenance chain as the project's core differentiation:
⬜ Artifact versioning and a provenance chain (code + execution log + dependency graph + environment snapshot + conversation context) tied to every output
There is currently no tracked issue for it, so this RFC proposes an implementation direction to help scope that milestone before any code is written.
The obvious way to build provenance — and the way the main competing project built it — is a parallel capture log: listen to tool events and append records to a dedicated provenance file, alongside the data the app already persists. Having studied that implementation, its costs are visible in practice: a second source of truth that drifts from the first, duplicated schemas across layers, O(n) rewrite-time reads on a hot write path, remote runs that are only captured if the agent voluntarily calls a recording script, failed runs that vanish from lineage, and no way to backfill history that predates the log.
This RFC argues that this codebase is in an unusually good position to not take that path.
Key observation: the provenance data already exists here
For any completed session, the lineage of an artifact is already recorded — just across three stores that are never joined:
Ownership encoded in the id (sessionId:ownerId:filename), .runs/<runId>.json finalize markers, .pending/current-run.json turn context
Several forward-looking anchors are also already in place: ArtifactReference.versionId (reserved, commented "future version switcher"), PersistedArtifact.sha256 (typed but never computed), NotebookRunRecord.artifacts[] (typed but always empty), and the metadata.open_science.runId projection in the ipynb exporter (#293 / #296).
What is missing is not capture — it is a read layer that joins what is already captured.
Proposed direction: provenance as a derived view, not a second system
Phase A — read-only lineage query (zero new writes, zero risk)
A main-process provenance query module: given an artifact id/path, reverse-join the three stores and answer "which message and which run produced this file, with what script, in which environment, in which conversation."
Surface it as a "History / Source" panel on artifact details (and the session notebook view).
No new files, no schema changes, no migration. Old sessions are covered automatically because the query reads the stores they already live in.
Crucially, this phase is also a measurement instrument: wherever the join fails, we learn precisely which anchor is missing — so the schema evolves from observed gaps rather than up-front design.
Phase B — fill only the joins that Phase A proves broken
Known candidates (all are field additions to existing records, not new stores):
Compute sha256 at artifact finalize time (the field already exists in PersistedArtifact).
Add notebookRunId / turnMessageId to .pending/current-run.json so agent turns and notebook runs get a structural cross-reference (today they are only weakly related via a shared session id).
Actually populate NotebookRunRecord.artifacts[].
Environment capture stays at the session/binding granularity that runtimeBindings already provides, recorded incrementally on change — not a per-write pip freeze on the hot path.
Phase C — artifact versioning, pulled by demand
Activate the reserved versionId only when users demonstrably overwrite same-named artifacts and need to see prior versions. The simplest storage that works: finalize no longer overwrites, it keeps the previous version's directory. Still no graph database.
Phase D — standard projections, not proprietary formats
The ipynb metadata.open_science namespace is already the provenance dialect for notebooks (#293). Further export targets (RO-Crate, W3C PROV) are projections generated from the query layer on demand — the internal stores never change to accommodate them.
Why this ordering (design rationale)
Repair existing tools rather than replace them. The three stores are the system of record; provenance should read them, not compete with them. A parallel log is a second truth that must be kept consistent forever.
Zero migration. A derived view covers historical sessions on day one. A capture log only knows about events after it shipped, and backfill is a permanent asterisk.
Optimize the moment that matters. Researchers need provenance at read time ("a reviewer asked where this number came from", "I want to reproduce this from three months ago"), not at write time. So the budget should go to query completeness and a one-click reproduce affordance — and capture must never sit synchronously on the agent's execution path.
Compatibility over elegance. A content-addressed provenance graph store is the elegant end-state and explicitly not the starting point: it brings schema versioning, migration and dual-write phases before it answers a single user question. Phases A–C answer the user question first and leave that option open.
Consistent with the public-API separation from feat(cli): add headless task SDK and CLI #316. The query layer starts as internal renderer RPC; whether to expose e.g. GET /api/v1/artifacts/:id/lineage on the versioned task API is a later, deliberate decision, keeping the public surface intentionally small.
Non-goals
No new append-only provenance log file, no .provenance.jsonl equivalent.
No provenance graph database or content-addressed store in the first iterations.
No per-write environment snapshotting on the execution hot path.
Environment snapshot/export tooling (the other half of Phase 2) is complementary but separable.
Reviewer TurnScope already performs turn-level artifact scoping with content digests — an existing in-codebase precedent for deriving lineage from the stores rather than logging it separately.
Each phase is independently shippable and reviewable. I'm happy to contribute, starting with Phase A, if the maintainers agree with the direction — and equally happy for this to just serve as decision input for scoping the Phase 2 milestone.
Preflight checklist
What problem does this solve?
The Roadmap names Phase 2's provenance chain as the project's core differentiation:
There is currently no tracked issue for it, so this RFC proposes an implementation direction to help scope that milestone before any code is written.
The obvious way to build provenance — and the way the main competing project built it — is a parallel capture log: listen to tool events and append records to a dedicated provenance file, alongside the data the app already persists. Having studied that implementation, its costs are visible in practice: a second source of truth that drifts from the first, duplicated schemas across layers, O(n) rewrite-time reads on a hot write path, remote runs that are only captured if the agent voluntarily calls a recording script, failed runs that vanish from lineage, and no way to backfill history that predates the log.
This RFC argues that this codebase is in an unusually good position to not take that path.
Key observation: the provenance data already exists here
For any completed session, the lineage of an artifact is already recorded — just across three stores that are never joined:
sessions/<project>/<session>.jsonPersistedToolActivity(raw input/output, terminal output), message ↔artifactIdslinksnotebooks/<project>/<session>/run.jsonrunId,script, outputs, status,environment,runtimeBindings(incl.EnvProvenanceclassification),workingFiles[].createdByRunIdartifacts/<project>/<session>/sessionId:ownerId:filename),.runs/<runId>.jsonfinalize markers,.pending/current-run.jsonturn contextSeveral forward-looking anchors are also already in place:
ArtifactReference.versionId(reserved, commented "future version switcher"),PersistedArtifact.sha256(typed but never computed),NotebookRunRecord.artifacts[](typed but always empty), and themetadata.open_science.runIdprojection in the ipynb exporter (#293 / #296).What is missing is not capture — it is a read layer that joins what is already captured.
Proposed direction: provenance as a derived view, not a second system
Phase A — read-only lineage query (zero new writes, zero risk)
Phase B — fill only the joins that Phase A proves broken
Known candidates (all are field additions to existing records, not new stores):
sha256at artifact finalize time (the field already exists inPersistedArtifact).notebookRunId/turnMessageIdto.pending/current-run.jsonso agent turns and notebook runs get a structural cross-reference (today they are only weakly related via a shared session id).NotebookRunRecord.artifacts[].Environment capture stays at the session/binding granularity that
runtimeBindingsalready provides, recorded incrementally on change — not a per-writepip freezeon the hot path.Phase C — artifact versioning, pulled by demand
Activate the reserved
versionIdonly when users demonstrably overwrite same-named artifacts and need to see prior versions. The simplest storage that works: finalize no longer overwrites, it keeps the previous version's directory. Still no graph database.Phase D — standard projections, not proprietary formats
The ipynb
metadata.open_sciencenamespace is already the provenance dialect for notebooks (#293). Further export targets (RO-Crate, W3C PROV) are projections generated from the query layer on demand — the internal stores never change to accommodate them.Why this ordering (design rationale)
GET /api/v1/artifacts/:id/lineageon the versioned task API is a later, deliberate decision, keeping the public surface intentionally small.Non-goals
.provenance.jsonlequivalent.Relationship to existing work
runIdanchor is the model for how provenance surfaces externally.TurnScopealready performs turn-level artifact scoping with content digests — an existing in-codebase precedent for deriving lineage from the stores rather than logging it separately.Each phase is independently shippable and reviewable. I'm happy to contribute, starting with Phase A, if the maintainers agree with the direction — and equally happy for this to just serve as decision input for scoping the Phase 2 milestone.