Skip to content

feat(host): prompt ingestion as a local provider (ADR 0006)#44

Merged
macanderson merged 3 commits into
mainfrom
worktree-prompt-ingest-provider
Jul 23, 2026
Merged

feat(host): prompt ingestion as a local provider (ADR 0006)#44
macanderson merged 3 commits into
mainfrom
worktree-prompt-ingest-provider

Conversation

@macanderson

@macanderson macanderson commented Jul 23, 2026

Copy link
Copy Markdown
Owner

What & why

The prompt's biggest un-disciplined input is the text a user pastes: a realistic turn mixes a log, a table, a directory reference, and the actual ask under one blob, and only the last is intent. Pasted whole it is re-sent verbatim every turn (no cache, no dedup), its cost is never accounted, nothing is content-addressed, and the model is handed material it must itself decide is mostly irrelevant.

This models the paste as a local, egress-free CGP provider — the ingestion-side dual of compose_context. It's host-side reference behavior; no envelope shape or SPEC.md change, using only the representation fields ADR 0005 added.

  • intentquery.goal, verbatim (never rewritten — the one load-bearing UX guarantee)
  • directory referencesquery.anchors (zero tokens; the graph provider resolves them)
  • evidence (logs, tables, code, notes) → content-addressed frames, served compact by default with the full bytes rehydratable via a [full] re-query

The guarantee is not "zero wasted tokens" (relevance is only knowable downstream) but bounded default cost with lossless retrieval.

Honest by construction

  • token_cost and the inline content_digest are recomputed for the exact representation emitted (§B3) — never carried across a representation flip
  • provenance is derivation, not file, so it doesn't trip §F5 (a paste has no re-readable URI); the real hash lives in canonical_content_hash
  • content-addressed + immutable ⇒ verify is exact (valid / stale / gone), and the same paste dedups to one frame
  • local-only (egress: false, LocalOnly scope) ⇒ auto-permitted, no consent friction

resolve: true vs ADR 0004

Serving compact/reference requires resolve: true (representations_consistent). Unlike the upsert/subscribe flags ADR 0004 deleted, resolve has a live consistency rule and a working, tested in-process rehydration path today (a [full]-preference re-query returns the full bytes from the store). The ADR spells this out; the wire context/resolve method remains a later phase.

Wire conformance — and one corrective schema fix

Verifying "frames are wire-conformant" (not just Rust-invariant-conformant) surfaced a real, pre-existing schema/serializer mismatch: the JSON Schema listed provenance and relations as globally required on a ContextFrame, but the reference serializer omits both when empty (skip_serializing_if = Vec::is_empty) and no frame-validity check requires either. Every ingest frame has no graph edges, so it failed schema validation. Prior schema coverage only validated hand-authored example transcripts — never Rust-serialized frames — so the mismatch sat unexercised.

  • schema: ContextFrame.required is now exactly id/kind/title/score/token_cost (what the serializer always emits); content stays governed per-representation by the existing allOf.
  • Empirically confirmed with jsonschema: all three representations validate against schema/contextgraph-envelope.schema.json; existing examples still pass.

Tests

  • contextgraph-host/src/ingest/tests.rs — segmentation, §B3 honesty across all three representations, representation invariants, SHA-256 known-answer vectors (incl. padding boundaries), content-addressed dedup, query budget/frame caps, verify
  • contextgraph-host/tests/prompt_ingest.rs — end-to-end through the real Host: fan-out, budget audit, byte-stable compose, verify_frames
  • contextgraph-conformance/tests/ingest_conformance.rs — real ingested frames (full/compact/reference) through check_frames + check_budget + representation_invariants + the schema's required-key set + NDJSON envelope round-trip (the guard that catches serialized frames drifting from the schema)

cargo test --workspace, cargo clippy --workspace --all-targets -- -D warnings, cargo fmt --all --check, and python3 schema/validate-examples.py all green. New direct dep: sha2 (host crate only; contextgraph-types stays zero-dep beyond serde).

Not in scope (additive follow-ups)

Stack-trace parsing and richer tabular inference behind the existing classify seam; the context/resolve wire method; making provenance non-required is left as-is (ingest frames always carry it).

Treat the user's paste — the prompt's largest un-disciplined input — as a
local, egress-free CGP provider. Intent passes through verbatim as
query.goal; directory references become query.anchors; pasted evidence
(logs, tables, code, notes) is deterministically segmented, content-
addressed, and served as compact frames with the full bytes rehydratable
via a [full] re-query.

Host-side reference behavior (the ingestion-side dual of compose_context),
so no wire, schema, or SPEC.md change — it uses only the representation
fields ADR 0005 added. Honest by construction: token_cost and the inline
content_digest are recomputed per representation served (B3), provenance is
`derivation` (not `file`, F5-exempt), and verify is exact on immutable
content. resolve:true is backed by a real, tested in-process rehydration
path — the ADR reconciles this against ADR 0004's dead-flag removals.

- contextgraph-host/src/ingest.rs (+ inline tests, sha256 KATs)
- contextgraph-host/tests/prompt_ingest.rs — end-to-end via the real Host
- docs/adr/0006-prompt-ingestion-as-a-local-provider.md
- sha2 dependency (host crate only; types stays zero-dep beyond serde)

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@vercel

vercel Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
context-graph-protocol Ready Ready Preview, Comment Jul 23, 2026 7:35pm

…ingest wire-conformance test

Verifying ADR 0006's "frames are wire-conformant" claim surfaced a real
schema/serializer mismatch: the JSON Schema listed `provenance` and
`relations` as globally required on a ContextFrame, but the reference
serializer omits both when empty (skip_serializing_if=Vec::is_empty) and no
frame-validity check requires either. Every ingest frame has no graph edges,
so it failed schema validation. Prior schema coverage only validated
hand-authored example transcripts, never Rust-serialized frames, so the
mismatch sat unexercised.

- schema: ContextFrame.required is now exactly id/kind/title/score/
  token_cost; content stays governed per-representation by the existing allOf
- contextgraph-conformance/tests/ingest_conformance.rs: validates real
  ingested frames (full/compact/reference) through check_frames, check_budget,
  representation_invariants, and the schema's required-key set + NDJSON
  envelope round-trip — the guard that catches serialized frames drifting
  from the schema
- ADR 0006 + CHANGELOG updated to record the corrective schema fix

Empirically confirmed: all three representations validate against
schema/contextgraph-envelope.schema.json via jsonschema; existing examples
still pass.
@macanderson
macanderson marked this pull request as ready for review July 23, 2026 19:35

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Sorry @macanderson, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@macanderson

Copy link
Copy Markdown
Owner Author

Filed the remaining follow-ups so nothing is left implicit:

context/resolve is already tracked by #50; I added a comment there noting this PR's IngestProvider as a concrete compact/reference consumer with a working in-process rehydration path (and its per-representation B3 handling), rather than opening a duplicate.

Correction to the PR description's "Not in scope" note: this PR removed both provenance and relations from the frame's required list (the reference serializer omits both when empty), not just relations — ingest frames always carry provenance, but the schema no longer requires it, matching the serializer. #54 covers auditing the same pattern across the other wire types.

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.

1 participant