Commit graph

47 commits

Author SHA1 Message Date
Yiorgis Gozadinos
9baa213b34
Hand storage the database location, not the configuration that placed it
Store, connect_lancedb, gather_database_info and run_doctor take a
location, a path or a URI, and classify it with ConnectionMode.of.
SingleDatabaseSession owns the resolved DatabaseRef and passes its
location down. This removes DatabaseRef.connection(), default_db_path,
the placeholder path for URI-backed databases and the per-database
config copies, so the configuration a client holds is the one the caller
gave it. The chat hands its capabilities the scope it opened along with
the client it lends, and the v0.58.0 migration no longer checks local
free disk for a database behind a URI.
2026-09-03 15:11:37 +03:00
Yiorgis Gozadinos
afdef92b5b
Finish the comment pass, and escape document fields everywhere Rich renders
`_rich_print_document` escapes uri, title and metadata, the sibling of
the escaped search-result renderer. The remaining comments and
docstrings that narrated rejected alternatives, consequences or history
now state the current invariant. The Sandbox class docstring names the
held connection close() releases, and wrapped docs paragraphs join to
one line.
2026-08-28 15:34:47 +03:00
Yiorgis Gozadinos
09a7076b7e
State what the code does, not what it replaced
Comments and docstrings across the branch narrated rejected
alternatives, consequences and history; each now states the current
contract. Renames test_a_legacy_uri_client_keeps_its_error to
test_an_unnamed_database_keeps_its_error. Documents the Sandbox
connection paths, the citation header's database segment, both
AmbiguousDatabaseError conditions on create_app, and run_inspector's
scope parameter. Doc paragraphs added by the branch in python.md,
storage.md and cli.md are one physical line each.
2026-08-28 15:13:52 +03:00
Yiorgis Gozadinos
ee835b77c2
Select a filtered document from the database that holds it
The chat filter modal keys a selection by (database, id): copies of a
database share document ids, and checking one copy left the other
reading as selected. Applying the filter narrows the question's
`sources` to the databases the selection names. A twin id inside
another selected database still matches there: a serialized id filter
cannot carry a source.
2026-08-28 15:12:55 +03:00
Yiorgis Gozadinos
b601489896
Render titles and database names as text, not markup
A database name, document title, uri or heading containing Rich markup
crashed search output, chat citations and the chat document filter with
MarkupError. Every interpolation into markup-parsed text now escapes.
2026-08-28 15:12:19 +03:00
Yiorgis Gozadinos
7dc853606f
Name the collection a chat citation came from
Two collections can hold documents with the same title, and the collapsed
citation carried only that title, so the two read as one source repeated. The
name is appended when the client covers a set, the decision already made for
search results and retrieved images.
2026-08-28 13:30:11 +03:00
Yiorgis Gozadinos
225a37ca73
Tell one collection's pictures from another's
Three places identified a picture by document and reference alone, and one
identified a citation's images by chunk id alone. Both repeat between copies of
a database, so a search returning a figure from two collections sent one, a
capsule retained one, and a citation rendered the other collection's figures.

Keyed on the source as well: `(source, document_id, self_ref)` for search
pictures, the capability and source for retained ones, and `qualified_id` for
the chat's citation images.
2026-08-28 10:35:46 +03:00
Yiorgis Gozadinos
5e81054a1e
Apply the filter modal's search on enter, not as you type
Typing narrowed the mounted checkboxes without touching the search the listing
was built from, so a term matching more than one page hid rows from whichever
page the user happened to be on, left the rest of the matches a page away, and
paged on the previous search while the term still sat in the box. The term now
applies on enter, and the footer says so until it does.
2026-08-28 09:59:33 +03:00
Yiorgis Gozadinos
bd8c1a6d15
Render a citation whose pictures no database claims
Chat asked the covering client for a picture with the citation's source, which
raises when there is none, losing the answer to one figure. It resolves the
reader first and omits the bytes when the database cannot be placed, as the rich
formatter already did.
2026-08-28 08:47:36 +03:00
Yiorgis Gozadinos
105b2628de
Page the filter modal, and list the selected
A selection outside the page stayed applied while its checkbox was gone, so
there was no way to remove it. Appending those documents to the page instead
loses the bound the page exists to keep, since selections accumulate across
searches.

Both listings page at `DOCUMENT_PAGE`, and `Selected` switches between them, so
the mounted widgets stay bounded whichever is showing and every selection is a
page away rather than unreachable.

The count reads the checkboxes on screen, so narrowing as the user types reports
what is visible. A listing with nothing in it says so, instead of leaving an
empty box that reads as still loading.
2026-08-27 18:04:27 +03:00
Yiorgis Gozadinos
d93cb5c891
Refuse a name the client does not cover, however many it covers
`reader_for` returned itself before looking at the name, so an "alpha" client
answered `reader_for("beta")` with alpha's reader. A citation naming another
database would have been read from the wrong one.

