Commit graph

114 commits

Author SHA1 Message Date
Yiorgis Gozadinos
5f795cb0cb
Let the MCP server be told which database once
`create_mcp_server` promised one database and accepted a scope covering a set,
where the write tools exist and fail on use. It refuses that now.

Resolving is the public factory's job, as it is `HaikuRAG`'s: `_covering` takes
a scope someone already resolved, so the configured name survives without a
`DatabaseScope` reaching the public signature.

The test that a scope decides the database asserted `all(...)` over a search
that could return nothing, which held whatever the server read. It reads the
listing instead, so alpha's documents being present and beta's absent both have
to be true.
2026-08-27 15:41:03 +03:00
Yiorgis Gozadinos
db7f0e0af6
Give the MCP server the database, not a description of it
`run_mcp` derived a path and a configuration for the server, and deriving drops
the configured name: MCP results and citations carried `source=None` where the
same database named through any other path carried "alpha".

`create_mcp_server` takes the resolved scope and opens through it, so the name
survives. Passing a path and configuration still works and resolves to the same
place.
2026-08-27 15:12:13 +03:00
Yiorgis Gozadinos
474245ab59
Serve the database the MCP command selected
`run_mcp` passed `_path`, the local stand-in a URI-backed ref resolves to for
display. A path overrides `lancedb.uri`, so `--db-name` on an S3 database
served the local default. It passes the ref's own path now, None where a URI
placed the database, and `create_mcp_server` accepts that.

The client opened around the server is gone. It never served a request, and it
opened the scope rather than the derived path, so startup validated the remote
database while the server read the local one.
2026-08-27 14:41:16 +03:00
Yiorgis Gozadinos
1995a7da36
Name the database of a result by what the operation covers
`_rich_print_search_result` asked how many databases were configured, so
`--db-name alpha` labelled every line with the database the caller had just
named. Citations already ask the client what it covers, which is the same
question the scope answers.
2026-08-26 16:55:56 +03:00
Yiorgis Gozadinos
b76c5c7db0
Show the configuration in the shape a config file has
`settings` printed each top-level block as one Python dict repr, so nesting
was invisible, long paths wrapped mid-token and `PosixPath(...)` leaked. It
renders as YAML now, which is what `haiku.rag.yaml` is written in.
2026-08-26 16:55:56 +03:00
Yiorgis Gozadinos
ab0bdff14c
Print only the document fields there are
`list` does not load content, which is where the docling blobs live, so the
`content:` header announced a field the command declined to fetch. A document
without a uri or metadata printed `uri: None` and `meta: {}`, where `title`
was already omitted when absent.
2026-08-26 16:48:27 +03:00
Yiorgis Gozadinos
8f41d42ab2
Correct what the client and the docs claim
`HaikuRAG.__init__` said an omitted `db_path` uses `storage.data_dir`, which
is the last of three; and that `sources` is ignored for a single `uri`, where
it raises, since only `lancedb.databases` names databases.

The name is not the only identity leaving the configuration: results,
citations, model input and errors opening a named database carry it, while
`info`, `init` and `tag` print the location. `sources=[]` returns no search
results, but `ask` and `analyze` still answer, without evidence.

Trim the comments that still narrated a failure or an alternative to the
invariant they were there for.
2026-08-26 16:47:59 +03:00
Yiorgis Gozadinos
1b3334b5af
Trim the comments to what a reader needs
One sentence for the contract, one or two more only where an invariant is not
obvious. The reasons that stay are about correctness and ownership: which
database a result belongs to, who closes what, why assembly order is the
tiebreak. The ones that go narrated how the code got here.
2026-08-26 13:29:00 +03:00
Yiorgis Gozadinos
d367b1eb5a
Resolve the databases a command works on, once
The CLI decides only what it knows — that --db and --database are the same
thing said twice, and whether a command reads more than one — and hands the
resolved scope down. Nothing rewrites the configuration, so a named database
keeps the name results and citations carry, and a remote one opens the URI
it was configured with rather than the local path standing in for it.

