feat(skills): integrate web-search, db-yaml, agent-cost, diataxis-docs, kb-search; retire searxng

This commit is contained in:
Andriy Oblivantsev
2026-08-10 20:50:25 +01:00
parent 390784d406
commit d4a88eead7
8 changed files with 294 additions and 0 deletions
+1
View File
@@ -0,0 +1 @@
/mnt/8TB/projects/ai/agent-skills/bin/db/psql-yq
+1
View File
@@ -0,0 +1 @@
/mnt/8TB/projects/ai/agent-skills/bin/web/search
+34
View File
@@ -0,0 +1,34 @@
---
name: agent-cost
description: >-
Measure what an agent session actually costs in tokens using bin/agents/cost.
Use before and after changing documentation, skills or context layout, and when
a session feels unexpectedly expensive.
---
# agent-cost
```bash
bin/agents/cost # every project, YAML
bin/agents/cost --repo brain-ui # only sessions whose cwd matches
bin/agents/cost --json | jq .cursor.by_tool
bin/agents/cost --snapshot after-x --repo brain-ui # append a row to docs/CONTEXT-BUDGET.md
```
Reads local session storage from OpenCode and Cursor transcripts. Reports the
always-loaded baseline, cache hit/miss/thrash, and which tools moved the most
bytes.
## How to read it
- **Baseline** is what every single message pays for: `AGENTS.md` plus anything
eagerly linked from it. Keep it small; it multiplies by message count.
- **Cache thrash** matters more than raw size. Editing a file that sits early in
the context invalidates the prompt cache for the whole session.
- **by_tool bytes** shows where the real spend is. Usually it is unfiltered
command output, not documentation.
## Rule
Measure before and after. A claim that something "reduces tokens" without a
before and an after number is an opinion, not a result.
+40
View File
@@ -0,0 +1,40 @@
---
name: db-yaml
description: >-
Read any Postgres as compact YAML through db/psql-yq, with a read-only guard and
named profiles. Use when a task needs table contents, column types or a SELECT
against cs_brain or another project database.
---
# db-yaml
`agent-skills/bin/db/psql-yq` talks to Postgres and returns YAML, which is far
cheaper than a psql ASCII table and easy to slice with `yq`.
```bash
bin/db/offline -s kunde # column list (brain-ui wrapper)
bin/db/offline -t tour -l 20 # 20 sample rows as YAML
bin/db/offline -c 'SELECT ...' # query -> YAML
bin/db/live -c 'SELECT ...' # live database instead of the snapshot
```
Ad-hoc targets without a profile:
```bash
agent-skills/bin/db/psql-yq --container my-pg --db app -c 'SELECT 1'
agent-skills/bin/db/psql-yq --dsn 'postgres://user@host:5432/db' -c 'SELECT 1'
```
## Profiles
Connection details live in `~/.config/brain/db-profiles.yml` (mode 600), never in a
project repo. A profile names either a `container` or a `host`; passwords are read
from a separate `password_env_file` and never appear in argv.
## Rules
- **Read-only.** Any `insert|update|delete|drop|truncate|alter|create|grant|
revoke|vacuum|copy` is rejected with exit 3. Do not work around it.
- **PII.** `cs_brain` holds client data. Aggregate and count freely; never copy
names or addresses into chat, issues or docs.
- Use `-l` to keep samples small. Twenty rows answer most questions.
+55
View File
@@ -0,0 +1,55 @@
---
name: diataxis-docs
description: >-
Classify and structure documentation by Diataxis (tutorial, howto, reference,
explanation) with PARA status, using frontmatter that the knowledge-base index
reads. Use when creating a document, when one file tries to do two jobs, or when
auditing a docs folder.
---
# diataxis-docs
Every markdown file answers exactly one of four questions. A file that answers
two should be split; that is the whole method.
| Type | Answers | Reader is |
|------|---------|-----------|
| `tutorial` | "teach me by doing" | learning |
| `howto` | "help me do this task" | working |
| `reference` | "tell me the facts" | looking up |
| `explanation` | "help me understand why" | studying |
## Frontmatter
```yaml
---
type: reference # one of the four above
status: current # current | archive (PARA)
related:
- docs/OPS.md
---
```
`bin/kb/index` reads this. `type` becomes a searchable column and `related`
becomes a graph edge:
```bash
bin/kb/search search "deploy" --type howto
bin/kb/search search "Stecktafel" --hop 1 # follow links and related
```
## Audit checklist
1. Does the title match the type? A reference that starts "first, open..." is a
howto wearing a disguise.
2. More than one H2 topic that a reader would search separately? Split the file.
The indexer chunks on H2, so split files also search better.
3. Is `status: archive` set on anything superseded? Archived files stay indexed
but stop competing with current ones for a reader's attention.
4. Does every explanation link the reference it explains, and vice versa? That
link is what `--hop 1` walks.
## Rule
Do not invent a fifth type. If a document does not fit, it is usually two
documents.
+45
View File
@@ -0,0 +1,45 @@
---
name: kb-search
description: >-
Deduction search over the 2dph brain (Ladybug graph: ops corpus, portfolio,
ssh hosts) with bin/kb/search instead of reading files or grepping repos.
Use whenever a question starts with "where is", "what runs on", "which file
describes", "who is", "how is X done", before opening any documentation.
---
# kb-search — deduction over facts and info
One embedded Ladybug graph (`var/kb.lbug`, read-only when queried) holding two
roots:
- **facts** — assertions backed by ≥2 independent sources (docker ps × compose
× ssh-config × docs), `confidence: confirmed`.
- **info** — descriptive/narrative leafs, searchable, never asserted.
Search = deduction: facts root first, info root second, `web-search` as the
second independent source when local roots cannot confirm. An answer is
`confirmed` only if it comes off the facts root; anything else is
`(not confirmed)`.
```bash
bin/kb/search "Matrix federation" # pointers + snippets, YAML
bin/kb/search "what runs on arc-2" --hop 1 # follow graph edges
bin/kb/search "onlyoffice postgres" --root facts # restrict to confirmed
bin/kb/search "where is cs-lexicon" --json | yq '.[].ref'
bin/kb/get <id> --body # full chunk only when needed
bin/kb/stats # index health
bin/kb/eval # recall@5 >= 0.95 gate
```
## Rules
- Search before you read. Never grep a repo for a concept the graph covers.
- `--root facts` returns only confirmed evidence-linked answers. Default shows
facts first, then info leafs clearly marked `(not confirmed)`.
- If recall looks wrong, run `bin/kb/eval`; it gates control questions and
should stay at or above 95% recall@5.
- `--hop N` follows sibling leaves, owning files, `related:` links and
vector-neighbours — that is the deduction walk, not random expansion.
- Escalate to `web-search` (the `web-search` skill) as the independent second
source when both local roots cannot confirm; never report an unconfirmed
single-source local answer as fact.
+51
View File
@@ -0,0 +1,51 @@
---
name: web-search
description: >-
Search the public web through the self-hosted SearXNG at search.ops.io
using bin/web/search. Use for German care law, SGB paragraphs, vendor
documentation and any fact that is not in our own repos - and as the second
independent source the detective method requires.
---
# web-search
```bash
bin/web/search "Pflegegrad SGB XI Einstufung"
bin/web/search "Toureffizienz" --site ticket.detective.de
bin/web/search "sqlite-vec" --category it -n 3 --json | jq -r '.results[].url'
bin/web/search "Pflegereform" --lang de --fresh year
```
## Web or knowledge base
`bin/kb/search` holds our own facts: product docs, detective business logic, the
lexicon. Go there first. Reach for `bin/web/search` when the answer is outside
our repos: legislation, vendor documentation, upstream library behaviour.
Keep the two apart. A finding is stronger when the reader can see that one
source was ours and one was not.
## PII: this query leaves the host
The search goes to external engines. Never put a client or staff identifier in
it. The tool refuses long digit runs, `Personalnummer`, `KV-Nr`, dates of birth
and street-with-number, and exits 2. Rephrase rather than reaching for `--force`.
## Read the status, not just the results
The instance answers HTTP 200 with an empty list when it throttles, so an empty
answer is ambiguous by construction. The tool resolves that for you:
| status | exit | meaning |
|--------|------|---------|
| `ok` | 0 | engines answered |
| `throttled` | 3 | nobody answered; say nothing about what exists |
Never turn a `throttled` result into "there is no information about X".
## Etiquette
Calls are serialised host-wide and kept ten seconds apart, and answers are
cached for seven days. Do not loop over queries, and do not use `--refresh`
unless the cached answer is genuinely stale: a burst suspends the engines for
several minutes for everyone.
@@ -0,0 +1,67 @@
---
type: howto
status: current
---
# Tuning the SearXNG instance
The client works around a fragile instance. These changes fix the cause, and
they need shell access to the host behind `search.ops.io`
(`90.169.228.16` / `ops.mywire.org`), which is a different machine from
the one the agents run on.
## Why it is needed
Measured on 2026-08-10 from this host:
- The default engine set for the `general` category is only `duckduckgo`,
`brave` and `startpage`. `brave` and `startpage` sit in
`Suspended: too many requests` or `Suspended: CAPTCHA` almost permanently, so
in practice a single engine carries every query.
- About 25 probe requests over a few minutes pushed `duckduckgo` into `CAPTCHA`
as well. The instance then answered HTTP 200 with `results: []` and an empty
`unresponsive_engines` - indistinguishable from "nothing found" without the
client-side handling we added.
- Recovery took roughly six minutes.
## Changes
1. **Allow our egress IP through the limiter.** In `limiter.toml`:
```toml
[botdetection.ip_lists]
pass_ip = ["77.7.46.234"]
```
2. **Shorten the suspensions.** In `settings.yml` the defaults are 24 hours for
a CAPTCHA and one hour for too-many-requests, which is far longer than the
condition lasts:
```yaml
search:
suspended_times:
SearxEngineCaptcha: 300
SearxEngineTooManyRequests: 120
SearxEngineAccessDenied: 300
```
3. **Give `general` more than one working engine.** `google` and `wikipedia`
report `enabled: true` in `/config` yet never appear in a `general` response,
so they are not in the default set. Put them in it; one live engine per
category is a single point of failure.
4. **Keep the JSON API on.** `formats: [html, json]` must stay, otherwise every
client here breaks.
## Verifying
Ten requests in a row used to suspend the instance for minutes. After the
change they should all answer:
```bash
for i in $(seq 10); do
bin/web/search "test $i" -n 1 --refresh --json | jq -r .status
done
```
Ten lines of `ok` means it is fixed.