Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ jobs:
runs-on: ubuntu-latest
outputs:
news: ${{ steps.filter.outputs.news }}
paper_flow: ${{ steps.filter.outputs.paper_flow }}
paper_semantic: ${{ steps.filter.outputs.paper_semantic }}
structure: ${{ steps.filter.outputs.structure }}

steps:
Expand All @@ -73,12 +73,12 @@ jobs:
- 'quantmind/preprocess/format/html.py'
- 'quantmind/preprocess/fetch/**'
- 'pyproject.toml'
paper_flow:
paper_semantic:
- '.github/workflows/e2e.yml'
- 'scripts/verify_pdf_rag_e2e.py'
- 'quantmind/configs/paper.py'
- 'quantmind/flows/_paper_summary.py'
- 'quantmind/flows/paper.py'
- 'quantmind/flows/paper/**'
- 'quantmind/knowledge/paper.py'
- 'quantmind/library/**'
- 'quantmind/preprocess/fetch/arxiv.py'
Expand Down Expand Up @@ -132,7 +132,7 @@ jobs:

paper-flow:
needs: changes
if: github.event_name != 'pull_request' || needs.changes.outputs.paper_flow == 'true'
if: github.event_name != 'pull_request' || needs.changes.outputs.paper_semantic == 'true'
runs-on: ubuntu-latest
timeout-minutes: 10
env:
Expand Down
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ handoff all come from `openai-agents`.
| `quantmind/configs/` | Operation cfg + typed input models or unions (`BaseFlowCfg`, `NewsWindow`, `PaperInput`) — depends only on `knowledge` |
| `quantmind/preprocess/` | Deterministic fetch / format / clean / time utilities — depends only on `utils` |
| `quantmind/rag/` | Opinionated LlamaIndex document chunking and retrieval — depends only on `preprocess` |
| `quantmind/flows/` | Apex layer: public library operations (`paper_flow`, `collect_news`, `batch_run`) |
| `quantmind/flows/` | Apex layer: public library operations (`PaperFlow`, `collect_news`, `batch_run`) |
| `quantmind/magic.py` | `resolve_magic_input`: natural language → `(input, cfg)` |
| `quantmind/mind/` | Pure-agentic reasoning layer — memory + agentic (reasoning-based) retrieval where an LLM decides; mechanical retrieval (similarity / BM25) lives in `rag` / `library` |
| `quantmind/utils/` | Logger only — keep it that way |
Expand Down
49 changes: 29 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,41 +161,50 @@ Component-specific guides and architecture notes live under [`docs/`](docs/).
```python
import asyncio

from quantmind.configs import PaperFlowCfg
from quantmind.configs import PaperSemanticCfg
from quantmind.configs.paper import ArxivIdentifier
from quantmind.flows import paper_flow
from quantmind.flows import PaperFlow


async def main() -> None:
result = await paper_flow(
ArxivIdentifier(id="1706.03762v7"),
cfg=PaperFlowCfg(model="gpt-4o-mini"),
)
result = await PaperFlow(
PaperSemanticCfg(model="gpt-5.6-luna"),
).build(ArxivIdentifier(id="1706.03762v7"))
print(result.global_summary.summary)
print(result.source_revision.id, result.chunk_set.id)


asyncio.run(main())
```

`PaperFlow` binds an immutable `PaperSemanticCfg` once; the cfg **type** selects
the shape (`PaperStructureCfg` → `PaperStructureTree`, `PaperSemanticCfg` →
`PaperSemanticResult`).

#### Fan out a batch with `batch_run`

```python
import asyncio
from datetime import datetime, timedelta, timezone

from quantmind.configs import PaperFlowCfg
from quantmind.configs.paper import ArxivIdentifier
from quantmind.flows import batch_run, paper_flow
from quantmind.configs import NewsCollectionCfg, NewsWindow
from quantmind.flows import batch_run, collect_news


async def main() -> None:
inputs = [ArxivIdentifier(id=aid) for aid in (
"2401.12345", "2401.12346", "2401.12347",
)]
end = datetime.now(timezone.utc)
windows = [
NewsWindow(
source="pr-newswire",
start=end - timedelta(days=day + 1),
end=end - timedelta(days=day),
)
for day in range(3)
]
result = await batch_run(
paper_flow,
inputs,
cfg=PaperFlowCfg(model="gpt-4o-mini"),
collect_news,
windows,
cfg=NewsCollectionCfg(retain_raw_html=False),
concurrency=3,
on_error="skip",
on_progress=lambda done, total: print(f"{done}/{total}"),
Expand All @@ -211,17 +220,17 @@ asyncio.run(main())
```python
import asyncio

