# mcp-fact-finder: Pre-Design Research

**Date**: 2026-03-11
**Purpose**: Architecture preparation for a semantic fact indexer over markdown document collections
**Corpus**: ~/Duetto/cto/ knowledge base

---

## Table of Contents

1. [Investigation 1: Bedrock Credentials and Available Models](#investigation-1-bedrock-credentials-and-available-models)
2. [Investigation 2: The ~/Duetto/cto Knowledge Base](#investigation-2-the-duettocto-knowledge-base)
3. [Investigation 3: Semantic Fact Model Research](#investigation-3-semantic-fact-model-research)
4. [Investigation 4: Language Choice — Python vs Rust](#investigation-4-language-choice--python-vs-rust)
5. [Synthesis and Recommendations](#synthesis-and-recommendations)

---

## Investigation 1: Bedrock Credentials and Available Models

### 1.1 Credential Files Found

Two credential locations exist. They serve different use cases:

#### `~/.aws/credentials` and `~/.aws/config` (IAM/SSO)
The standard AWS CLI credential store. The `config` file contains:

```ini
[default]
region = us-west-2
output = json

[profile spaces]
region = us-east-1
output = json

[sso-session duetto]
sso_start_url = https://d-9267a43967.awsapps.com/start/
sso_region = us-west-2
sso_registration_scopes = sso:account:access

[profile dev-ec2]
region = us-west-2
```

The `credentials` file has a `[default]` profile with:
- `AWS_ACCESS_KEY_ID` = `AKIAVCJ45XI77BUHTJQD` (long-lived IAM key)
- `AWS_SECRET_ACCESS_KEY` = present

There is also a `[dev-ec2]` profile with a session token (ASIA... prefix = STS temporary credentials), which expire.

**Default region: `us-west-2`**. The `spaces` profile uses `us-east-1`.

#### `~/Duetto/cto/scripts/analysis/explorer/.env` (boto3 script credentials)
This is the credential file loaded by `scripts/analysis/budget/common.py`. It is not committed; the common.py module loads it with:
```python
env_path = Path(__file__).parent.parent / "explorer" / ".env"
```
Expected keys (from `common.py` validation):
- `AWS_ACCESS_KEY_ID`
- `AWS_SECRET_ACCESS_KEY`
- `AWS_SESSION_TOKEN`
- `AWS_REGION`

The file was not found in the filesystem scan, meaning it is either gitignored or not yet created.

### 1.2 Existing boto3/Bedrock Usage Patterns

**Only two Python files use boto3**, and they are in `scripts/analysis/budget/`:

`common.py` — canonical credential loading and session factory:
```python
import boto3

def get_session(profile=None, region=None) -> boto3.Session:
    creds = load_env_credentials()  # reads explorer/.env
    return boto3.Session(
        aws_access_key_id=creds["AWS_ACCESS_KEY_ID"],
        aws_secret_access_key=creds["AWS_SECRET_ACCESS_KEY"],
        aws_session_token=creds["AWS_SESSION_TOKEN"],
        region_name=region or creds.get("AWS_REGION", "us-east-1"),
    )
```

`validate.py` — checks that boto3 and botocore are installed before running.

**No existing Bedrock model invocation code exists in this codebase.** The main app (`server.py`) uses **OpenRouter** (not Bedrock) with:
- `MODEL_OPUS = "anthropic/claude-sonnet-4"` (via OpenRouter)
- `MODEL_HAIKU = "anthropic/claude-3.5-haiku"` (via OpenRouter)

mcp-fact-finder will be the **first Bedrock consumer** in this environment.

### 1.3 Model Pricing Comparison

Pricing as of March 2026 (us-east-1 on-demand):

| Model | Input / 1M tokens | Output / 1M tokens | Notes |
|---|---|---|---|
| `amazon.nova-micro-v1:0` | **$0.035** | **$0.14** | Fastest, cheapest |
| `amazon.nova-lite-v1:0` | **$0.06** | **$0.24** | Multimodal capable |
| `us.amazon.nova-micro-v1:0` | ~$0.035 | ~$0.14 | Cross-region, same price |
| `anthropic.claude-3-haiku-20240307` | **$0.25** | **$1.25** | Reliable instruction following |
| `anthropic.claude-3-5-haiku` | **$0.80** | **$4.00** | Best instruction following |

**Batch mode (async)**: 50% discount on all models above.

**Cost estimate for our workload** (200 tok in / 400 tok out per paragraph):

| Model | Cost per 1000 paragraphs | Cost for 50K paragraphs |
|---|---|---|
| Nova Micro | $0.007 + $0.056 = $0.063 | $3.15 |
| Nova Lite | $0.012 + $0.096 = $0.108 | $5.40 |
| Claude 3 Haiku (original) | $0.050 + $0.500 = $0.550 | $27.50 |
| Claude 3.5 Haiku | $0.160 + $1.600 = $1.760 | $88.00 |

### 1.4 Model Recommendation

**For structured JSON extraction from short paragraphs:**

**Primary recommendation: `amazon.nova-micro-v1:0`**
- 4-8x cheaper than Claude 3 Haiku, 24x cheaper than Claude 3.5 Haiku
- Adequate for well-prompted JSON extraction on simple factual text
- Use with explicit JSON schema in system prompt

**Fallback: `anthropic.claude-3-haiku-20240307`** (original)
- When Nova Micro produces malformed JSON or fails on complex prose
- ~7x cheaper than Claude 3.5 Haiku
- Proven reliable for instruction-following with structured outputs

**Do not use Claude 3.5 Haiku** for bulk extraction — cost is prohibitive at scale. Reserve it for quality-control spot-checking or ambiguous passages.

**Cross-region inference (`us.amazon.nova-micro-v1:0`)**: Use when us-east-1 capacity is constrained — same price, routes across regions automatically.

### 1.5 Credential Pattern for mcp-fact-finder

Follow the established pattern from `common.py`. Recommended `.env` structure:
```ini
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_SESSION_TOKEN=...          # optional, for STS/SSO
AWS_REGION=us-east-1           # Bedrock is best in us-east-1
BEDROCK_MODEL_ID=amazon.nova-micro-v1:0
BEDROCK_FALLBACK_MODEL_ID=anthropic.claude-3-haiku-20240307-v1:0
```

---

## Investigation 2: The ~/Duetto/cto Knowledge Base

### 2.1 Corpus Summary

| Source | Document Count | Notes |
|---|---|---|
| Confluence (all spaces) | **4,537** | Main corpus — tech specs, PRDs, retros, RFCs |
| Projects (derived analysis) | **3,004** | Research artifacts, reports, analysis |
| Jira | 17 | Issue exports |
| Slack | 7 | Digests |
| Notion | 10 | Scripts and setup docs |
| Gmail | 3 | Extracted emails |
| **Total** | **~7,600+** | |

Total markdown content: ~875,000 lines across all .md files.

### 2.2 Confluence Space Breakdown

| Space | Documents | Character |
|---|---|---|
| ENGINEERIN | 1,402 | Engineering specs, tech debt, architecture, sprint retros |
| PROD | 248 | PRDs, feature specs, product decisions |
| DevOps | 69 | Infrastructure, deployment, CI/CD |
| DP2026 | 40 | 2026 planning docs |
| SC | 10 | Security and compliance |
| DATA | 1 | Data engineering |

### 2.3 Document Structure

**Confluence documents** have a consistent pseudo-frontmatter header (not YAML, but structured markdown):
```markdown
# Document Title

**Space:** ENGINEERIN  |  **Created:** 2020-08-03T20:52:17Z  |  **Updated:** 2020-08-03T21:25Z
**Author:** Name  |  **Last Modified By:** Name
**Labels:** retrospective
**Confluence Link:** https://duettoresearch.atlassian.net/wiki/spaces/...

---
## Content
...
```

This is **not YAML frontmatter** — it is rendered markdown metadata in a fixed header block.

**Frontmatter presence by source:**
- Confluence: ~0.4% have YAML `---` frontmatter (nearly none)
- Jira: 94% have YAML frontmatter (structured exports)
- Slack: 86% have YAML frontmatter
- Notion: 40% have YAML frontmatter

**Projects directory**: Mixed — derived reports and analysis documents, mostly dense prose without structured frontmatter.

### 2.4 Paragraph Length Distribution

Analysis of 35,557 paragraphs from the Confluence corpus:

| Metric | Words |
|---|---|
| P25 (short) | 5 |
| Median | 19 |
| P75 | 34 |
| P90 | 65 |
| Max | 3,720 |

**Distribution:**
- Short (< 20 words): 51% — bullets, table cells, one-liners
- Medium (20-100 words): 43% — the sweet spot for fact extraction
- Long (> 100 words): 5% — narrative prose, needs chunking

**Implication for chunking**: The median paragraph is already under 20 words. A strategy of **paragraph-level fact extraction** (not sentence-level, not page-level) is appropriate. Long paragraphs (> 100 words) should be split at sentence boundaries before LLM processing.

### 2.5 Document Type Taxonomy

From filenames and headers, the corpus contains:

| Type | Examples | Fact Density |
|---|---|---|
| Tech specs / TDD | `bb-2190-tech-spec-*.md` | High — decisions, constraints |
| PRDs | `market-domain-service-v1-prd.md` | High — requirements, timelines |
| Retrospectives | `gcp360-retrospective-*.md` | Medium — action items, observations |
| Architecture docs | `DATA-FLOW-DIAGRAM.md`, `SYSTEM-ARCHITECTURE.md` | Very high — system facts |
| Meeting notes / digests | `daily-digest.md`, `attention-digest-*.md` | Medium — stated plans, decisions |
| Analysis reports | `MASTER-WORK-ANALYSIS-REPORT.md` | Dense — analytical conclusions |
| Gitflow narratives | `narrative_report_*.md` | Low-medium — narrative summaries |
| People / org docs | `ONBOARDING-PROCESS-ANALYSIS.md` | Medium — procedural facts |

### 2.6 Entity/Topic Taxonomy Visible from Corpus

Recurring entities across filenames and headers:
- **Services**: `monolith`, `pricerator`, `market-domain-service`, `hotstats`, `GCP360`
- **Product areas**: `group-quoting`, `manage-rates`, `dynamic-optimization`, `booking-sync`
- **Teams**: `team-dev`, `on-call`, `devops`
- **Process types**: PRD, RFC, retrospective, sprint, security, budget
- **Time markers**: quarterly (Q2, Q3), sprint dates, fiscal year references

This is a **dense, high-fact corpus**. A typical tech spec has 10-50 extractable facts. A PRD has 20-80.

---

## Investigation 3: Semantic Fact Model Research

### 3.1 Approach Evaluation Matrix

| Criterion | SPO Triples | Atomic Props (OpenIE) | Typed Fact Schema | Embedding-Only | Hybrid (Schema + Embed) |
|---|---|---|---|---|---|
| Represents "X is Y" | Yes | Yes | Yes | Yes (implicit) | Yes |
| Represents "X said Y" | Poorly | Partially | Yes (via fact_type) | Yes (implicit) | Yes |
| Represents "X happened" | Poorly | Partially | Yes (event type) | Yes (implicit) | Yes |
| Represents "X is planned" | Poorly | No | Yes (status field) | Yes (implicit) | Yes |
| Supports authority ranking | No | No | Yes (authority field) | No | Yes |
| Queryable without LLM | Yes | Partially | Yes | No | Yes |
| Cross-document dedup | Poor | Poor | Good (hash on normalized SPO) | Good (cosine sim) | Excellent |
| Implementation complexity | Low | High (NLP pipeline) | Medium | Very low | Medium |

### 3.2 Detailed Analysis

**1. SPO Triples (RDF-style)**

Classic `(subject, predicate, object)` representation. Clean and well-studied.

Strengths:
- Highly queryable (SPARQL, graph traversal)
- Wide tooling ecosystem
- Dedup via string normalization on `(s, p, o)` tuple

Weaknesses:
- Cannot naturally express tense ("was said", "is planned")
- Authority/confidence not part of the model without extensions
- Predicate normalization is hard (open predicates = infinite vocabulary)
- Ambiguity: "Pricerator runs in GCP" — is GCP the object of `runs-in` or `deployed-to`?

Verdict: Insufficient alone for this corpus. Works for factual "is/has" statements but fails on temporal, epistemic, or agentive facts.

**2. Atomic Propositions (OpenIE-style)**

NLP pipeline (spaCy, Stanford CoreNLP, Rebel) extracts claims as minimal proposition units.

Strengths:
- Handles complex sentence structure
- Proven in knowledge graph construction

Weaknesses:
- Requires heavy NLP stack (spaCy models, CoreNLP JVM)
- Predicate extraction quality is unreliable on technical jargon
- Cannot score authority without external metadata
- No standard dedup — requires entity linking as a separate step
- Adds significant latency; not batch-friendly

Verdict: Too complex for our infrastructure. The NLP approach is good for academic KG construction but poorly suited to an async LLM-based extraction pipeline.

**3. Typed Fact Schema (Structured JSON)**

LLM extracts facts into a typed schema:
```json
{
  "fact_type": "decision|is|said|happened|planned|metric",
  "subject": "Pricerator",
  "predicate": "runs on",
  "object": "GCP",
  "context": "as of Q3 2024 migration",
  "tense": "past|present|future",
  "confidence": 0.9
}
```

Strengths:
- Handles all four fact types natively via `fact_type`
- Authority ranking via `confidence` + `source_hierarchy` (spec > meeting notes > digest)
- Dedup via normalized hash of `(fact_type, subject, predicate, object)`
- Fully queryable with SQL or DuckDB — no LLM round-trip needed
- LLM-native: models produce JSON reliably with few-shot prompting

Weaknesses:
- Schema design choices are load-bearing (wrong types = bad retrieval)
- Requires careful prompt engineering for consistent extraction
- Does not support semantic search over facts without embeddings

Verdict: Strong standalone option. Best queryability, clearest authority model.

**4. Embedding-Only**

Store the raw fact sentence ("Pricerator was migrated to GCP in Q3 2024") as an embedding in a vector store.

Strengths:
- Extremely simple to implement
- Semantic search works well
- No schema decisions

Weaknesses:
- Cannot filter by fact type, subject, or date without re-reading the text
- Dedup requires approximate nearest-neighbor + LLM judgment (expensive)
- No authority ranking — all facts are equally weighted
- Cannot answer "what decisions were made about Pricerator?" without LLM

Verdict: Insufficient alone. Good as a retrieval layer, not as the primary structure.

**5. Hybrid (Typed Schema + Embedding)**

Combine structured schema with an embedding of the normalized fact text.

```json
{
  "id": "sha256(fact_type+subject+predicate+object)",
  "fact_type": "decision",
  "subject": "Pricerator",
  "predicate": "migrated to",
  "object": "GCP",
  "context": "Q3 2024",
  "tense": "past",
  "verbatim": "Pricerator was migrated to GCP during Q3 2024",
  "embedding": [...],
  "authority_score": 0.85,
  "sources": [
    {"doc_id": "gcp360-migration-plan.md", "chunk_id": 42, "quote": "...", "recency": "2024-09-15"}
  ]
}
```

Strengths:
- Semantic search via embedding (finds "moved to Google Cloud" when querying "GCP migration")
- Structured filtering: `WHERE fact_type = 'decision' AND subject LIKE '%Pricerator%'`
- Dedup: exact match on ID hash, semantic dedup via embedding similarity
- Authority scoring: source hierarchy × recency decay
- Provenance: full source chain in `sources` array

Weaknesses:
- Requires embedding generation per fact (cost, latency)
- More complex to implement than either approach alone

Verdict: **The correct approach for this use case.**

### 3.3 Recommended Fact Schema

```python
@dataclass
class ExtractedFact:
    # Identity
    id: str                    # sha256(fact_type+subject+predicate+object)

    # Core structure
    fact_type: Literal["is", "said", "happened", "planned", "decided", "metric"]
    subject: str               # normalized entity name
    predicate: str             # normalized verb/relation
    object: str                # value, entity, or description

    # Context
    context: str               # temporal or situational qualifier ("as of Q3 2024")
    tense: Literal["past", "present", "future", "unknown"]
    verbatim: str              # exact extracted text for audit

    # Semantic search
    embedding: list[float]     # vector of the verbatim text

    # Authority
    authority_score: float     # 0.0-1.0, computed from source hierarchy + recency
    sources: list[FactSource]  # provenance list; grows as same fact found in more docs

    # Metadata
    created_at: datetime
    updated_at: datetime

@dataclass
class FactSource:
    doc_id: str                # filename or document identifier
    doc_type: str              # "spec" | "prd" | "retro" | "digest" | "meeting"
    chunk_id: int              # chunk index within document
    quote: str                 # the specific sentence containing this fact
    author: str                # extracted from document metadata
    date: datetime             # document date (for recency scoring)
    authority_tier: int        # 1=direct spec, 2=PRD, 3=meeting notes, 4=digest/inferred
```

**Authority score formula:**
```
authority_score = max(source_tier_score) × recency_weight × confidence
source_tier_score: tier_1=1.0, tier_2=0.8, tier_3=0.5, tier_4=0.3
recency_weight: exp(-days_since_doc / 365)
confidence: LLM-assigned extraction confidence (0.5-1.0)
```

**Deduplication strategy:**
1. Exact dedup: SHA-256 of `normalize(fact_type + subject + predicate + object)` → merge into single fact, append to `sources`
2. Semantic dedup: cosine similarity > 0.92 on embeddings → LLM judge to confirm merge or keep separate
3. Update policy: same fact found in newer document → update recency weight in authority score

### 3.4 Embedding Model Recommendation

For **short atomic facts** (typically 10-40 words):

| Model | Dims | Speed | MTEB | Cost | Verdict |
|---|---|---|---|---|---|
| `all-MiniLM-L6-v2` | 384 | ~5-14k/sec CPU | 84-85% STS | Free (local) | Best for local inference |
| `BAAI/bge-small-en-v1.5` | 384 | Comparable | 84.7% retrieval | Free (local) | Slight retrieval edge |
| `all-mpnet-base-v2` | 768 | 4-5x slower | 87-88% STS | Free (local) | Overkill for short facts |
| `amazon.titan-embed-text-v2` | 1024 | API latency | 60.4 MTEB avg | $0.02/1M tokens | No local model needed |

**Recommendation: `BAAI/bge-small-en-v1.5`**

Rationale:
- Short atomic facts match its training distribution (it is optimized for retrieval, not long documents)
- Marginally better retrieval accuracy than MiniLM at same size/speed
- 384 dimensions keeps LanceDB storage compact
- Free, local, no API rate limits, no per-token cost
- For 50,000 facts: ~19MB of vector storage (384 × 4 bytes × 50,000)

**When to choose Titan Embeddings v2 instead**: If you want zero local model management and are already paying for Bedrock. At $0.02/1M tokens and 50K facts averaging ~20 words (~27 tokens each), cost is ~$0.027 — negligible. But adds Bedrock API latency and rate limits (2,000 RPM).

**Stick with local `bge-small-en-v1.5`** unless the deployment environment cannot support it.

---

## Investigation 4: Language Choice — Python vs Rust

### 4.1 Requirements Matrix

| Requirement | Python | Rust |
|---|---|---|
| Async Bedrock calls (aioboto3) | Excellent — aioboto3 v15.5.0, well-supported | Good — aws-sdk-rust, more verbose |
| LanceDB SDK | Excellent — Python SDK most feature-complete | Good — Rust is the core but Python wraps it |
| Markdown parsing | Excellent — `markdown-it-py`, `mistletoe`, `pymdown` | Good — `comrak`, `pulldown-cmark` |
| MCP server interface | Excellent — `mcp` Python SDK (official Anthropic) | Partial — `rmcp` exists but less mature |
| Throughput (90K docs) | Good — asyncio semaphore + gather | Excellent — native async, no GIL |
| Development velocity | Fast — rich ecosystem, direct REPL testing | Slower — compile times, more verbosity |
| Pydantic/structured output | Native with `instructor` library | Requires serde, more setup |

### 4.2 LanceDB SDK Status (December 2025)

As of Lance SDK 1.0.0 (released December 2025):
- **Python SDK**: Most feature-complete. Sync + async. Full-text search, hybrid search, embedding support, all stable.
- **Rust SDK**: Core foundation. Async-native. Full-text search being moved into Rust core for parity. Hybrid search and embedding support still catching up.
- Architecture: Both are wrappers over the same Rust core (PyO3). Python SDK adds negligible overhead.

**Python SDK wins on features today; Rust will reach parity over time.**

### 4.3 Throughput Analysis

For **7,600+ documents** (the actual corpus size, not the "93K" figure):

Typical processing path per document:
1. Parse markdown: ~1ms
2. Chunk into paragraphs: ~1ms
3. LLM calls (avg 10 paragraphs/doc × 500ms/call): ~5s per document
4. Embed facts (avg 20 facts/doc × 1ms/fact local): ~20ms
5. LanceDB write: ~5ms

**Bottleneck is Bedrock API latency, not CPU or memory.** Python asyncio with semaphore-limited concurrency (50-100 parallel requests) handles this efficiently. A single Python process can saturate Bedrock's rate limits.

At 50 concurrent requests: ~50 docs/5s = 10 docs/sec. Full 7,600-doc corpus: ~12.7 minutes.
At 100 concurrent requests: ~20 docs/sec. Full corpus: ~6 minutes.

**Rust would not meaningfully improve this** — the bottleneck is network I/O to Bedrock, not computation.

### 4.4 MCP Interface

The official `mcp` Python SDK (maintained by Anthropic) is the correct choice for building an MCP server. It supports:
- `@server.tool()` decorators for tool registration
- `StdioServerTransport` for CLI embedding
- `SseServerTransport` for HTTP streaming
- First-class type annotations with Pydantic

`rmcp` (Rust MCP) exists but has far less community support and fewer documented patterns.

### 4.5 Decision

**Python is the correct choice.**

Justifications:
1. Throughput bottleneck is Bedrock API I/O — asyncio handles this optimally
2. Python LanceDB SDK is more feature-complete than Rust SDK today
3. Official MCP Python SDK provides the most complete server interface
4. `aioboto3` + `asyncio.Semaphore` pattern is proven for parallel Bedrock invocations
5. `instructor` library gives Pydantic-validated structured JSON from any Bedrock model
6. Corpus size (7,600 docs) does not require Rust-level throughput

**When Rust would win**: corpus > 1M documents, sub-second per-document processing required, and LanceDB Rust SDK is at full feature parity. Neither condition applies here.

---

## Synthesis and Recommendations

### Architecture Summary

```
mcp-fact-finder/
├── src/
│   ├── ingest/
│   │   ├── parser.py          # Markdown chunking, frontmatter extraction
│   │   ├── extractor.py       # aioboto3 + Nova Micro → JSON facts
│   │   └── embedder.py        # bge-small-en-v1.5 local embedding
│   ├── storage/
│   │   ├── lancedb.py         # LanceDB table management, upsert
│   │   └── dedup.py           # Hash-based + semantic dedup
│   ├── search/
│   │   ├── semantic.py        # Vector search over fact embeddings
│   │   ├── structured.py      # SQL-like filter queries
│   │   └── hybrid.py          # Combine both
│   └── mcp_server.py          # MCP tool registration and dispatch
├── .env                       # AWS creds (follow common.py pattern)
└── pyproject.toml
```

### Key Decisions

| Decision | Choice | Rationale |
|---|---|---|
| Language | Python 3.12 | Ecosystem fit, MCP SDK, LanceDB maturity |
| LLM for extraction | Nova Micro (primary), Claude 3 Haiku (fallback) | 4-8x cost saving over Haiku |
| Embedding model | `bge-small-en-v1.5` | Short-text optimized, free, 384d compact |
| Fact representation | Typed schema + embedding (Hybrid) | All 4 fact types, authority, dedup, queryable |
| Vector store | LanceDB | Already in mcp-vector-search ecosystem |
| Async pattern | `aioboto3` + `asyncio.Semaphore(50)` | Proven for parallel Bedrock calls |
| Dedup | Hash on normalized (type+subject+predicate+object) | O(1) exact match, fallback to cosine |
| AWS region | `us-east-1` | Best Bedrock model availability |

### Open Questions for Architecture Proposal

1. **Chunking unit**: Paragraph-level (recommended) vs. sentence-level vs. fixed-token? Paragraph seems right given median 19-word paragraphs.
2. **Re-indexing strategy**: Full rebuild vs. incremental (detect new/changed docs)? Incremental preferred given corpus size.
3. **Fact confidence threshold**: What minimum confidence score to store? Suggest 0.6 to start, tunable.
4. **Authority tier assignment**: Should it be hardcoded per source type (spec=1, digest=4) or inferred per document?
5. **Semantic dedup threshold**: 0.92 cosine is a starting point — needs empirical tuning against the Duetto corpus.
6. **MCP tool surface**: What queries should the MCP server expose? (suggest: `search_facts`, `get_facts_about`, `get_decisions_about`, `get_timeline_for`)

---

## Sources

- [Amazon Bedrock Pricing – AWS](https://aws.amazon.com/bedrock/pricing/)
- [Amazon Nova Pricing – AWS](https://aws.amazon.com/nova/pricing/)
- [Amazon Bedrock Pricing 2026 | go-cloud.io](https://go-cloud.io/amazon-bedrock-pricing/)
- [Claude 3.5 Haiku Pricing – pricepertoken.com](https://pricepertoken.com/pricing-page/model/anthropic-claude-3.5-haiku)
- [Claude 3 Haiku Pricing – llm-stats.com](https://llm-stats.com/models/claude-3-haiku-20240307)
- [Structured Outputs with AWS Bedrock and Pydantic – Instructor](https://python.useinstructor.com/integrations/bedrock/)
- [aioboto3 Documentation](https://aioboto3.readthedocs.io/en/latest/usage.html)
- [BedrockRuntimeClient – types-aioboto3 docs](https://youtype.github.io/types_aioboto3_docs/types_aiobotocore_bedrock_runtime/client/)
- [Announcing Lance SDK 1.0.0 – LanceDB Blog](https://lancedb.com/blog/announcing-lance-sdk/)
- [LanceDB Python SDK – DeepWiki](https://deepwiki.com/lancedb/lancedb/3.1-python-sdk)
- [LanceDB FAQ – OSS](https://docs.lancedb.com/faq/faq-oss)
- [Best Open-Source Embedding Models Benchmarked – Supermemory](https://supermemory.ai/blog/best-open-source-embedding-models-benchmarked-and-ranked/)
- [The Best Open-Source Embedding Models in 2026 – BentoML](https://www.bentoml.com/blog/a-guide-to-open-source-embedding-models)
- [all-MiniLM-L6-v2 vs all-mpnet-base-v2 – Milvus](https://milvus.io/ai-quick-reference/what-are-some-popular-pretrained-sentence-transformer-models-and-how-do-they-differ-for-example-allminilml6v2-vs-allmpnetbasev2)
- [Amazon Titan Text Embeddings V2 – AWS Blog](https://aws.amazon.com/blogs/machine-learning/get-started-with-amazon-titan-text-embeddings-v2-a-new-state-of-the-art-embeddings-model-on-amazon-bedrock/)
- [Open Information Extraction – Wikipedia](https://en.wikipedia.org/wiki/Open_information_extraction)
- [Zero-Shot Fact-Checking with Semantic Triples – arXiv](https://arxiv.org/html/2312.11785v1)
- [Parallelizing AWS API Calls – Trek10](https://www.trek10.com/blog/aws-lambda-python-asyncio)
- [Semaphores in Python Async Programming – Soumendrak](https://www.soumendrak.com/blog/semaphores-python-async-programming/)
