How the 99 Plugin's Request History System Tracks Previous Operations

The 99 Neovim plugin maintains a comprehensive audit trail of every AI request through a centralized state object that stores operations in ordered arrays and hash maps, enabling fast lookup by unique trace IDs and persistent tracking of status from initiation to completion.

ThePrimeagen's 99 plugin implements a robust request history system that records every interaction with AI providers. This system enables users to review past operations, navigate to previous request locations, and manage the lifecycle of AI-generated content. By maintaining structured state across multiple data structures in lua/99/init.lua, the plugin ensures that no operation is lost and every request remains queryable by its unique identifier.

Core State Architecture for Request History

The _99_State Object and History Fields

The request history system centers on the _99_State object defined in lua/99/init.lua. When the plugin initializes, create_99_state() instantiates several critical fields that form the backbone of the tracking system:

--- @field __request_history _99.RequestEntry[]
--- @field __request_by_id table<number, _99.RequestEntry>
...
local function create_99_state()
  return {
    ...
    __request_history = {},
    __request_by_id = {},
    __tutorials = {},
    __searches = {},
    ...
  }
end

These fields serve distinct purposes within the request history system:

  • __request_history: An ordered array of _99.RequestEntry objects representing every request chronologically, including both in-flight and completed operations.
  • __request_by_id: A hash map enabling O(1) lookup of any request entry by its unique trace identifier (xid).
  • __tutorials and __searches: Specialized collections that cache successful tutorial and search results for rapid retrieval and display.

How Operations Are Tracked

Initiating Requests with track_request

When a user triggers an AI operation—whether a search, tutorial, or visual command—the plugin constructs a RequestContext (defined in lua/99/request-context.lua) and immediately registers it with the history system via _99_State:track_request(context).

function _99_State:track_request(context)
  assert(context.operation, "must have an operation defined to track the request")
  local point = context.range and context.range.start or Point:from_cursor()
  local entry = {
    context = context,
    status = "requesting",
    point = point,
    started_at = time.now(),
    operation_data = nil,
  }
  table.insert(self.__request_history, entry)      -- append to the ordered list
  self.__request_by_id[context.xid] = entry       -- map id → entry
  return entry
end

This method performs three critical actions:

  1. Captures metadata including the cursor position (point) and timestamp (started_at) to support later navigation and timing analysis.
  2. Sets the initial status to "requesting", marking the entry as in-flight.
  3. Indexes the entry in both the chronological array and the ID-based hash map using the context's unique xid.

Completing Requests with finish_request

When the AI provider returns a response—success or failure—the plugin invokes _99_State:finish_request(context, status) to finalize the history entry.

function _99_State:finish_request(context, status)
  local id = context.xid
  local entry = self.__request_by_id[id]
  if not entry then return end

  entry.status = status                     -- e.g. "success" or "error"
  local data = entry.operation_data
  if entry.status == "success" and data then
    if data.type == "tutorial" then
      table.insert(self.__tutorials, data)
    elseif data.type == "search" then
      table.insert(self.__searches, data)
    end
  end
end

This method updates the entry's status and, for successful operations, migrates the data to specialized collections. Tutorial results populate __tutorials, while search results populate __searches, enabling rapid access to historical AI outputs without traversing the entire request history.

Querying and Managing Request History

Counting and Clearing Previous Requests

The request history system provides utility methods for inventory management. The _99_State:previous_request_count() method iterates through __request_history to tally completed operations:

function _99_State:previous_request_count()
  local count = 0
  for _, entry in ipairs(self.__request_history) do
    if entry.status ~= "requesting" then count = count + 1 end
  end
  return count
end

To reclaim memory and declutter the interface, _99_State:clear_previous_requests() purges all completed entries while preserving in-flight operations:

function _99_State:clear_previous_requests()
  local keep = {}
  for _, entry in ipairs(self.__request_history) do
    if entry.status == "requesting" then
      table.insert(keep, entry)
    else
      self.__request_by_id[entry.context.xid] = nil
    end
  end
  self.__request_history = keep
  self.__searches = {}
  self.__tutorials = {}