HaikuRAGApp, ChatApp and InspectorApp take that scope and nothing else.
Selection reaches the client through a private constructor, so the public
signature still takes a path or names.
2026-08-25 15:48:47 +03:00
Yiorgis Gozadinos
a9e66b001b
Drop the database name from single-database document output
Naming one database on the command line points the configuration at it,
so no command that prints a document ever has a name to print.
2026-08-24 15:28:21 +03:00
Yiorgis Gozadinos
e94623ec37
Tell a document which database it came from
Document.source names the configured database, as SearchResult and
Citation already do. A listing spanning databases is unreadable
without it, and `--database NAME list` could not name the one it
opened.
2026-08-24 10:03:47 +03:00
Yiorgis Gozadinos
fdb5710491
Ask and analyze across several databases
Chunk 2 gave search a configured set to fan out over. ask and analyze
covered one database still: the RAG capability had no way to be told which
databases a question spanned, and the analysis sandbox mounted one
document tree.

The selection travels as sources on EvidenceState, beside the filter it
scopes with, so both capabilities read it the same way. clients_covering
is the one rule that turns a selection into clients, used by search, the
sandbox mount and the cite fallback, so a question scoped to some
databases cannot search, mount or cite another. Citations carry the
database they came from, and format_for_agent names it, so the model can
attribute evidence while it answers rather than only afterwards.

The sandbox keeps one flat /documents/{id}/ namespace and resolves each id
to the client holding it, which rests on ids being UUID4. A database
copied from another breaks that, so an id held twice is refused rather
than resolved to whichever arrived last.

On the CLI, search, ask and analyze cover the configured set and label
each result with its database. Every other command works on one, named
with --database NAME (a name reaches a database behind a URI, which --db
cannot) or --db PATH, and refuses a set it cannot choose from instead of
silently reading the default database. Cold databases open together, so a
first query costs the slowest open rather than their sum.
2026-08-24 10:03:46 +03:00
Yiorgis Gozadinos
4967765878
Delete comments that restate the line below them
Sixty-three comments said what the next statement already said: # Connect to
LanceDB above connect_lancedb, # Path object above isinstance(source, Path),
# Get page numbers from provenance above the prov loop, # Clear and populate
results above list_view.clear(). They cost a read and carry nothing.

The line is whether a comment restates one statement or labels a phase. Phase
labels stay: the migrations keep # Create staging table with new schema and
# Copy from staging to final table in batches, each heading ten lines of a
long procedure. So do comments carrying a fact the code cannot: the
merge_insert update-only note on document_meta, why the poller builds sources
eagerly, why create_document_from_source returns a list for directories, that
indexes need training data, the field-group markers in the config models, and
the file:// URL-encoding note in create_document_from_source.

capabilities/ is untouched. Its docstrings sit next to prompt surface, and
changing them needs an eval to back it.

The cassette-recording docs were wrong three ways. They named
tests/test_qa.py::test_qa_anthropic, which no longer exists; they targeted
whole modules, so a rewrite would re-record cassettes for services the
recorder is not running; and they used COHERE_API_KEY where the SDK reads
CO_API_KEY. docs/development.md now names exact tests with -n0, and the keyed
example is test_cohere_reranker, which owns the one cassette recording
api.cohere.com.
2026-08-20 15:22:33 +03:00
Yiorgis Gozadinos
7f54eb4dbc
Measure the CLI and its application layer
cli.py carried 40 pragmas over whole command bodies and app.py a
class-level one over all 412 statements, while tests/test_cli.py already
drove 29 commands through CliRunner. The pragmas hid lines the suite
executed, so the 100% gate understated real coverage and gave new CLI code
no scrutiny.

Both are measured now. 38 CLI tests stub HaikuRAGApp and assert the parsed
arguments reach the right method; 60 app tests stub the client and record
the console, pinning what each command asks for and what it prints. The only
pragma left in either file is cli() under __main__. The omit list is back to
the two Textual TUIs.

Three defects the coverage surfaced:

haiku-rag settings masked only top-level secret-named fields, so nested ones
printed in full — lancedb.api_key, providers.docling_serve.api_key, WebDAV
source passwords. It uses redact_secrets, which walks the dump.

