Build sub-Linear app in sub-linear time.
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. Themain backend stores entities and supports SQL queries; the vec backend stores issue embeddings and supports vector search.
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 aLabel 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.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.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).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 labelobject_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.
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 simpleLLMSession 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.
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
Runpython 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 separateLLMSession 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:
- Create the
HB-GUIproject withupsert, omittingobject_idand letting HeavenBase hash the projectname. - Query the project and label vocabulary, then create issues
S1throughS7with due dates, labels, tags, and computed embeddings. - Patch
S1withsetso onlyassigneeandestimatechange while computed fields remain consistent. - Answer the debugging-count question from stored rows, usually by filtering
Issue.labelsfor thedebugginglabel ID. JSON specs may usearray_contains; HeavenBase also normalizescontainson array fields to the same operation. - Answer the semantic-search question with a text
nearquery againstIssue.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.