from quantmind.flows import paper_flow
from quantmind.flows import collect_news
from quantmind.magic import resolve_magic_input


async def main() -> None:
inp, cfg = await resolve_magic_input(
"Pull arXiv 2401.12345 about cross-sectional momentum; use gpt-4o-mini.",
target_flow=paper_flow,
"Collect the last day of PR Newswire company news.",
target_flow=collect_news,
)
result = await paper_flow(inp, cfg=cfg)
print(result.global_summary.summary)
batch = await collect_news(inp, cfg=cfg)
print(f"documents={batch.success_count} complete={batch.complete}")


asyncio.run(main())
Expand Down
20 changes: 10 additions & 10 deletions contexts/design/flow/paper.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
## Quick Summary

- **Purpose**: Define how one exact PDF revision becomes a durable page-aware chunk set and a cited global summary.
- **Read when**: Changing `paper_flow`, paper inputs, summarization limits, citation validation, or the end-to-end paper verifier.
- **Status**: Implemented by `quantmind.flows.paper_flow` for PDF-backed arXiv, HTTP, and local inputs.
- **Read when**: Changing the paper build (`PaperFlow(PaperSemanticCfg).build`), paper inputs, summarization limits, citation validation, or the end-to-end paper verifier.
- **Status**: Implemented by the config-bound `quantmind.flows.PaperFlow` — a `PaperSemanticCfg` selects this source-first shape — for PDF-backed arXiv, HTTP, and local inputs.
- **Core rule**: Preserve and validate the source revision and chunk set before any model-generated summary is accepted.
- **Canonical models**: [Paper source and artifact design](../knowledge/paper.md).

Expand All @@ -23,10 +23,10 @@

## Contract

`paper_flow(input, *, cfg)` returns one validated `PaperFlowResult`:
`PaperFlow(PaperSemanticCfg(...)).build(input)` returns one validated `PaperSemanticResult`. `PaperFlow` binds the immutable `PaperSemanticCfg` once and its cfg **type** selects this source-first shape, so `batch_run(flow.build, inputs)` runs a batch under one setting. It is the single entry point for the semantic shape; there is no standalone `*_flow` function.

```text
PaperFlowResult
PaperSemanticResult
├── source_revision: PaperSourceRevision
├── chunk_set: PaperChunkSet
└── global_summary: PaperGlobalSummary
Expand Down Expand Up @@ -57,11 +57,11 @@ The operation has a strict order:
7. Fan out one bounded research agent per group (bounded concurrency), each returning typed findings for its own range only.
8. Run one reducer agent over the collected findings to synthesize the summary draft.
9. Resolve model-returned chunk/page coordinates into canonical citations in code.
10. Build and validate `PaperGlobalSummary` and the cross-artifact `PaperFlowResult`.
10. Build and validate `PaperGlobalSummary` and the cross-artifact `PaperSemanticResult`.

Steps 3, 5, 9, and 10 mint no IDs in the flow: the flow calls the knowledge-layer smart constructors `PaperSourceRevision.from_parsed`, `PaperChunkSet.from_parsed_chunks`, and `PaperGlobalSummary.from_draft`, which own every ID, content/producer hash, and citation resolution. The flow only fetches, parses, reads asset bytes, and maps those path-based artifacts into knowledge-native inputs. See [orchestration principles](../operations/orchestration.md).

A summarization failure occurs after source and chunks exist in memory, but `paper_flow` returns no partial success value. Persistence is a separate explicit operation.
A summarization failure occurs after source and chunks exist in memory, but the build returns no partial success value. Persistence is a separate explicit operation.

## Source Revision

Expand Down Expand Up @@ -103,7 +103,7 @@ Code accepts the draft only when:

Summarization is a deterministic map-reduce, not an autonomous coordinator. Code — not a model — decides the decomposition: it tiles the chunk set into `summary_research_group_size` groups and runs one research agent per group, so complete chunk coverage is guaranteed by construction and never needs to be reconciled afterward.

`PaperFlowCfg` exposes only structural bounds:
`PaperSemanticCfg` exposes only structural bounds:

- `summary_research_group_size` sets how many consecutive chunks each research agent receives;
- `summary_concurrency` bounds simultaneous research-agent runs;
Expand All @@ -124,7 +124,7 @@ Bounding is delegated to the Agents SDK (per-agent `max_tokens`, structured `out
- A research finding that cites outside its assigned group is rejected in code; a reducer timeout raises `PaperSummaryError`.
- Any canonical identity, content hash, membership, lineage, or cross-artifact mismatch fails Pydantic validation.