end

Exporting to Quickfix List

For interactive navigation, the plugin exposes previous_requests_to_qfix(), which transforms the entire request history into Vim's quickfix list:

function _99.previous_requests_to_qfix()
  local items = {}
  for _, entry in ipairs(_99_state.__request_history) do
    table.insert(items, request_entry_to_qfix_item(entry))
  end
  vim.fn.setqflist({}, "r", { title = "99 Requests", items = items })
  vim.cmd("copen")
end

This enables users to browse historical AI requests alongside their original buffer locations, leveraging Neovim's native navigation infrastructure.

Practical Examples

Example 1: Running a Search and Inspecting History

Trigger an AI search and subsequently review the request history through the quickfix interface:

-- Initiate a search operation (automatically tracked)
require("99").search({ additional_prompt = "How does Lua table.concat work?" })

-- After the AI responds, open the quickfix list to see all historical requests
require("99").previous_requests_to_qfix()

The search function internally invokes track_request, appending the operation to __request_history. Upon completion, finish_request updates the entry status, making it visible to previous_requests_to_qfix.

Example 2: Manually Accessing the History Table

For custom UI integrations, access the raw history state directly:

local _99 = require("99")
local state = _99.__get_state()            -- internal accessor
local all = state.__request_history        -- raw array of request entries

for i, entry in ipairs(all) do
  print(string.format(
    "#%d – %s – %s – %s",
    i,
    entry.context.operation or "<none>",
    entry.status,
    vim.inspect(entry.point)
  ))
end

This iterates through the chronological request history, displaying each operation's type, current status, and original cursor position.

Example 3: Clearing Completed History

Manage memory and clutter by purging finished operations while preserving in-flight requests:

require("99").clear_previous_requests()   -- removes all entries whose status ≠ "requesting"
print("Remaining in‑flight requests:", require("99").__get_state():active_request_count())

This demonstrates the cleanup workflow, ensuring that only active "requesting" entries remain in __request_history.

Summary

  • The 99 plugin implements a dual-structure request history system using __request_history (ordered array) and __request_by_id (hash map) to track every AI operation.
  • Tracking lifecycle: track_request creates entries with "requesting" status, while finish_request updates status and migrates successful tutorial/search data to dedicated collections.
  • Fast lookup: The unique xid (trace ID) from RequestContext serves as the primary key for O(1) access to any historical entry.
  • User interface: The system exposes previous_requests_to_qfix() for quickfix navigation, previous_request_count() for inventory, and clear_previous_requests() for memory management.
  • Source location: All history logic resides in lua/99/init.lua, with context creation handled in lua/99/request-context.lua.

Frequently Asked Questions

How does the 99 plugin store request history in memory?

The plugin stores request history in two complementary Lua tables within the _99_State object: __request_history maintains chronological order for iteration, while __request_by_id provides constant-time lookup using the request's unique xid identifier. This dual-structure approach enables both sequential access for UI display and instant retrieval for status updates when AI responses arrive.

What information is captured for each request entry?

Each request entry stores the original RequestContext (including operation type and buffer coordinates), a status field tracking the lifecycle from "requesting" to "success" or "error", the cursor point for navigation, and a started_at timestamp. For completed operations, successful tutorial and search results are additionally cached in separate __tutorials and __searches collections for rapid retrieval.

How can I view or clear my request history in Neovim?

To view history, call require("99").previous_requests_to_qfix() to populate the quickfix list with all historical requests and their original buffer locations. To clear completed operations while preserving in-flight requests, invoke require("99").clear_previous_requests(), which purges finished entries from __request_history and empties the __tutorials and __searches collections.

Where is the request history logic implemented in the source code?

The core request history system is implemented in lua/99/init.lua, which defines the _99_State object containing track_request, finish_request, and management utilities like previous_request_count and clear_previous_requests. The RequestContext objects that feed into this system are constructed in lua/99/request-context.lua, while operation-specific handlers in lua/99/ops/*.lua invoke the tracking methods at the appropriate lifecycle stages.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →