Cognitive briefs and capture pipeline¶
End-to-end user guide for the daily brief, weekly synthesis, and the Telegram + Readwise capture adapters. Together they turn SecondBrain from "a thing you query" into "a thing that surfaces patterns at you each morning."
Origin. Inspired by CyrilXBT's four-layer Obsidian + Claude system. SB already had richer Capture / Pipeline / Storage layers; this guide covers the Layer 4 (Intelligence) work plus the two Layer 1 capture adapters that closed the gap.
TL;DR¶
| What | Command | When |
|---|---|---|
| Daily morning brief | sb today |
Manual, or routine today-cognitive-brief at 06:00 daily |
| Weekly synthesis | sb week |
Manual, or routine week-synthesis at 06:00 Sunday |
| Persist brief to vault for compound recall | --write-vault |
Add to either command (the routines pass it automatically) |
| Capture from Telegram | Send any message containing #capture |
Real-time |
| Capture from Readwise | POST /inbound/readwise (Readwise webhook target) |
Real-time |
| Use Obsidian as the editor | Open $SB_VAULT_DIR as an Obsidian vault |
One-time setup |
| Ingest an existing Obsidian vault | sb obsidian ingest (or sb obsidian watch) |
Manual / blocking |
Briefs land in vault/00_inbox/briefs/ and are auto-ingested back into memory,
so each brief becomes searchable context for the next one — the compound effect.
Obsidian opens that same directory natively, so briefs are readable in Obsidian
the moment they're written. See Obsidian integration.
What this gives you¶
Before this work, sb today produced an Antahkarana operator brief:
priorities, commitments, memory health, identity themes. It was useful, but
it never answered the three questions a "morning thinking partner" should:
- Are there surprising connections between the things I've captured recently and older notes?
- What pattern keeps re-appearing that I haven't named yet?
- What one question should I be holding today?
Now sb today answers all three. And there's a sibling sb week that, on a
7-day window, surfaces:
- The emerging thesis — what is this week actually about?
- The contradictions — what beliefs of mine are in tension, with the actual record IDs?
- The knowledge gaps — what topics keep coming up without me deciding anything?
Sources are real: cross-community wiki bridges (WikiGraph), compound memory
clusters (CompoundMemorySynthesizer), tracked Chitta tensions
(ChittaEvalHarness.eval_contradictions). No model hallucination of "patterns"
that don't exist.
Prerequisites¶
- A working SecondBrain install (see getting-started/index.md).
- A vault at
$SB_VAULT_DIR(defaults tovault/in the repo). - For LLM-rephrased questions and thesis text, at least one provider key configured (see provider-keys.md). Both commands work fully without an LLM — the deterministic fallbacks are still useful.
- For the Readwise adapter and Telegram
#capture:sb serverunning and a bearer token inSB_CHANNEL_TOKEN.
Daily brief — sb today¶
sb today # full LLM-synthesised brief (Rich panel)
sb today --no-llm # deterministic structured output
sb today --json # raw context dict — pipe into jq / scripts
sb today --write-vault # also persist to vault/00_inbox/briefs/
What's in the output¶
The deterministic render now appends three sections after the existing operator-state ones:
SURPRISING CONNECTIONS (3)
Voyage embeddings ↔ Ingest pipeline rework (cross-community link (concept ↔ event))
Telegram capture ↔ Open loops review (cross-community link (entity ↔ workflow))
Readwise highlights ↔ Wiki synthesis (cross-community link (entity ↔ concept))
CROSS-CAPTURE PATTERN (2 cluster(s))
[synthesis] Compound insight across 5 memories (topics: platform, launch, readiness, review): …
[synthesis] Compound insight across 4 memories (topics: meeting, decision, owner): …
topics: platform, launch, readiness, review, meeting, decision
OPEN QUESTION
What's blocking the migration cutover decision?
[from open_loop · vault/01_projects/data-platform/migration.md:42]
When an LLM provider is configured, the synthesised brief incorporates these
sections according to the prompt at
brain/prompts/specs/briefing/today.brief.v1.yaml — see the 2. SURPRISING
CONNECTIONS / 3. CROSS-CAPTURE PATTERN / 6. ONE OPEN QUESTION instructions in
the system message.
JSON shape (relevant new keys)¶
{
// ... existing today_cmd keys (pending_actions, commitments, memory_health, …)
"connections": [
{
"source_slug": "voyage-embeddings",
"source_title": "Voyage embeddings",
"source_type": "concept",
"target_slug": "ingest-pipeline-rework",
"target_title": "Ingest pipeline rework",
"target_type": "event",
"why": "cross-community link (concept ↔ event)"
}
],
"pattern": {
"insights": ["[synthesis] Compound insight across 5 memories…"],
"cluster_sizes": [5, 4],
"sample_topics": ["platform", "launch", "readiness", "review"]
},
"open_question": {
"question": "What's blocking the migration cutover decision?",
"source": "open_loop",
"detail": "vault/01_projects/data-platform/migration.md:42"
}
}
Where each section comes from¶
| Section | Primitive | File |
|---|---|---|
connections |
WikiGraph.surprising_connections() (Louvain communities + edge-mismatch scoring) recency-filtered to last 7d |
brain/brief/connections.py |
pattern |
CompoundMemorySynthesizer.synthesize_dry() over recent Chitta memories — clusters + LLM/deterministic insight, never persisted back |
brain/brief/pattern.py |
open_question |
Highest-priority OPENLOOP/TODO (no wikilink → ungrounded) → optional LLM rephrase. Falls back to top decision-watchlist row, then a generic prompt |
brain/brief/open_question.py |
Weekly synthesis — sb week¶
sb week # rendered Markdown panel
sb week --no-llm # deterministic only
sb week --json # raw context dict
sb week --window-days 14 # custom lookback (default 7)
sb week --write-vault # persist to vault/00_inbox/briefs/YYYY-Wnn-weekly.md
Sample output (deterministic mode)¶
# Weekly synthesis — 2026-W19
## Emerging thesis
Recurring themes this week: ingest, readiness, lakehouse, migration, decision.
_Recurring themes: ingest, readiness, lakehouse, migration, decision, owner_
## Contradictions
- **Decision "lakehouse cutover Q3" conflicts with stale commitment to "freeze Q3 platform changes"** _(severity 0.78)_
- **Memory m_142 says "Voyage embeddings default" while m_198 says "bge-m3 default"** _(severity 0.55)_
## Knowledge gaps
- **lakehouse** — Mentioned 9× in recent captures, no decision recorded _ingest_count=9_
- **figure out the migration cutover timing** — Open loop without a [[wiki link]] — no anchored context _vault/01_projects/data-platform/migration.md:42_
Why "real" contradictions¶
ChittaEvalHarness.eval_contradictions() already computed top_tensions —
each entry has a description, severity, and the two memory IDs in conflict.
Until now, only the count was exposed in sb today's memory_health.unresolved_tensions.
sb week exposes the records themselves, so you see what the contradictions are.
Knowledge gaps use two heuristics, both deterministic:
1. Ungrounded open loops. A TODO: or OPENLOOP: whose body has no
[[wikilink]] — it has no anchored context.
2. Recurring undecided topics. A word seen ≥3× in recent ingest events that
does NOT appear as a token in any decision-catalog title.
The compound effect — persisting briefs to vault¶
Both commands accept --write-vault. When set:
| Command | File |
|---|---|
sb today --write-vault |
vault/00_inbox/briefs/YYYY-MM-DD-daily.md |
sb week --write-vault |
vault/00_inbox/briefs/YYYY-Wnn-weekly.md |
The repo's existing PostToolUse hook on Edit/Write under vault/ auto-ingests
these files into the vector store. So each brief becomes searchable context for
the next one — month-1 captures show up as "surprising connections" or
"recurring topics" in month-3 briefs without any extra wiring.
If you don't want this, drop --write-vault; the brief still prints to the
terminal and to the event log.
Scheduling — running the briefs automatically¶
Two routine templates ship with the package:
sb routines templates list # see all templates
sb routines templates show today-cognitive-brief # full prompt + deploy command
sb routines templates show week-synthesis
Each template is just YAML + a Claude prompt; deployment uses the standard
Claude Code /schedule command (the runtime executes on Anthropic's
infrastructure, not a local cron daemon). After running templates show, copy
the printed /schedule line into a Claude Code session.
| Template | Cron | What the prompt does |
|---|---|---|
today-cognitive-brief |
0 6 * * * (daily 6am) |
Runs sb today --json --write-vault, validates the three new sections are present, replies with the open question + top connection |
week-synthesis |
0 6 * * 0 (Sunday 6am) |
Runs sb week --json --write-vault --window-days 7, replies with the thesis + counts of contradictions / gaps |
Both templates use claude-sonnet-4-7-20251001. To swap models, edit the YAML
under brain/routines/templates/.
Capture adapters¶
Telegram #capture¶
Wire a Telegram bot through the gateway as you normally would (see
guides/gateway.md). Then any message containing
#capture and not starting with a slash command gets routed to
/inbound/ingest instead of through the normal gateway.
Example interactions:
You: airline crew briefings are a great metaphor for sb today #capture
Bot: captured (7 chunks)
You: /ask what was that capture from yesterday?
Bot: (normal gateway flow — slash commands are NOT intercepted, even if they
happen to mention #capture)
You: #capture
Bot: captured (1 chunks) # falls back to "(empty capture)" body
Implementation lives in brain/channels/capture.py (a sync helper module so
it's unit-testable) and is called from brain/channels/telegram.py's
_handle_message. The routing decision uses is_capture_message():
def is_capture_message(text: str) -> bool:
if not text:
return False
stripped = text.strip()
if stripped.startswith("/"):
return False
return "#capture" in stripped.lower()
The captured payload is written to
vault/00_inbox/telegram-<chat_id>-<message_id>.md and runs through the
normal ingest pipeline (auto-routing applies).
Required env vars¶
| Variable | Purpose |
|---|---|
SB_CHANNEL_TOKEN |
Bearer token the Telegram adapter sends to /inbound/ingest |
SB_SERVE_BASE_URL (optional) |
Base URL for sb serve. Defaults to http://127.0.0.1:8765 |
Readwise webhook¶
POST /inbound/readwise accepts Readwise's export-style payload directly. It
writes one Markdown file per highlight under
vault/00_inbox/readwise/<book-slug>/<highlight-id>.md with frontmatter, then
runs a single ingest pass over the book directory.
Wiring it up in Readwise¶
- In Readwise, go to Settings → Export → Custom Webhook.
- Set the URL to
https://<your-host>/inbound/readwise. - Set
Authorization: Bearer <SB_CHANNEL_TOKEN>in the webhook headers. - Choose the export-style payload format.
Local smoke test¶
export SB_CHANNEL_TOKEN=test-token # any non-empty value enables the endpoint
sb serve & # starts on http://127.0.0.1:8765
curl -X POST http://127.0.0.1:8765/inbound/readwise \
-H "Authorization: Bearer $SB_CHANNEL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"books": [{
"title": "Thinking in Systems",
"author": "Donella Meadows",
"category": "books",
"highlights": [
{"id": 101, "text": "Stocks change as flows fill or drain.",
"note": "core idea", "location": 42,
"highlighted_at": "2026-05-09T12:00:00Z"},
{"id": 102, "text": "Feedback loops are the dynamics, not the structure.",
"location": 88, "highlighted_at": "2026-05-10T09:00:00Z"}
]
}]
}'
# → {"ok": true,
# "message": "Ingested 2 highlight(s) across 1 book(s)",
# "data": {"highlights_added": 2, "books": [...], "chunks_added": …}}
ls vault/00_inbox/readwise/thinking-in-systems/
# → 101.md 102.md
Each generated file looks like:
---
source: readwise
book: Thinking in Systems
author: Donella Meadows
category: books
highlight_id: 101
location: 42
highlighted_at: 2026-05-09T12:00:00Z
received_at: 2026-05-10
---
Stocks change as flows fill or drain.
> _Note:_ core idea
The endpoint is idempotent at the file level — re-posting the same highlight ID is a no-op (the file already exists, the body is skipped).
Health check¶
curl http://127.0.0.1:8765/inbound/health
# → {"enabled": true, "endpoints": ["/ingest", "/alert", "/meeting", "/readwise"]}
Obsidian integration¶
The vault is plain markdown, so Obsidian "just works" alongside SecondBrain. There are two valid setups — pick whichever matches your existing workflow.
Pattern A — point Obsidian at SB's vault (recommended for new users)¶
Open the SB vault directory itself as an Obsidian vault. Both tools see the same files, so:
- Briefs written by
sb today --write-vaultandsb week --write-vaultshow up immediately in Obsidian (they're in00_inbox/briefs/and use real Markdown, including wikilink-friendly anchors). - Notes you create in Obsidian land in the same
vault/tree — the existingPostToolUsehook auto-ingests them, so Obsidian-authored notes participate in the next morning'sconnectionsandpatternsections.
Setup (one-off):
echo $SB_VAULT_DIR # → vault by default
# In Obsidian:
# File → Open vault → Open folder as vault → choose the path printed above
That's it. Recommended Obsidian settings:
- Files & Links → Detect all file extensions: ON (so
.canvasfiles SB ingests are visible). - Core plugins → Backlinks / Outline / Daily notes: ON — they pair well with how SB structures the vault (
00_inbox/,01_projects/,03_decisions/,04_meetings/, etc.). - Daily notes location: point to
00_inbox/so your daily-note captures flow through SB's ingest hook automatically.
Pattern B — keep your existing Obsidian vault, point SB at it¶
If you already have an Obsidian vault you don't want to move, configure the SB connector to read from it:
# In your SB config (~/.config/secondbrain/config.yaml or repo .secondbrain/config/config.yaml)
connectors:
obsidian:
enabled: true
vault_path: ~/Documents/MyVault
skip_dirs: [.obsidian, .trash, .git]
Or via env / CLI tools that produce the same config.
Then ingest:
sb obsidian status # confirm SB sees the vault
sb obsidian ingest # full pass
sb obsidian ingest --incremental # last 24h only
sb obsidian ingest --incremental --hours 48 # last 48h
sb obsidian watch --interval 300 --hours 1 # blocking poll loop
sb obsidian watch is the simplest "live ingest" option — leave it in a tmux
pane and any note you save in Obsidian gets indexed within --interval
seconds.
In Pattern B, your daily/weekly briefs still live under $SB_VAULT_DIR (which
is separate from your Obsidian vault). You have two sub-options:
| You want | Do this |
|---|---|
| Briefs visible in Obsidian | Point $SB_VAULT_DIR at a subdirectory of your Obsidian vault, e.g. ~/Documents/MyVault/SecondBrain/. Briefs land there and Obsidian sees them |
| Briefs separate from Obsidian | Leave default. Briefs live in vault/00_inbox/briefs/; query them via sb ask instead |
What SB now understands from Obsidian¶
The ingest pipeline preserves and indexes Obsidian-specific markup. Every chunk written to the vector store carries:
fm_*fields — selected scalar / list YAML frontmatter keys (tags,type,source,aliases,category,author). Lists are JSON-encoded asfm_<key>_jsonso the vector backend stays scalar-typed.wikilinks_json— page targets from[[Page]],[[Page|alias]],[[Page#heading]],[[Page#^block-id]]. Aliases and anchors are stripped; the link is to the page. Per-block-id orphan links ([[#^id]]) are ignored since they have no target.- GFM tasks —
- [ ](unchecked),- [/](in-progress),- [!](important) are real open loops that participate insb open-loops,sb today'ssurface_open_question, andsb week'ssurface_knowledge_gaps.
Programmatic access from Python:
from brain.ingest.parser import parse_markdown
from brain.ingest.chunker import chunk_document
doc = parse_markdown(Path("vault/01_projects/x.md"))
chunks = chunk_document(doc)
for c in chunks:
print(c.wikilinks) # ['platform-readiness', 'migration-cutover']
print(c.extra_metadata) # {'tags': ['obsidian', 'sb'], 'status': 'draft', ...}
In retrieval, you can post-filter on wikilinks_json or fm_tags_json (the
JSON columns are stored verbatim in the vector store metadata).
Verifying the round-trip¶
A quick end-to-end check that Obsidian and SB are sharing state:
# 1. Create a note in Obsidian under 00_inbox/ named "obsidian-roundtrip.md"
# with body:
# ---
# tags: [obsidian, roundtrip]
# status: testing
# ---
# # Roundtrip
# - [ ] figure out next thing for [[platform-readiness]]
# - [!] urgent decision on [[migration-cutover]]
# 2. Wait ~5s for the auto-ingest hook to fire, then:
sb open-loops | grep "next thing" # GFM `- [ ]` surfaces
sb open-loops | grep "urgent decision" # `- [!]` surfaces with TASK_IMPORTANT priority
sb today --no-llm --json | jq '.open_question' # picks up the highest-priority task
If the open loop doesn't appear, check ~/.claude/settings.json for the
PostToolUse hook on Edit|Write — it's the mechanism that auto-ingests
markdown writes under vault/.
Obsidian + capture adapters¶
The two work together neatly:
- A Telegram message containing
#capture→vault/00_inbox/telegram-…md→ visible in Obsidian's file explorer + searchable via Obsidian search. - A Readwise highlight →
vault/00_inbox/readwise/<book>/<id>.mdwith rich frontmatter — Obsidian's properties view renders the frontmatter as a structured table, so you can scan highlights by book/author without leaving Obsidian.
What you get out of the box now¶
After the gap-fix pass (see the Obsidian gaps section below), the round-trip is:
| Direction | Feature |
|---|---|
| Obsidian → SB | YAML frontmatter (tags, aliases, status, custom fields) propagates into chunk metadata, so vector queries can filter by fm_tags / fm_status / etc. |
| Obsidian → SB | [[Page]] / [[Page\|alias]] / [[Page#heading]] wikilinks are extracted per chunk (chunk.wikilinks) and surfaced as wikilinks_json on every vector record |
| Obsidian → SB | GFM tasks - [ ], - [/] (in-progress), - [!] (important) appear in sb open-loops — completed - [x] are skipped. Resolving an open loop rewrites the marker to - [x] instead of DONE: when the source was a GFM checkbox |
| SB → Obsidian | All briefs ship with YAML frontmatter (type, brief_type, tags: [sb/brief/...], counts) that the Properties view renders as structured fields |
| SB → Obsidian | Daily brief is no longer code-fence-wrapped — body renders as proper markdown, not as a code block |
| SB → Obsidian | Weekly brief evidence renders as [[path/to/note.md#L42\|note.md:42]] wikilinks → click to jump to the source line |
| SB → Obsidian | Readwise highlights ship with tags: [readwise, readwise/<category>, book/<slug>] → highlights show up in the Obsidian tag pane. A per-book index.md is auto-created so the per-highlight [[readwise/<slug>/index]] backlinks resolve |
| SB → Obsidian | Telegram captures (anything posted to /inbound/ingest with text) get auto-wrapped frontmatter (source: telegram, tags: [capture, capture/telegram]) — no double-wrap if the body already starts with --- |
| SB → Obsidian | Meeting saves include type: meeting, participants: [...], action_item_count, action items rendered as - [ ] [[owner]]: … GFM tasks (clickable in Obsidian) |
What's still NOT integrated¶
- No Obsidian plugin that calls SB commands from inside Obsidian's UI. If
you want one-click "ingest current note", install the Obsidian Shell
commands community plugin and bind it to
sb ingest <vault>/<current-file>. .canvasfiles are listed insb obsidian ingestbut parsed as plain text (not as JSON node-graphs). Canvas content is searchable as raw JSON; semantic canvas understanding is a follow-up.- WikiGraph still uses SB's
vault/wiki/pages, not Obsidian's[[…]]graph. Wikilinks are now extracted at chunk time (chunk.wikilinksandwikilinks_jsonon every vector record), so retrieval can filter by them — but the daily brief'ssurface_connectionsstill computes its bridges from the explicitvault/wiki/page graph. Wiringchunk.wikilinksintoWikiGraph.build()so Obsidian-authored links count as community edges is the next step. - Dataview queries are stored as text and never executed.
- Embeds
![[Image]]are not resolved — image references are stored as text but the binary isn't followed.
End-to-end walkthrough¶
A new SecondBrain user setting up the full stack would do this:
# 1. Install + configure (skip if you already have SB running)
make quickstart
# 2. Run the brief manually so you understand what it shows
sb today --no-llm
sb today --no-llm --write-vault # also persist
# 3. (Optional) Add provider keys for richer LLM-synthesised briefs
scripts/sb-secrets set ANTHROPIC_API_KEY "..."
sb today # now uses LLM synthesis
# 4. Set up scheduling — copy the deploy command into a Claude Code session
sb routines templates show today-cognitive-brief
sb routines templates show week-synthesis
# 5. Enable inbound channels
export SB_CHANNEL_TOKEN="$(openssl rand -hex 32)"
sb serve &
# 6. Wire Readwise webhook to /inbound/readwise (Settings → Export)
# 7. Wire Telegram bot through gateway (see guides/gateway.md), then
# test #capture by sending yourself a message:
# "platform readiness review notes #capture"
# 8. The next morning, sb today (and the routine) will surface
# your highlights and Telegram captures as fresh connections + patterns.
Troubleshooting¶
| Symptom | Cause | Fix |
|---|---|---|
connections: [] even though wiki has many pages |
Recency filter — no wiki page touched in last 7d | Either ingest something fresh, or relax the window via the window_days param if you call surface_connections programmatically |
pattern.insights: [] despite many memories |
CompoundMemorySynthesizer.min_cluster_size = 3 and your recent memories don't cluster |
Lower min_importance filter or wait for more memories. synthesize_dry returns [] rather than failing |
open_question falls back to the generic prompt |
No open loops AND no decision watchlist rows | Capture some OPENLOOP: markers in any vault .md file |
sb week thesis is empty |
Empty event log AND empty Chitta memory | Ingest some content; sb week needs ≥1 event in the window |
Routine templates don't appear in templates list |
YAML parse error | sb routines templates list logs warnings — check the template YAML matches the shape of daily-ingest-sweep.yaml |
/inbound/readwise returns 503 |
SB_CHANNEL_TOKEN is unset |
export SB_CHANNEL_TOKEN=… and restart sb serve |
/inbound/readwise returns 401 / 403 |
Wrong or missing Bearer token | Match the Authorization: Bearer … value to SB_CHANNEL_TOKEN |
/inbound/readwise returns 400 |
Empty books array |
Provide at least one book with at least one highlight |
Telegram message contains #capture but doesn't get routed |
Message starts with / |
Slash commands are intentionally exempt — strip the / if you meant to capture |
| Brief is written but doesn't appear in next brief's connections | Hash store dedupe | The vault hash store skips unchanged files; this is correct. Edit the brief or wait for the next day |
Reference¶
Files added or changed in this work¶
brain/brief/ # NEW package
__init__.py
models.py # Pydantic dataclasses
connections.py # surface_connections()
pattern.py # surface_pattern()
open_question.py # surface_open_question()
thesis.py # surface_emerging_thesis()
contradictions.py # surface_contradictions()
gaps.py # surface_knowledge_gaps()
render.py # render_today_extras() / render_week()
brain/cli/today_cmd.py # +3 sections, --write-vault
brain/cli/week_cmd.py # NEW
brain/cli/__init__.py # registers week_cmd
brain/prompts/specs/briefing/today.brief.v1.yaml # extended prompt
brain/prompts/specs/briefing/week.brief.v1.yaml # NEW prompt
brain/antahkarana/chitta/synthesis.py # +synthesize_dry()
brain/routines/templates/today-cognitive-brief.yaml # NEW
brain/routines/templates/week-synthesis.yaml # NEW
brain/serve/routers/inbound.py # +/inbound/readwise route + Readwise models
brain/channels/capture.py # NEW — #capture helpers (testable)
brain/channels/telegram.py # intercepts #capture
brain/ui_schema/cli_schema.json # regenerated (sb week, --write-vault)
Programmatic API¶
If you want to call the surfaces directly (e.g. from a custom script or
agent tool), they're all importable from brain.brief:
from pathlib import Path
from brain.brief import (
surface_connections,
surface_pattern,
surface_open_question,
surface_emerging_thesis,
surface_contradictions,
surface_knowledge_gaps,
render_today_extras,
render_week,
)
vault = Path("vault")
# Each function works deterministically without an LLM provider.
connections = surface_connections(wiki_dir=vault / "wiki", window_days=7, k=3)
question = surface_open_question(vault_dir=vault, decision_watchlist=[])
contras = surface_contradictions(chitta_store=my_chitta_store, limit=5)
All return Pydantic models with .model_dump() for JSON serialization.
Tests¶
# Just the new surfaces
.venv/bin/python -m pytest tests/brief/ -q --override-ini="addopts="
# Capture adapters + routine templates
.venv/bin/python -m pytest \
tests/capture/test_capture_route.py \
tests/serve/test_inbound_readwise.py \
tests/routines/test_brief_routines_load.py \
-q --override-ini="addopts="
# Full regression for anything this work touches
.venv/bin/python -m pytest \
tests/brief/ tests/capture/ tests/serve/ tests/routines/ \
tests/infra/test_today_cli.py \
tests/antahkarana/ \
-q --override-ini="addopts="
See also¶
- guides/automations-routines.md — broader automation runtime (workflow vs session vs routine templates)
- guides/gateway.md — wiring Telegram / Teams channels through the gateway
- components/antahkarana/api.md — the cognitive-loop primitives the briefs build on
- MEMORY_API.md — the durable memory + grounded retrieval layer that compound briefs participate in
sb obsidian --help— full reference forsb obsidian status / ingest / watch(theconnectors.obsidian.*config block lives inbrain/config_types_runtime.py::ConnectorObsidianSettings)