Both paths decide through `clients_covering` now, so one database refuses a
wrong name the way a set already did.

The chat citation test said mounting lends the client without showing it; a
chat test shows it and the docstring points there.
2026-08-27 14:49:40 +03:00
Yiorgis Gozadinos
0e0ce8d3f0
Fix document-filter searches after the initial load
Every load looked up the loading indicator, which only the first page has,
so pressing enter in the search box raised `NoMatches`. The indicator is a
child of the list `remove_children()` already clears.

A document's id and search text travel on a `DocumentCheckbox` instead of
being assigned onto a `Checkbox` behind type suppressions. The positional
widget ids are gone; nothing queried them.
2026-08-27 11:06:22 +03:00
Yiorgis Gozadinos
db5f61d740
Rename the database selector to --db-name
`--database` and `--db` read as one word abbreviated but take different
things, a configured name against a filesystem path, and they sit in
different positions: `haiku-rag --db /path info` fails with `No such option`.
The CLI already says "database" to the user everywhere it prints one, so the
name it selects by should say the same. Unreleased, so no deprecation.

Also: without a reranker, raise `search.limit` with the number of databases
searched, since each contributes its best matches to a list truncated back
to the limit.
2026-08-26 16:48:27 +03:00
Yiorgis Gozadinos
71e4e4a40a
Resolve a capability's databases once, into a scope
`resolve_db_path` manufactured the default path whenever `lancedb.databases`
was empty, and `covers_several_databases` read coverage back out of the
configuration, so a capability built without a client opened
`storage.data_dir/haiku.rag.lancedb` instead of what `lancedb.uri` placed.
The entry point resolves a `DatabaseScope` instead: instructions ask it what
it covers and `_ensure_rag` opens it through `HaikuRAG._covering`, so
coverage is decided once rather than encoded in a path and re-derived.
`Sandbox._covering` takes the scope the capability already resolved, beside
the public constructor that takes a path. The factory signatures are
unchanged.
2026-08-26 12:20:21 +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
8db447e095
Ask the client what it covers
covers_multiple, source_names, source and reader_for replace the private
state seven modules were reading to work out how many databases they had.
The configured selection is kept intact, so entering a client twice derives
the same database rather than the last derivation.
2026-08-25 14:16:22 +03:00
Yiorgis Gozadinos
307e250f29
Select documents to filter by id, not by displayed name
A title repeats within a corpus and across databases, so a substring
match on the displayed name widened the filter to documents the user did
not pick. The label names the database.
2026-08-24 15:27:59 +03:00
Yiorgis Gozadinos
9a17ff7457
Show one page of documents in the filter modal
The modal listed every document and mounted a checkbox per document, so a
corpus of tens of thousands never finished rendering: 67k sequential
mounts across two databases, and the same for one database that size.
Titles repeat at that scale too, and the ids were derived from the title,
so they collided.

It shows a page of 200 now, mounted in one call and identified by
position, and the search box asks the database for the rest on enter,
matching titles and URIs. Typing still narrows the page on screen, for
feedback while typing. `search_filter` escapes the term, which is
whatever was typed.

A federated listing takes its window across the databases rather than
filling it from the first one: concatenating hid every database after
whichever was listed first, which for a set of a thousand papers and
sixty thousand articles meant a page of papers alone. Sorting would not
have helped, since document ids and article titles sort into separate
runs, so the page is picked by interleaving and the modal sorts it for
display.
2026-08-24 10:03:46 +03:00
Yiorgis Gozadinos
bb92ccf67e
Cover the configured set in the chat TUI
Chat answers with the same capabilities `ask` does, so it federates as
naturally as `ask` and `analyze` — but it went through the one-database
guard and refused a configured set outright, which left no way to chat
across several databases.

The guard was the visible half. `run_chat` also defaulted `db_path` to the
single default path whenever it was None, so lifting the refusal alone
would still have opened one database. It now leaves the path unresolved
when `lancedb.databases` names the set, and the client resolves it.

Listing and counting documents fan out over the set, which is what the
document filter reads, and visual grounding resolves the database holding
the cited chunk through the citation's source: chunks, pages and bounding
boxes all come from that one database. A limit on a listing means that
many documents in total, not that many per database.

The info modal reports every database it covers, each under its
configured name and without its location, since names are the only
identity that leaves the configuration. `database_lines` is what one
database reports about itself, shared by both paths, and it reports a
failure as a line so one unreachable database does not cost the report on
the others.

`inspect` stays a one-database command. It browses one database's
documents and chunks, so a set has nothing to show it.
2026-08-24 10:03:46 +03:00
Yiorgis Gozadinos
11b1bfbc94
Give the capsule to one return, and the run its own copy of state
A request can carry several of this capability's returns — a model can call search
twice in one response — and the carrier was identified by message alone, so each of
them received the whole capsule. It is selected by message and part now, so exactly
one carries it however many share the request.

