#4501·hindsight

Provider Request: Cursor's agent CLI (agent / cursor-agent) as an LLM provider

Author: M-r-ACreated Sep 18, 2026Updated Sep 18, 2026
Labelsenhancement

Use Case

I run a self-hosted Hindsight and want it to do its internal work — retain-time extraction, consolidation and reflect — against an existing Cursor subscription, the same way github-copilot, claude-code and openai-codex already let an existing subscription serve as the backend.

The appeal is the same as those three: no new per-token billing relationship, and one vendor already in use rather than another to procure. Cursor ships a documented headless mode (cursor-agent -p --output-format json), so it is at least mechanically drivable from a server.

Worth stating explicitly to avoid confusion with existing work: this is not about the existing Cursor integration (hindsight-integrations/cursor, hindsight-integrations/cursor-cli), which makes Cursor a client that calls Hindsight. This is the opposite direction — Cursor as a model backend that Hindsight calls.

Problem Statement

There is no way to configure this today, and no supported workaround.

1. cursor is not a recognised provider. create_llm_provider() in engine/llm_wrapper.py dispatches on a hard-coded if/elif chain over the lowercased provider name and falls through to a raise. Calling it with provider="cursor" inside the container gives:

ValueError: Unknown provider: cursor

2. The OpenAI-compatible provider does not help here — I want to flag this early, since it's the natural first suggestion, and it's the workaround that was offered on #1449 for GitHub Models. Cursor has no chat-completions endpoint to point HINDSIGHT_API_LLM_BASE_URL at, and Cursor states this themselves.

From https://cursor.com/docs/api:

"The Cloud Agents API and SDKs run Cursor agent workflows (workspace context, tools, commands, and edits). They are not a standalone model-inference or chat-completions API."

From https://cursor.com/docs/sdk/typescript:

"The Cursor SDK is an agent SDK, not a standalone model-inference or chat-completions API. Router picks models for Cursor agent runs that can reason over a workspace, call tools, run commands, and edit files. Cursor does not currently document a raw Router endpoint for arbitrary model calls."

The public surface is agent-shaped — the Cloud Agents API on api.cursor.com creates and manages agent runs — not inference-shaped.

3. LLM providers are not an extension point. The image does expose class-path extension slots via env vars — HINDSIGHT_API_TENANT_EXTENSION, HINDSIGHT_API_HTTP_EXTENSION, HINDSIGHT_API_MCP_EXTENSION, HINDSIGHT_API_OPERATION_VALIDATOR_EXTENSION, HINDSIGHT_API_FILE_STORAGE_EXTENSION, HINDSIGHT_API_VECTOR_EXTENSION, HINDSIGHT_API_TEXT_SEARCH_EXTENSION and HINDSIGHT_API_MEMORIES_EXTENSION are all referenced in the package — but none of them is an LLM provider slot, and llm_wrapper.py contains no importlib, import_module, pkgutil or entry_points usage at all. So the only route open to a user today is patching the image and maintaining a fork across releases.

How This Feature Would Help

It would let a Cursor subscriber point Hindsight at capacity they already pay for, without adding a per-token vendor — which is precisely the gap github-copilot, claude-code and openai-codex already close for their respective subscriptions. For anyone whose per-token budget is ending but whose editor subscription continues, that is the difference between keeping Hindsight running and switching it off.

Proposed Solution

A CursorLLM implementing LLMInterface (which has exactly four abstract methods: verify_connection(), call(), call_with_tools(), cleanup()).

There is already good precedent for driving a subscription CLI, and github_copilot_llm.py looks like the closest template — it runs mode="copilot-cli" through CopilotClient from the copilot package, which drives the CLI as a child process (subprocess.Popen in copilot/client.py, with an FFI host offering a Popen-shaped alternative over a native library).

Notably it still obtains structured output, by constraining the model to a single synthetic tool: it sets available_tools = [f"custom:{_STRUCTURED_TOOL_NAME}"] where _STRUCTURED_TOOL_NAME = "structured_response", instructs the model that it "MUST call the 'structured_response' tool exactly once. Do not answer with prose.", and then reads the answer out of invocation.tool_calls. codex_llm.py uses the same forced-tool idea over HTTP. So a CLI-backed provider is not inherently second-class in this codebase — but note that this technique depends on the CLI exposing a tool interface at all, which is where Cursor differs (below).

Sketch of the change:

  • providers/cursor_llm.py implementing the four abstract methods
  • export in providers/__init__.py
  • an elif provider_lower == "cursor": branch in create_llm_provider()
  • an entry in PROVIDER_DEFAULT_MODELS, and the id added to the valid_providers list that LLMProvider.__init__ checks
  • docs entry in hindsight-docs/src/data/llmProviders.json

Invocation would be cursor-agent -p --output-format json --model <model>, parsing .result from the single emitted {"type":"result",...} object. Auth comes from CURSOR_API_KEY or the CLI's existing login. (Cursor's docs now write the command as agent; on my install agent and cursor-agent are both symlinks to the same binary, so either name works — I use cursor-agent here because it is unambiguous.)

The one real gap is that Cursor's CLI gives less to work with than Copilot's, and it affects both remaining interface methods:

  • No caller-facing tool or schema surface, so structured output would have to be soft. cursor-agent --help exposes no response_format, JSON-schema or temperature flag, and no flag for supplying tool or function definitions — only --output-format text|json|stream-json, which describes the envelope: result carries free-form assistant text. The only mentions of tools in its help text refer to Cursor's own built-ins that the agent runs itself. With no way to declare a tool and read back its arguments, the forced-tool technique above is unavailable, leaving the weaker approach claude_code_llm.py takes — inject "You must respond with valid JSON matching this schema:" into the prompt, then json.loads the reply with a JSONDecodeError retry.
  • call_with_tools() has no clean mapping, for the same reason: the headless mode gives the caller no way to define tools and receive tool calls back.

Given the second point, a reasonable first cut might scope the provider to retain and consolidation via the per-operation settings (HINDSIGHT_API_RETAIN_LLM_PROVIDER, HINDSIGHT_API_CONSOLIDATION_LLM_PROVIDER) and leave reflect to another provider, rather than trying to support every operation on day one.

Alternatives Considered

  • OpenAI-compatible provider pointed at Cursor — not possible; no such endpoint exists (see Problem Statement, with Cursor's own wording).
  • A local shim exposing /v1/chat/completions that shells out to cursor-agent — needs no changes to Hindsight, but has to synthesise structured-output support and manage a subprocess per call, so it is the least maintainable option rather than the easiest.
  • Local OpenAI-compatible server (Ollama / vLLM / llama.cpp container) with HINDSIGHT_API_LLM_STRICT_SCHEMA=true — this works today and is what I am using in the meantime. It answers the cost problem, just not "reuse the Cursor subscription."
  • openai-codex / claude-code / gemini — all viable if the corresponding subscription or key exists.

Priority

Important - affects my workflow

Additional Context

Environment: self-hosted ghcr.io/vectorize-io/hindsight:latest in Docker, currently HINDSIGHT_API_LLM_PROVIDER=github-copilot. cursor-agent version 2026.09.15-d2fe57e.

Checklist

  • I would be willing to contribute this feature