feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""FastAPI route definitions."""
|
|
|
|
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, HTTPException
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
2026-04-05 18:33:52 -04:00
|
|
|
import atocore.config as _config
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
from atocore.context.builder import (
|
|
|
|
|
build_context,
|
|
|
|
|
get_last_context_pack,
|
|
|
|
|
_pack_to_dict,
|
|
|
|
|
)
|
2026-04-05 09:41:59 -04:00
|
|
|
from atocore.context.project_state import (
|
|
|
|
|
CATEGORIES,
|
|
|
|
|
get_state,
|
|
|
|
|
invalidate_state,
|
|
|
|
|
set_state,
|
|
|
|
|
)
|
2026-04-05 18:33:52 -04:00
|
|
|
from atocore.ingestion.pipeline import (
|
2026-04-06 14:58:14 -04:00
|
|
|
exclusive_ingestion,
|
2026-04-05 18:33:52 -04:00
|
|
|
get_ingestion_stats,
|
|
|
|
|
get_source_status,
|
|
|
|
|
ingest_configured_sources,
|
|
|
|
|
ingest_file,
|
|
|
|
|
ingest_folder,
|
|
|
|
|
)
|
feat(phase9-A): interaction capture loop foundation
Phase 9 Commit A from the agreed plan: turn AtoCore from a stateless
context enhancer into a system that records what it actually fed to an
LLM and what came back. This is the audit trail Reflection (Commit B)
and Extraction (Commit C) will be layered on top of.
The interactions table existed in the schema since the original PoC
but nothing wrote to it. This change makes it real:
Schema migration (additive only):
- response full LLM response (caller decides how much)
- memories_used JSON list of memory ids in the context pack
- chunks_used JSON list of chunk ids in the context pack
- client identifier of the calling system
(openclaw, claude-code, manual, ...)
- session_id groups multi-turn conversations
- project project name (mirrors the memory module pattern,
no FK so capture stays cheap)
- indexes on session_id, project, created_at
The created_at column is now written explicitly with a SQLite-compatible
'YYYY-MM-DD HH:MM:SS' format so the same string lives in the DB and the
returned dataclass. Without this the `since` filter on list_interactions
would silently fail because CURRENT_TIMESTAMP and isoformat use different
shapes that do not compare cleanly as strings.
New module src/atocore/interactions/:
- Interaction dataclass
- record_interaction() persists one round-trip (prompt required;
everything else optional). Refuses empty prompts.
- list_interactions() filters by project / session_id / client / since,
newest-first, hard-capped at 500
- get_interaction() fetch by id, full response + context pack
API endpoints:
- POST /interactions capture one interaction
- GET /interactions list with summaries (no full response)
- GET /interactions/{id} full record incl. response + pack
Trust model:
- Capture is read-only with respect to memories, project state, and
source chunks. Nothing here promotes anything into trusted state.
- The audit trail becomes the dataset Commit B (reinforcement) and
Commit C (extraction + review queue) will operate on.
Tests (13 new, all green):
- service: persist + roundtrip every field
- service: minimum-fields path (prompt only)
- service: empty / whitespace prompt rejected
- service: get by id returns None for missing
- service: filter by project, session, client
- service: ordering newest-first with limit
- service: since filter inclusive on cutoff (the bug the timestamp
fix above caught)
- service: limit=0 returns empty
- API: POST records and round-trips through GET /interactions/{id}
- API: empty prompt returns 400
- API: missing id returns 404
- API: list filter returns summaries (not full response bodies)
Full suite: 118 passing (was 105).
master-plan-status.md updated to move Phase 9 from "not started" to
"started" with the explicit note that Commit A is in and Commits B/C
remain.
2026-04-06 19:31:43 -04:00
|
|
|
from atocore.interactions.service import (
|
|
|
|
|
get_interaction,
|
|
|
|
|
list_interactions,
|
|
|
|
|
record_interaction,
|
|
|
|
|
)
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
from atocore.memory.service import (
|
|
|
|
|
MEMORY_TYPES,
|
|
|
|
|
create_memory,
|
|
|
|
|
get_memories,
|
|
|
|
|
invalidate_memory,
|
|
|
|
|
supersede_memory,
|
|
|
|
|
update_memory,
|
|
|
|
|
)
|
2026-04-05 09:35:37 -04:00
|
|
|
from atocore.observability.logger import get_logger
|
feat: tunable ranking, refresh status, chroma backup + admin endpoints
Three small improvements that move the operational baseline forward
without changing the existing trust model.
1. Tunable retrieval ranking weights
- rank_project_match_boost, rank_query_token_step,
rank_query_token_cap, rank_path_high_signal_boost,
rank_path_low_signal_penalty are now Settings fields
- all overridable via ATOCORE_* env vars
- retriever no longer hard-codes 2.0 / 1.18 / 0.72 / 0.08 / 1.32
- lets ranking be tuned per environment as Wave 1 is exercised
without code changes
2. /projects/{name}/refresh status
- refresh_registered_project now returns an overall status field
("ingested", "partial", "nothing_to_ingest") plus roots_ingested
and roots_skipped counters
- ProjectRefreshResponse advertises the new fields so callers can
rely on them
- covers the case where every configured root is missing on disk
3. Chroma cold snapshot + admin backup endpoints
- create_runtime_backup now accepts include_chroma and writes a
cold directory copy of the chroma persistence path
- new list_runtime_backups() and validate_backup() helpers
- new endpoints:
- POST /admin/backup create snapshot (optional chroma)
- GET /admin/backup list snapshots
- GET /admin/backup/{stamp}/validate structural validation
- chroma snapshots are taken under exclusive_ingestion() so a refresh
or ingest cannot race with the cold copy
- backup metadata records what was actually included and how big
Tests:
- 8 new tests covering tunable weights, refresh status branches
(ingested / partial / nothing_to_ingest), chroma snapshot, list,
validate, and the API endpoints (including the lock-acquisition path)
- existing fake refresh stubs in test_api_storage.py updated for the
expanded ProjectRefreshResponse model
- full suite: 105 passing (was 97)
next-steps doc updated to reflect that the chroma snapshot + restore
validation gap from current-state.md is now closed in code; only the
operational retention policy remains.
2026-04-06 18:42:19 -04:00
|
|
|
from atocore.ops.backup import (
|
|
|
|
|
create_runtime_backup,
|
|
|
|
|
list_runtime_backups,
|
|
|
|
|
validate_backup,
|
|
|
|
|
)
|
2026-04-06 08:02:13 -04:00
|
|
|
from atocore.projects.registry import (
|
2026-04-06 09:11:11 -04:00
|
|
|
build_project_registration_proposal,
|
2026-04-06 08:46:37 -04:00
|
|
|
get_project_registry_template,
|
2026-04-06 08:02:13 -04:00
|
|
|
list_registered_projects,
|
2026-04-06 09:52:19 -04:00
|
|
|
register_project,
|
2026-04-06 08:02:13 -04:00
|
|
|
refresh_registered_project,
|
2026-04-06 12:31:24 -04:00
|
|
|
update_project,
|
2026-04-06 08:02:13 -04:00
|
|
|
)
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
from atocore.retrieval.retriever import retrieve
|
|
|
|
|
from atocore.retrieval.vector_store import get_vector_store
|
|
|
|
|
|
|
|
|
|
router = APIRouter()
|
2026-04-05 09:35:37 -04:00
|
|
|
log = get_logger("api")
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
# --- Request/Response models ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class IngestRequest(BaseModel):
|
2026-04-05 09:35:37 -04:00
|
|
|
path: str
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class IngestResponse(BaseModel):
|
|
|
|
|
results: list[dict]
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 18:33:52 -04:00
|
|
|
class IngestSourcesResponse(BaseModel):
|
|
|
|
|
results: list[dict]
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 08:02:13 -04:00
|
|
|
class ProjectRefreshResponse(BaseModel):
|
|
|
|
|
project: str
|
|
|
|
|
aliases: list[str]
|
|
|
|
|
description: str
|
|
|
|
|
purge_deleted: bool
|
feat: tunable ranking, refresh status, chroma backup + admin endpoints
Three small improvements that move the operational baseline forward
without changing the existing trust model.
1. Tunable retrieval ranking weights
- rank_project_match_boost, rank_query_token_step,
rank_query_token_cap, rank_path_high_signal_boost,
rank_path_low_signal_penalty are now Settings fields
- all overridable via ATOCORE_* env vars
- retriever no longer hard-codes 2.0 / 1.18 / 0.72 / 0.08 / 1.32
- lets ranking be tuned per environment as Wave 1 is exercised
without code changes
2. /projects/{name}/refresh status
- refresh_registered_project now returns an overall status field
("ingested", "partial", "nothing_to_ingest") plus roots_ingested
and roots_skipped counters
- ProjectRefreshResponse advertises the new fields so callers can
rely on them
- covers the case where every configured root is missing on disk
3. Chroma cold snapshot + admin backup endpoints
- create_runtime_backup now accepts include_chroma and writes a
cold directory copy of the chroma persistence path
- new list_runtime_backups() and validate_backup() helpers
- new endpoints:
- POST /admin/backup create snapshot (optional chroma)
- GET /admin/backup list snapshots
- GET /admin/backup/{stamp}/validate structural validation
- chroma snapshots are taken under exclusive_ingestion() so a refresh
or ingest cannot race with the cold copy
- backup metadata records what was actually included and how big
Tests:
- 8 new tests covering tunable weights, refresh status branches
(ingested / partial / nothing_to_ingest), chroma snapshot, list,
validate, and the API endpoints (including the lock-acquisition path)
- existing fake refresh stubs in test_api_storage.py updated for the
expanded ProjectRefreshResponse model
- full suite: 105 passing (was 97)
next-steps doc updated to reflect that the chroma snapshot + restore
validation gap from current-state.md is now closed in code; only the
operational retention policy remains.
2026-04-06 18:42:19 -04:00
|
|
|
status: str
|
|
|
|
|
roots_ingested: int
|
|
|
|
|
roots_skipped: int
|
2026-04-06 08:02:13 -04:00
|
|
|
roots: list[dict]
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 09:11:11 -04:00
|
|
|
class ProjectRegistrationProposalRequest(BaseModel):
|
|
|
|
|
project_id: str
|
|
|
|
|
aliases: list[str] = []
|
|
|
|
|
description: str = ""
|
|
|
|
|
ingest_roots: list[dict]
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 12:31:24 -04:00
|
|
|
class ProjectUpdateRequest(BaseModel):
|
|
|
|
|
aliases: list[str] | None = None
|
|
|
|
|
description: str | None = None
|
|
|
|
|
ingest_roots: list[dict] | None = None
|
|
|
|
|
|
|
|
|
|
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
class QueryRequest(BaseModel):
|
|
|
|
|
prompt: str
|
|
|
|
|
top_k: int = 10
|
|
|
|
|
filter_tags: list[str] | None = None
|
2026-04-06 13:32:33 -04:00
|
|
|
project: str | None = None
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
class QueryResponse(BaseModel):
|
|
|
|
|
results: list[dict]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContextBuildRequest(BaseModel):
|
|
|
|
|
prompt: str
|
|
|
|
|
project: str | None = None
|
|
|
|
|
budget: int | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ContextBuildResponse(BaseModel):
|
|
|
|
|
formatted_context: str
|
|
|
|
|
full_prompt: str
|
|
|
|
|
chunks_used: int
|
|
|
|
|
total_chars: int
|
|
|
|
|
budget: int
|
|
|
|
|
budget_remaining: int
|
|
|
|
|
duration_ms: int
|
|
|
|
|
chunks: list[dict]
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
class MemoryCreateRequest(BaseModel):
|
|
|
|
|
memory_type: str
|
|
|
|
|
content: str
|
|
|
|
|
project: str = ""
|
|
|
|
|
confidence: float = 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class MemoryUpdateRequest(BaseModel):
|
|
|
|
|
content: str | None = None
|
|
|
|
|
confidence: float | None = None
|
|
|
|
|
status: str | None = None
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 09:41:59 -04:00
|
|
|
class ProjectStateSetRequest(BaseModel):
|
|
|
|
|
project: str
|
|
|
|
|
category: str
|
|
|
|
|
key: str
|
|
|
|
|
value: str
|
|
|
|
|
source: str = ""
|
|
|
|
|
confidence: float = 1.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectStateGetRequest(BaseModel):
|
|
|
|
|
project: str
|
|
|
|
|
category: str | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ProjectStateInvalidateRequest(BaseModel):
|
|
|
|
|
project: str
|
|
|
|
|
category: str
|
|
|
|
|
key: str
|
|
|
|
|
|
|
|
|
|
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
# --- Endpoints ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/ingest", response_model=IngestResponse)
|
2026-04-05 09:35:37 -04:00
|
|
|
def api_ingest(req: IngestRequest) -> IngestResponse:
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""Ingest a markdown file or folder."""
|
|
|
|
|
target = Path(req.path)
|
2026-04-05 09:35:37 -04:00
|
|
|
try:
|
2026-04-06 14:58:14 -04:00
|
|
|
with exclusive_ingestion():
|
|
|
|
|
if target.is_file():
|
|
|
|
|
results = [ingest_file(target)]
|
|
|
|
|
elif target.is_dir():
|
|
|
|
|
results = ingest_folder(target)
|
|
|
|
|
else:
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"Path not found: {req.path}")
|
2026-04-05 09:35:37 -04:00
|
|
|
except HTTPException:
|
|
|
|
|
raise
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error("ingest_failed", path=req.path, error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Ingestion failed: {e}")
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
return IngestResponse(results=results)
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 18:33:52 -04:00
|
|
|
@router.post("/ingest/sources", response_model=IngestSourcesResponse)
|
|
|
|
|
def api_ingest_sources() -> IngestSourcesResponse:
|
|
|
|
|
"""Ingest enabled configured source directories."""
|
|
|
|
|
try:
|
2026-04-06 14:58:14 -04:00
|
|
|
with exclusive_ingestion():
|
|
|
|
|
results = ingest_configured_sources()
|
2026-04-05 18:33:52 -04:00
|
|
|
except Exception as e:
|
|
|
|
|
log.error("ingest_sources_failed", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Configured source ingestion failed: {e}")
|
|
|
|
|
return IngestSourcesResponse(results=results)
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 08:02:13 -04:00
|
|
|
@router.get("/projects")
|
|
|
|
|
def api_projects() -> dict:
|
|
|
|
|
"""Return registered projects and their resolved ingest roots."""
|
|
|
|
|
return {
|
|
|
|
|
"projects": list_registered_projects(),
|
|
|
|
|
"registry_path": str(_config.settings.resolved_project_registry_path),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 08:46:37 -04:00
|
|
|
@router.get("/projects/template")
|
|
|
|
|
def api_projects_template() -> dict:
|
|
|
|
|
"""Return a starter template for project registry entries."""
|
|
|
|
|
return {
|
|
|
|
|
"template": get_project_registry_template(),
|
|
|
|
|
"registry_path": str(_config.settings.resolved_project_registry_path),
|
|
|
|
|
"allowed_sources": ["vault", "drive"],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 09:11:11 -04:00
|
|
|
@router.post("/projects/proposal")
|
|
|
|
|
def api_project_registration_proposal(req: ProjectRegistrationProposalRequest) -> dict:
|
|
|
|
|
"""Return a normalized project registration proposal without writing it."""
|
|
|
|
|
try:
|
|
|
|
|
return build_project_registration_proposal(
|
|
|
|
|
project_id=req.project_id,
|
|
|
|
|
aliases=req.aliases,
|
|
|
|
|
description=req.description,
|
2026-04-06 09:52:19 -04:00
|
|
|
ingest_roots=req.ingest_roots,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/projects/register")
|
|
|
|
|
def api_project_registration(req: ProjectRegistrationProposalRequest) -> dict:
|
|
|
|
|
"""Persist a validated project registration to the registry file."""
|
|
|
|
|
try:
|
|
|
|
|
return register_project(
|
|
|
|
|
project_id=req.project_id,
|
|
|
|
|
aliases=req.aliases,
|
|
|
|
|
description=req.description,
|
2026-04-06 09:11:11 -04:00
|
|
|
ingest_roots=req.ingest_roots,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 12:31:24 -04:00
|
|
|
@router.put("/projects/{project_name}")
|
|
|
|
|
def api_project_update(project_name: str, req: ProjectUpdateRequest) -> dict:
|
|
|
|
|
"""Update an existing project registration."""
|
|
|
|
|
try:
|
|
|
|
|
return update_project(
|
|
|
|
|
project_name=project_name,
|
|
|
|
|
aliases=req.aliases,
|
|
|
|
|
description=req.description,
|
|
|
|
|
ingest_roots=req.ingest_roots,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
detail = str(e)
|
|
|
|
|
if detail.startswith("Unknown project"):
|
|
|
|
|
raise HTTPException(status_code=404, detail=detail)
|
|
|
|
|
raise HTTPException(status_code=400, detail=detail)
|
|
|
|
|
|
|
|
|
|
|
2026-04-06 08:02:13 -04:00
|
|
|
@router.post("/projects/{project_name}/refresh", response_model=ProjectRefreshResponse)
|
|
|
|
|
def api_refresh_project(project_name: str, purge_deleted: bool = False) -> ProjectRefreshResponse:
|
|
|
|
|
"""Refresh one registered project from its configured ingest roots."""
|
|
|
|
|
try:
|
2026-04-06 14:58:14 -04:00
|
|
|
with exclusive_ingestion():
|
|
|
|
|
result = refresh_registered_project(project_name, purge_deleted=purge_deleted)
|
2026-04-06 08:02:13 -04:00
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=404, detail=str(e))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error("project_refresh_failed", project=project_name, error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Project refresh failed: {e}")
|
|
|
|
|
return ProjectRefreshResponse(**result)
|
|
|
|
|
|
|
|
|
|
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
@router.post("/query", response_model=QueryResponse)
|
2026-04-05 09:35:37 -04:00
|
|
|
def api_query(req: QueryRequest) -> QueryResponse:
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""Retrieve relevant chunks for a prompt."""
|
2026-04-05 09:35:37 -04:00
|
|
|
try:
|
2026-04-06 13:32:33 -04:00
|
|
|
chunks = retrieve(
|
|
|
|
|
req.prompt,
|
|
|
|
|
top_k=req.top_k,
|
|
|
|
|
filter_tags=req.filter_tags,
|
|
|
|
|
project_hint=req.project,
|
|
|
|
|
)
|
2026-04-05 09:35:37 -04:00
|
|
|
except Exception as e:
|
|
|
|
|
log.error("query_failed", prompt=req.prompt[:100], error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Query failed: {e}")
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
return QueryResponse(
|
|
|
|
|
results=[
|
|
|
|
|
{
|
|
|
|
|
"chunk_id": c.chunk_id,
|
|
|
|
|
"content": c.content,
|
|
|
|
|
"score": c.score,
|
|
|
|
|
"heading_path": c.heading_path,
|
|
|
|
|
"source_file": c.source_file,
|
|
|
|
|
"title": c.title,
|
|
|
|
|
}
|
|
|
|
|
for c in chunks
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/context/build", response_model=ContextBuildResponse)
|
2026-04-05 09:35:37 -04:00
|
|
|
def api_build_context(req: ContextBuildRequest) -> ContextBuildResponse:
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""Build a full context pack for a prompt."""
|
2026-04-05 09:35:37 -04:00
|
|
|
try:
|
|
|
|
|
pack = build_context(
|
|
|
|
|
user_prompt=req.prompt,
|
|
|
|
|
project_hint=req.project,
|
|
|
|
|
budget=req.budget,
|
|
|
|
|
)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error("context_build_failed", prompt=req.prompt[:100], error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Context build failed: {e}")
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
pack_dict = _pack_to_dict(pack)
|
|
|
|
|
return ContextBuildResponse(
|
|
|
|
|
formatted_context=pack.formatted_context,
|
|
|
|
|
full_prompt=pack.full_prompt,
|
|
|
|
|
chunks_used=len(pack.chunks_used),
|
|
|
|
|
total_chars=pack.total_chars,
|
|
|
|
|
budget=pack.budget,
|
|
|
|
|
budget_remaining=pack.budget_remaining,
|
|
|
|
|
duration_ms=pack.duration_ms,
|
|
|
|
|
chunks=pack_dict["chunks"],
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
@router.post("/memory")
|
|
|
|
|
def api_create_memory(req: MemoryCreateRequest) -> dict:
|
|
|
|
|
"""Create a new memory entry."""
|
|
|
|
|
try:
|
|
|
|
|
mem = create_memory(
|
|
|
|
|
memory_type=req.memory_type,
|
|
|
|
|
content=req.content,
|
|
|
|
|
project=req.project,
|
|
|
|
|
confidence=req.confidence,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
return {"status": "ok", "id": mem.id, "memory_type": mem.memory_type}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/memory")
|
|
|
|
|
def api_get_memories(
|
|
|
|
|
memory_type: str | None = None,
|
2026-04-05 17:53:23 -04:00
|
|
|
project: str | None = None,
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
active_only: bool = True,
|
|
|
|
|
min_confidence: float = 0.0,
|
|
|
|
|
limit: int = 50,
|
|
|
|
|
) -> dict:
|
|
|
|
|
"""List memories, optionally filtered."""
|
|
|
|
|
memories = get_memories(
|
|
|
|
|
memory_type=memory_type,
|
2026-04-05 17:53:23 -04:00
|
|
|
project=project,
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
active_only=active_only,
|
|
|
|
|
min_confidence=min_confidence,
|
|
|
|
|
limit=limit,
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"memories": [
|
|
|
|
|
{
|
|
|
|
|
"id": m.id,
|
|
|
|
|
"memory_type": m.memory_type,
|
|
|
|
|
"content": m.content,
|
2026-04-05 17:53:23 -04:00
|
|
|
"project": m.project,
|
feat: Phase 2 Memory Core — structured memory with context integration
Memory Core implementation:
- Memory service with 6 types: identity, preference, project, episodic, knowledge, adaptation
- CRUD operations: create (with dedup), get (filtered), update, invalidate, supersede
- Confidence scoring (0.0-1.0) and lifecycle management (active/superseded/invalid)
- Memory API endpoints: POST/GET/PUT/DELETE /memory
Context builder integration (trust precedence per Master Plan):
1. Trusted Project State (highest trust, 20% budget)
2. Identity + Preference memories (10% budget)
3. Retrieved chunks (remaining budget)
Also fixed database.py to use dynamic settings reference for test isolation.
45/45 tests passing.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:54:52 -04:00
|
|
|
"confidence": m.confidence,
|
|
|
|
|
"status": m.status,
|
|
|
|
|
"updated_at": m.updated_at,
|
|
|
|
|
}
|
|
|
|
|
for m in memories
|
|
|
|
|
],
|
|
|
|
|
"types": MEMORY_TYPES,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.put("/memory/{memory_id}")
|
|
|
|
|
def api_update_memory(memory_id: str, req: MemoryUpdateRequest) -> dict:
|
|
|
|
|
"""Update an existing memory."""
|
|
|
|
|
try:
|
|
|
|
|
success = update_memory(
|
|
|
|
|
memory_id=memory_id,
|
|
|
|
|
content=req.content,
|
|
|
|
|
confidence=req.confidence,
|
|
|
|
|
status=req.status,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
if not success:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Memory not found")
|
|
|
|
|
return {"status": "updated", "id": memory_id}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/memory/{memory_id}")
|
|
|
|
|
def api_invalidate_memory(memory_id: str) -> dict:
|
|
|
|
|
"""Invalidate a memory (error correction)."""
|
|
|
|
|
success = invalidate_memory(memory_id)
|
|
|
|
|
if not success:
|
|
|
|
|
raise HTTPException(status_code=404, detail="Memory not found")
|
|
|
|
|
return {"status": "invalidated", "id": memory_id}
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 09:41:59 -04:00
|
|
|
@router.post("/project/state")
|
|
|
|
|
def api_set_project_state(req: ProjectStateSetRequest) -> dict:
|
|
|
|
|
"""Set or update a trusted project state entry."""
|
|
|
|
|
try:
|
|
|
|
|
entry = set_state(
|
|
|
|
|
project_name=req.project,
|
|
|
|
|
category=req.category,
|
|
|
|
|
key=req.key,
|
|
|
|
|
value=req.value,
|
|
|
|
|
source=req.source,
|
|
|
|
|
confidence=req.confidence,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error("set_state_failed", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Failed to set state: {e}")
|
|
|
|
|
return {"status": "ok", "id": entry.id, "category": entry.category, "key": entry.key}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/project/state/{project_name}")
|
|
|
|
|
def api_get_project_state(project_name: str, category: str | None = None) -> dict:
|
|
|
|
|
"""Get trusted project state entries."""
|
|
|
|
|
entries = get_state(project_name, category=category)
|
|
|
|
|
return {
|
|
|
|
|
"project": project_name,
|
|
|
|
|
"entries": [
|
|
|
|
|
{
|
|
|
|
|
"id": e.id,
|
|
|
|
|
"category": e.category,
|
|
|
|
|
"key": e.key,
|
|
|
|
|
"value": e.value,
|
|
|
|
|
"source": e.source,
|
|
|
|
|
"confidence": e.confidence,
|
|
|
|
|
"status": e.status,
|
|
|
|
|
"updated_at": e.updated_at,
|
|
|
|
|
}
|
|
|
|
|
for e in entries
|
|
|
|
|
],
|
|
|
|
|
"categories": CATEGORIES,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/project/state")
|
|
|
|
|
def api_invalidate_project_state(req: ProjectStateInvalidateRequest) -> dict:
|
|
|
|
|
"""Invalidate (supersede) a project state entry."""
|
|
|
|
|
success = invalidate_state(req.project, req.category, req.key)
|
|
|
|
|
if not success:
|
|
|
|
|
raise HTTPException(status_code=404, detail="State entry not found or already invalidated")
|
|
|
|
|
return {"status": "invalidated", "project": req.project, "category": req.category, "key": req.key}
|
|
|
|
|
|
|
|
|
|
|
feat(phase9-A): interaction capture loop foundation
Phase 9 Commit A from the agreed plan: turn AtoCore from a stateless
context enhancer into a system that records what it actually fed to an
LLM and what came back. This is the audit trail Reflection (Commit B)
and Extraction (Commit C) will be layered on top of.
The interactions table existed in the schema since the original PoC
but nothing wrote to it. This change makes it real:
Schema migration (additive only):
- response full LLM response (caller decides how much)
- memories_used JSON list of memory ids in the context pack
- chunks_used JSON list of chunk ids in the context pack
- client identifier of the calling system
(openclaw, claude-code, manual, ...)
- session_id groups multi-turn conversations
- project project name (mirrors the memory module pattern,
no FK so capture stays cheap)
- indexes on session_id, project, created_at
The created_at column is now written explicitly with a SQLite-compatible
'YYYY-MM-DD HH:MM:SS' format so the same string lives in the DB and the
returned dataclass. Without this the `since` filter on list_interactions
would silently fail because CURRENT_TIMESTAMP and isoformat use different
shapes that do not compare cleanly as strings.
New module src/atocore/interactions/:
- Interaction dataclass
- record_interaction() persists one round-trip (prompt required;
everything else optional). Refuses empty prompts.
- list_interactions() filters by project / session_id / client / since,
newest-first, hard-capped at 500
- get_interaction() fetch by id, full response + context pack
API endpoints:
- POST /interactions capture one interaction
- GET /interactions list with summaries (no full response)
- GET /interactions/{id} full record incl. response + pack
Trust model:
- Capture is read-only with respect to memories, project state, and
source chunks. Nothing here promotes anything into trusted state.
- The audit trail becomes the dataset Commit B (reinforcement) and
Commit C (extraction + review queue) will operate on.
Tests (13 new, all green):
- service: persist + roundtrip every field
- service: minimum-fields path (prompt only)
- service: empty / whitespace prompt rejected
- service: get by id returns None for missing
- service: filter by project, session, client
- service: ordering newest-first with limit
- service: since filter inclusive on cutoff (the bug the timestamp
fix above caught)
- service: limit=0 returns empty
- API: POST records and round-trips through GET /interactions/{id}
- API: empty prompt returns 400
- API: missing id returns 404
- API: list filter returns summaries (not full response bodies)
Full suite: 118 passing (was 105).
master-plan-status.md updated to move Phase 9 from "not started" to
"started" with the explicit note that Commit A is in and Commits B/C
remain.
2026-04-06 19:31:43 -04:00
|
|
|
class InteractionRecordRequest(BaseModel):
|
|
|
|
|
prompt: str
|
|
|
|
|
response: str = ""
|
|
|
|
|
response_summary: str = ""
|
|
|
|
|
project: str = ""
|
|
|
|
|
client: str = ""
|
|
|
|
|
session_id: str = ""
|
|
|
|
|
memories_used: list[str] = []
|
|
|
|
|
chunks_used: list[str] = []
|
|
|
|
|
context_pack: dict | None = None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/interactions")
|
|
|
|
|
def api_record_interaction(req: InteractionRecordRequest) -> dict:
|
|
|
|
|
"""Capture one interaction (prompt + response + what was used).
|
|
|
|
|
|
|
|
|
|
This is the foundation of the AtoCore reflection loop. It records
|
|
|
|
|
what the system fed to an LLM and what came back, but does not
|
|
|
|
|
promote anything into trusted state. Phase 9 Commit B/C will layer
|
|
|
|
|
reinforcement and extraction on top of this audit trail.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
interaction = record_interaction(
|
|
|
|
|
prompt=req.prompt,
|
|
|
|
|
response=req.response,
|
|
|
|
|
response_summary=req.response_summary,
|
|
|
|
|
project=req.project,
|
|
|
|
|
client=req.client,
|
|
|
|
|
session_id=req.session_id,
|
|
|
|
|
memories_used=req.memories_used,
|
|
|
|
|
chunks_used=req.chunks_used,
|
|
|
|
|
context_pack=req.context_pack,
|
|
|
|
|
)
|
|
|
|
|
except ValueError as e:
|
|
|
|
|
raise HTTPException(status_code=400, detail=str(e))
|
|
|
|
|
return {
|
|
|
|
|
"status": "recorded",
|
|
|
|
|
"id": interaction.id,
|
|
|
|
|
"created_at": interaction.created_at,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/interactions")
|
|
|
|
|
def api_list_interactions(
|
|
|
|
|
project: str | None = None,
|
|
|
|
|
session_id: str | None = None,
|
|
|
|
|
client: str | None = None,
|
|
|
|
|
since: str | None = None,
|
|
|
|
|
limit: int = 50,
|
|
|
|
|
) -> dict:
|
|
|
|
|
"""List captured interactions, optionally filtered by project, session,
|
|
|
|
|
client, or creation time. Hard-capped at 500 entries per call."""
|
|
|
|
|
interactions = list_interactions(
|
|
|
|
|
project=project,
|
|
|
|
|
session_id=session_id,
|
|
|
|
|
client=client,
|
|
|
|
|
since=since,
|
|
|
|
|
limit=limit,
|
|
|
|
|
)
|
|
|
|
|
return {
|
|
|
|
|
"count": len(interactions),
|
|
|
|
|
"interactions": [
|
|
|
|
|
{
|
|
|
|
|
"id": i.id,
|
|
|
|
|
"prompt": i.prompt,
|
|
|
|
|
"response_summary": i.response_summary,
|
|
|
|
|
"response_chars": len(i.response),
|
|
|
|
|
"project": i.project,
|
|
|
|
|
"client": i.client,
|
|
|
|
|
"session_id": i.session_id,
|
|
|
|
|
"memories_used": i.memories_used,
|
|
|
|
|
"chunks_used": i.chunks_used,
|
|
|
|
|
"created_at": i.created_at,
|
|
|
|
|
}
|
|
|
|
|
for i in interactions
|
|
|
|
|
],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/interactions/{interaction_id}")
|
|
|
|
|
def api_get_interaction(interaction_id: str) -> dict:
|
|
|
|
|
"""Fetch a single interaction with the full response and context pack."""
|
|
|
|
|
interaction = get_interaction(interaction_id)
|
|
|
|
|
if interaction is None:
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"Interaction not found: {interaction_id}")
|
|
|
|
|
return {
|
|
|
|
|
"id": interaction.id,
|
|
|
|
|
"prompt": interaction.prompt,
|
|
|
|
|
"response": interaction.response,
|
|
|
|
|
"response_summary": interaction.response_summary,
|
|
|
|
|
"project": interaction.project,
|
|
|
|
|
"client": interaction.client,
|
|
|
|
|
"session_id": interaction.session_id,
|
|
|
|
|
"memories_used": interaction.memories_used,
|
|
|
|
|
"chunks_used": interaction.chunks_used,
|
|
|
|
|
"context_pack": interaction.context_pack,
|
|
|
|
|
"created_at": interaction.created_at,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
feat: tunable ranking, refresh status, chroma backup + admin endpoints
Three small improvements that move the operational baseline forward
without changing the existing trust model.
1. Tunable retrieval ranking weights
- rank_project_match_boost, rank_query_token_step,
rank_query_token_cap, rank_path_high_signal_boost,
rank_path_low_signal_penalty are now Settings fields
- all overridable via ATOCORE_* env vars
- retriever no longer hard-codes 2.0 / 1.18 / 0.72 / 0.08 / 1.32
- lets ranking be tuned per environment as Wave 1 is exercised
without code changes
2. /projects/{name}/refresh status
- refresh_registered_project now returns an overall status field
("ingested", "partial", "nothing_to_ingest") plus roots_ingested
and roots_skipped counters
- ProjectRefreshResponse advertises the new fields so callers can
rely on them
- covers the case where every configured root is missing on disk
3. Chroma cold snapshot + admin backup endpoints
- create_runtime_backup now accepts include_chroma and writes a
cold directory copy of the chroma persistence path
- new list_runtime_backups() and validate_backup() helpers
- new endpoints:
- POST /admin/backup create snapshot (optional chroma)
- GET /admin/backup list snapshots
- GET /admin/backup/{stamp}/validate structural validation
- chroma snapshots are taken under exclusive_ingestion() so a refresh
or ingest cannot race with the cold copy
- backup metadata records what was actually included and how big
Tests:
- 8 new tests covering tunable weights, refresh status branches
(ingested / partial / nothing_to_ingest), chroma snapshot, list,
validate, and the API endpoints (including the lock-acquisition path)
- existing fake refresh stubs in test_api_storage.py updated for the
expanded ProjectRefreshResponse model
- full suite: 105 passing (was 97)
next-steps doc updated to reflect that the chroma snapshot + restore
validation gap from current-state.md is now closed in code; only the
operational retention policy remains.
2026-04-06 18:42:19 -04:00
|
|
|
class BackupCreateRequest(BaseModel):
|
|
|
|
|
include_chroma: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/admin/backup")
|
|
|
|
|
def api_create_backup(req: BackupCreateRequest | None = None) -> dict:
|
|
|
|
|
"""Create a runtime backup snapshot.
|
|
|
|
|
|
|
|
|
|
When ``include_chroma`` is true the call holds the ingestion lock so a
|
|
|
|
|
safe cold copy of the vector store can be taken without racing against
|
|
|
|
|
refresh or ingest endpoints.
|
|
|
|
|
"""
|
|
|
|
|
payload = req or BackupCreateRequest()
|
|
|
|
|
try:
|
|
|
|
|
if payload.include_chroma:
|
|
|
|
|
with exclusive_ingestion():
|
|
|
|
|
metadata = create_runtime_backup(include_chroma=True)
|
|
|
|
|
else:
|
|
|
|
|
metadata = create_runtime_backup(include_chroma=False)
|
|
|
|
|
except Exception as e:
|
|
|
|
|
log.error("admin_backup_failed", error=str(e))
|
|
|
|
|
raise HTTPException(status_code=500, detail=f"Backup failed: {e}")
|
|
|
|
|
return metadata
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/admin/backup")
|
|
|
|
|
def api_list_backups() -> dict:
|
|
|
|
|
"""List all runtime backups under the configured backup directory."""
|
|
|
|
|
return {
|
|
|
|
|
"backup_dir": str(_config.settings.resolved_backup_dir),
|
|
|
|
|
"backups": list_runtime_backups(),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/admin/backup/{stamp}/validate")
|
|
|
|
|
def api_validate_backup(stamp: str) -> dict:
|
|
|
|
|
"""Validate that a previously created backup is structurally usable."""
|
|
|
|
|
result = validate_backup(stamp)
|
|
|
|
|
if not result.get("exists", False):
|
|
|
|
|
raise HTTPException(status_code=404, detail=f"Backup not found: {stamp}")
|
|
|
|
|
return result
|
|
|
|
|
|
|
|
|
|
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
@router.get("/health")
|
2026-04-05 09:35:37 -04:00
|
|
|
def api_health() -> dict:
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""Health check."""
|
|
|
|
|
store = get_vector_store()
|
2026-04-05 18:33:52 -04:00
|
|
|
source_status = get_source_status()
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
return {
|
|
|
|
|
"status": "ok",
|
|
|
|
|
"version": "0.1.0",
|
|
|
|
|
"vectors_count": store.count,
|
2026-04-05 18:33:52 -04:00
|
|
|
"env": _config.settings.env,
|
|
|
|
|
"machine_paths": {
|
|
|
|
|
"db_path": str(_config.settings.db_path),
|
|
|
|
|
"chroma_path": str(_config.settings.chroma_path),
|
|
|
|
|
"log_dir": str(_config.settings.resolved_log_dir),
|
|
|
|
|
"backup_dir": str(_config.settings.resolved_backup_dir),
|
|
|
|
|
"run_dir": str(_config.settings.resolved_run_dir),
|
|
|
|
|
},
|
|
|
|
|
"sources_ready": all(
|
|
|
|
|
(not source["enabled"]) or (source["exists"] and source["is_dir"])
|
|
|
|
|
for source in source_status
|
|
|
|
|
),
|
|
|
|
|
"source_status": source_status,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/sources")
|
|
|
|
|
def api_sources() -> dict:
|
|
|
|
|
"""Return configured ingestion source directories and readiness."""
|
|
|
|
|
return {
|
|
|
|
|
"sources": get_source_status(),
|
|
|
|
|
"vault_enabled": _config.settings.source_vault_enabled,
|
|
|
|
|
"drive_enabled": _config.settings.source_drive_enabled,
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2026-04-05 09:41:59 -04:00
|
|
|
@router.get("/stats")
|
|
|
|
|
def api_stats() -> dict:
|
|
|
|
|
"""Ingestion statistics."""
|
|
|
|
|
return get_ingestion_stats()
|
|
|
|
|
|
|
|
|
|
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
@router.get("/debug/context")
|
2026-04-05 09:35:37 -04:00
|
|
|
def api_debug_context() -> dict:
|
feat: implement AtoCore Phase 0 + Phase 0.5 (foundation + PoC)
Complete implementation of the personal context engine foundation:
- FastAPI server with 5 endpoints (ingest, query, context/build, health, debug)
- SQLite database with 5 tables (documents, chunks, memories, projects, interactions)
- Heading-aware markdown chunker (800 char max, recursive splitting)
- Multilingual embeddings via sentence-transformers (EN/FR)
- ChromaDB vector store with cosine similarity retrieval
- Context builder with project boosting, dedup, and budget enforcement
- CLI scripts for batch ingestion and test prompt evaluation
- 19 unit tests passing, 79% coverage
- Validated on 482 real project files (8383 chunks, 0 errors)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-05 09:21:27 -04:00
|
|
|
"""Inspect the last assembled context pack."""
|
|
|
|
|
pack = get_last_context_pack()
|
|
|
|
|
if pack is None:
|
|
|
|
|
return {"message": "No context pack built yet."}
|
|
|
|
|
return _pack_to_dict(pack)
|