Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,36 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.0] — 2026-06-16

Headline: the wiki maintainer now batches related orphan triage into ONE LLM call —
same-subject keywords that share a source fact are decided together, one decision per
seed — cutting maintainer LLM calls while keeping decisions and the job lifecycle
identical to before. Also in this release: a self-hosted Gemma 4 12B LLM profile and an
env-configurable Hermes ask-timeout.

### Added

- **Batched wiki triage (clustered maintainer).** When the maintainer runs and the
claimed seed is a non-hub keyword, it also claims the other pending triage jobs whose
entity shares a source fact (sibling keywords + that fact's own job) and decides them
all in ONE agent call — one decision per seed. `run_cron` and the per-job lifecycle are
unchanged (each job still closes with its own status); a singleton, or the flag off, is
a one-seed unit identical to before. Gated by `WIKI_TRIAGE_CLUSTER` (default on, with
`WIKI_TRIAGE_CLUSTER_MAX` / `WIKI_TRIAGE_HUB_DEGREE` to tune it). Validated A/B against
the one-at-a-time path on the same snapshot: identical decision split, near-identical
wikis, ~1.6 orphans decided per LLM call, and co-occurring seeds decided independently
(no conflation).
- **`vllm_workstation_gemma12b` LLM profile.** A self-hosted vLLM profile for Gemma 4 12B;
select it with `LLM_PROFILE=vllm_workstation_gemma12b` to run the internal agent on the
local Gemma model.

### Changed

- **Hermes `MemoryProvider` ask-timeout is env-configurable.** `BRAINDB_ASK_TIMEOUT`
(default 600s, previously a hardcoded 90s) so a slow `/agent/query` LLM loop does not
time out before the agent finishes.

## [0.7.0] — 2026-06-14

Headline: BrainDB becomes a native long-term-memory backend for **Hermes Agent**
Expand Down
35 changes: 19 additions & 16 deletions braindb/agent/prompts/wiki_maintainer_prompt.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
You are the **BrainDB Wiki Maintainer**, working on exactly ONE case.
You are the **BrainDB Wiki Maintainer**. You are given a small batch of SEEDS —
usually one, sometimes a few orphan entities that share a source fact — and you
decide ONE action for EACH seed, independently.

A "wiki" is a synthesised, human-readable page (entity_type = `wiki`) about ONE
real-world subject, built from the fact/thought/source entities that are
genuinely about that subject.

Your case (THE SEED) and the numbered WIKIS catalog are at the **END** of
this prompt. Read the static rules here first, then act on the data there.
The single seed is rarely enough to decide correctly — you MUST investigate
the surrounding reality before deciding.
Your SEEDS and the numbered WIKIS catalog are at the **END** of this prompt.
Read the static rules here first, then act on the data there. A seed is rarely
enough to decide correctly — you MUST investigate the surrounding reality before
deciding, FOR EACH seed. The seeds may share context (they co-occur on a fact),
but **co-occurrence is NOT identity**: a person, a company, and a technique can
sit on one fact yet be three different subjects with three different decisions —
decide each seed on its OWN subject.

## Research FIRST with the powerful tools (this is mandatory)

Expand Down Expand Up @@ -73,7 +78,7 @@ attach/consolidate to wikis that appear in that numbered catalog. You never
see or emit a uuid; the harness maps your number back to the real wiki. If
the subject is not in the catalog, you cannot attach/consolidate to it.

## Decide ONE action for THIS seed — STRICT PRECEDENCE, in this order
## Decide ONE action PER SEED — STRICT PRECEDENCE, in this order

Evaluate top to bottom and take the FIRST that applies. `create` is the last
resort, not the default. This ordering is how the wiki set heals over time —
Expand Down Expand Up @@ -104,10 +109,13 @@ writer stage does, and it will research further.

## Output — STRICT

Finish by calling `final_answer` exactly once. Its argument is a typed
object — the tool's schema defines and validates the fields; you just fill
them (no raw JSON text, no prose):
Finish by calling `final_answer` exactly once. Its argument is a typed object
with a `decisions` array — **exactly one entry per seed** in the SEEDS list
below (do not omit a seed, do not invent seeds). The tool's schema defines and
validates the fields; you just fill them (no raw JSON text, no prose). Each
entry has:

- `entity_id` — the seed's id, copied VERBATIM from the SEEDS list.
- `action` — one of `attach`, `create`, `consolidate`, `skip`, `ambiguous`.
- `target_wiki_no` — required for `attach`: the catalog NUMBER of the wiki
(an integer from the WIKIS list at the end); null otherwise.
Expand All @@ -121,14 +129,9 @@ them (no raw JSON text, no prose):

---

## THE SEED (your one case)
## THE SEEDS (decide one action for EACH)

- entity_id: `{entity_id}`
- entity_type: `{entity_type}`
- keywords: {keywords}
- summary: {summary}
- content:
{content}
{seeds}

## WIKIS catalog (existing wikis — reference these BY NUMBER)

Expand Down
35 changes: 35 additions & 0 deletions braindb/agent/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,41 @@ def _coerce_consolidate_nos(cls, v):
return _coerce_to_list(v)


class MaintainerClusterItem(MaintainerDecision):
"""One per-seed decision inside a clustered triage. Identical to
`MaintainerDecision` (same fields + the same forgiving coercion validators,
inherited) plus the `entity_id` of the seed it applies to, so the harness
maps each decision back to its orphan."""
entity_id: str = Field(
...,
description=(
"The seed's entity_id, copied VERBATIM from the SEEDS list in the "
"prompt. Identifies which orphan THIS decision is for."
),
)


class MaintainerClusterDecision(BaseModel):
"""The maintainer's output for a triage cluster: one decision per seed. A
singleton triage is just a cluster of one (a single-element list), so this
is the maintainer's only output schema. The harness applies each item
exactly as it applied a `MaintainerDecision` before."""
decisions: list[MaintainerClusterItem] = Field(
...,
description=(
"Exactly one entry per seed in the SEEDS list — do not omit a seed "
"and do not invent seeds. Each entry is a full decision (action + "
"its action-specific fields + rationale) plus the seed's entity_id."
),
)

# Top-level coercion: accept JSON-string-of-dict (vLLM/Qwen quirk).
@model_validator(mode="before")
@classmethod
def _accept_json_string(cls, v):
return _maybe_parse_json_string(v)


class WikiWriteResult(BaseModel):
"""The wiki writer's full output. `body` is the complete markdown page —
a typed field of the schema, exactly like any other field (not loose
Expand Down
5 changes: 3 additions & 2 deletions braindb/agent/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
from braindb.agent.run_state import record_handoff, record_submit
from braindb.agent.schemas import (
AgentAnswer,
MaintainerClusterDecision,
MaintainerDecision,
SubagentResult,
WikiWriteResult,
Expand Down Expand Up @@ -1180,8 +1181,8 @@ async def submit_answer(payload: AgentAnswer) -> str:

@function_tool(name_override="final_answer", strict_mode=False)
@_verbose("final_answer")
async def submit_maintainer(payload: MaintainerDecision) -> str:
"""Submit the maintainer decision. Call this exactly once when you're done."""
async def submit_maintainer(payload: MaintainerClusterDecision) -> str:
"""Submit the maintainer decisions (one entry per seed). Call this exactly once when you're done."""
record_submit(payload)
return "ok"

Expand Down
8 changes: 8 additions & 0 deletions braindb/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,14 @@
"api_key_env": "VLLM_API_KEY",
"base_url": "http://host.docker.internal:8009/v1",
},
# Gemma 4 12B (QAT w4a16) on the workstation vLLM — a SMALLER, distinct model
# from the 31B above, on its own port. Reached over the laptop/Pi -> workstation
# tunnel to :8012.
"vllm_workstation_gemma12b": {
"model": "openai/google/gemma-4-12B-it-qat-w4a16-ct",
"api_key_env": "VLLM_API_KEY",
"base_url": "http://host.docker.internal:8012/v1",
},
}


Expand Down
2 changes: 1 addition & 1 deletion braindb/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
app = FastAPI(
title="BrainDB",
description="Memory database and REST API for LLM agents",
version="0.7.0",
version="0.8.0",
)

app.add_middleware(
Expand Down
Loading
Loading