Skip to main content
Build sub-Linear app in sub-linear time.
In this workshop, you build Sublinear from scratch: a project and issue tracker where one agent can create records, update work, answer status questions, and run semantic issue search through HeavenBase MCP tools. The whole app is one Python file. It defines the data model, drops and recreates the demo workspace for repeatable runs, seeds allowed labels, then lets the agent operate the workspace through MCP.
Set OPENROUTER_API_KEY before running the workshop. By default, Sublinear uses OpenRouter for both chat (deepseek/deepseek-v4-flash) and embeddings (openai/text-embedding-3-small), which may incur small usage costs (< $0.01).

1. Initialization

Start with imports, constants, and backend configuration. For simplicity, we only use SQLite + In-memory backends, but you can swap in other backends or add more as needed. The main backend stores entities and supports SQL queries; the vec backend stores issue embeddings and supports vector search.
Then for convenience, we start by defining some helper functions including label normalization, priority ranking, text embedding, and issue text construction. The workshop uses the embed preset, which resolves to OpenRouter’s OpenAI-compatible openai/text-embedding-3-small route by default. You can switch it to embed-local to run small embedding models locally on Ollama / LM Studio / oMLX / vLLM.

2. Entity Definition

To build an app from scratch, start by clarifying your data model in a declarative fashion similar to Pydantic. In our design of sublinear, we have projects, milestones, and issues. Projects group work and set goals. Milestones are project checkpoints that can be used for progress tracking. Issues are the individual work items that can be assigned, labeled, tagged, and embedded for semantic search. We also have a Label entity to store the allowed label vocabulary and a View entity to store saved filters and display preferences. The Label entity:
Use .compute to annotate fields that are computed from an arbitrary Python function. The function will take the initialization inputs as keyword arguments and return the transformed value that will be stored on the field.
The Milestone entity:
The Project entity:
Here we see a new concept strategy, which is used to determine how the field is stored on the backend. Even if the same type storing in the same backend, different strategies will result in different physical storage layouts. For example, for the hb.SideTable strategy, the array will be stored as a separate table with a foreign key column to the main table, and each item will be stored as a separate row.
The View entity:
As a comparison, display is also an array field, but it does not specifies strategy, so it uses the default hb.InlineColumn strategy for arrays, which is a simple inline column in the main table (as TEXT, JSON/JSONB or ARRAY according to the specific backends).
The Issue entity (the most complex one):
Here we see another new concept query_compute, which is used to transform the query-time arguments. For example, when writing query emb.near("Hello"), the query args "Hello" will be processed by the embed_text and become a vector to match to the nearest embeddings.

3. Workspace Construction

Create the workspace, register the entities, and seed the label vocabulary. The agent queries these rows and stores label object_id values on projects and issues. Label and tag arrays are routed to SQLite side tables, so analytical filters such as labels.array_contains("debugging") work without a separate search backend.
The setup calls sublinear_workspace(reset=True), which drops the demo workspace before each run. Keep this reset while following the workshop, and remove it when you want Sublinear data to persist.
Now, let’s seed the workspace with some label vocabulary.

4. Sublinear Agent

When the workspace is constructed, we can directly expose it to the agent through MCP. While any Harness (Claude Code, Codex, Copilot, etc.) can be used to create the agent, we use HeavenBase’s simple LLMSession to create the agent. To add MCP to a session, use session.add_mcp(...). Meanwhile, we can use a simple system prompt to guide the agent.
Now sublinear is already an agentic function that can answer questions and execute commands over the Sublinear workspace. The agent sees one MCP surface in each session: the HeavenBase workspace toolkit. It can write records, query structured rows, and run semantic near searches until the session ends with a content or reaching max_tool_turns. Here are the list of interfaces available to the agent (same as the HeavenBase MCP page): Among the operations, upsert is best for creates and full-row replacement. For agent edits, set is more convenient because it patches only the changed fields after the agent queries the existing row. For semantic search, the important point is that the model sends text, not vectors. The query_compute(embed_text) hook on Issue.emb turns the query string into a vector before HeavenBase routes the near operation to vec; HeavenBase then hydrates the matching issue rows from SQLite. Example semantic search query:

5. Try It Out

Run python workshops/sublinear/sublinear_app.py from the docs repo root to try the sublinear agent. The following script sends five user requests to Sublinear. Each request uses a new session, but all sessions share the same persistent HeavenBase workspace. The requests include: initializing a project, adding issues, self-assigning an issue, counting debugging issues, and semantic searching for “launch readiness, debugging, and final polish”.

5.1. Expected Behavior

The five calls intentionally use separate LLMSession instances. This shows that the agent is not relying on chat memory; each turn recovers context by querying the same HeavenBase workspace through MCP. During the run, the agent should:
  1. Create the HB-GUI project with upsert, omitting object_id and letting HeavenBase hash the project name.
  2. Query the project and label vocabulary, then create issues S1 through S7 with due dates, labels, tags, and computed embeddings.
  3. Patch S1 with set so only assignee and estimate change while computed fields remain consistent.
  4. Answer the debugging-count question from stored rows, usually by filtering Issue.labels for the debugging label ID. JSON specs may use array_contains; HeavenBase also normalizes contains on array fields to the same operation.
  5. Answer the semantic-search question with a text near query against Issue.emb, returning the closest related issues with their scores.
A successful run creates the HB-GUI project, adds seven issues with inferred label IDs, assigns S1 to Bob with set, answers that the debugging work includes S3 and S6, and uses near.query text on the vector field to rank launch/debugging/polish related issues such as S6, S7, and S2.

5.2. Example Replies

Your model may phrase the replies differently, but a successful run should be this concrete:
The important part is not the exact wording of the responses. The app demonstrates that one agent can manipulate structured records, compute fields, route vectors to the vector backend, and query the same workspace for analytical answers.

Further Exploration

Related resources:
  • HeavenBase MCP - expose a workspace over MCP
  • First LLM - configure the chat preset used by Sublinear
  • First MCP - persist and reuse registered Toolkits
  • Entities - class syntax, defaults, compute Hooks, and logical types
  • Routing - field-level backend placement
  • Query - JSON near queries and filtering