The chat TUI passed its persisted state into the run, so tool synchronisation mutated
it in place while the message history was promoted only on success. A cancelled or
failed run therefore kept the evidence the tools had recorded and discarded the
messages that justified it, and the next question derived its identity from the
shorter history: behind the recorded epoch, refused as non-append-only, the
conversation unusable until cleared. The run gets a copy, promoted with the messages
or not at all.

Five decorators had been left attached to a helper by an earlier extraction, which
pytest does not collect, so the resume case they carried was silently untested. The
wire test covers both resume shapes again, no prompt and deferred results.
2026-08-13 13:00:02 +03:00
Yiorgis Gozadinos
cdaeaa94e6
Constrain the chat prompt container height 2026-07-25 14:40:40 +03:00
Yiorgis Gozadinos
93824cdee5
Surface database-open errors in the TUIs
ChatApp and InspectorApp assigned self.client before __aenter__
completed, so a failed open was masked by an AttributeError from
on_unmount tearing down the never-opened client.
2026-07-25 11:27:29 +03:00
Yiorgis Gozadinos
5f4c73f89f
Add image attachment to the chat TUI 2026-07-25 10:58:08 +03:00
Yiorgis Gozadinos
f87fabe556
Make the capability image-attachment gate a vision bool 2026-07-24 16:00:01 +03:00
Yiorgis Gozadinos
6c5bc0aae1
Gate combined-chat capability vision on the driving model 2026-07-24 15:27:20 +03:00
Yiorgis Gozadinos
597808c56e
Fix chat analysis-model selection, AG-UI example state, and chat docs
Drive analysis-only chat with analysis.model (falling back to qa.model) so
the running model matches the one the analysis capability configures,
including its vision flag; RAG-bearing chats still run on qa.model.

Give the AG-UI example state-bearing deps and a final STATE_SNAPSHOT so
registered citations reach the client, mirroring the app backend.

Update chat docs: haiku-rag chat uses --capability/-c, and drop the removed
"View state" command-palette entry.
2026-07-24 15:27:20 +03:00
Yiorgis Gozadinos
77585ef26a
fix per-question capability limits and tool isolation 2026-07-24 15:26:18 +03:00
Yiorgis Gozadinos
43c17a6777
fix capability execution limits and chat loading 2026-07-24 15:26:17 +03:00
Yiorgis Gozadinos
9deb1f2bd4
replace haiku.skills with native Pydantic AI capabilities 2026-07-24 15:26:17 +03:00
Yiorgis Gozadinos
188d35023f
rotate chat TUI conversation id per launch / clear-chat 2026-05-21 14:21:51 +03:00
Yiorgis Gozadinos
8c57a3ca99
Drop the multi-agent research workflow 2026-05-20 12:46:48 +03:00
Yiorgis Gozadinos
cb9360ef7b
fix chat TUI citation rendering after state flattening 2026-04-24 13:48:57 +03:00
Yiorgis Gozadinos
2a0e89bbe7
add --skill flag to chat TUI for rag and analysis skills 2026-04-17 18:35:16 +03:00
Yiorgis Gozadinos
cb41f84615
Add tests 2026-02-20 16:36:51 +02:00
Yiorgis Gozadinos
c567842420
Rewire document filter 2026-02-20 16:36:50 +02:00
Yiorgis Gozadinos
643ed20d6f
Rewrite chat TUI and app backend with haiku.skills 2026-02-20 16:36:48 +02:00
Yiorgis Gozadinos
0434cd068b
Simplify TUI app 2026-02-11 15:10:50 +02:00
Yiorgis Gozadinos
9e63ff1ff7
Remove session_id from state layer, remove module-level caches 2026-02-11 14:55:53 +02:00
Yiorgis Gozadinos
a7b79c433d
TUI now generates a UUID session_id on mount and on chat clear 2026-02-10 14:21:22 +02:00
Yiorgis Gozadinos
d1a9439500
Fix session_id not persisting across AG-UI requests 2026-02-10 13:14:05 +02:00
Yiorgis Gozadinos
77894b8c35
Clena citation handling now that we handle the total state 2026-01-29 12:12:48 +02:00
Yiorgis Gozadinos
202df368a0
TUI now syncs full session state from AG-UI events 2026-01-29 12:05:46 +02:00
Yiorgis Gozadinos
e2158c7dad
Handle snapshot deltas in TUI 2026-01-28 15:23:56 +02:00
Yiorgis Gozadinos
7835509213
Fix tui test that still wanted to use shortcuts 2026-01-26 16:43:11 +02:00
Yiorgis Gozadinos
9ed8773672
Unify Citation class and context formatting between chat and research agents 2026-01-26 16:42:38 +02:00
Yiorgis Gozadinos
8b2cc19288
Fix tests 2026-01-19 13:47:20 +02:00
Yiorgis Gozadinos
6b3ff5ef1e
CLI app 2026-01-13 11:31:21 +02:00