chat guarded the wrong thing: haiku.rag.chat imports without Textual, and
run_chat raises when it imports ChatApp, so the missing extra escaped as an
ImportError. The guard is on the call. inspector raises at module import
instead, so inspect keeps its guard on the import; each has a test that
fails the way the real installation fails.

search --limit/--search-type and history --limit default to None so the
config resolves the default. Now pinned.

CI passed --cov=haiku while pyproject declares source = ["haiku_rag_slim"];
pass --cov and let the config decide. build-docs.yml only ran on push to
main, so a broken docs build merged and failed at deploy: build on pull
requests, with configure-pages, upload-pages-artifact and deploy gated to
push, and a per-ref concurrency group.
2026-08-20 13:23:00 +03:00
Yiorgis Gozadinos
629e1ba4ea
Split the store module by responsibility
engine.py held four unrelated things: what the tables are, how to open a
connection, how to read a database's state, and the Store that coordinates
writes. At 1240 lines the Store's own concerns — locks, migrations, vacuum,
tags — were hard to find among them.

Table records, Arrow schemas, index_specs, ensure_indexes, REQUIRED_TABLES
and query_to_pydantic move to store/schema.py, which imports nothing from
haiku.rag: it describes the tables and never opens or mutates one.

gather_database_info, get_database_stats, DatabaseInfo and its result models
move to store/info.py. Nothing in Store calls them — they are read paths for
the CLI, doctor, inspector and ingester API — so info depends on engine and
not the reverse.

engine.py keeps the Store, ConnectionMode, connect_lancedb, the tag helpers
and the restore-order and retention constants. No re-exports: importers
point at the new modules.

test_app_info_uses_connect_lancedb_for_remote patched
haiku.rag.store.engine.connect_lancedb; gather_database_info now binds that
name in info.py, so the patch targets where the call is looked up.
2026-08-20 12:13:51 +03:00
Yiorgis Gozadinos
e8f00fcff4
Make get_config the only configuration lookup
haiku.rag.config exported two configuration instances: the lazy _config
behind get_config/set_config, and Config, loaded at import time. Nothing
linked them, and eleven signatures captured Config as a default argument,
so set_config could not reach the factories, the client, the store or the
MCP server. reranking/base.py went further and snapshotted the configured
reranker name into a class attribute at import.

Config is removed. Internal defaults are config: AppConfig | None = None,
resolved through get_config() per call. RerankerBase._model is None and
CohereReranker takes its model name as an argument, like every other
reranker.

