From 5acaff6197cd668ff4d3c38219763c9a48dd0c6e Mon Sep 17 00:00:00 2001 From: Zeki-AI-Agent Date: Mon, 15 Jun 2026 17:17:05 +0200 Subject: [PATCH 1/6] docs: add Fincept AI Ops integration guide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Documents the QuantMind ↔ fincept-ai-ops architecture: - Vector search over quantitative finance literature - MCP tool schema for knowledge queries - Factor library synergy with backtest strategies --- docs/fincept_integration.md | 61 +++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 docs/fincept_integration.md diff --git a/docs/fincept_integration.md b/docs/fincept_integration.md new file mode 100644 index 0000000..72cf947 --- /dev/null +++ b/docs/fincept_integration.md @@ -0,0 +1,61 @@ +# 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 + +```python +from quantmind import QuantMindClient + +client = QuantMindClient(api_key="...") + +# Search for momentum factor research +results = client.search( + query="momentum factor decay regime change", + top_k=5, + filters={"source_type": "paper", "year_gte": 2020} +) + +for r in results: + print(r.title, r.insight_summary, r.confidence_score) +``` + +## 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. From bad60f581d5e23d6dba48a09d860caabf59c3349 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:01:39 +0000 Subject: [PATCH 2/6] docs: refresh agentic project positioning --- .venv/lib64 | 1 + .venv/pyvenv.cfg | 5 + .../share/jupyter/kernels/python3/kernel.json | 16 ++ .../jupyter/kernels/python3/logo-32x32.png | Bin 0 -> 1084 bytes .../jupyter/kernels/python3/logo-64x64.png | Bin 0 -> 2180 bytes .../jupyter/kernels/python3/logo-svg.svg | 265 ++++++++++++++++++ .venv/share/man/man1/ipython.1 | 60 ++++ CONTRIBUTING.md | 206 +++++++------- README.md | 189 +++++++++---- docs/ARCHITECTURE_FOR_NEW_DOMAINS.md | 145 ++++++++++ quantmind/flows/__init__.py | 7 +- 11 files changed, 731 insertions(+), 163 deletions(-) create mode 120000 .venv/lib64 create mode 100644 .venv/pyvenv.cfg create mode 100644 .venv/share/jupyter/kernels/python3/kernel.json create mode 100644 .venv/share/jupyter/kernels/python3/logo-32x32.png create mode 100644 .venv/share/jupyter/kernels/python3/logo-64x64.png create mode 100644 .venv/share/jupyter/kernels/python3/logo-svg.svg create mode 100644 .venv/share/man/man1/ipython.1 create mode 100644 docs/ARCHITECTURE_FOR_NEW_DOMAINS.md diff --git a/.venv/lib64 b/.venv/lib64 new file mode 120000 index 0000000..7951405 --- /dev/null +++ b/.venv/lib64 @@ -0,0 +1 @@ +lib \ No newline at end of file diff --git a/.venv/pyvenv.cfg b/.venv/pyvenv.cfg new file mode 100644 index 0000000..fdcdbd1 --- /dev/null +++ b/.venv/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /usr/bin +include-system-site-packages = false +version = 3.12.3 +executable = /usr/bin/python3.12 +command = /usr/bin/python3 -m venv /home/runner/work/quant-mind/quant-mind/.venv diff --git a/.venv/share/jupyter/kernels/python3/kernel.json b/.venv/share/jupyter/kernels/python3/kernel.json new file mode 100644 index 0000000..788843f --- /dev/null +++ b/.venv/share/jupyter/kernels/python3/kernel.json @@ -0,0 +1,16 @@ +{ + "argv": [ + "python", + "-m", + "ipykernel_launcher", + "-f", + "{connection_file}" + ], + "display_name": "Python 3 (ipykernel)", + "language": "python", + "metadata": { + "debugger": true, + "supported_encryption": "curve" + }, + "kernel_protocol_version": "5.5" +} \ No newline at end of file diff --git a/.venv/share/jupyter/kernels/python3/logo-32x32.png b/.venv/share/jupyter/kernels/python3/logo-32x32.png new file mode 100644 index 0000000000000000000000000000000000000000..be81330765764699553aa4fbaf0e9fc27c20c6d2 GIT binary patch literal 1084 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 + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.venv/share/man/man1/ipython.1 b/.venv/share/man/man1/ipython.1 new file mode 100644 index 0000000..0f4a191 --- /dev/null +++ b/.venv/share/man/man1/ipython.1 @@ -0,0 +1,60 @@ +.\" Hey, EMACS: -*- nroff -*- +.\" First parameter, NAME, should be all caps +.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection +.\" other parameters are allowed: see man(7), man(1) +.TH IPYTHON 1 "July 15, 2011" +.\" Please adjust this date whenever revising the manpage. +.\" +.\" Some roff macros, for reference: +.\" .nh disable hyphenation +.\" .hy enable hyphenation +.\" .ad l left justify +.\" .ad b justify to both left and right margins +.\" .nf disable filling +.\" .fi enable filling +.\" .br insert line break +.\" .sp insert n+1 empty lines +.\" for manpage-specific macros, see man(7) and groff_man(7) +.\" .SH section heading +.\" .SS secondary section heading +.\" +.\" +.\" To preview this page as plain text: nroff -man ipython.1 +.\" +.SH NAME +ipython \- Tools for Interactive Computing in Python. +.SH SYNOPSIS +.B ipython +.RI [ options ] " files" ... + +.B ipython subcommand +.RI [ options ] ... + +.SH DESCRIPTION +An interactive Python shell with automatic history (input and output), dynamic +object introspection, easier configuration, command completion, access to the +system shell, integration with numerical and scientific computing tools, +web notebook, Qt console, and more. + +For more information on how to use IPython, see 'ipython \-\-help', +or 'ipython \-\-help\-all' for all available command\(hyline options. + +.SH "ENVIRONMENT VARIABLES" +.sp +.PP +\fIIPYTHONDIR\fR +.RS 4 +This is the location where IPython stores all its configuration files. The default +is $HOME/.ipython if IPYTHONDIR is not defined. + +You can see the computed value of IPYTHONDIR with `ipython locate`. + +.SH FILES + +IPython uses various configuration files stored in profiles within IPYTHONDIR. +To generate the default configuration files and start configuring IPython, +do 'ipython profile create', and edit '*_config.py' files located in +IPYTHONDIR/profile_default. + +.SH AUTHORS +IPython is written by the IPython Development Team . 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..b213a3d 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 a finance-proven, 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 quantitative-finance research. +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 finance-heavy research extraction, but its core value is +more general: -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 domain 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 +- **Finance-first, not finance-only**: the first production flow is optimized + for research papers in quant finance, 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,36 @@ The financial research landscape is overwhelming. Every day, hundreds of papers, ![quantmind-outline](assets/quantmind-stage-outline.png) -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. - -#### **Stage 1: Knowledge Extraction** +QuantMind is built on a decoupled architecture that separates source handling, +typed extraction, and future memory/store layers. -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 +#### **Permanent modules** -#### **Stage 2: Intelligent Retrieval** - -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/` — reserved for the upcoming memory/store layer -- **Embedding Generation**: Converts knowledge units into high-dimensional vectors for semantic search - -- Solution Scenarios: Multiple retrieval patterns including: - - - **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 +See [docs/ARCHITECTURE_FOR_NEW_DOMAINS.md](docs/ARCHITECTURE_FOR_NEW_DOMAINS.md) +for how to extend this stack beyond finance. --- @@ -107,7 +128,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 +175,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 +193,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 +267,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 +337,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 +352,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 +368,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..0f7fdf3 --- /dev/null +++ b/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md @@ -0,0 +1,145 @@ +# Extending QuantMind Beyond Finance + +QuantMind currently ships a finance-first 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 finance-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 keeps finance-specific fields such as `asset_classes`. + +That does **not** make the whole repository finance-only. It means the current +reference implementation is finance-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/` is the planned working-memory layer +- `mind/store/` is the planned retrieval/store layer +- `flows/_runner.py` already reserves the runtime seam for memory integration + +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/quantmind/flows/__init__.py b/quantmind/flows/__init__.py index 78dd9bf..bc0305b 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 finance-first (``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 From d3ea090e5cc73e7e63d4f829dc3c6079d9e90a62 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 30 Jun 2026 06:02:32 +0000 Subject: [PATCH 3/6] chore: ignore local virtualenv artifacts --- .gitignore | 1 + .venv/lib64 | 1 - .venv/pyvenv.cfg | 5 - .../share/jupyter/kernels/python3/kernel.json | 16 -- .../jupyter/kernels/python3/logo-32x32.png | Bin 1084 -> 0 bytes .../jupyter/kernels/python3/logo-64x64.png | Bin 2180 -> 0 bytes .../jupyter/kernels/python3/logo-svg.svg | 265 ------------------ .venv/share/man/man1/ipython.1 | 60 ---- 8 files changed, 1 insertion(+), 347 deletions(-) delete mode 120000 .venv/lib64 delete mode 100644 .venv/pyvenv.cfg delete mode 100644 .venv/share/jupyter/kernels/python3/kernel.json delete mode 100644 .venv/share/jupyter/kernels/python3/logo-32x32.png delete mode 100644 .venv/share/jupyter/kernels/python3/logo-64x64.png delete mode 100644 .venv/share/jupyter/kernels/python3/logo-svg.svg delete mode 100644 .venv/share/man/man1/ipython.1 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/.venv/lib64 b/.venv/lib64 deleted file mode 120000 index 7951405..0000000 --- a/.venv/lib64 +++ /dev/null @@ -1 +0,0 @@ -lib \ No newline at end of file diff --git a/.venv/pyvenv.cfg b/.venv/pyvenv.cfg deleted file mode 100644 index fdcdbd1..0000000 --- a/.venv/pyvenv.cfg +++ /dev/null @@ -1,5 +0,0 @@ -home = /usr/bin -include-system-site-packages = false -version = 3.12.3 -executable = /usr/bin/python3.12 -command = /usr/bin/python3 -m venv /home/runner/work/quant-mind/quant-mind/.venv diff --git a/.venv/share/jupyter/kernels/python3/kernel.json b/.venv/share/jupyter/kernels/python3/kernel.json deleted file mode 100644 index 788843f..0000000 --- a/.venv/share/jupyter/kernels/python3/kernel.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "argv": [ - "python", - "-m", - "ipykernel_launcher", - "-f", - "{connection_file}" - ], - "display_name": "Python 3 (ipykernel)", - "language": "python", - "metadata": { - "debugger": true, - "supported_encryption": "curve" - }, - "kernel_protocol_version": "5.5" -} \ No newline at end of file diff --git a/.venv/share/jupyter/kernels/python3/logo-32x32.png b/.venv/share/jupyter/kernels/python3/logo-32x32.png deleted file mode 100644 index be81330765764699553aa4fbaf0e9fc27c20c6d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1084 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 - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/.venv/share/man/man1/ipython.1 b/.venv/share/man/man1/ipython.1 deleted file mode 100644 index 0f4a191..0000000 --- a/.venv/share/man/man1/ipython.1 +++ /dev/null @@ -1,60 +0,0 @@ -.\" Hey, EMACS: -*- nroff -*- -.\" First parameter, NAME, should be all caps -.\" Second parameter, SECTION, should be 1-8, maybe w/ subsection -.\" other parameters are allowed: see man(7), man(1) -.TH IPYTHON 1 "July 15, 2011" -.\" Please adjust this date whenever revising the manpage. -.\" -.\" Some roff macros, for reference: -.\" .nh disable hyphenation -.\" .hy enable hyphenation -.\" .ad l left justify -.\" .ad b justify to both left and right margins -.\" .nf disable filling -.\" .fi enable filling -.\" .br insert line break -.\" .sp insert n+1 empty lines -.\" for manpage-specific macros, see man(7) and groff_man(7) -.\" .SH section heading -.\" .SS secondary section heading -.\" -.\" -.\" To preview this page as plain text: nroff -man ipython.1 -.\" -.SH NAME -ipython \- Tools for Interactive Computing in Python. -.SH SYNOPSIS -.B ipython -.RI [ options ] " files" ... - -.B ipython subcommand -.RI [ options ] ... - -.SH DESCRIPTION -An interactive Python shell with automatic history (input and output), dynamic -object introspection, easier configuration, command completion, access to the -system shell, integration with numerical and scientific computing tools, -web notebook, Qt console, and more. - -For more information on how to use IPython, see 'ipython \-\-help', -or 'ipython \-\-help\-all' for all available command\(hyline options. - -.SH "ENVIRONMENT VARIABLES" -.sp -.PP -\fIIPYTHONDIR\fR -.RS 4 -This is the location where IPython stores all its configuration files. The default -is $HOME/.ipython if IPYTHONDIR is not defined. - -You can see the computed value of IPYTHONDIR with `ipython locate`. - -.SH FILES - -IPython uses various configuration files stored in profiles within IPYTHONDIR. -To generate the default configuration files and start configuring IPython, -do 'ipython profile create', and edit '*_config.py' files located in -IPYTHONDIR/profile_default. - -.SH AUTHORS -IPython is written by the IPython Development Team . From da52908a6a913d5685c54f5892e5b93c774209bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A7=81=E0=BC=BA=20=F0=9D=93=A9=F0=9D=93=AE=F0=9D=93=B4?= =?UTF-8?q?=F0=9D=93=B2=20=F0=9D=93=9E=F0=9D=93=B0=F0=9D=93=BE=F0=9D=94=83?= =?UTF-8?q?=20=E0=BC=BB=EA=A7=82?= Date: Thu, 2 Jul 2026 09:52:10 +0200 Subject: [PATCH 4/6] docs(memory): add verified shortlist correction draft Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- draft_corrections.md | 58 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 draft_corrections.md 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 From 58912cc453abd3dcd06fd66bb78ee89e925635ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A7=81=E0=BC=BA=20=F0=9D=93=A9=F0=9D=93=AE=F0=9D=93=B4?= =?UTF-8?q?=F0=9D=93=B2=20=F0=9D=93=9E=F0=9D=93=B0=F0=9D=93=BE=F0=9D=94=83?= =?UTF-8?q?=20=E0=BC=BB=EA=A7=82?= Date: Wed, 8 Jul 2026 00:57:29 +0200 Subject: [PATCH 5/6] feat(flows): add recoverable validation, governance, and hybrid memory Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .impeccable/hook.cache.json | 1 + README.md | 27 ++- docs/ARCHITECTURE_FOR_NEW_DOMAINS.md | 16 +- examples/mind/recoverable_memory_demo.py | 54 ++++++ quantmind/__init__.py | 6 +- quantmind/configs/__init__.py | 3 + quantmind/configs/recoverable.py | 42 +++++ quantmind/flows/__init__.py | 20 ++- quantmind/flows/governance.py | 210 +++++++++++++++++++++++ quantmind/flows/governance.yaml | 43 +++++ quantmind/magic.py | 49 ++++++ quantmind/mind/__init__.py | 9 + quantmind/mind/memory.py | 95 ++++++++++ tests/configs/test_recoverable.py | 39 +++++ tests/flows/test_governance.py | 135 +++++++++++++++ tests/mind/__init__.py | 1 + tests/mind/test_memory.py | 84 +++++++++ tests/test_magic.py | 33 ++++ 18 files changed, 847 insertions(+), 20 deletions(-) create mode 100644 .impeccable/hook.cache.json create mode 100644 examples/mind/recoverable_memory_demo.py create mode 100644 quantmind/configs/recoverable.py create mode 100644 quantmind/flows/governance.py create mode 100644 quantmind/flows/governance.yaml create mode 100644 quantmind/mind/__init__.py create mode 100644 quantmind/mind/memory.py create mode 100644 tests/configs/test_recoverable.py create mode 100644 tests/flows/test_governance.py create mode 100644 tests/mind/__init__.py create mode 100644 tests/mind/test_memory.py 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/README.md b/README.md index b213a3d..6d82a38 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ --- -**QuantMind** is a finance-proven, agent-centric knowledge extraction library +**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 quantitative-finance research. +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. @@ -51,8 +51,8 @@ document-heavy domain. QuantMind is designed for teams building **serious AI-agent workflows** around documents, research, and structured memory. -Its current strength is finance-heavy research extraction, but its core value is -more general: +Its current strength is typed research extraction, but its core value is +domain-agnostic: - fetch or accept raw source material from the web, local files, or inline text - normalize it into markdown that an agent can reliably read @@ -73,11 +73,11 @@ more general: - **Better long-term direction**: the `mind/` roadmap is explicitly about durable memory and agent-friendly retrieval, not throwaway prompting. -#### For domain teams +#### For engineering teams -- **Finance-first, not finance-only**: the first production flow is optimized - for research papers in quant finance, but the layering is reusable for any - domain that needs document ingestion, typed extraction, and agentic reuse. +- **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, @@ -115,11 +115,20 @@ typed knowledge object with provenance - `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/` — reserved for the upcoming memory/store layer +- `quantmind/mind/` — hybrid memory primitives (L1/L2/L3) +- `quantmind/flows/governance.*` — policy loader + runtime gates See [docs/ARCHITECTURE_FOR_NEW_DOMAINS.md](docs/ARCHITECTURE_FOR_NEW_DOMAINS.md) for how to extend this stack beyond finance. +#### Governance and loop observability + +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. + --- ### 🚀 Quick Start diff --git a/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md b/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md index 0f7fdf3..ba4f353 100644 --- a/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md +++ b/docs/ARCHITECTURE_FOR_NEW_DOMAINS.md @@ -1,6 +1,6 @@ -# Extending QuantMind Beyond Finance +# Extending QuantMind Beyond a Single Domain -QuantMind currently ships a finance-first extraction flow, but the architecture +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. @@ -23,14 +23,14 @@ quantmind/ These layers already work well for agent teams because they create a strict contract between source material, extraction, and downstream reuse. -## What is finance-specific today +## 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 keeps finance-specific fields such as `asset_classes`. +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 finance-first while the underlying architecture is +reference implementation is paper-first while the underlying architecture is already reusable: - `preprocess/` is domain-agnostic @@ -125,9 +125,11 @@ hidden agreements in prompt text when a Pydantic object can carry the contract. The repository is explicitly moving toward a memory-aware architecture: -- `mind/memory/` is the planned working-memory layer -- `mind/store/` is the planned retrieval/store layer +- `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. 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 bc0305b..839f9c9 100644 --- a/quantmind/flows/__init__.py +++ b/quantmind/flows/__init__.py @@ -1,7 +1,7 @@ """Apex layer — composes configs / knowledge / preprocess on the SDK. Each flow function takes a typed input and a ``FlowCfg`` and returns a -knowledge item. The current production flow is finance-first (``paper_flow``), +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. @@ -15,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]( From cbe96a6a08535cdb272bd50123f5ca23f2fc65f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=A7=81=E0=BC=BA=20=F0=9D=93=A9=F0=9D=93=AE=F0=9D=93=B4?= =?UTF-8?q?=F0=9D=93=B2=20=F0=9D=93=9E=F0=9D=93=B0=F0=9D=93=BE=F0=9D=94=83?= =?UTF-8?q?=20=E0=BC=BB=EA=A7=82?= <163497755+Zekiog@users.noreply.github.com> Date: Wed, 8 Jul 2026 01:16:31 +0200 Subject: [PATCH 6/6] Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- docs/fincept_integration.md | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/docs/fincept_integration.md b/docs/fincept_integration.md index 72cf947..d0c7304 100644 --- a/docs/fincept_integration.md +++ b/docs/fincept_integration.md @@ -26,21 +26,24 @@ fincept-ai-ops (FastAPI) ## Usage Example -```python -from quantmind import QuantMindClient + import asyncio + from pathlib import Path -client = QuantMindClient(api_key="...") + from quantmind.configs import PaperFlowCfg + from quantmind.configs.paper import LocalFilePath + from quantmind.flows import paper_flow -# Search for momentum factor research -results = client.search( - query="momentum factor decay regime change", - top_k=5, - filters={"source_type": "paper", "year_gte": 2020} -) -for r in results: - print(r.title, r.insight_summary, r.confidence_score) -``` + 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