No failure is converted into a partially valid `PaperFlowResult`. Callers may retry with the same source and producer settings; stable IDs make successful repeated runs idempotent.
No failure is converted into a partially valid `PaperSemanticResult`. Callers may retry with the same source and producer settings; stable IDs make successful repeated runs idempotent.

## Persistence and Retrieval

Expand All @@ -142,7 +142,7 @@ The bounded live slice is:
python scripts/verify_pdf_rag_e2e.py
```

It fetches exact arXiv revision `1706.03762v7`, parses at least 15 physical pages, creates multiple page-aware chunks, delegates complete coverage to bounded `gpt-4o-mini` research subagents, synthesizes a cited global summary, persists with `text-embedding-3-small`, closes and reopens the database, runs summary and chunk searches, resolves every returned locator, and prints useful summary, page, citation, and score diagnostics. The dedicated `paper-flow` job in `.github/workflows/e2e.yml` owns this non-required public-network check.
It fetches exact arXiv revision `1706.03762v7`, parses at least 15 physical pages, creates multiple page-aware chunks, delegates complete coverage to bounded `gpt-5.6-luna` research subagents, synthesizes a cited global summary, persists with `text-embedding-3-small`, closes and reopens the database, runs summary and chunk searches, resolves every returned locator, and prints useful summary, page, citation, and score diagnostics. The dedicated `paper-flow` job in `.github/workflows/e2e.yml` owns this non-required public-network check.

## Out of Scope

Expand All @@ -152,4 +152,4 @@ It fetches exact arXiv revision `1706.03762v7`, parses at least 15 physical page
- DOI-to-open-PDF resolution;
- question answering over search results;
- hidden or unbounded model calls;
- implicit persistence from `paper_flow`.
- implicit persistence from the paper build.
10 changes: 5 additions & 5 deletions contexts/design/knowledge/paper.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Source-first paper handling separates four layers:
| Semantic artifact | `PaperGlobalSummary` | `PaperCitation` | Store one independently versioned model summary with resolvable chunk/page evidence. |
| Structural artifact | `PaperStructureTree` | `TreeNode`, `Citation` | Store one independently versioned natural-section hierarchy over exact source pages. |

`PaperFlowResult` validates one compatible source, chunk set, and summary combination. It is a transfer result, not a fourth stored artifact.
`PaperSemanticResult` validates one compatible source, chunk set, and summary combination. It is a transfer result, not a fourth stored artifact.

All models are frozen Pydantic values with `extra="forbid"`. Canonical values contain no embedding vectors, provider node objects, or storage handles.

Expand All @@ -53,7 +53,7 @@ These identities make an identical run idempotent. They also keep a changed spli

The source's canonical JSON excludes blobs. This keeps canonical hashes stable and reviewable while allowing SQLite to store exact bytes in a normalized linked table. Rehydration checks both directions: stored blob bytes must match their table hashes, and table asset metadata must match the canonical source manifest.

Every chunk span is also checked against that manifest: its page must exist, its character range must fit the page text, and every visual asset ID must resolve to a screenshot or image from the same page. This check runs for a complete `PaperFlowResult` and when a stored chunk set is rehydrated independently.
Every chunk span is also checked against that manifest: its page must exist, its character range must fit the page text, and every visual asset ID must resolve to a screenshot or image from the same page. This check runs for a complete `PaperSemanticResult` and when a stored chunk set is rehydrated independently.

## Artifact Versioning

Expand All @@ -69,13 +69,13 @@ Every chunk span is also checked against that manifest: its page must exist, its
- per-agent output limit;
- research group size.

Changing any producer field creates a distinct artifact ID. Multiple chunk sets and summaries may coexist for one source revision. Loading a complete `PaperFlowResult` without explicit artifact IDs is allowed only when one unambiguous linked pair exists.
Changing any producer field creates a distinct artifact ID. Multiple chunk sets and summaries may coexist for one source revision. Loading a complete `PaperSemanticResult` without explicit artifact IDs is allowed only when one unambiguous linked pair exists.

`PaperStructureTree.producer` records model and prompt identity, the instructions hash, the bounded physical-page text input policy, and tree/output bounds. It deliberately records no splitter or chunk-set identity. Rechunking an unchanged source therefore does not create a different structure tree.

## Citation and Lineage Integrity

A `PaperCitation` identifies the exact chunk set, chunk, page, and optional verbatim quote. `PaperFlowResult` rejects citations to missing chunks, pages outside the cited chunk spans, or quotes absent from chunk text.
A `PaperCitation` identifies the exact chunk set, chunk, page, and optional verbatim quote. `PaperSemanticResult` rejects citations to missing chunks, pages outside the cited chunk spans, or quotes absent from chunk text.

`PaperGlobalSummary.derived_from` contains `ArtifactLocator` values. At least one locator must point to its producer's exact input chunk set, with the same source revision and no member ID. The library stores this relationship explicitly so lineage can be checked independently from the summary JSON.

Expand All @@ -95,6 +95,6 @@ Canonical paper models do not implement `embedding_text()` and do not select ret

## Compatibility Boundary

`LegacyPaper` retains the pre-V1 `TreeKnowledge` shape only so existing version-2 databases and the bundled legacy example can be opened. It is not exported as `Paper`, is not produced by `paper_flow`, and is not part of the V1 paper contract.
`LegacyPaper` retains the pre-V1 `TreeKnowledge` shape only so existing version-2 databases and the bundled legacy example can be opened. It is not exported as `Paper`, is not produced by `PaperFlow`, and is not part of the V1 paper contract.

There is no nested `PaperTree` on the V1 result. `PaperStructureTree` is an independently versioned paper-artifact binding of the shared `StructureTree` base, derived directly from one exact source revision and independent of every chunk-set version; see [Build and retrieve from a page-preserving structure tree](../mind/retrieval.md).
6 changes: 3 additions & 3 deletions contexts/design/library/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
|---|---|
| `open()` | Open or migrate a SQLite library without network I/O. |
| `put()` | Store one conventional `BaseKnowledge` item and its required projections. |
| `put_paper()` | Store one `PaperFlowResult`, including exact source assets, two artifacts, lineage, and required projections. |
| `put_paper()` | Store one `PaperSemanticResult`, including exact source assets, two artifacts, lineage, and required projections. |
| `put_paper_structure_tree()` | Atomically store one exact source revision and validated structure tree without projections or embeddings. |
| `get()` | Rehydrate one conventional knowledge item. |
| `get_paper()` | Rehydrate one unambiguous source/chunk-set/summary result, or use explicit artifact IDs when versions coexist. |
Expand All @@ -50,7 +50,7 @@ The public types are `LocalKnowledgeLibrary`, `SemanticQuery`, `SemanticHit`, an
| `quantmind.flows` | Produce validated results and leave persistence explicit. |
| Caller | Choose database and temporary parser-artifact locations and manage application lifecycle. |

For conventional `BaseKnowledge`, source references remain pointers and the caller retains external raw files. For `PaperFlowResult`, the exact source PDF, screenshots, and extracted images are part of the durable source revision and are copied into the library transaction.
For conventional `BaseKnowledge`, source references remain pointers and the caller retains external raw files. For `PaperSemanticResult`, the exact source PDF, screenshots, and extracted images are part of the durable source revision and are copied into the library transaction.

## Canonical Storage

Expand All @@ -75,7 +75,7 @@ There is no single opaque paper JSON blob and no canonical vector field. Aggrega

## Paper Transaction

`put_paper()` first validates the complete `PaperFlowResult`, computes its source and artifact canonical forms, determines affected projections, and obtains every required embedding. Only then does it begin one `BEGIN IMMEDIATE` transaction.
`put_paper()` first validates the complete `PaperSemanticResult`, computes its source and artifact canonical forms, determines affected projections, and obtains every required embedding. Only then does it begin one `BEGIN IMMEDIATE` transaction.

The transaction writes or reuses the source, asset blobs, artifacts, members, lineage, and all required projections. Any constraint, integrity, or write failure rolls back the transaction. An embedding-provider failure occurs before the transaction and therefore leaves no partial source or artifact rows.

Expand Down
2 changes: 1 addition & 1 deletion contexts/design/mind/retrieval.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ trees = await batch_run(tree_flow.build, inputs)
Rules:

- **Bind the config, not the input.** `PaperFlow(cfg)` stores the immutable cfg; `build(input)` takes only the operand. A batch therefore runs every input under one unified setting, and a config can never drift mid-run — a reproducibility requirement, not ergonomics.
- **The cfg type picks the shape.** `PaperStructureCfg` → `PaperStructureTree` (implemented now). A later `PaperCardCfg` → the chunk/summary shape reached through the same `build` seam; the existing `paper_flow(input, *, cfg)` function stays as a thin compatibility wrapper for that semantic shape until it lands.
- **The cfg type picks the shape.** `PaperStructureCfg` → `PaperStructureTree` and `PaperSemanticCfg` → `PaperSemanticResult`, both reached through the same `build` seam. A later `PaperCardCfg` would add a further shape through that seam.
- **Pure processing.** `build` fetches, parses, and structures, producing the complete self-contained artifact. It binds **no** library, persists nothing, and does not retrieve.
- A caller who wants only the parsed source uses `quantmind.preprocess` directly; "parse only" is a component seam, not a public pipeline.

Expand Down
Loading
Loading