The suite patched attributes on Config while production read the instance
get_config() returns, a different object, so those patches were no-ops
waiting to happen. They now go through get_config().
2026-08-19 14:43:40 +03:00
Yiorgis Gozadinos
c62fd78c7a
Accept images on CLI ask/analyze and MCP tools 2026-07-25 10:35:25 +03:00
Yiorgis Gozadinos
9deb1f2bd4
replace haiku.skills with native Pydantic AI capabilities 2026-07-24 15:26:17 +03:00
Yiorgis Gozadinos
0308220769
Read verbs skip the embeddings config-compatibility check 2026-07-24 12:14:31 +03:00
Yiorgis Gozadinos
3034280c78
Skip embedding validation on document deletion 2026-07-22 10:48:00 +03:00
Yiorgis Gozadinos
ce1b9ac88e
Harden history against tag annotation failures; document tag restore 2026-07-16 13:11:34 +03:00
Yiorgis Gozadinos
4522fbdf1b
Add tag restore CLI 2026-07-16 13:11:34 +03:00
Yiorgis Gozadinos
f997d7adc9
Consolidate tag primitives 2026-07-16 13:11:34 +03:00
Yiorgis Gozadinos
0e271eaf4b
Remove --before/--at time travel 2026-07-16 13:11:33 +03:00
Yiorgis Gozadinos
0813a1c980
Add tag CLI commands and history tag annotations 2026-07-16 13:11:27 +03:00
Yiorgis Gozadinos
18c0f6c5e8
Add --no-expand to the visualize command for chunk-only grounding 2026-07-09 12:49:42 +03:00
Yiorgis Gozadinos
4f8cc4eb30
Show a progress spinner while doctor runs 2026-06-28 10:44:30 +03:00
Yiorgis Gozadinos
f1e1a16f9b
Add doctor --duplicates-out YAML export; summarize terminal report 2026-06-26 16:52:22 +03:00
Yiorgis Gozadinos
cc1d8d1e4c
Add provider connectivity probes to haiku-rag doctor 2026-06-23 10:34:27 +03:00
Yiorgis Gozadinos
c2cb3cedf3
Add haiku-rag doctor database health check 2026-06-23 10:03:27 +03:00
Yiorgis Gozadinos
bfc51b8b28
urface document_meta in info, history, and the inspector 2026-06-12 10:17:18 +03:00
Yiorgis Gozadinos
0be2e5b24f
Extract gather_database_info shared by the info command 2026-06-09 11:09:28 +03:00
Yiorgis Gozadinos
414be551fb
Open read CLI verbs read-only 2026-06-05 10:48:49 +03:00
Yiorgis Gozadinos
0e3d53791f
Add rebuild --set-embedder to reconcile embedder identity 2026-06-05 10:42:15 +03:00
Yiorgis Gozadinos
f4468b65ee
Add --host, --port to ingester cli, update docker compose example 2026-05-26 11:44:45 +03:00
Yiorgis Gozadinos
7ea61a7b10
drop the old monitor, rename serve→mcp, add e2e tests 2026-05-26 11:42:41 +03:00
Yiorgis Gozadinos
8c57a3ca99
Drop the multi-agent research workflow 2026-05-20 12:46:48 +03:00
Yiorgis Gozadinos
a317a951d9
CLI citations: compact panel, inline figures, doc/chunk IDs in footer 2026-05-20 12:00:24 +03:00
Yiorgis Gozadinos
947a26b391
client.analyze routes through the rag-analysis skill; drop documents= and AnalysisResult.program. Re-record all cassettes that are relevant 2026-05-19 10:46:32 +03:00
Yiorgis Gozadinos
d96d2eeb0f
client.ask routes through the rag skill; always show citations in CLI 2026-05-19 10:19:39 +03:00
Tres Seaver
ad0161e6da
chore: isort 2026-05-18 09:31:12 -04:00
Tres Seaver
f9ce3bc401
refactor: move 'SearchType' to 'store.models.chunk' 2026-05-18 09:27:34 -04:00
Tres Seaver
7953de46b9
fix: declare 'SearchType' for 'search_type' arg
fix: allow 'search_type' only for text searches
2026-05-18 06:35:51 -04:00
Tres Seaver
b2ffc50168
WIP: add '--search-type' option to CLI 'search' 2026-05-18 06:35:50 -04:00
Yiorgis Gozadinos
37099988a6
poll S3 prefixes with S3Watcher and wire into serve 2026-05-11 11:28:40 +03:00
Yiorgis Gozadinos
ff82d36c2f
add rebuild --descriptions: run VLM over stored picture bytes only 2026-05-05 12:02:46 +03:00
Yiorgis Gozadinos
65d9c74224
expose image-as-query through MCP and the CLI. 2026-05-04 11:46:45 +03:00
Yiorgis Gozadinos
e814459e1f
Extract download_models into client/downloads.py 2026-04-24 14:42:53 +03:00
Yiorgis Gozadinos
82fd10e0ee
Migrate LanceDB to native async API
Convert all LanceDB operations from sync calls wrapped in async
functions to the native async API (connect_async, AsyncConnection,
AsyncTable, AsyncQuery). Database I/O no longer blocks the event loop.

- Store and HaikuRAG use async context managers (async with). Store
  initialization is deferred to __aenter__; direct construction
  without async with is no longer supported.
- Index creation uses config objects (FTS, BTree, IvfPq) instead of
  string-based index_type parameter.
- Upgrade callbacks are async.
- HaikuRAG tracks background vacuum tasks and awaits them in __aexit__
  and before destructive rebuild operations to avoid races with
  concurrent table mutations.
- temp_db_path fixture uses pytest's tmp_path for reliable async
  cleanup.
2026-04-24 14:42:52 +03:00
Yiorgis Gozadinos
d2b3ba1b59
rename RLM agent to analysis throughout the codebase 2026-04-17 18:32:01 +03:00