How the Hindsight TUI Explorer Navigates Banks and Memories
The Hindsight TUI explorer provides a keyboard-driven, k9s-style hierarchical browser that lets users navigate from bank selection to memory inspection using Enter and Esc keys, with background async queries and auto-refresh capabilities powered by the ratatui crate.
The Hindsight TUI explorer is a full-screen terminal interface built for the vectorize-io/hindsight semantic memory system. Using the Rust-based ratatui crate, it renders an interactive hierarchy that allows developers to browse banks, inspect stored memories, and execute recall queries without leaving the command line.
Core Navigation Flow
Selecting a Bank (View::Banks)
When you launch the explorer with hindsight explore, the interface starts in the Banks view. In hindsight-cli/src/commands/explore.rs, the App::load_banks() method (lines 36‑44) calls client.list_agents(false) to fetch all available banks as BankListItem structs. These populate self.banks, and the UI selects the first entry by default using a ListState.
The view state is defined by the View::Banks variant (lines 30‑34). This initial screen presents a scrollable list where you can use arrow keys to highlight different banks before pressing Enter to drill down.
Opening Bank Memories
Pressing Enter on a bank triggers App::enter_view() (lines 604‑616). This method pushes the current view onto a view_history stack, then sets self.view = View::Memories(bank_id) and calls self.load_memories(&bank_id) to fetch the bank's contents. The View enum tracks the active screen, and the bank_id() helper (lines 49‑53) extracts the identifier for sub-views.
Browsing the Memory List
Once inside a bank, the Memories view renders a scrollable table showing each memory's type, mentioned-at timestamp, occurred-at timestamp, and a truncated text snippet. The render_memories() function (lines 735‑758) constructs a ratatui::widgets::List of ListItems from self.memories, preserving the selected index across refreshes via &mut app.memories_state.
Long fields are handled with horizontal scrolling managed by self.horizontal_scroll, allowing you to view truncated content without leaving the list view.
Inspecting Memory Details
To inspect a specific memory, press Enter on any row. The enter_view() method handles this via the View::Memories branch (lines 618‑623), which stores the selected memory in self.viewing_memory. The render path at lines 774‑818 then checks if let Some(memory) = &app.viewing_memory and draws a detailed panel displaying full metadata and the complete text field.
Press Esc to close the detail view and return to the memory list. Press Esc again from the list to pop the previous view from view_history via App::go_back() (lines 734‑761), returning you to the bank selection screen.
Executing Queries
From any view, press / to open the Query view. After typing a query and hitting Enter, the execute_query() method (lines 309‑376) spawns a background thread that calls either client.recall or client.reflect depending on self.query_mode. Results are delivered asynchronously via an mpsc channel as QueryResult variants (enum defined at lines 71‑75), allowing the UI to remain responsive while the server processes the request. The render_query() function (lines 1243‑1329) displays the final results or reflection.
Architecture and State Management
The App Struct
The core state machine resides in the App struct within hindsight-cli/src/commands/explore.rs. It holds the ApiClient instance, the current View enum, a history stack for navigation, and multiple ListState objects for managing selection indices across banks, memories, entities, documents, and query results. Additional flags track UI states such as loading, help, and auto_refresh_enabled.
Background Query Processing
To prevent blocking the main thread during expensive recall operations, the explorer uses a multi-producer, single-consumer (mpsc) channel. When execute_query() spawns a worker thread, it sends QueryResult values back to the main loop, which calls check_query_result() to update app.query_results without freezing the interface.
Auto-Refresh Mechanism
The explorer supports automatic data refreshing every five seconds. The toggle_auto_refresh() method (lines 214‑222) flips the auto_refresh_enabled flag, while do_auto_refresh() (lines 228‑233) checks should_refresh() (lines 24‑26) and calls self.refresh() to reload data for the current view. This ensures you are always viewing the latest memories without manual intervention.
Practical Usage Examples
Launching the Explorer
# Start the interactive TUI (k9s-style interface)
hindsight explore
# or use the alias
hindsight tui
Typical Navigation Sequence
let mut app = App::new(client);
// Load and display all banks
app.refresh()?; // triggers load_banks()
ui::draw(&mut terminal, &mut app);
// User selects the third bank (index 2)
app.banks_state.select(Some(2));
app.enter_view()?; // switches to View::Memories(bank_id)
// Load memories for the selected bank
app.refresh()?; // triggers load_memories(bank_id)
ui::draw(&mut terminal, &mut app);
// User selects a specific memory (index 5)
app.memories_state.select(Some(5));
app.enter_view()?; // sets viewing_memory = Some(memory)
// Render detail view
ui::draw(&mut terminal, &mut app);
// Press Esc to return to memory list
app.go_back();
ui::draw(&mut terminal, &mut app);
Programmatic API Access (Non-TUI)
If you need the same data without the terminal interface, use the underlying ApiClient directly from hindsight-cli/src/api.rs:
let client = ApiClient::new("http://localhost:8888", None)?;
let banks = client.list_agents(false)?; // List all banks
let memories = client.list_memories(
"my-bank",
None,
None,
Some(500), // limit
Some(0), // offset
false
)?;
let recall = client.recall("my-bank", &RecallRequest {
query: "what did we decide about the UI?".into(),
budget: Some(Budget::Mid),
..Default::default()
}, false)?;
Summary
- The Hindsight TUI explorer is implemented in
hindsight-cli/src/commands/explore.rsusing the ratatui crate for terminal rendering. - Navigation relies on a view stack (
View::Banks→View::Memories→ detail view) managed byenter_view()andgo_back(). - State preservation uses
ListStateobjects to maintain selection indices across banks and memories during refreshes. - Async queries run in background threads via
mpscchannels, keeping the UI responsive during recall or reflect operations. - Auto-refresh occurs every five seconds when enabled, reloading data via
do_auto_refresh().
Frequently Asked Questions
How do I launch the Hindsight TUI explorer?
Run hindsight explore or its alias hindsight tui from your terminal. This command initializes the ratatui-based interface and immediately calls App::load_banks() to populate the initial bank selection screen.
What keyboard shortcuts are available for navigation?
Use ↑/↓ to navigate lists, Enter to open a selected bank or memory, and Esc to go back. Press ←/→ to horizontally scroll long text fields in memory details. Press / to open the query view, R to force a manual refresh, and Q or Ctrl+C to quit.
How does the TUI handle data refreshes?
The explorer checks should_refresh() every five seconds when auto-refresh is enabled (toggle_auto_refresh() at lines 214‑222). The do_auto_refresh() method (lines 228‑233) triggers self.refresh(), which reloads data for the current view (banks or memories) while preserving your selected index via ListState.
Can I query memories without using the interactive interface?
Yes. The same API calls used by the TUI are available programmatically through ApiClient in hindsight-cli/src/api.rs. You can call client.recall() or client.reflect() directly from Rust code or use the Hindsight HTTP API, bypassing the terminal interface entirely while accessing identical functionality.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →