diff --git a/.gitignore b/.gitignore
index 539aa6c..d598e4a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@ CMakeLists_modified.txt
build/
*.egg-info
+.venv/
lib/
bin/
@@ -32,3 +33,6 @@ docs/superpowers/
.coverage
htmlcov/
coverage.xml
+
+# Tool/hook caches that get regenerated locally and should not be tracked.
+.impeccable/
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index e5eb4d7..2ad7211 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -8,7 +8,9 @@ setup and the contribution process. The canonical development workflow
live in `AGENTS.md` / `CLAUDE.md`. If you develop with a coding agent, it
will pick these up automatically.
-## 🚀 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**:
@@ -68,7 +70,7 @@ pytest tests//
- **Examples**: one focused example under `examples//` for each
new feature.
-## 🔄 Pull Request Process
+### New domain extension
1. **Create a feature branch** from `master`.
2. **Follow Conventional Commits**: `type(scope): description`, in English.
@@ -82,10 +84,10 @@ For significant changes (new modules, new dependencies, API redesigns),
open an [issue](https://github.com/LLMQuant/quant-mind/issues) to discuss
first.
-## ❓ Questions?
+## ❓Questions?
- Check existing [issues](https://github.com/LLMQuant/quant-mind/issues)
- Review architecture patterns in existing code
- See `AGENTS.md` / `CLAUDE.md` for repository-wide rules
-Thank you for contributing! 🚀
+Thank you for contributing.
diff --git a/README.md b/README.md
index 25e3e51..98f5473 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,50 @@ 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 exposed via
+`quantmind.flows.governance` helpers (e.g. `ensure_tool_allowed`,
+`loop_budget_manager`, `enforce_l3_commit_gates`). Callers opt in by passing
+the loaded `GovernancePolicy` (or a default-loaded one) into entry points such
+as `magic.resolve_magic_input(..., governance_policy=...)`; the helpers are
+not auto-wired into every flow. This maps directly to loop SLI/SLO operations:
+loops that opt in are expected to be traceable, budgeted, and quality-gated
+before durable writes.
---
@@ -107,7 +142,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:**
@@ -174,6 +209,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())
```
@@ -226,32 +283,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`):**
@@ -267,7 +353,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.
------
@@ -281,11 +368,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
```
@@ -297,7 +384,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..2e875b3
--- /dev/null
+++ b/docs/fincept_integration.md
@@ -0,0 +1,72 @@
+# Fincept ↔ QuantMind Integration Guide
+
+## Overview
+
+QuantMind serves as the **knowledge extraction layer** for Fincept AI Ops.
+Rather than raw market data, Fincept can call QuantMind to:
+
+- Distill research insights from financial papers
+- Produce structured signal metadata (alpha factors, risk signals)
+- Answer natural-language questions over quantitative finance literature
+
+## Integration Architecture
+
+QuantMind is consumed from Python (no dedicated HTTP service ships in this
+repo). Fincept can call QuantMind flows directly from its worker process, or
+wrap them in a thin internal endpoint if a network boundary is required.
+
+```
+fincept-ai-ops (worker / FastAPI endpoint)
+ |
+ | from quantmind.flows import paper_flow
+ | doc = await paper_flow(LocalFilePath(...), cfg=PaperFlowCfg(...))
+ v
+QuantMind Flow (paper_flow)
+ |
+ v
+Extracted Insights JSON (typed Pydantic + provenance)
+```
+
+## Usage Example
+
+```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("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. Install QuantMind alongside Fincept in the same Python environment
+ (`pip install -e .` from this repository).
+2. From Fincept, import the flow you need (for example `paper_flow`) and
+ call it as shown in the usage example. No `quantmind serve` CLI ships
+ with this repo; if Fincept needs a network boundary, expose `paper_flow`
+ (or any other flow) through your own thin FastAPI wrapper.
+
+For broader context on the Fincept integration milestone, see the Fincept
+project's own roadmap in the Fincept repository.
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..6908742
--- /dev/null
+++ b/quantmind/flows/governance.py
@@ -0,0 +1,225 @@
+"""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
+from quantmind.mind.memory import L3CommitRequirements, can_commit_to_l3
+
+
+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.
+
+ Mirrors :class:`quantmind.mind.memory.L3CommitRequirements` field for
+ field so that :func:`enforce_l3_commit_gates` can delegate to the shared
+ ``can_commit_to_l3`` helper. Keep these defaults in sync.
+ """
+
+ 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
+
+ def to_requirements(self) -> L3CommitRequirements:
+ """Convert to the dataclass used by ``mind.memory``."""
+ return L3CommitRequirements(
+ min_confidence=self.min_confidence,
+ require_provenance=self.require_provenance,
+ require_schema_validity=self.require_schema_validity,
+ require_dedup_check=self.require_dedup_check,
+ require_contradiction_check=self.require_contradiction_check,
+ )
+
+
+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.
+
+ Delegates to :func:`quantmind.mind.memory.can_commit_to_l3` so there is
+ a single source of truth for L3 commit gating semantics across flows
+ and the mind/memory engine.
+ """
+ return can_commit_to_l3(
+ artifact,
+ schema_valid=schema_valid,
+ dedup_ok=dedup_ok,
+ contradiction_free=contradiction_free,
+ requirements=policy.l3_commit.to_requirements(),
+ )
+
+
+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..86f905e
--- /dev/null
+++ b/tests/configs/test_recoverable.py
@@ -0,0 +1,44 @@
+"""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")
+ # Assert on a stable substring that Pydantic consistently emits across
+ # versions/locales for this kind of extra-field error, rather than on
+ # the full human-readable message text (which can change between
+ # Pydantic releases).
+ self.assertIn("unexpected", out.error_message)
+ self.assertTrue(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..68aac48
--- /dev/null
+++ b/tests/flows/test_governance.py
@@ -0,0 +1,139 @@
+"""Tests for ``quantmind.flows.governance``."""
+
+import tempfile
+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:
+ # Use a temp directory instead of writing into the repo's tests/
+ # tree; keeps the test safe in read-only envs and under parallel
+ # test runs.
+ with tempfile.TemporaryDirectory() as tmp_dir:
+ empty_path = Path(tmp_dir) / "empty_governance.yaml"
+ empty_path.write_text("", encoding="utf-8")
+ 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](