diff --git a/.gitignore b/.gitignore
index 539aa6c..ac90035 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ CMakeLists_modified.txt
build/
*.egg-info
+.venv/
lib/
bin/
diff --git a/.impeccable/hook.cache.json b/.impeccable/hook.cache.json
new file mode 100644
index 0000000..ebc7c5d
--- /dev/null
+++ b/.impeccable/hook.cache.json
@@ -0,0 +1 @@
+{"version":1,"sessions":{}}
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 014f892..736deef 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,144 +1,144 @@
# Contributing to QuantMind
-Thank you for contributing to QuantMind! This guide provides essential information for developers.
+Thank you for contributing to QuantMind.
-## ๐ Quick Setup
+QuantMind is now a **domain library on top of the OpenAI Agents SDK**, not a
+self-contained agent framework. The first production flow is finance-first, but
+the architecture is intentionally useful for broader agentic knowledge work.
-1. **Fork and clone** the repository
-2. **Set up environment**:
- ```bash
- uv venv && source .venv/bin/activate
- uv pip install -e .
- ```
-3. **Install pre-commit hooks**:
- ```bash
- ./scripts/pre-commit-setup.sh
- ```
+## ๐ Quick setup
-## ๐ ๏ธ Development Setup
+```bash
+uv venv
+source .venv/bin/activate
+uv pip install -e ".[dev]"
+./scripts/pre-commit-setup.sh
+```
-### Pre-commit Hooks
+If `uv` is unavailable in your environment, a standard Python venv is an
+acceptable fallback:
+
+```bash
+python3 -m venv .venv
+source .venv/bin/activate
+pip install -e ".[dev]"
+```
-We use pre-commit hooks to ensure code quality and consistency. These hooks automatically format code, run linting, and perform other quality checks before each commit.
+## โ
Canonical verification loop
-**Install pre-commit hooks:**
+Run this before every push:
```bash
-# Automated setup (recommended)
-./scripts/pre-commit-setup.sh
-
-# Or manual setup
-pip install pre-commit
-pre-commit install
-pre-commit install --hook-type pre-push
+bash scripts/verify.sh
```
-**What the hooks do:**
+This is the single source of truth for branch health. It runs:
-- **On every commit:**
- - Code formatting with `ruff format` (80-char line length)
- - Linting with `ruff check --fix` (auto-fixes issues)
- - File quality checks (trailing whitespace, EOF, YAML syntax)
- - Safety checks (large files, merge conflicts)
+1. `ruff format --check`
+2. `ruff check`
+3. `basedpyright`
+4. `lint-imports`
+5. `pytest --cov`
-- **On push to remote:**
- - Full unit test suite via `scripts/unittest.sh`
+CI runs the same script.
-**Manual execution:**
+## ๐๏ธ Current architecture
-```bash
-# Run formatting and linting
-./scripts/lint.sh
+The permanent module roots are:
-# Run all pre-commit hooks on all files
-pre-commit run --all-files
+- `quantmind/flows/` โ apex orchestration layer
+- `quantmind/configs/` โ typed inputs and config
+- `quantmind/knowledge/` โ typed knowledge shapes and provenance
+- `quantmind/preprocess/` โ fetch + format + clean
+- `quantmind/magic.py` โ natural-language resolver
+- `quantmind/mind/` โ upcoming memory/store work
+- `quantmind/utils/` โ logger only
-# Run specific tests
-./scripts/unittest.sh tests/quantmind/sources/
-./scripts/unittest.sh all # Run all tests
-```
+Do **not** re-introduce the deleted transitional/runtime packages such as
+`quantmind.flow`, `quantmind.config`, `quantmind.llm`, or `quantmind.models`.
+
+## ๐งญ Design rules
-**Troubleshooting:**
+- Prefer **pure functions** over framework classes.
+- Use the **OpenAI Agents SDK directly**; do not build a new runtime wrapper.
+- Keep **Pydantic models at boundaries** and small internal value types simple.
+- Use **absolute imports** across module boundaries.
+- Keep **comments and docstrings in English** with Google-style formatting.
+- Preserve architectural boundaries enforced by `import-linter`.
-- If hooks fail, fix the issues and commit again
-- To skip hooks temporarily (not recommended): `git commit --no-verify`
-- Update hooks: `pre-commit autoupdate`
+## ๐งช Testing expectations
-## ๐ Development Standards
+- Add or update tests under `tests/` when behavior changes.
+- New feature work should cover both success and failure paths.
+- Mock external services and network calls.
+- Keep coverage above the configured floor.
-### Code Requirements
-- **Location**: All new code in `quantmind/` module
-- **Style**: Google-style docstrings, 80-char line length
-- **Architecture**: Abstract base classes + dependency injection
-- **Type Safety**: Pydantic models + comprehensive type hints
+For implementation work, add a simple example when it helps demonstrate the new
+behavior clearly.
-### Testing
-- **Unit tests**: Required in `tests/quantmind/` (mirror module structure)
-- **Coverage**: Test success and error cases
-- **Mocking**: Mock external APIs and file systems
+## ๐งฉ Common contribution paths
-### Documentation
-- **Examples**: Add to `examples/quantmind/` for new features
-- **Docstrings**: Google-style format for all public methods
+### New knowledge type
-## ๐๏ธ Contribution Types
+- Add a schema under `quantmind/knowledge/`
+- Choose the right base shape (`FlattenKnowledge`, `TreeKnowledge`, or future
+ `GraphKnowledge`)
+- Implement `embedding_text()`
+- Add tests under `tests/knowledge/`
-### New Sources
-- Extend `BaseSource[ContentType]` in `quantmind/sources/`
-- Add config in `quantmind/config/sources.py`
-- Include tests and usage example
+### New flow
-### New Parsers
-- Extend `BaseParser` in `quantmind/parsers/`
-- Handle multiple content formats with error handling
+- Add a typed input/config module under `quantmind/configs/`
+- Add a pure `async def ..._flow(...)` under `quantmind/flows/`
+- Use `preprocess/` helpers instead of duplicating fetch/format logic
+- Return a typed knowledge object
+- Add tests under `tests/flows/`
-### New Taggers
-- Extend `BaseTagger` in `quantmind/tagger/`
-- Support rule-based and ML approaches
+### New source or format support
-### Storage Backends
-- Extend `BaseStorage` in `quantmind/storage/`
-- Implement indexing, querying, and concurrent access
+- Add leaf functionality under `quantmind/preprocess/`
+- Keep `preprocess/` independent of `configs/`, `knowledge/`, and `flows/`
+- Add focused tests under `tests/preprocess/`
-## ๐ Pull Request Process
+### New domain extension
-1. **Create feature branch** from `master`
-2. **Follow conventional commits**: `type(scope): description`
-3. **Pre-commit hooks** run automatically on commit/push
-4. **Before submitting**:
- ```bash
- pre-commit run --all-files
- ./scripts/unittest.sh all
- ```
-5. **Submit PR** using our template
+If you are adapting QuantMind beyond finance, read
+`docs/ARCHITECTURE_FOR_NEW_DOMAINS.md` first and keep the extension aligned with
+the existing layering.
-### PR Checklist
-- [ ] Code in `quantmind/` following architecture patterns
-- [ ] Unit tests with comprehensive coverage
-- [ ] Usage example (for new features)
-- [ ] All pre-commit hooks pass
-- [ ] Conventional commit format
+## ๐ Pull request expectations
-## ๐ก Development Tips
+Before submitting a PR:
```bash
-# Run specific tests
-pytest tests/quantmind/sources/
-pytest tests/quantmind/models/
+pre-commit run --all-files
+bash scripts/verify.sh
+```
-# Test CLI functionality
-quantmind extract "test query" --max-papers 5
-quantmind config show
+Also make sure:
-# Check code quality
-./scripts/lint.sh
-```
+- commits use conventional-commit style (`feat:`, `fix:`, `docs:`, `refactor:`)
+- PR titles and descriptions are written in English
+- docs are updated when behavior or extension points change
+
+## ๐ค Agent-facing documentation
+
+QuantMind is increasingly consumed by AI agents as well as humans. Treat the
+following files as product surfaces:
+
+- `README.md`
+- `CONTRIBUTING.md`
+- `docs/ARCHITECTURE_FOR_NEW_DOMAINS.md`
+
+If you change architecture, memory semantics, or extension paths, update the
+relevant documentation in the same PR. During active iteration, these agent-
+facing docs should be reviewed regularly so agent workflows do not drift away
+from the real codebase.
-## โ Questions?
+## โQuestions?
- Check existing [issues](https://github.com/LLMQuant/quant-mind/issues)
-- Review architecture patterns in existing code
-- Look at `examples/` for usage patterns
-- See `CLAUDE.md` for detailed architecture
+- Review `CLAUDE.md` for the detailed architecture context
+- Read `docs/ARCHITECTURE_FOR_NEW_DOMAINS.md` for extension guidance
-Thank you for contributing! ๐
+Thank you for contributing.
diff --git a/README.md b/README.md
index fb6390f..6d82a38 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
- Transform Financial Knowledge into Actionable Intelligence
+ Turn Unstructured Documents into Durable, Agent-Ready Knowledge
@@ -30,7 +30,15 @@
---
-**QuantMind** is an intelligent knowledge extraction and retrieval framework for quantitative finance. It transforms unstructured financial contentโpapers, news, blogs, reportsโinto a queryable knowledge base, enabling AI-powered research at scale.
+**QuantMind** is an agent-centric knowledge extraction library
+built on top of the OpenAI Agents SDK. It gives AI agents better eyes over
+PDF/HTML/text inputs, typed knowledge instead of brittle strings, and a clean
+path toward durable memory and loop-based workflows.
+
+Today, the first production flow is focused on research-paper extraction.
+But the architecture is intentionally broader: the same preprocess โ typed
+config โ typed knowledge โ agentic workflow stack can be reused for any
+document-heavy domain.
### ๐ฐ News
| ๐๏ธ News | ๐ Description |
@@ -40,26 +48,40 @@
### ๐ง Overview
-QuantMind is a next-generation AI platform that ingests, processes, and structures **every** new piece of quantitative-finance research, including papers, news, blogs, and SEC filings into a **semantic knowledge graph**. Institutional investors, hedge funds, and research teams can now explore the frontier of factor strategies, risk models, and market insights in **seconds**, unlocking alpha that would otherwise remain buried.
+QuantMind is designed for teams building **serious AI-agent workflows** around
+documents, research, and structured memory.
-### โจ Why QuantMind?
+Its current strength is typed research extraction, but its core value is
+domain-agnostic:
-The financial research landscape is overwhelming. Every day, hundreds of papers, articles, and reports are published.
+- fetch or accept raw source material from the web, local files, or inline text
+- normalize it into markdown that an agent can reliably read
+- force output into strict Pydantic knowledge objects
+- preserve provenance so downstream agents can review, cite, and reuse results
+- support repeatable loops instead of one-off prompt chains
-#### ๐ The Opportunity
+### โจ Why QuantMind?
-- **Information Overload**: 500 new research papers & reports published daily. Manual review takes weeksโcostly, error-prone, and non-scalable
-- **Massive Market**: Financial data & analytics market โซ expected to grow to US$961.89 billion by 2032, with a compound annual growth rate of 13.5%. Tens of thousands of quant teams & asset managers hungry for speed
-- **High ROI**: 1% improvement in research efficiency can translate to millions saved or earned in trading performance
+#### For AI agents
----
+- **Better eyes**: `preprocess/` turns PDFs, HTML, and raw text into stable
+ markdown before the model sees them.
+- **Better shared language**: `configs/` and `knowledge/` replace ad-hoc JSON
+ with typed inputs, typed outputs, and explicit provenance.
+- **Better loops**: `magic.py`, `flows/`, and `batch_run()` help agents resolve
+ intent, execute work, and repeat tasks predictably.
+- **Better long-term direction**: the `mind/` roadmap is explicitly about
+ durable memory and agent-friendly retrieval, not throwaway prompting.
-#### ๐ก **QuantMind** solves this by
+#### For engineering teams
-- ๐ **Extracting** structured knowledge from any source (PDFs, web pages, APIs)
-- ๐ง **Understanding** content with domain-specific LLMs fine-tuned for finance
-- ๐พ **Storing** information in a semantic knowledge graph
-- ๐ **Retrieving** insights through natural language queries
+- **Domain-ready by design**: the first production flow is paper-oriented, but
+ the layering is reusable for any domain that needs document ingestion, typed
+ extraction, and agentic reuse.
+- **Strict architecture**: dependency boundaries are enforced with
+ `import-linter`, and the repo ships with a single canonical verification loop.
+- **Composable by design**: customization happens at three levelsโconfig,
+ flow kwargs, or forking a flow file.
---
@@ -67,37 +89,45 @@ The financial research landscape is overwhelming. Every day, hundreds of papers,

-QuantMind is built on a decoupled, two-stage architecture. This design separates the concerns of data ingestion from intelligent retrieval, ensuring both robustness and flexibility.
+QuantMind is built on a decoupled architecture that separates source handling,
+typed extraction, and future memory/store layers.
-#### **Stage 1: Knowledge Extraction**
-
-This layer is responsible for collecting, parsing, and structuring raw information into standardized knowledge units.
+#### **Current production path**
```text
-Source APIs (arXiv, News, Blogs) โ Intelligent Parser โ Workflow/Agent โ Structured Knowledge Base
+Natural-language intent
+ โ
+magic.resolve_magic_input(...)
+ โ
+typed input + typed cfg
+ โ
+preprocess.fetch + preprocess.format
+ โ
+flow(agent=OpenAI Agents SDK)
+ โ
+typed knowledge object with provenance
```
-- **Source**: Connects to various sources (academic APIs, news feeds, financial blogs, perplexity search source) to pull content
-- **Parser**: Extracts text, tables, and figures from PDFs, HTML, and other formats
-- **Tagger**: Automatically categorizes content into research areas and topics
-- **Workflow/Agent**: Orchestrates the extraction pipeline with quality control and deduplication
-
-#### **Stage 2: Intelligent Retrieval**
+#### **Permanent modules**
-This layer transforms structured knowledge into actionable insights through various retrieval mechanisms.
-
-```
-Knowledge Base โ Embeddings โ Solution Scenarios (DeepResearch, RAG, Data MCP, ...)
-```
+- `quantmind/flows/` โ apex layer (`paper_flow`, `batch_run`, observability)
+- `quantmind/configs/` โ typed inputs and flow configuration
+- `quantmind/knowledge/` โ typed knowledge shapes and provenance contracts
+- `quantmind/preprocess/` โ fetch + format + cleaning helpers
+- `quantmind/magic.py` โ natural language to typed `(input, cfg)` resolution
+- `quantmind/mind/` โ hybrid memory primitives (L1/L2/L3)
+- `quantmind/flows/governance.*` โ policy loader + runtime gates
-- **Embedding Generation**: Converts knowledge units into high-dimensional vectors for semantic search
+See [docs/ARCHITECTURE_FOR_NEW_DOMAINS.md](docs/ARCHITECTURE_FOR_NEW_DOMAINS.md)
+for how to extend this stack beyond finance.
-- Solution Scenarios: Multiple retrieval patterns including:
+#### Governance and loop observability
- - **DeepResearch**: Complex multi-hop reasoning across documents
- - **RAG**: Retrieval-augmented generation for Q&A
- - **Data MCP**: Structured data access protocols
- - Custom retrieval patterns based on use case
+QuantMind now treats governance as executable policy instead of passive config.
+Tool allowlists, loop budgets, fallback behavior, and L3 commit gates are
+declared in `quantmind/flows/governance.yaml` and enforced at runtime.
+This maps directly to loop SLI/SLO operations: every loop is expected to be
+traceable, budgeted, and quality-gated before durable writes.
---
@@ -107,7 +137,7 @@ We use [uv](https://github.com/astral-sh/uv) for fast and reliable Python packag
**Prerequisites:**
-- Python 3.8+
+- Python 3.10+
- Git
**Installation:**
@@ -154,7 +184,7 @@ We use [uv](https://github.com/astral-sh/uv) for fast and reliable Python packag
### ๐ Usage Examples
-#### Run a single paper through `paper_flow`
+#### Run a single finance paper through `paper_flow`
```python
import asyncio
@@ -172,6 +202,28 @@ async def main() -> None:
print(paper.model_dump_json(indent=2))
+asyncio.run(main())
+```
+
+#### Use the same pipeline on a local memo or technical brief
+
+```python
+import asyncio
+from pathlib import Path
+
+from quantmind.configs import PaperFlowCfg
+from quantmind.configs.paper import LocalFilePath
+from quantmind.flows import paper_flow
+
+
+async def main() -> None:
+ doc = await paper_flow(
+ LocalFilePath(path=Path("docs/internal-research-note.md")),
+ cfg=PaperFlowCfg(model="gpt-4o-mini"),
+ )
+ print(doc.root.summary)
+
+
asyncio.run(main())
```
@@ -224,32 +276,61 @@ async def main() -> None:
asyncio.run(main())
```
-> **Note**: QuantMind is mid-migration to OpenAI Agents SDK
-> (see [#71](https://github.com/LLMQuant/quant-mind/issues/71)). PR5 lands the
-> apex layer (`flows/` + `magic.py`); the remaining work is the `mind/`
-> memory + store layer scheduled for PR6 and PR7.
+### ๐ Agentic loops and durable memory
+
+QuantMind is being tuned for workflows where multiple agents can understand one
+another through **shared typed artifacts**, not just prompt conventions.
+
+Today, that means:
+
+- resolving loose intent into strict inputs with `magic.resolve_magic_input()`
+- extracting typed knowledge with `paper_flow`
+- scaling stateless fan-out work with `batch_run()`
+- preserving provenance for review and downstream reuse
+
+Next, that means:
+
+- filesystem-backed working memory under `mind/memory`
+- a store layer for retrieval and longer-lived agent loops
+- stronger multi-step patterns for review, refinement, and replay
+
+If you are building agent teams, think of QuantMind as the layer that provides
+stable inputs, stable outputs, and a durable path from observation to memory.
---
### ๐บ๏ธ Roadmap
-- [x] Better `flow` design for user-friendly usage
-- [x] First production level example (Quant Paper Agent)
-- [ ] Migrate Agent layer to OpenAI Agents SDK
-- [ ] Standardize knowledge format with `knowledge/` (Pydantic-based)
-- [ ] Additional content sources (financial news, blogs, reports)
-- [ ] Cross-step working memory (`mind/memory`) for batch document processing
+- [x] Remove the legacy in-repo agent runtime
+- [x] Reposition QuantMind on top of OpenAI Agents SDK
+- [x] Land the permanent module roots: `flows/`, `configs/`, `knowledge/`,
+ `preprocess/`, `magic.py`
+- [x] Ship a canonical verification loop (`scripts/verify.sh`)
+- [ ] Add filesystem-backed working memory in `mind/memory`
+- [ ] Add the store/retrieval layer in `mind/store`
+- [ ] Expand beyond the first paper flow with more domain flows
+- [ ] Improve multi-agent loop patterns, observability, and replay support
+- [ ] Keep agent-facing docs and extension paths under regular review during
+ active weekly iteration
---
### The Vision: An Intelligent Research Framework
> [!IMPORTANT]
-> **This section describes our long-term vision, not current capabilities.** While QuantMind today provides a solid knowledge extraction framework, the features described below represent our aspirational goals for future development.
+> **This section describes our long-term vision, not current capabilities.**
+> QuantMind already ships a useful extraction stack, but the broader memory and
+> retrieval story is still under construction.
-QuantMind is designed with a larger vision: to become a comprehensive intelligence layer for all financial knowledge. We're building toward a system that understands the interconnections between academic research, market news, analyst reports, and social sentimentโcreating a unified knowledge base that powers better financial decisions.
+QuantMind is being shaped into a durable intelligence layer for document-heavy
+workflows. Finance remains a major proving ground, but the long-term value is
+larger: helping AI agents see source material clearly, preserve structured
+understanding over time, and operate in loops that do not lose context between
+runs.
-The foundation we're building todayโstarting with papersโwill expand to encompass the entire financial information ecosystem.
+The near-term roadmap starts with papers and research-heavy workflows. The
+longer-term goal is a reusable foundation for agentic knowledge work across
+domains.
> [!NOTE]
> **Future Conceptual Example (PR6 brings `FilesystemMemory`):**
@@ -265,7 +346,8 @@ The foundation we're building todayโstarting with papersโwill expand to enco
> paper: Paper = await paper_flow(ArxivIdentifier(id=arxiv_id), memory=memory)
> ```
-This future state represents our commitment to moving beyond simple data aggregation and toward genuine machine intelligence in the financial domain.
+This future state represents the shift from one-off extraction to reusable,
+memory-aware, agentic knowledge systems.
------
@@ -279,11 +361,11 @@ We welcome contributions of all forms, from bug reports to feature development.
**Quick Start for Contributors:**
1. **Fork** the repository
-2. **Setup development environment**:
+2. **Setup the development environment**:
```bash
uv venv && source .venv/bin/activate
- uv pip install -e .
+ uv pip install -e ".[dev]"
./scripts/pre-commit-setup.sh
```
@@ -295,7 +377,7 @@ We welcome contributions of all forms, from bug reports to feature development.
- Open an [issue](https://github.com/LLMQuant/quant-mind/issues) to discuss significant changes
- Use our issue templates for bug reports and feature requests
-- Ensure all pre-commit hooks pass before submitting PR
+- Ensure `bash scripts/verify.sh` passes before submitting PR
### License
diff --git a/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md b/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md
new file mode 100644
index 0000000..ba4f353
--- /dev/null
+++ b/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md
@@ -0,0 +1,147 @@
+# Extending QuantMind Beyond a Single Domain
+
+QuantMind currently ships a paper-oriented extraction flow, but the architecture
+is meant to support broader agentic knowledge work. This guide explains which
+parts are stable, which parts are domain-specific today, and how to extend the
+stack without fighting the current design.
+
+## What is stable today
+
+The permanent architecture is:
+
+```text
+quantmind/
+โโโ flows/ # apex orchestration layer
+โโโ knowledge/ # typed knowledge outputs
+โโโ preprocess/ # fetch + format + clean
+โโโ configs/ # typed flow inputs and cfg
+โโโ mind/ # memory/store roadmap
+โโโ magic.py # natural-language resolver
+โโโ utils/ # logger
+```
+
+These layers already work well for agent teams because they create a strict
+contract between source material, extraction, and downstream reuse.
+
+## What is domain-specific today
+
+The first production flow is `paper_flow`, and its output schema is
+`quantmind.knowledge.Paper`. That flow is optimized for research-paper style
+documents and still includes some finance-origin fields such as `asset_classes`.
+
+That does **not** make the whole repository finance-only. It means the current
+reference implementation is paper-first while the underlying architecture is
+already reusable:
+
+- `preprocess/` is domain-agnostic
+- `magic.resolve_magic_input()` is flow-agnostic
+- `BaseFlowCfg` and `BaseInput` are reusable contracts
+- `BaseKnowledge` and the three knowledge shapes are reusable patterns
+- `batch_run()` is reusable for stateless fan-out work
+
+## How to add a new domain
+
+### 1. Pick the right knowledge shape
+
+Use:
+
+- `FlattenKnowledge` for atomic records, events, or summaries
+- `TreeKnowledge` for long documents with section/subsection structure
+- `GraphKnowledge` only when the relationship layer is actually ready to land
+
+Start from the retrieval shape you want downstream agents to use, not from the
+source format you happen to ingest first.
+
+### 2. Define typed inputs and configuration
+
+Add a new config module under `quantmind/configs/`:
+
+- define one or more `BaseInput` subclasses
+- create a discriminated union for the flow input
+- extend `BaseFlowCfg` with only domain-relevant knobs
+
+The goal is for agents and humans to share the same explicit input contract.
+
+### 3. Implement a pure flow function
+
+Add a new `async def ..._flow(...)` under `quantmind/flows/` that:
+
+- accepts one typed input plus `cfg=...`
+- fetches or normalizes source content through `preprocess/`
+- runs an Agents SDK `Agent(output_type=...)`
+- returns a typed knowledge object
+
+Do not introduce a custom runtime, plugin registry, or class-based flow
+hierarchy.
+
+### 4. Make the flow usable from natural language
+
+If the flow follows the same `(input, *, cfg, ...)` signature convention,
+`magic.resolve_magic_input()` can already resolve free-form intent into typed
+input and config objects.
+
+This is the bridge that helps external agent systems work with QuantMind
+without writing brittle prompt parsers.
+
+## Recommended agentic loop patterns
+
+### Stateless scout loop
+
+Use when you want breadth first:
+
+1. resolve or build many typed inputs
+2. run the flow in parallel with `batch_run()`
+3. collect typed outputs
+4. hand those outputs to a review or ranking agent
+
+This is the best fit for discovery, triage, and broad corpus scanning.
+
+### Serial memory loop
+
+Use when each step depends on previous results:
+
+1. read one document
+2. extract a typed knowledge object
+3. update memory or a local run archive
+4. feed the next item with that accumulated context
+
+Today, this pattern should be implemented as an explicit serial loop. `batch_run`
+intentionally rejects `memory=` because shared-memory fan-out is not yet the
+MVP design.
+
+### Multi-agent handoff loop
+
+A simple, durable pattern is:
+
+1. **Scout agent** resolves intent and gathers sources
+2. **Extractor agent** creates typed knowledge objects
+3. **Reviewer agent** validates structure, provenance, and confidence
+4. **Planner agent** decides what to fetch or revisit next
+
+The key rule is to pass typed artifacts between agents whenever possible. Avoid
+hidden agreements in prompt text when a Pydantic object can carry the contract.
+
+## Memory and durability roadmap
+
+The repository is explicitly moving toward a memory-aware architecture:
+
+- `mind/memory.py` provides the current L1/L2/L3 memory primitives
+- `mind/store/` remains the planned retrieval/store layer
+- `flows/_runner.py` already reserves the runtime seam for memory integration
+- `flows/governance.py` enforces policy for loop budgets, tool allowlists,
+ fallback behavior, and L3 commit gates
+
+Until those land, treat QuantMind as a strong typed-extraction layer with a
+clear upgrade path toward durable agent loops.
+
+## Documentation discipline
+
+QuantMind is increasingly meant to be read by both humans and AI agents.
+Because of that:
+
+- keep `README.md`, `CONTRIBUTING.md`, and this file aligned with code changes
+- document new domain assumptions in the same PR that introduces them
+- review agent-facing docs regularly during active iteration
+
+This discipline matters because stale documentation causes broken agent loops
+just as quickly as stale code.
diff --git a/docs/fincept_integration.md b/docs/fincept_integration.md
new file mode 100644
index 0000000..d0c7304
--- /dev/null
+++ b/docs/fincept_integration.md
@@ -0,0 +1,64 @@
+# Fincept โ QuantMind Integration Guide
+
+## Overview
+
+QuantMind serves as the **knowledge extraction layer** for Fincept AI Ops.
+Rather than raw market data, Fincept can query QuantMind for:
+- Distilled research insights from financial papers
+- Structured signal metadata (alpha factors, risk signals)
+- Natural language Q&A over quantitative finance literature
+
+## Integration Architecture
+
+```
+fincept-ai-ops (FastAPI)
+ โ
+ โโโ GET /quant/search?q={query}
+ โ โ
+ โ QuantMind Knowledge API
+ โ โ
+ โ Vector Search (LanceDB / Chroma)
+ โ โ
+ โ Extracted Insights JSON
+ โ
+ โโโ MCP Tool: query_quantmind
+```
+
+## Usage Example
+
+ import asyncio
+ from pathlib import Path
+
+ from quantmind.configs import PaperFlowCfg
+ from quantmind.configs.paper import LocalFilePath
+ from quantmind.flows import paper_flow
+
+
+ async def main() -> None:
+ doc = await paper_flow(
+ LocalFilePath(path=Path("path/to/document.pdf")),
+ cfg=PaperFlowCfg(model="gpt-4o-mini"),
+ )
+ # Persist / index this JSON in Fincept's storage layer.
+ print(doc.model_dump(mode="json"))
+
+
+ asyncio.run(main())
+
+## Synergy with Fincept
+
+| Fincept Component | QuantMind Feature |
+|---|---|
+| Strategy generation | Research paper extraction |
+| Risk model inputs | Factor library |
+| Audit log enrichment | Source citation tracking |
+| Backtest hypothesis | Literature-validated signals |
+
+## Setup
+
+1. Clone both repos side-by-side
+2. Set `QUANTMIND_API_URL` in fincept `.env`
+3. Run `quantmind serve --port 8001`
+4. Fincept auto-discovers at startup
+
+See [fincept-ai-ops ROADMAP](../fincept-ai-ops/ROADMAP.md) for v1.2 integration milestone.
diff --git a/draft_corrections.md b/draft_corrections.md
new file mode 100644
index 0000000..d4ca917
--- /dev/null
+++ b/draft_corrections.md
@@ -0,0 +1,58 @@
+# Draft Corrections: Verified Memory Repo Shortlist
+
+## 1) Dogrulama olcutu
+
+Bu calisma "en iyi" iddiasi degil, "dogrulanmis adaylar" uretir. Bir repo shortlist'e girmek icin asagidaki kosullari birlikte saglamalidir:
+
+1. Son 90 gunde aktif commit hareketi var.
+2. Gercek bir memory architecture sunuyor (tier, retrieval, persistence, governance gibi somut katmanlar).
+3. Agentic AI veya autonomous agent akislarina dogrudan temas ediyor.
+4. Acik dokumantasyon ve calisan ornek kullanim veriyor.
+5. Yildiz/fork/adoption sinyali ile topluluk ilgisi goruluyor.
+
+Degerlendirme notu:
+- Stars tek basina karar kriteri degildir.
+- Mimari netlik + bakim aktivitesi + entegrasyon kaniti birincil agirliktir.
+
+## 2) Haric tutma kurallari
+
+Asagidaki repo tipleri shortlist disinda tutulur:
+
+1. Sadece repo adi veya README icinde "memory" geciyor, fakat gercek memory altyapisi yok.
+2. Genel framework sunuyor, ancak memory disiplini (state lifecycle, retrieval policy, persistence strategy) icermiyor.
+3. Eski, pasif, bakim disi veya son donemde anlamli gelistirme gostermeyen projeler.
+4. Mimariyi dogrulayan kanitlar eksik (zayif docs, belirsiz API, calismayan ornekler).
+
+## 3) QuantMind icin cikarim
+
+### Copilot Memory benzeri patternler
+
+- Citation-backed facts
+- Validation before use
+- Stale item cleanup
+- Repo-scoped memory
+
+### QuantMind'e dogrudan uyarlama
+
+- `RawFallbackNode`: Yapilandirilamayan veya dusuk guvenli ciktilari kaybetmeden ham iz olarak saklama.
+- `QualityGate`: Durable katmana commit oncesi sema, kaynak ve tutarlilik denetimi.
+- `Hybrid memory tiers`: Working, episodic ve durable katmanlari amaca gore ayirma.
+- `Graph only for distilled facts`: Graph katmanina yalnizca rafine edilmis, kaynaklanmis ve tekrar dogrulanmis bilgi yazma.
+
+### L3 commit icin zorunlu kosullar
+
+`confidence >= 0.85` tek basina yeterli degildir. Asagidaki kosullar birlikte zorunludur:
+
+1. Provenance mevcut ve dogrulanabilir.
+2. Schema validity basarili.
+3. Dedup ve contradiction kontrolu basarili.
+
+## Sonraki adim (uygulama hazirligi)
+
+Bu taslak sonraki iterasyonda su ciktiya donusturulmelidir:
+
+- Dogrulanmis 21 repo shortlist
+- Her repo icin memory yaklasimi siniflandirmasi
+- QuantMind uyarlanabilirlik puani
+- Risk/avantaj analizi
+- Kopyalanacak mimari pattern oneri matrisi
diff --git a/examples/mind/recoverable_memory_demo.py b/examples/mind/recoverable_memory_demo.py
new file mode 100644
index 0000000..55ba939
--- /dev/null
+++ b/examples/mind/recoverable_memory_demo.py
@@ -0,0 +1,54 @@
+"""Minimal demo for recoverable ingestion and gated memory commit."""
+
+from tempfile import TemporaryDirectory
+
+from pydantic import BaseModel, ConfigDict
+
+from quantmind.configs.recoverable import RecoverableValidation
+from quantmind.flows.governance import load_governance_policy
+from quantmind.mind.memory import HybridMemoryEngine
+
+
+class DemoArtifact(BaseModel):
+ """Strict schema that rejects unexpected fields."""
+
+ model_config = ConfigDict(extra="forbid")
+ id: str
+ title: str
+ validation_confidence: float
+ provenance: dict[str, str]
+
+
+def main() -> None:
+ validator = RecoverableValidation(DemoArtifact)
+ policy = load_governance_policy()
+ payload = {
+ "id": "artifact-1",
+ "title": "Recovered architecture summary",
+ "validation_confidence": 0.91,
+ "provenance": {"source": "demo"},
+ "unexpected_field": "will trigger fallback",
+ }
+
+ with TemporaryDirectory() as tmpdir:
+ memory = HybridMemoryEngine(base_path=tmpdir)
+ parsed = validator.execute_safely(payload)
+ memory.write_l1_trace("Parser", parsed.model_dump(mode="json"))
+
+ if isinstance(parsed, DemoArtifact):
+ committed = memory.commit_to_l3_graph(
+ parsed.model_dump(mode="json"),
+ schema_valid=True,
+ dedup_ok=True,
+ contradiction_free=True,
+ )
+ print("Committed to L3:", committed)
+ else:
+ fallback_node = parsed
+ fallback = policy.global_settings.fallback_policy
+ print("Fallback policy:", fallback)
+ print("Quarantined schema:", fallback_node.target_schema)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/quantmind/__init__.py b/quantmind/__init__.py
index 91edcda..02dcb2c 100644
--- a/quantmind/__init__.py
+++ b/quantmind/__init__.py
@@ -1,7 +1,7 @@
-"""QuantMind: Intelligent Knowledge Extraction and Retrieval Framework.
+"""QuantMind: Intelligent knowledge extraction for agentic workflows.
-QuantMind transforms unstructured financial content into a queryable knowledge graph
-through a two-stage architecture focused on knowledge extraction and intelligent retrieval.
+QuantMind transforms unstructured documents into typed, provenance-aware
+artifacts that can be validated, governed, and reused in durable loops.
"""
__version__ = "0.0.1"
diff --git a/quantmind/configs/__init__.py b/quantmind/configs/__init__.py
index 271be37..eb88e6d 100644
--- a/quantmind/configs/__init__.py
+++ b/quantmind/configs/__init__.py
@@ -12,6 +12,7 @@
from quantmind.configs.earnings import EarningsFlowCfg, EarningsInput
from quantmind.configs.news import NewsFlowCfg, NewsInput
from quantmind.configs.paper import PaperFlowCfg, PaperInput
+from quantmind.configs.recoverable import RawFallbackNode, RecoverableValidation
__all__ = [
"BaseFlowCfg",
@@ -22,4 +23,6 @@
"NewsInput",
"PaperFlowCfg",
"PaperInput",
+ "RawFallbackNode",
+ "RecoverableValidation",
]
diff --git a/quantmind/configs/recoverable.py b/quantmind/configs/recoverable.py
new file mode 100644
index 0000000..dd4909b
--- /dev/null
+++ b/quantmind/configs/recoverable.py
@@ -0,0 +1,42 @@
+"""Recoverable parsing helpers for tolerant ingestion boundaries.
+
+This module keeps strict schema contracts in place (`extra="forbid"` on
+the target model) while preventing pipeline paralysis during malformed
+inputs. Callers receive either a validated model or a structured
+quarantine node that preserves raw context.
+"""
+
+from typing import Any, Generic, TypeVar
+
+from pydantic import BaseModel, ConfigDict, Field, ValidationError
+
+T = TypeVar("T", bound=BaseModel)
+
+
+class RawFallbackNode(BaseModel):
+ """Quarantine payload used when schema validation fails."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ raw_payload: dict[str, Any] = Field(default_factory=dict)
+ target_schema: str
+ error_message: str
+ context_loss_prevented: bool = True
+
+
+class RecoverableValidation(Generic[T]):
+ """Validate against a target model and quarantine failures."""
+
+ def __init__(self, target_model: type[T]) -> None:
+ self.target_model = target_model
+
+ def execute_safely(self, data: dict[str, Any]) -> T | RawFallbackNode:
+ """Return validated model or a fallback node with original payload."""
+ try:
+ return self.target_model.model_validate(data)
+ except ValidationError as error:
+ return RawFallbackNode(
+ raw_payload=data,
+ target_schema=self.target_model.__name__,
+ error_message=str(error),
+ )
diff --git a/quantmind/flows/__init__.py b/quantmind/flows/__init__.py
index 78dd9bf..839f9c9 100644
--- a/quantmind/flows/__init__.py
+++ b/quantmind/flows/__init__.py
@@ -1,7 +1,10 @@
"""Apex layer โ composes configs / knowledge / preprocess on the SDK.
-Each flow function (``paper_flow``, future ``news_flow`` / ``earnings_flow``)
-takes a typed input and a ``FlowCfg`` and returns a knowledge item.
+Each flow function takes a typed input and a ``FlowCfg`` and returns a
+knowledge item. The current production flow is paper-oriented (``paper_flow``),
+but the apex-layer contract itself is reusable for future domain flows as long
+as they follow the same typed ``(input, *, cfg, ...)`` pattern.
+
Cross-flow utilities live alongside:
- ``batch_run`` runs any flow over a list of inputs with bounded
@@ -12,11 +15,29 @@
"""
from quantmind.flows.batch import BatchResult, batch_run
+from quantmind.flows.governance import (
+ GovernancePolicy,
+ GovernancePolicyError,
+ LoopBudgetManager,
+ enforce_l3_commit_gates,
+ ensure_tool_allowed,
+ load_governance_policy,
+ loop_budget_manager,
+ run_fallback_policy,
+)
from quantmind.flows.paper import UnsupportedContentTypeError, paper_flow
__all__ = [
"BatchResult",
+ "GovernancePolicy",
+ "GovernancePolicyError",
+ "LoopBudgetManager",
"UnsupportedContentTypeError",
"batch_run",
+ "enforce_l3_commit_gates",
+ "ensure_tool_allowed",
+ "load_governance_policy",
+ "loop_budget_manager",
"paper_flow",
+ "run_fallback_policy",
]
diff --git a/quantmind/flows/governance.py b/quantmind/flows/governance.py
new file mode 100644
index 0000000..ab496f6
--- /dev/null
+++ b/quantmind/flows/governance.py
@@ -0,0 +1,210 @@
+"""Governance policy loader and runtime enforcement helpers."""
+
+from pathlib import Path
+from typing import Any, Literal
+
+import yaml
+from pydantic import BaseModel, ConfigDict, Field, ValidationError
+
+from quantmind.configs import RawFallbackNode
+
+
+class GovernancePolicyError(ValueError):
+ """Raised when governance policy content cannot be loaded or validated."""
+
+
+class AgentGovernanceRule(BaseModel):
+ """Tool + tier constraints for one role in a scenario."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ role: str
+ allowed_tools: list[str] = Field(default_factory=list)
+ output_tier: Literal["L1", "L2", "L3"]
+ required_schema: str | None = None
+ confidence_threshold: float | None = None
+ fallback_node: str | None = None
+
+
+class ScenarioGovernanceRule(BaseModel):
+ """Scenario-level set of role policies."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ description: str
+ agents: list[AgentGovernanceRule] = Field(default_factory=list)
+
+
+class GlobalGovernanceSettings(BaseModel):
+ """Global runtime limits and failure behavior."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ loop_budget_max: int = 5
+ fallback_policy: Literal["quarantine_and_continue", "fail_fast"] = (
+ "quarantine_and_continue"
+ )
+
+
+class L3CommitPolicy(BaseModel):
+ """Hard gates required before committing to durable memory."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ min_confidence: float = 0.85
+ require_provenance: bool = True
+ require_schema_validity: bool = True
+ require_dedup_check: bool = True
+ require_contradiction_check: bool = True
+
+
+class GovernancePolicy(BaseModel):
+ """Full governance policy document."""
+
+ model_config = ConfigDict(extra="forbid")
+
+ version: str
+ global_settings: GlobalGovernanceSettings = Field(
+ default_factory=GlobalGovernanceSettings
+ )
+ l3_commit: L3CommitPolicy = Field(default_factory=L3CommitPolicy)
+ scenarios: dict[str, ScenarioGovernanceRule] = Field(default_factory=dict)
+
+ def role_rule(
+ self, scenario_name: str, role: str
+ ) -> AgentGovernanceRule:
+ """Resolve the role rule for one scenario."""
+ try:
+ scenario = self.scenarios[scenario_name]
+ except KeyError as error:
+ raise KeyError(
+ f"Unknown governance scenario: {scenario_name!r}"
+ ) from error
+ for rule in scenario.agents:
+ if rule.role == role:
+ return rule
+ raise KeyError(
+ f"Role {role!r} is not defined in scenario {scenario_name!r}"
+ )
+
+
+def load_governance_policy(path: str | Path | None = None) -> GovernancePolicy:
+ """Load and validate governance policy YAML."""
+ policy_path = Path(path) if path is not None else _default_policy_path()
+ try:
+ loaded = yaml.safe_load(policy_path.read_text(encoding="utf-8"))
+ except OSError as error:
+ raise GovernancePolicyError(
+ f"Unable to read governance policy: {policy_path}"
+ ) from error
+ except yaml.YAMLError as error:
+ raise GovernancePolicyError(
+ f"Invalid YAML in governance policy: {policy_path}"
+ ) from error
+
+ if loaded is None:
+ raise GovernancePolicyError(
+ f"Governance policy is empty: {policy_path}"
+ )
+ try:
+ return GovernancePolicy.model_validate(loaded)
+ except ValidationError as error:
+ raise GovernancePolicyError(
+ f"Governance policy schema validation failed: {policy_path}"
+ ) from error
+
+
+def ensure_tool_allowed(
+ policy: GovernancePolicy,
+ *,
+ scenario_name: str,
+ role: str,
+ tool_name: str,
+) -> None:
+ """Raise when a role tries to use a tool outside its allowlist."""
+ rule = policy.role_rule(scenario_name, role)
+ if tool_name not in rule.allowed_tools:
+ raise PermissionError(
+ f"Tool {tool_name!r} is not allowed for role {role!r} "
+ f"in scenario {scenario_name!r}"
+ )
+
+
+class LoopBudgetManager:
+ """Track and enforce max loop budget from governance policy."""
+
+ def __init__(self, max_loops: int) -> None:
+ if max_loops <= 0:
+ raise ValueError("max_loops must be positive")
+ self.max_loops = max_loops
+ self._consumed = 0
+
+ @property
+ def remaining(self) -> int:
+ """Return remaining budget steps."""
+ return self.max_loops - self._consumed
+
+ def consume(self, *, steps: int = 1) -> int:
+ """Consume loop budget and return the remaining amount."""
+ if steps <= 0:
+ raise ValueError("steps must be positive")
+ if self._consumed + steps > self.max_loops:
+ raise RuntimeError(
+ f"Loop budget exceeded: requested {steps}, "
+ f"remaining {self.remaining}"
+ )
+ self._consumed += steps
+ return self.remaining
+
+
+def loop_budget_manager(policy: GovernancePolicy) -> LoopBudgetManager:
+ """Create a budget manager from policy defaults."""
+ return LoopBudgetManager(policy.global_settings.loop_budget_max)
+
+
+def run_fallback_policy(
+ policy: GovernancePolicy,
+ *,
+ target_schema: str,
+ payload: dict[str, Any],
+ error_message: str,
+) -> RawFallbackNode:
+ """Run fallback behavior configured by governance policy."""
+ fallback = policy.global_settings.fallback_policy
+ if fallback == "quarantine_and_continue":
+ return RawFallbackNode(
+ raw_payload=payload,
+ target_schema=target_schema,
+ error_message=error_message,
+ )
+ raise RuntimeError(
+ f"Fallback policy {fallback!r} blocks continuation"
+ )
+
+
+def enforce_l3_commit_gates(
+ policy: GovernancePolicy,
+ *,
+ artifact: dict[str, Any],
+ schema_valid: bool,
+ dedup_ok: bool,
+ contradiction_free: bool,
+) -> bool:
+ """Return true only when all configured L3 commit gates pass."""
+ rules = policy.l3_commit
+ confidence = float(artifact.get("validation_confidence", 0.0))
+ if confidence < rules.min_confidence:
+ return False
+ if rules.require_provenance and not artifact.get("provenance"):
+ return False
+ if rules.require_schema_validity and not schema_valid:
+ return False
+ if rules.require_dedup_check and not dedup_ok:
+ return False
+ if rules.require_contradiction_check and not contradiction_free:
+ return False
+ return True
+
+
+def _default_policy_path() -> Path:
+ return Path(__file__).with_name("governance.yaml")
diff --git a/quantmind/flows/governance.yaml b/quantmind/flows/governance.yaml
new file mode 100644
index 0000000..f816c53
--- /dev/null
+++ b/quantmind/flows/governance.yaml
@@ -0,0 +1,43 @@
+version: "2.0.0"
+
+global_settings:
+ loop_budget_max: 5
+ fallback_policy: "quarantine_and_continue"
+
+l3_commit:
+ min_confidence: 0.85
+ require_provenance: true
+ require_schema_validity: true
+ require_dedup_check: true
+ require_contradiction_check: true
+
+scenarios:
+ architecture_analysis:
+ description: "Technical document analysis and refactor suggestion"
+ agents:
+ - role: "Scout"
+ allowed_tools: ["scan_repo", "read_file"]
+ output_tier: "L1"
+ - role: "Extractor"
+ allowed_tools: ["parse_ast"]
+ required_schema: "ArchitectureSummary"
+ output_tier: "L2"
+ - role: "Reviewer"
+ allowed_tools: ["diff_analyze"]
+ confidence_threshold: 0.8
+ output_tier: "L3"
+
+ tolerant_ingestion:
+ description: "Ingesting malformed multi-source datasets safely"
+ agents:
+ - role: "Scout"
+ allowed_tools: ["fetch_source"]
+ output_tier: "L1"
+ - role: "Parser"
+ allowed_tools: ["recoverable_validate"]
+ fallback_node: "RawFallbackNode"
+ output_tier: "L2"
+ - role: "GraphWriter"
+ allowed_tools: ["graph_write"]
+ confidence_threshold: 0.9
+ output_tier: "L3"
diff --git a/quantmind/magic.py b/quantmind/magic.py
index 71e522a..5c44b96 100644
--- a/quantmind/magic.py
+++ b/quantmind/magic.py
@@ -23,6 +23,7 @@
from pydantic import BaseModel
from quantmind.configs.base import BaseFlowCfg
+from quantmind.flows.governance import GovernancePolicy, ensure_tool_allowed
InputT = TypeVar("InputT", bound=BaseModel)
CfgT = TypeVar("CfgT", bound=BaseModel)
@@ -65,6 +66,10 @@ async def resolve_magic_input(
target_flow: Callable[..., Awaitable[Any]],
resolver_model: str = "gpt-4o-mini",
resolver_instructions: str | None = None,
+ governance_policy: GovernancePolicy | None = None,
+ governance_scenario: str | None = None,
+ requested_tools: list[str] | None = None,
+ resolver_role: str = "Resolver",
) -> tuple[Any, Any]:
"""Parse ``natural_language`` into ``(input_obj, cfg_obj)`` for ``target_flow``.
@@ -76,11 +81,24 @@ async def resolve_magic_input(
resolver_instructions: Optional override for the resolver's
system prompt template. Receives ``flow_name``,
``input_schema``, and ``cfg_schema`` via ``str.format``.
+ governance_policy: Optional governance policy to enforce before
+ model execution.
+ governance_scenario: Scenario name used with
+ ``governance_policy`` for tool allowlist checks.
+ requested_tools: Tool names that the resolver intends to use.
+ resolver_role: Role name used for governance lookup when policy
+ checks are enabled.
Returns:
Tuple of ``(input_obj, cfg_obj)`` populated by the resolver.
"""
input_type, cfg_type = _introspect_flow_signature(target_flow)
+ _enforce_governance(
+ governance_policy=governance_policy,
+ governance_scenario=governance_scenario,
+ requested_tools=requested_tools,
+ resolver_role=resolver_role,
+ )
template = resolver_instructions or _RESOLVER_INSTRUCTIONS
instructions = template.format(
flow_name=target_flow.__name__,
@@ -103,12 +121,20 @@ async def preview_resolve(
*,
target_flow: Callable[..., Awaitable[Any]],
resolver_model: str = "gpt-4o-mini",
+ governance_policy: GovernancePolicy | None = None,
+ governance_scenario: str | None = None,
+ requested_tools: list[str] | None = None,
+ resolver_role: str = "Resolver",
) -> tuple[Any, Any]:
"""Resolve and pretty-print the result without invoking the flow."""
inp, cfg = await resolve_magic_input(
natural_language,
target_flow=target_flow,
resolver_model=resolver_model,
+ governance_policy=governance_policy,
+ governance_scenario=governance_scenario,
+ requested_tools=requested_tools,
+ resolver_role=resolver_role,
)
print("input_obj:", inp.model_dump_json(indent=2))
print("cfg_obj:", cfg.model_dump_json(indent=2))
@@ -199,3 +225,26 @@ def _pydantic_schema_str(t: Any) -> str:
]
return json.dumps({"oneOf": schemas}, indent=2)
return repr(t)
+
+
+def _enforce_governance(
+ *,
+ governance_policy: GovernancePolicy | None,
+ governance_scenario: str | None,
+ requested_tools: list[str] | None,
+ resolver_role: str,
+) -> None:
+ """Apply optional resolver-side governance checks."""
+ if governance_policy is None:
+ return
+ if not governance_scenario:
+ raise ValueError(
+ "governance_scenario is required when governance_policy is set"
+ )
+ for tool_name in requested_tools or []:
+ ensure_tool_allowed(
+ governance_policy,
+ scenario_name=governance_scenario,
+ role=resolver_role,
+ tool_name=tool_name,
+ )
diff --git a/quantmind/mind/__init__.py b/quantmind/mind/__init__.py
new file mode 100644
index 0000000..331bd38
--- /dev/null
+++ b/quantmind/mind/__init__.py
@@ -0,0 +1,9 @@
+"""Mind layer primitives (memory/storage surfaces)."""
+
+from quantmind.mind.memory import (
+ L3CommitRequirements,
+ HybridMemoryEngine,
+ can_commit_to_l3,
+)
+
+__all__ = ["HybridMemoryEngine", "L3CommitRequirements", "can_commit_to_l3"]
diff --git a/quantmind/mind/memory.py b/quantmind/mind/memory.py
new file mode 100644
index 0000000..9e787b7
--- /dev/null
+++ b/quantmind/mind/memory.py
@@ -0,0 +1,95 @@
+"""Hybrid memory primitives for L1/L2/L3 data handling."""
+
+import json
+from dataclasses import dataclass
+from pathlib import Path
+from typing import Any
+
+
+@dataclass(frozen=True)
+class L3CommitRequirements:
+ """Hard gates required before durable L3 commit."""
+
+ min_confidence: float = 0.85
+ require_provenance: bool = True
+ require_schema_validity: bool = True
+ require_dedup_check: bool = True
+ require_contradiction_check: bool = True
+
+
+def can_commit_to_l3(
+ artifact: dict[str, Any],
+ *,
+ schema_valid: bool,
+ dedup_ok: bool,
+ contradiction_free: bool,
+ requirements: L3CommitRequirements | None = None,
+) -> bool:
+ """Return true only when artifact passes all required L3 gates."""
+ req = requirements or L3CommitRequirements()
+ confidence = float(artifact.get("validation_confidence", 0.0))
+ if confidence < req.min_confidence:
+ return False
+ if req.require_provenance and not artifact.get("provenance"):
+ return False
+ if req.require_schema_validity and not schema_valid:
+ return False
+ if req.require_dedup_check and not dedup_ok:
+ return False
+ if req.require_contradiction_check and not contradiction_free:
+ return False
+ return True
+
+
+class HybridMemoryEngine:
+ """L1 (transient) -> L2 (working) -> L3 (durable) memory manager."""
+
+ def __init__(self, base_path: str = "~/.quantmind/mind") -> None:
+ self.base_path = Path(base_path).expanduser()
+ self.base_path.mkdir(parents=True, exist_ok=True)
+ self.l1_path = self.base_path / "l1_ephemeral.jsonl"
+ self.l2_path = self.base_path / "l2_working.json"
+ self.l3_path = self.base_path / "l3_durable.jsonl"
+
+ def write_l1_trace(self, agent_role: str, tool_output: Any) -> None:
+ """Append raw tool output to L1 trace."""
+ record = {"role": agent_role, "payload": tool_output}
+ with self.l1_path.open("a", encoding="utf-8") as file:
+ file.write(json.dumps(record, ensure_ascii=False) + "\n")
+
+ def update_l2_state(self, key: str, value: Any) -> dict[str, Any]:
+ """Update and persist L2 working state as JSON."""
+ current: dict[str, Any] = {}
+ if self.l2_path.exists():
+ with self.l2_path.open("r", encoding="utf-8") as file:
+ loaded = json.load(file)
+ if isinstance(loaded, dict):
+ current = loaded
+ current[key] = value
+ with self.l2_path.open("w", encoding="utf-8") as file:
+ json.dump(current, file, ensure_ascii=False, indent=2, sort_keys=True)
+ return current
+
+ def commit_to_l3_graph(
+ self,
+ validated_artifact: dict[str, Any],
+ *,
+ schema_valid: bool,
+ dedup_ok: bool,
+ contradiction_free: bool,
+ requirements: L3CommitRequirements | None = None,
+ ) -> bool:
+ """Commit artifact to L3 only when every hard gate passes."""
+ if not can_commit_to_l3(
+ validated_artifact,
+ schema_valid=schema_valid,
+ dedup_ok=dedup_ok,
+ contradiction_free=contradiction_free,
+ requirements=requirements,
+ ):
+ return False
+ with self.l3_path.open("a", encoding="utf-8") as file:
+ file.write(
+ json.dumps(validated_artifact, ensure_ascii=False) + "\n"
+ )
+ return True
diff --git a/tests/configs/test_recoverable.py b/tests/configs/test_recoverable.py
new file mode 100644
index 0000000..f0565a1
--- /dev/null
+++ b/tests/configs/test_recoverable.py
@@ -0,0 +1,39 @@
+"""Tests for recoverable validation helpers."""
+
+import unittest
+
+from pydantic import BaseModel, ConfigDict
+
+from quantmind.configs.recoverable import RawFallbackNode, RecoverableValidation
+
+
+class _StrictSample(BaseModel):
+ model_config = ConfigDict(extra="forbid")
+ name: str
+ score: float
+
+
+class RecoverableValidationTests(unittest.TestCase):
+ def test_returns_validated_model_on_success(self) -> None:
+ validator = RecoverableValidation(_StrictSample)
+ out = validator.execute_safely({"name": "node-1", "score": 0.91})
+ self.assertIsInstance(out, _StrictSample)
+ assert isinstance(out, _StrictSample)
+ self.assertEqual(out.name, "node-1")
+ self.assertEqual(out.score, 0.91)
+
+ def test_returns_fallback_node_on_validation_error(self) -> None:
+ validator = RecoverableValidation(_StrictSample)
+ out = validator.execute_safely(
+ {"name": "node-1", "score": 0.91, "unexpected": "x"}
+ )
+ self.assertIsInstance(out, RawFallbackNode)
+ assert isinstance(out, RawFallbackNode)
+ self.assertEqual(out.target_schema, "_StrictSample")
+ self.assertTrue(out.context_loss_prevented)
+ self.assertEqual(out.raw_payload["unexpected"], "x")
+ self.assertIn("Extra inputs are not permitted", out.error_message)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/flows/test_governance.py b/tests/flows/test_governance.py
new file mode 100644
index 0000000..f5e5580
--- /dev/null
+++ b/tests/flows/test_governance.py
@@ -0,0 +1,135 @@
+"""Tests for ``quantmind.flows.governance``."""
+
+import unittest
+from pathlib import Path
+
+from quantmind.flows.governance import (
+ GovernancePolicy,
+ GovernancePolicyError,
+ enforce_l3_commit_gates,
+ ensure_tool_allowed,
+ load_governance_policy,
+ loop_budget_manager,
+ run_fallback_policy,
+)
+
+
+class GovernanceLoaderTests(unittest.TestCase):
+ def test_load_default_policy(self) -> None:
+ policy = load_governance_policy()
+ self.assertEqual(policy.version, "2.0.0")
+ self.assertEqual(policy.global_settings.loop_budget_max, 5)
+ self.assertIn("tolerant_ingestion", policy.scenarios)
+
+ def test_load_empty_policy_raises(self) -> None:
+ empty_path = Path(__file__).with_name("empty_governance.yaml")
+ empty_path.write_text("", encoding="utf-8")
+ self.addCleanup(empty_path.unlink)
+ with self.assertRaises(GovernancePolicyError):
+ load_governance_policy(empty_path)
+
+
+class ToolAllowlistTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.policy = load_governance_policy()
+
+ def test_allowed_tool_passes(self) -> None:
+ ensure_tool_allowed(
+ self.policy,
+ scenario_name="architecture_analysis",
+ role="Scout",
+ tool_name="scan_repo",
+ )
+
+ def test_disallowed_tool_raises(self) -> None:
+ with self.assertRaises(PermissionError):
+ ensure_tool_allowed(
+ self.policy,
+ scenario_name="architecture_analysis",
+ role="Scout",
+ tool_name="graph_write",
+ )
+
+
+class LoopBudgetTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.policy = load_governance_policy()
+
+ def test_budget_consumption(self) -> None:
+ manager = loop_budget_manager(self.policy)
+ self.assertEqual(manager.remaining, 5)
+ self.assertEqual(manager.consume(), 4)
+ self.assertEqual(manager.consume(steps=2), 2)
+
+ def test_budget_overflow_raises(self) -> None:
+ manager = loop_budget_manager(self.policy)
+ manager.consume(steps=5)
+ with self.assertRaises(RuntimeError):
+ manager.consume()
+
+
+class FallbackPolicyTests(unittest.TestCase):
+ def test_quarantine_fallback_returns_node(self) -> None:
+ policy = load_governance_policy()
+ node = run_fallback_policy(
+ policy,
+ target_schema="ExampleSchema",
+ payload={"x": 1},
+ error_message="invalid",
+ )
+ self.assertEqual(node.target_schema, "ExampleSchema")
+ self.assertEqual(node.raw_payload["x"], 1)
+
+ def test_fail_fast_policy_raises(self) -> None:
+ policy = GovernancePolicy.model_validate(
+ {
+ "version": "2.0.0",
+ "global_settings": {
+ "loop_budget_max": 1,
+ "fallback_policy": "fail_fast",
+ },
+ "scenarios": {},
+ }
+ )
+ with self.assertRaises(RuntimeError):
+ run_fallback_policy(
+ policy,
+ target_schema="ExampleSchema",
+ payload={},
+ error_message="x",
+ )
+
+
+class L3CommitGateTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.policy = load_governance_policy()
+
+ def test_accepts_when_all_gates_pass(self) -> None:
+ ok = enforce_l3_commit_gates(
+ self.policy,
+ artifact={
+ "validation_confidence": 0.9,
+ "provenance": {"source": "test"},
+ },
+ schema_valid=True,
+ dedup_ok=True,
+ contradiction_free=True,
+ )
+ self.assertTrue(ok)
+
+ def test_rejects_when_any_gate_fails(self) -> None:
+ rejected = enforce_l3_commit_gates(
+ self.policy,
+ artifact={
+ "validation_confidence": 0.9,
+ "provenance": {"source": "test"},
+ },
+ schema_valid=True,
+ dedup_ok=False,
+ contradiction_free=True,
+ )
+ self.assertFalse(rejected)
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/mind/__init__.py b/tests/mind/__init__.py
new file mode 100644
index 0000000..ea6dad5
--- /dev/null
+++ b/tests/mind/__init__.py
@@ -0,0 +1 @@
+"""Tests for quantmind.mind package."""
diff --git a/tests/mind/test_memory.py b/tests/mind/test_memory.py
new file mode 100644
index 0000000..db5d981
--- /dev/null
+++ b/tests/mind/test_memory.py
@@ -0,0 +1,84 @@
+"""Tests for ``quantmind.mind.memory``."""
+
+import json
+import tempfile
+import unittest
+
+from quantmind.mind.memory import HybridMemoryEngine, can_commit_to_l3
+
+
+class HybridMemoryEngineTests(unittest.TestCase):
+ def test_write_l1_trace_appends_jsonl(self) -> None:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ engine = HybridMemoryEngine(base_path=tmpdir)
+ engine.write_l1_trace("Scout", {"tool": "scan_repo", "ok": True})
+ content = engine.l1_path.read_text(encoding="utf-8").strip()
+ parsed = json.loads(content)
+ self.assertEqual(parsed["role"], "Scout")
+ self.assertTrue(parsed["payload"]["ok"])
+
+ def test_update_l2_state_round_trip(self) -> None:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ engine = HybridMemoryEngine(base_path=tmpdir)
+ current = engine.update_l2_state("step", {"status": "running"})
+ self.assertEqual(current["step"]["status"], "running")
+ persisted = json.loads(engine.l2_path.read_text(encoding="utf-8"))
+ self.assertEqual(persisted["step"]["status"], "running")
+
+ def test_commit_to_l3_requires_all_gates(self) -> None:
+ with tempfile.TemporaryDirectory() as tmpdir:
+ engine = HybridMemoryEngine(base_path=tmpdir)
+ accepted = engine.commit_to_l3_graph(
+ {
+ "id": "artifact-1",
+ "validation_confidence": 0.9,
+ "provenance": {"source": "test"},
+ },
+ schema_valid=True,
+ dedup_ok=True,
+ contradiction_free=True,
+ )
+ rejected = engine.commit_to_l3_graph(
+ {
+ "id": "artifact-2",
+ "validation_confidence": 0.9,
+ "provenance": {"source": "test"},
+ },
+ schema_valid=True,
+ dedup_ok=False,
+ contradiction_free=True,
+ )
+ self.assertTrue(accepted)
+ self.assertFalse(rejected)
+ lines = engine.l3_path.read_text(encoding="utf-8").strip().splitlines()
+ self.assertEqual(len(lines), 1)
+ self.assertEqual(json.loads(lines[0])["id"], "artifact-1")
+
+
+class CanCommitToL3Tests(unittest.TestCase):
+ def test_rejects_without_provenance(self) -> None:
+ self.assertFalse(
+ can_commit_to_l3(
+ {"validation_confidence": 0.9},
+ schema_valid=True,
+ dedup_ok=True,
+ contradiction_free=True,
+ )
+ )
+
+ def test_rejects_low_confidence(self) -> None:
+ self.assertFalse(
+ can_commit_to_l3(
+ {
+ "validation_confidence": 0.5,
+ "provenance": {"source": "test"},
+ },
+ schema_valid=True,
+ dedup_ok=True,
+ contradiction_free=True,
+ )
+ )
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/tests/test_magic.py b/tests/test_magic.py
index 426f6cd..f8b03e9 100644
--- a/tests/test_magic.py
+++ b/tests/test_magic.py
@@ -12,8 +12,10 @@
from quantmind.configs import PaperFlowCfg
from quantmind.configs.paper import ArxivIdentifier, PaperInput
from quantmind.flows import paper_flow
+from quantmind.flows.governance import load_governance_policy
from quantmind.magic import (
ResolvedFlowConfig,
+ _enforce_governance,
_introspect_flow_signature,
_pydantic_schema_str,
_strip_optional,
@@ -196,6 +198,37 @@ def _capture_agent(*_a: object, **kwargs: object) -> object:
self.assertEqual(captured["model"], "claude-3-5-sonnet")
+class GovernanceIntegrationTests(unittest.TestCase):
+ def setUp(self) -> None:
+ self.policy = load_governance_policy()
+
+ def test_policy_requires_scenario(self) -> None:
+ with self.assertRaises(ValueError):
+ _enforce_governance(
+ governance_policy=self.policy,
+ governance_scenario=None,
+ requested_tools=["scan_repo"],
+ resolver_role="Scout",
+ )
+
+ def test_disallowed_tool_raises_permission_error(self) -> None:
+ with self.assertRaises(PermissionError):
+ _enforce_governance(
+ governance_policy=self.policy,
+ governance_scenario="architecture_analysis",
+ requested_tools=["graph_write"],
+ resolver_role="Scout",
+ )
+
+ def test_allowed_tools_pass(self) -> None:
+ _enforce_governance(
+ governance_policy=self.policy,
+ governance_scenario="architecture_analysis",
+ requested_tools=["scan_repo", "read_file"],
+ resolver_role="Scout",
+ )
+
+
class PreviewResolveTests(unittest.IsolatedAsyncioTestCase):
async def test_prints_and_returns_tuple(self) -> None:
resolved = ResolvedFlowConfig[PaperInput, PaperFlowCfg](