BrainOS
Persistent, modular AI research system.
The normative architectural source of truth is indexed in
specification/README.md; operational explanations
are indexed in docs/README.md.
Infrastructure
The current two-resident-model decision, live measurements, Muse Glimmer /
Qwen3.6 compatibility gate, activation procedure, and rollback are documented
in docs/GPU_AND_MODEL_PLAN_2026-08-11.md.
The 2026-07-27 plan is retained as
historical benchmark and hardware-selection context.
- PostgreSQL with pgvector
- Redis
- host Ollama
- Python BrainOS application
Deployment snapshot (verified 2026-08-16): PostgreSQL, Redis, SearXNG, API,
scheduler, autonomy worker, and Matrix are running; Matrix /sync returns HTTP
200; Telegram is disabled and its profile is absent. Muse Glimmer 30B and
Qwen3.8-27B are installed, assigned, and simultaneously resident at 122,880
tokens as of 2026-08-16 (raised from 32,768; see "Real parallelism" below) —
as two derived Ollama models, muse-glimmer-coding and qwen3.8-coding,
each with a PARAMETER num_gpu baked in so the larger context fits
alongside the other resident instead of evicting it. The coding, lease-recovery, host-repair, paper-reassessment, and
dashboard-push timers are installed and armed; duplicate Claude triage is
disabled. Every brainos-*.timer install script now also runs
scripts/ensure-linger.sh (loginctl enable-linger), so these
systemctl --user timers survive logout instead of existing only while a
session is open — found 2026-08-15 after the paper timer's 30-minute schedule
turned out to have actual gaps of 26h, 25h, 11h, and 7h across four days,
invisible to systemctl --user list-timers because running that command
itself requires being logged in. The host backup timer was not installed at
that snapshot and remains
a current TODO until live verification says otherwise. Volatile queue,
publication, quota, and paper counts belong on the live dashboard at
https://simb.one/, not in this README. Exact dated engineering evidence is
recorded in TODO.md.
Implemented capabilities
Current state, in one paragraph: BrainOS runs continuously, unattended, on
this host today as a computer-science research system. Its approved portfolio
objective is rigorous, reproducible, paper-ready work for reputable IT
conferences—not generic activity across unrelated social domains. A systemd
timer polls roughly once a minute and advances coding jobs in "active mode,"
using exactly two locally routed generative models (currently Muse Glimmer
30B and Qwen3.8-27B, via Ollama, as the -coding-suffixed derived tags —
see the deployment snapshot above) to find a research gap, implement
methods and credible baselines, run
controlled evaluations, preserve machine-readable results, and close the next
scientific evidence gap. Both are live-verified co-resident at 122,880 tokens
and both have executed disposable Codex OSS edit/test/finish sessions.
Role rows seed one fully symmetric resident pool: every role, including
coder/coding_model/code_reviewer/review_model, round-robins across
both residents whenever both satisfy that role's capability requirements —
role assignment picks a fallback/tiebreak, not an exclusive owner (see
model_registry.capable_role_model_pool). On 2026-08-16 a real capability
gap briefly collapsed coding to one lane: coding/code_review additionally
require structured_output evidence, and only qwen3.8-coding:27b had it,
so muse-glimmer-coding:30b-q4_K_M dropped out of that pool specifically
(every other role, which doesn't require structured_output, kept
round-robining across both the whole time). That was a capability-probe gap,
not a design choice, and it closed itself automatically once probed evidence
caught up — reconfirmed live 2026-08-17 11:00: both residents report
structured_output: true, and brainos-coding.timer's own poll output
(worker_models) has listed both since. The two-lane path needs no code
change to "reactivate" because it was never actually gated by anything other
than that evidence.
Both installed models report completion,
vision, tools, and thinking, so each can perform every local generative role.
Every one of those generative calls runs through pinned Codex OSS in YOLO mode.
BrainOS supplies the role and relevant state; Codex owns inspection, commands,
editing, compaction, and web search. No BrainOS agent, command executor,
controller tool protocol, or production raw Ollama chat path remains.
Busy-state tracking directs work to the idle peer and balances queued calls
when both are occupied.
Twice a day a separate timer runs "sleep mode": persisted-state maintenance
(memory consolidation, stalled-project restructuring, stale-lease recovery),
then an independent review by Claude Code CLI (sonnet, with Codex fallback)
that inspects the whole system,
fixes critical bugs it finds directly — edit, run the quality gate, commit —
and steers the portfolio away from weak, repetitive, non-IT, or non-publishable
work toward its largest novelty, method, baseline, experiment, statistics, or
reproducibility bottleneck. The same hosted review response owns the bounded
BrainOS improvement plan; a local model no longer reinterprets architecture
findings. A separate hosted repair lane runs no more often than every six hours,
with at most three hosted CLI launches in a pass. A provider-wide owner-day
ledger caps BrainOS's Claude launches at eight and Codex launches at twelve by
default, reserving two calls for architecture review and three for paper stages
before triage or repair can consume capacity. Claude leads and Codex may fall
back for both ordinary research repair and BrainOS self-improvement. The
local models run inside Codex OSS with the filesystem boundary of their host
service or container. Codex uses its maintained workspace/shell/edit/web tools;
BrainOS supplies role-relevant durable state in the prompt or job bundle. See
Host coding and review agents below and
the autonomous purpose loop for the full
mechanics. The rest of this section describes how each capability layer was
added, oldest first.
BrainOS is a typed modular monolith with an async FastAPI API and Typer CLI, PostgreSQL/pgvector persistence, Redis execution locking, an Ollama adapter, episodic/semantic/procedural memory, project/goal/task management, approval and audit records, and a bounded supervisor cycle.
The supervisor processes at most one ready task per invocation. It validates structured director output, enforces typed-tool policy, and records every transition. Registered class-2 local actions may run automatically; class-3 external effects require human approval and class-4 actions are prohibited. Shell and workspace execution is not tier-restricted: local models and hosted agents get the same capability, scoped to the job workspace.
Run 2 adds controlled web and academic research, immutable source snapshots and citation evidence, RSS/Atom and PDF ingestion, a provider-neutral persistent model registry and deterministic router, multimodal artifact/vision routing, typed workspace tools, a hardened Docker Python sandbox, restartable sleep maintenance, model discovery and benchmarks, strongest-model strategic review, and validated tuning-dataset export.
Local setup
Prerequisites: Docker with Compose, uv, and an existing Ollama service bound
to the host with nomic-embed-text and both configured generative role models
available. BrainOS discovers and invokes Ollama models. Root-owned Ollama
startup parameters remain owner operations. Model installation is also owner-
only; an agent may delete an installed model, but only an exact tag the owner
names in that request and only after confirming no role still points at it
(CLAUDE.md, "Allowed scope", carve-out added 2026-08-16).
cd /opt/brain
make setup
Edit .env and replace the local PostgreSQL password in both
POSTGRES_PASSWORD and the host-side BRAINOS_DATABASE_URL. Compose overrides
the application connection hosts internally. Never commit .env.
Start PostgreSQL and Redis, migrate, then run the API locally:
docker compose up -d postgres redis
make migrate
make run
The local API listens only on 127.0.0.1:8000. In another terminal:
make health
uv run brain health
To run the API in Compose instead (also localhost-only):
docker compose up -d --build postgres redis api
curl --fail --silent http://127.0.0.1:18000/health
Database and CLI operations
Apply migrations:
make migrate
Create a project and retain the printed UUID:
uv run brain project-create "Research project" --description "Initial scope"
Create a task with that project UUID, retain its UUID, and move it from
proposed to ready through the audited transition endpoint:
uv run brain task-create PROJECT_UUID "Investigate a claim" --description "Collect evidence"
curl --fail --silent -X POST http://127.0.0.1:8000/tasks/TASK_UUID/transitions \
-H 'content-type: application/json' \
-d '{"state":"ready","actor":"owner"}'
uv run brain supervisor-cycle
The manual HTTP equivalent is:
curl --fail --silent -X POST http://127.0.0.1:8000/supervisor/cycles
Interactive API documentation is at http://127.0.0.1:8000/docs.
Quality checks
make format
make lint
make typecheck
make test
./scripts/install-git-hooks.sh # enforce this gate on every commit, rebuild
# containers automatically when brain/src changes
Docker builds copy the working tree, not git HEAD, so a change can end up
running in production without ever being committed — this happened once in the
now-removed native agent's context-compaction code, which ran uncommitted and
broken in production for an unknown stretch. Build application images with
./scripts/docker-build.sh (used automatically by the post-commit hook, and
by hand for a manual rebuild), not a bare docker compose build: it bakes
the exact git commit and a brain/src dirty-file count into the image,
readable at GET /health's build field and logged at every service's
startup, and the same check is included in the twice-daily review's data
snapshot with an explicit instruction to commit or revert anything found
dirty. The post-commit hook also warns immediately if brain/src is still
dirty right after a commit.
Run the opt-in PostgreSQL/pgvector integration test after exporting a URL that is reachable from the host:
export BRAINOS_TEST_DATABASE_URL='postgresql+psycopg://brain:YOUR_PASSWORD@127.0.0.1:15432/brain'
uv run pytest -m integration -q
Configuration
All application variables use the BRAINOS_ prefix. Important values are
BRAINOS_DATABASE_URL, BRAINOS_REDIS_URL, BRAINOS_OLLAMA_URL,
BRAINOS_CHAT_MODEL, BRAINOS_EMBEDDING_MODEL,
BRAINOS_OLLAMA_CHAT_KEEP_ALIVE_SECONDS,
BRAINOS_OLLAMA_EMBEDDING_KEEP_ALIVE_SECONDS, BRAINOS_LOG_LEVEL,
BRAINOS_SUPERVISOR_MAX_ACTIONS, and BRAINOS_SUPERVISOR_MAX_RETRIES.
On this host the two generative models use chat keep-alive -1 (resident until
explicitly unloaded) and embeddings use 0 (unload immediately). The
root-owned Ollama service must allow three loaded slots: two generative models
plus the transient embedding model. This does not authorize a third generative
model; measured VRAM permits only two.
General web search additionally requires BRAINOS_RESEARCH_PROVIDER and
BRAINOS_RESEARCH_BASE_URL; credentials belong only in the untracked .env.
Supported JSON adapters cover Brave, Tavily, Serper, Bing-compatible and
SearXNG services. Academic adapters support arXiv, Crossref, OpenAlex, Semantic
Scholar and PubMed metadata without paid test dependencies.
The schema fixes embedding columns at 768 dimensions for the configured
nomic-embed-text model. Every Ollama embedding response is checked before it
can be inserted; changing models therefore requires an explicit dimensional
migration and matching BRAINOS_EMBEDDING_DIMENSIONS value.
Run 2 commands
# Cited general web research (after provider configuration)
uv run brain research "What evidence addresses this question?"
# Academic metadata and local PDF ingestion
uv run brain academic-search "retrieval augmented generation" --provider crossref
uv run brain pdf-ingest /tmp/brainos-artifacts/paper.pdf --render-directory /tmp/brainos-artifacts/pages
# Registry and deterministic routing
uv run brain models --refresh
uv run brain model-route vision
# Build and use the network-disabled, non-root sandbox
make sandbox-build
printf 'print(6 * 7)\n' >/tmp/example.py
uv run brain python-run /tmp/example.py
# One-shot workflows
uv run brain sleep-run manual-2026-07-26
uv run brain model-discover
uv run brain benchmark MODEL_UUID
uv run brain strategic-review
uv run brain tuning-export /tmp/brainos-artifacts/tuning.jsonl
The corresponding APIs are POST /research/runs, POST /research/academic,
POST /research/pdf, GET /models, POST /models/refresh/ollama,
POST /models/route, and POST /sleep/runs. See
specification/RESEARCH.md, specification/MODELS.md, and
specification/OPERATIONS_RUN2.md.
Run 3 adds owner-approved missions, durable pause/emergency controls, a checkpointed autonomous worker, a persistent timezone-aware scheduler with dispatch/retry/recovery for every schedule, environment observations, an optional owner-only Telegram or Matrix long-polling daemons (commands and ordinary text), a local dashboard, verified backups, and user-systemd/Compose deployment.
Autonomous discovery generates multiple evidence-backed computer-science research opportunities, ranks them using BrainOS-owned scientific value, novelty, feasibility, and learning factors, and automatically starts one bounded evidence task when its threshold is met. Each opportunity must name a literature gap or falsifiable hypothesis, contribution, public data or benchmark, credible baselines, metrics, validity threats, reproducibility artifacts, and a first experiment. Generic apps, reports, directories, and toolkits are not contributions, and unit tests are not scientific results.
Publication is now a closed feedback loop rather than a once-daily prose attempt. The paper timer reassesses changed candidates every 30 minutes by default; unchanged source fingerprints cost no hosted calls. A negative area- chair or repair-review verdict becomes a durable, deduplicated publication gap. One candidate is focused by default, and its exact missing experiment, baseline, evidence, or reproducibility work is preferred by the Cortex until changed evidence resolves the verdict; pivots replace weak questions and stop verdicts retire them. The dashboard shows mission areas, eligible anchors, run outcomes, active/deferred gaps, hosted calls, and publication-ready papers.
# Core scheduled workflows (see below for installers)
./scripts/run-brain.sh brain coding-cycle # active mode: drain both resident-model lanes
./scripts/run-brain.sh brain sleep-review-cycle # maintenance + independent host-agent review
./scripts/run-brain.sh brain host-coding-repair-cycle # exhausted/self-improvement jobs
./scripts/run-brain.sh brain improvement-cycle # host-agent review pass
./scripts/run-brain.sh brain problem-scan # decompose the mission tree further
./scripts/run-brain.sh brain paper-writing-cycle # readiness, draft, repair/review, render
# Everyday operational visibility
./scripts/run-brain.sh brain status # task/coding-job/queue snapshot
./scripts/run-brain.sh brain doctor # environment and dependency checks
./scripts/run-brain.sh brain autonomy # inspect/control the autonomous worker
./scripts/run-brain.sh brain scheduler # inspect/control the persistent scheduler
./scripts/run-brain.sh brain service # manage installed systemd units
./scripts/run-brain.sh brain backup # verified backup operations
./scripts/run-brain.sh brain workspace-cleanup # preview reconstructible cache cleanup
./scripts/run-brain.sh brain workspace-cleanup --apply # preserve source/results/Git history
./scripts/run-brain.sh brain work-submit / work-retry # owner-originated coding jobs
./scripts/run-brain.sh brain mission --adopt-default --owner owner # adopt mission
./scripts/run-brain.sh brain mission / project-create / task-create # manual seeding
Run ./scripts/run-brain.sh brain --help for the complete, current command list; the ones above
are the ones most relevant to understanding
how the system actually runs day to day, not a full reference.
POSTGRES_* is the production credential source of truth. Compose and
scripts/run-brain.sh construct the application URL without printing it; this
keeps API, workers, migrations, CLI, and the initialized volume consistent.
See the autonomous purpose loop, operations, recovery, security, architecture, and policies.
Matrix owner interface
Matrix is the deployed owner transport on this host. It uses outbound HTTPS
/sync long polling and opens no inbound port. Only the
configured owner user ID in the configured private room is accepted, and the
bot ignores its own events. Telegram remains implemented for compatibility but
is disabled and its Compose profile is not started. Free-text replies use a
220-token generation ceiling and a 1,200-character transport ceiling. Markdown
is normalized to plain text, and unsolicited fenced/code-shaped output is
withheld unless the owner explicitly asks for code.
See MATRIX_SETUP.md for the complete setup and test procedure.
Use /dashboard for the combined live snapshot. Additional structured commands
include /workers, /events, /errors, /resources, /network, /proxy,
/memory, /tools, /repositories, /improvement, and /needs-user.
Ordinary chat uses durable owner conversation history and answers the question directly.
Operational questions and explicit status commands receive a bounded runtime snapshot;
stale or unavailable operational data is reported explicitly. Status answers about a
specific task or job walk that task's problem-tree ancestry
(title/statement/rationale at every level up to the mission) so "what are you
doing and why" is answered from real stored planner output, never invented at chat
time.
External status dashboard (static, pushed over SSH)
Separate from the Matrix /dashboard chat command above: dashboard.py's
DashboardService renders a static HTML snapshot (problem-tree counts and
top active modules, recent coding jobs with errors, each job's workspace
top-level contents, and README/TODO/ARCHITECTURE rendered from Markdown) and
pushes it via rsync over outbound SSH to an owner-controlled external
webserver, refreshed by brainos-dashboard-push.timer every 20 minutes. This
exists because api_host is hard-enforced to loopback
(Settings.secure_configuration) and the institute firewall cannot be
changed, so nothing on this host can be reached directly from outside it -
the dashboard is pushed out instead of served, the same outbound-only shape
Matrix already uses.
The canonical public dashboard and paper site is directly at
https://simb.one/. It does not require BrainOS, a /brainOS path, or any
other dashboard host in the URL. The configured
SSH destination may still use the server-side directory
/home/daniel/brainOS/; that filesystem path is an implementation detail and
must not be presented as part of the public URL.
Configure via BRAINOS_DASHBOARD_PUSH_* in .env (see .env.example);
BRAINOS_DASHBOARD_PUSH_ENABLED=false (the default) makes the CLI command
and timer a no-op. Institute traffic must go through the mandatory HTTP(S)
proxy (webproxy.berlin.ptb.de:8080), which also tunnels arbitrary
CONNECT targets including raw SSH — BRAINOS_DASHBOARD_PUSH_SSH_PROXY_COMMAND
carries that as an nc -X connect -x host:port %h %p ProxyCommand. Install
or remove the timer with scripts/install-dashboard-push-timer.sh /
scripts/rollback-dashboard-push-timer.sh; run it once by hand with
brain render-and-push-dashboard. See TODO.md's "Dashboard delivery to an
owner-controlled webserver over SSH" for the live proxy-tunneling
investigation this is built on and its known limitations (no auth on the
pushed pages, uses the owner's general SSH key rather than a scoped one).
Host coding and review agents
Two different tiers do implementation work. They differ in cost and capability, not in what CLAUDE.md permits: execution capability is the same for both, while rule 11 still requires approval for external side effects. The no-root and Ollama/firewall/SSH restrictions also bind both alike.
Active mode (the ~1-minute brainos-coding.timer loop, coding-cycle)
uses locally hosted resident models exclusively through Codex OSS in YOLO
mode, confined to the job's own disposable or shared-project workspace. It
admits up to two jobs at a time — one per resident capable of the coding
task category — through separate model and database-session lanes, while a
database exclusion prevents two lanes from writing the same workspace. As of
2026-08-16 that capability check also requires structured_output evidence,
not just tool_use, so coder/code_reviewer briefly resolved to a single
lane (qwen3.8-coding:27b) while muse-glimmer-coding:30b-q4_K_M still read
structured_output: false; the two-lane code path itself never changed and
needed no fix to reactivate, only fresh evidence. Measured live that
evening: this single-lane bottleneck was not theoretical - 18 real
experiment-implementation tasks (not scaffolding; titles like "Implement
fixed atol/rtol baseline plus one candidate localization method, with fault
injections") sat queued behind it at once, each CodingJob correctly
queued with its Task correctly proposed (coding-owned tasks are
deliberately excluded from the generic Supervisor's own eligibility query,
so proposed there is expected, not stuck) - the real constraint was purely
one active lane. Reconfirmed live 2026-08-17 11:00: a fresh capability probe
gives muse-glimmer-coding structured_output: true, and both models have
appeared together in worker_models on ordinary brainos-coding.timer
polls (and running concurrently in ps) since — the second lane is back
without any code change, exactly as designed.
It explicitly excludes any job whose
requested_by starts with improvement-cycle: — a local model never edits
BrainOS's own source, in active mode or otherwise. A local reviewer model
independently checks each job before it can be marked complete; a job that
exhausts its retry budget (coding_max_attempts) becomes failed and waits
for the next tier. That review sees a real pytest run, not the implementer's
self-report — and, because problem-tree jobs share one workspace per project,
it sees that run attributed: the same suite is executed once before the job
starts, so the reviewer is told which failures are newly introduced and which
were already there. Only newly introduced ones are grounds for rejection. The faster autonomy/Cortex loop applies cross-pipeline
backpressure before creating more coding work. Two distinct pending workspaces
supply the two local-model lanes; multiple sequential jobs in one shared Git
tree count as one runnable lane. If the Cortex cannot find a second workspace,
BRAINOS_PROBLEM_TREE_MAX_PENDING_CODING_JOBS is the hard queue ceiling
(default six), while the unmaterialized problem tree remains the durable backlog.
Sleep mode (brainos-review.timer, twice daily) is where a frontier
hosted agent CLI runs—Claude Code first, with Codex fallback inside the same
cycle. The timer runs exactly twice daily and a durable owner-timezone limit
rejects manual/catch-up attempts beyond two architecture-improvement cycles per
day. The successful hosted response owns both assessment and plan, with no
separate local planning call. This tier gets real write, edit, and
shell access to /opt/brain — the same trust an engineer doing this work by
hand would have — so it can fix a critical bug it finds directly (edit, run
make format && make lint && make typecheck && make test, commit). It also inspects
every shared project workspace for accumulated duplicate/divergent files (a
historically observed implementation failure mode) and is authorized to consolidate that
directly, in that workspace's own git repo. It never deploys, promotes a
model, spends money, or initiates external communication without approval.
The architecture review call uses Settings.claude_review_model—sonnet in
both repository defaults and this host's untracked .env. The former live
opus override was removed after the hosted-call audit showed that architecture
review, repair, triage, and paper writing could collectively launch Claude up
to nineteen times per day. Each of the two daily cycles accepts one structured review result; its
response includes the improvement plan, so no local model subsequently plans
BrainOS architecture. Its reasoning depth is now configurable
(Settings.claude_review_effort, added 2026-08-15, threading the CLI's
--effort argument — Claude's invocation never set one at all before, unlike
Codex's codex_reasoning_effort) but deliberately left at "medium", the
CLI's own default and hence a no-op today: codex_reasoning_effort carries
the matching warning from the other direction, having been raised to "high"
once and reverted 2026-08-03 after it exhausted the account's budget, on a
lane that runs indefinitely. Its snapshot also now includes
publication_focus — each active publication gap's disposition, age, prior
continue-verdict count on that scientific module, gap rows created in the
last 48h, and its work module's completed/live descendant counts — the same
treatment research_output already got for whether a manuscript has rendered
at all, so a reviewer no longer has to reconstruct a stalled candidate's
gap-churn history from scratch to notice it.
Hosted repair is a distinct lane,
configured by BRAINOS_CODING_PROVIDER and its fallback. Its six-hour cycle
(repair_stuck_jobs, at most 3 hosted calls per cycle, the same "implement"
purpose active-mode local retries never reach) also uses sonnet
(Settings.claude_coding_model). Both used to be
opus: repair moved off it 2026-08-02, after one nominally "twice-daily"
cycle placing up to 4 opus calls contributed to exhausting the account's
rolling session limit overnight (an unrelated bug, see TODO.md, had inflated
how many jobs needed repair that night); review itself moved off opus to
sonnet on 2026-08-03, for the account's overall token budget rather than
any one incident. The 2026-08-14 quota audit removed the stale live override.
Triage (TriageService in triage.py, provider-agnostic) runs through
Codex four times daily (brainos-codex-triage.timer). The duplicate Claude
triage timer may remain installed, but its lane is disabled by default; when
explicitly enabled it uses sonnet and shares the global Claude launch budget.
The twice-daily review pass is separate. Both triage implementations are deliberately narrow,
single-agent passes, not a fallback list — the point is a specific agent's
own pass at a specific time, not "whichever answers first" — and all timers
are offset from each other so no two ever compete for the same host-agent
CLI quota at once. Neither touches BrainOS's own architecture or source; that
remains confined to the review and dedicated self-improvement repair lanes.
Each pass sanity-checks the
live problem tree (reporting, not directly editing, any module that is
simply incoherent or depends on a resource BrainOS can genuinely never
obtain — recruiting real people, for instance) and repairs real bugs,
including broken or fabricated-looking external links and citations in
workspace documentation, inside coding-job workspaces under
/opt/brain/workspaces/. A module that is not fundamentally impossible but
is blocked on one specific, nameable resource only the owner can provide (a
paid API key, a payment processor account, hosting, licensed data, ...) is
never abandoned — it is marked blocked (Module.blocked_reason), the owner
is notified via NotificationService naming exactly what is needed, and it
stays available to resume once provided, while the tree moves on to a
genuinely different branch instead of idling on siblings that hit the same
wall. It also reads a per-branch completion/activity summary and may nudge
priority_score on specific modules (tree_rebalancing) when a small number
of branches have absorbed nearly all of next_actionable's greedy selection
while others sit undeveloped for no structural reason — including, since
2026-08-01, when a branch has simply drifted from the current mission's
actual wording, not just when the tree is mechanically imbalanced. Two
further, newer capabilities give the tree's own shape and lifespan real
judgement rather than only ~10 fixed top-level branches that only ever end in
abandonment: it can add a new, already-scored top-level branch directly
(ProblemTreeService.add_root_branch, bypassing decompose()'s own LLM call
since triage's candidate already exists — bounded to 3 per run) when real,
mission-aligned territory is unexplored, and it can conclude a branch that
has produced genuine value and has diminishing returns left
(ProblemTreeService.conclude_branch — a new concluded module status,
distinct from both completed and abandoned) by abandoning its other live
descendants and giving it exactly one final task: a report or paper
synthesizing what it accomplished, with the branch itself only becoming
concluded once that finishes. Each run's own prompt input is kept
deliberately tight: a short, fixed grounding line (TriageService.
_mission_text, the mission statement, one query, not a general system
essay) rather than nothing, and a workspace listing pre-sorted by
last-modified time and capped (MAX_WORKSPACES) rather than an unranked
directory dump the agent would otherwise have to explore blind with its own
bounded tool-call budget. It runs in Codex's
--dangerously-bypass-approvals-and-sandbox mode, or Claude Code's always-on
bypassPermissions. This is now true of every Codex and Claude launch, not
only triage; Codex also bypasses hook-trust prompts. The systemd writable-path
allowlist and credential-scrubbed environment are the external sandbox. These
CLI flags do not bypass BrainOS approval for external effects.
Triage is now explicitly a scientific-program steering pass. It sees recent
area-chair rejections and their missing-work lists, treats feasible work with
no defensible CS contribution as superseded, and asks the local loop for the
next experiment or artifact that changes paper readiness. superseded differs
from both abandoned and deletion: the work and evidence remain auditable, but
the branch no longer competes for compute. Approving a materially new mission
retargets the root and applies the same transition to unfinished old-mission
modules, cancellable tasks/jobs, and active opportunities, preventing a mission
row from changing while the actual queue silently continues the old objective.
Scientific publication (PaperWritingService in paper_writing.py, Claude
with hosted Codex fallback, brainos-paper.timer, change-gated every 30 minutes) is a third and
separate lane —
neither the review lane above nor either triage lane writes papers, and this
lane never touches BrainOS's own architecture or source. It replaces relying
on paper_pipeline.py's older gate (a branch reaching Module.status ==
"concluded", which live data showed had fired zero times across roughly
five thousand modules — see purpose.py's _research_output_health) with
anchoring directly on a current-mission depth-2 problem-tree module (depth-3
when a depth-2 subtree exceeds MAX_ANCHOR_SUBTREE_DESCENDANTS). A preflight
requires at least eight usable evidence items and a completed program,
inventories actual result artifacts, and independently reruns each distinct
workspace's discoverable pytest suite once. Content hashes keep timestamp-only
output churn from invalidating a cached rejection. The first hosted stage acts
as a senior area chair: it may reject the work or persist a complete tier-2
study plan with the large problem, motivation, gap, claims, baselines,
datasets/workloads, metrics, statistical treatment, validity threats, exact
rerun commands, evidence map, outline, and target venues. Claude leads each
stage and Codex retries the same schema after provider failure. A passing plan
permits one drafting stage and one repair/review stage—three
successful stages maximum. Provider attempts are separately audited. All
stages and the source fingerprint are durable, so an outage
resumes rather than restarts and an unchanged weak result consumes no more
calls. A rejection now creates a durable PublicationGap: continue creates
one focused problem-tree branch for the missing evidence or experiment,
pivot retires the weak framing and creates the named falsifiable replacement,
and stop supersedes the direction. The default single focus slot boosts the
gap and its ancestors ahead of unrelated breadth until the evidence fingerprint
changes; further gaps wait in a durable deferred queue. The final reviewer has
full shell/write access throughout the workspace tree while BrainOS source
remains read-only. It must attempt feasible repairs to the paper, programs,
analyses, and artifacts, verify them, apply its revisions, and judge the
repaired paper. Optional future strengthening and honest limitations do not
block a sound bounded contribution. Deterministic validation rejects
unsupported or altered citations, short or long drafts, leaked orchestration
details, failed scientific review, and PDFs outside the actual 15-20 page
range. Only then is
publication_ready set; the dashboard hides every legacy or rejected draft.
Because this is the only scheduled lane that runs on the host rather than in a
container, it also owns rendering for the other two. coding_workspace_root is
a host path that the scheduler and worker containers do not mount, so a
manuscript drafted there is saved as render_deferred and rendered on this
lane's next run. Frequent reassessment is inexpensive because unchanged
fingerprints consume no hosted call. Nothing is lost, but only quality-gated papers are
downloadable.
The agent subprocess receives a strict environment allowlist. As of
2026-08-17 (owner instruction, given after the tradeoff was raised
explicitly) that allowlist includes real database credentials
(BRAINOS_DATABASE_URL, POSTGRES_*) for every tier, hosted and local
alike, so any agent can read and write BrainOS's own durable memory itself
(brain memory-record, or a direct query) instead of a bounded job's
findings being reachable only through its own summary field. Every
owner-interface/chat token (BRAINOS_MATRIX_ACCESS_TOKEN etc.) remains
excluded regardless of tier — that stays a separate concern from database
access.
Every local generative role now uses the selected Muse/Qwen model through Codex
OSS (codex exec --oss --local-provider ollama). Codex receives the
same 32K working-window limit and runs with
--dangerously-bypass-approvals-and-sandbox plus hook-trust bypass. The coding
user service's writable-path allowlist is the external sandbox, so the process
never asks for execution permission but remains non-root and OS-confined.
Tasks target a tested result within 30 minutes. Codex OSS and one
verification-guided corrective pass share a two-hour hung-process safety
ceiling. A timeout kills the complete CLI process group, preventing an orphan
from continuing to write; the old
45-minute wall stopped a live, GPU-active job and was removed.
Live verification on 2026-08-12 found Muse
actively editing files and running pytest through Codex OSS. Qwen launched via
the same Codex OSS path but its first strict disposable smokes returned before
all requested changes/artifacts/test evidence existed. BrainOS now rejects that
result. Acceptance requires real workspace changes, reported test commands, and
a Codex-created completion marker that is removed before persistence. BrainOS
repeats the full task with exact verification feedback once. If both framework
passes fail, the job follows its normal bounded retry path; no BrainOS-owned
agent or command executor bypasses Codex. Claude/Codex hosted agents remain scheduled, bounded
review/triage/paper/exhausted-repair lanes; they are not inserted into ordinary
active-mode retries.
Live repeat verification after hardening produced one complete Qwen Codex OSS pass followed by one pass that edited and tested successfully but again stopped before its requested artifacts. The pure Qwen/Codex wire path is therefore not claimed repeat-reliable. The production path is fail-closed: incomplete results are rejected and retried through Codex OSS rather than accepted or handed to a second local framework.
Root-caused 2026-08-16. --output-schema validates the model's final
message against the supplied schema and silently drops it from stdout if it
fails — it does not error, retry, or fall back to raw text; Codex simply
prints nothing. Reproduced live against a scratch repo: qwen3.8:27b
completed a trivial task correctly (edited the file, verified it) but
answered with a markdown "Result" block instead of bare JSON, and the
correct answer sat in the CLI's own transcript while coding.py received an
empty string. 48h of production coding_jobs logs showed every single
implementation attempt landing on muse-glimmer instead of the assigned
qwen3.8:27b (idle failover always preferred whichever resident wasn't
currently busy, and qwen3.8:27b was tied up serving five other roles), and
every one of those attempts failed the identical way. Two independent fixes
landed the same day: agent_cli.py's CodexAgent.run() now also captures
--output-last-message (the raw final message, written regardless of schema
validation) and salvages a JSON object out of it via first_json_object
whenever stdout comes back blank — the same embedded-JSON-salvage pattern
Reviewer.review()/Director.plan() already used for direct Ollama calls,
just never applied to the Codex-OSS coding path before; and
TASK_REQUIREMENTS["coding"/"code_review"] now also requires a
structured_output capability, recorded false for both qwen3.8:27b and
muse-glimmer:30b-q4_K_M (evidence: this reproduction) so idle failover can
no longer hand schema-critical work to a resident already falsified in
production. Full incident writeup in the 35cf38c/3b38225 commit
messages, per this repo's convention of putting the detailed narrative in
git log rather than here.
Concentrated context for Codex OSS
More prompt text is not treated as more knowledge — and as of 2026-08-17
(owner instruction, following a same-day live investigation), that principle
was pushed further than "concentrated": the twice-daily review prompt and
the active-mode coding-lane prompt no longer carry any pre-digested context
bundle at all. Both real bugs found that day were found by ignoring
BrainOS's own curated context and verifying the live system directly - a
publication_steering field silently null across a module pivot, and a
contradiction counter that reads zero only because its detector needs exact
string equality. A curated bundle the agent is told to trust is exactly the
failure mode that let both survive unnoticed. Every agent, hosted or local,
also now has real database credentials in its environment (see "Host
coding and review agents" above for the environment-allowlist change),
specifically so it doesn't need a bundle assembled for it - it can query
the database itself.
- The twice-daily hosted review (
HostAgentReviewProvider._prompt) no longer receivesSTATUS_SNAPSHOTat all. It is told plainly that it has no pre-supplied summary on purpose, and to explore /opt/brain and every job workspace itself - code,git log, the database, running services, recent logs - the way an engineer investigating a live system would. - The active-mode coding lane (
CodingCycleService._prompt) no longer points the agent at.brainos-context/orientation.json,memory.json, orresearch.jsonas a starting bundle. It still receivesOWNER_REQUEST(the actual assigned task - a work order, not curated narrative) and is told to verify the workspace's real state itself (git log, what's actually on disk) before trusting a possibly-stale instruction. - The
.brainos-context/workspace bundle (orientation.json, memory.json, research.json, a short README) is still generated and still available - Codex can open it with native file tools if it chooses to - but no prompt points an agent at it as a first move anymore. Whether removing it from the prompt path changes real outcomes (versus just changing what an agent reaches for first) is not yet verified by a full day of production runs. - Both prompts now require ending with a durable memory write
(
brain memory-record) covering what was found/fixed, so the next reviewer or job starts from that instead of exploring blind again; the review lane additionally must actively correct or archive existing memory its own investigation shows is now wrong or superseded (the existing dedup path only merges byte-identical statements). - Cortex, Supervisor, and the problem-tree planner (
Director.plan,ProblemTreeService.decompose) are a different case, left unchanged: these are single non-agentic structured-output calls with no tools at all (OllamaClient.chat_with_usage, not an agentic CLI session), so they cannot "explore instead" - they only ever have whatever context is supplied directly in the call. They still receive a compact, relevance-filtered orientation/memory bundle for that reason, and that bundle's own known bug (publication_steeringgoingnullacross a pivot) was fixed at its source (context_navigation.py) the same day rather than routed around. - The other hosted lane with a large pre-digested prompt
(
TriageService._promptintriage.py, ~250 lines, passedtree/branch_summary/workspaces/publication_feedbackas curated JSON) was identified as the same pattern during this audit but not yet rewritten - flagged here rather than left undocumented.paper_writing.py's stage prompts (_readiness_prompt/_draft_prompt/_review_prompt) already explicitly tell the agent to re-inspect the real workspaces with Read/Glob/Grep rather than trust theirEVIDENCE_BUNDLEoutright, so they were judged lower priority. - Owner chat (Matrix) is a distinct case on purpose: it is reactive Q&A with a live human, not autonomous work, and its own prompt explicitly forbids inventing status claims - it still receives a verified runtime snapshot/orientation so it answers the owner truthfully rather than guessing, which is the opposite failure mode from an autonomous agent over-trusting stale curated context.
This makes the task-relevant durable subset accessible while preserving attention and KV cache for the task itself. Raise context beyond 32K only when measured agent traces show relevant retrieved material is still being truncated.
Implementation streams progress: each tool use is persisted with a heartbeat, so
/work JOB_UUID shows the current step while a long run is still going, and a
stalled job raises a notification (once per attempt, deduplicated) instead of
failing silently — coding_stall_seconds, raised from 15 to 45 minutes
2026-08-16 after it paged the owner for jobs that were merely slow, not dead.
This is a warning threshold only, unrelated to the 45-minute hard kill-wall
mentioned above that was removed 2026-08-12 for the opposite reason (it
stopped genuinely live work); nothing here kills a running job early. Failed
attempts are requeued with backoff and the previous failure is fed into the
retry prompt. Token usage and cost are recorded per job.
Real parallelism, not just separate schedules
A dedicated host-coding-repair lane is the sole automated hosted-repair path;
the twice-daily review no longer adds hidden repair passes between its timer
ticks. It runs concurrently with the active-mode local coding-cycle — see
CodingCycleService._repairable_job_query. The host-coding-repair-cycle
timer uses an owner-selected six-hour minimum interval. Hosted-agent quota is
scarce even when local GPU time is not, so the installer no longer launches an
immediate repair call. Each repair pass admits at most three hosted CLI
invocations total (BRAINOS_HOST_REPAIR_MAX_AGENT_CALLS_PER_CYCLE=3), counting
both primary and fallback attempts; three jobs can therefore never silently
become six calls. Both admission queries exclude a
candidate whose workspace another job is already running in
(_workspace_not_already_running_clause, a self-join on workspace_path, no
separate lock table), so two lanes running at once can never end up as two
concurrent writers in the same shared project git working tree. Both also
exclude a candidate whose source problem-tree module is already
abandoned/superseded/blocked/concluded
(_source_module_not_retired_clause, added 2026-08-15): neither query had
filtered on module status before, and nothing retires a queued job when its
branch dies — recover_stale only ever looks at jobs already running —
while both lanes order oldest-first, so dead work sorted first. Measured live
before the fix: 11 immediately selectable queued jobs from 2026-08-05..09, all
under abandoned modules, all ahead of that day's live research work, and 195
of the repair lane's 265 candidate rows (74%) belonged to dead branches,
competing for three admissions a pass with the self-improvement queue that
lane exists to drain.
Local model concurrency has a real, measured hardware ceiling on this host:
two big models — currently qwen3.8-coding:27b and
muse-glimmer-coding:30b-q4_K_M, the model identities behind these role
names have changed more than once, check brain model-role-list rather
than trusting a tag named here — run genuinely concurrently, verified by
timing simultaneous requests (reconfirmed 2026-08-16: 26.7s+15.1s
sequential vs. 26.1s wall concurrent, i.e. the slower model's own solo
time, not the sum). A third concurrent model load while both are already
resident caused an eviction and a stall rather than clean queuing, not just
extra latency — this is about a third loaded model, not
OLLAMA_NUM_PARALLEL, which only bounds how many concurrent requests one
loaded model serves and has no bearing on whether two different resident
models run together (they do). OllamaClient therefore tracks a per-model
busy flag in Redis (visible across the separate OS processes that each
talk to Ollama — the coding cycle, the review/repair cycles, and the
Matrix chat daemon are never the same process), and owner chat can use the
other capable resident when its selected model is busy. Codex OSS requests
are covered by the same busy marker even though the CLI talks to Ollama
directly. Every capable resident shares every generative role by
cross-process round robin, with the two coding lanes pinned one per resident
so a selection/reservation race never hands both lanes the same worker. As
of 2026-08-16 the coding/code_review task categories additionally gate
on structured_output evidence (see the dated note earlier in this
section); that gate briefly left those two roles resolving to
qwen3.8-coding:27b alone while muse-glimmer-coding still read
structured_output: false, reconfirmed true 2026-08-17 and back to two
lanes since — the pool computation itself was never role-exclusive, only
temporarily short one qualifying resident. owner_chat/fast_model are the
one deliberate, permanent exception: pinned to the same tag as coder so
only one Qwen instance is ever resident (a third loaded model evicts one of
the other two — see below), not because owner chat is incapable of sharing.
Every other role (planner, researcher, summariser, critic, ...) shares
across both residents as before. A third dedicated chatbot is neither
needed nor part of the target role map, and the model roster is exactly
these two generative residents plus nomic-embed-text — every non-current
tag (qwen3.5:9b, qwen3.6:27b-q4_K_M, qwen3:32b, the Devstral GGUF) was
deleted with all role assignments stripped first (2026-08-16).
The "third model load evicts" finding turned out to generalize: a bigger
context request for an already-2-resident model evicts the other one
too, live-confirmed 2026-08-16 — requesting 65,536 or 131,072 tokens for
one resident dropped both GPUs to 0MB used, not a graceful resize, with
reload times ranging 47s to 240s under real concurrent load. Root cause:
Ollama's scheduler doesn't try to pack a larger context alongside the other
resident even when total VRAM says it should fit. Fixed with num_gpu
(the model's own transformer-layer count, unrelated to host CPU count):
holding back a handful of a model's layers from GPU — they compute on
CPU/RAM instead, of which this host has 251GB/48 cores mostly idle — frees
enough VRAM for the other resident to stay loaded. Binary-searched live at
122,880 context: qwen3.8:27b (65 layers total) needs num_gpu<=~55 to
coexist with muse-glimmer resident; muse-glimmer:30b-q4_K_M (52 layers)
coexists even fully GPU-loaded. Verified both -coding variants loaded
simultaneously at full 122,880 context with ~10GB VRAM still free, correct
output (17*24=408, unprompted) confirming the CPU-offloaded layers don't
corrupt results, at 15-18 tok/s — roughly 30% of full-GPU speed, but on a
lane with up to a 2-hour budget that is still ~105,000 generated tokens per
attempt, more than a single exploration-only pass used before this fix.
Codex CLI has no per-request num_gpu passthrough (checked its full config
surface), so this is baked into two derived Ollama models instead of passed
at call time — qwen3.8-coding:27b (num_gpu=52) and
muse-glimmer-coding:30b-q4_K_M (num_gpu=50), each built with ollama
create from a Modelfile referencing the existing weight blobs (no extra
disk cost) — and every role now points at the -coding tag instead of the
bare model name. codex_oss_num_ctx moved from 32,768 to 122,880 to match
(see that setting's docstring for the full 88,649-cumulative-token
measurement that motivated it).
The exact upgrade, compatibility gate, historical smoke, role map, residency
settings, and rollback are documented in
docs/GPU_AND_MODEL_PLAN_2026-08-11.md
(reconciled 2026-08-16).
Codex OSS owns model interaction and tool execution, so BrainOS no longer
normalizes model-specific textual function-call formats or carries a parallel
read/create/edit/run loop. Actual activation passed ollama show capability confirmation and the live disposable
smoke on 2026-08-12; a model card alone would not have been sufficient evidence.
./scripts/install-coding-worker.sh # active-mode local implementation loop
./scripts/install-review-timer.sh # twice-daily independent review
./scripts/install-codex-triage-timer.sh # four-times-daily tree/workspace triage
./scripts/install-claude-triage-timer.sh # optional duplicate triage transport; disabled
# by default to preserve Claude capacity
./scripts/install-host-repair-timer.sh # host-agent job repair, no more often than every
# six hours; this is its sole automated owner
./scripts/install-paper-timer.sh # fingerprint-gated publication reassessment;
# default every 30 minutes
./scripts/install-lease-recovery-timer.sh # stale coding-lease recovery, every 3 minutes,
# independent of the coding-cycle timer itself
Every installer above also runs scripts/ensure-linger.sh first, which is not
a standalone timer — it enables systemctl --user's systemd lingering for the
owning user (loginctl enable-linger, no privilege required) so these
brainos-*.timer units keep running across logout instead of existing only
while a login session happens to be open.
A single active publication-focus candidate turned out to be a monopoly, not
a concentration: pinning one branch's whole ancestor chain to top priority
let it win every future selection against every sibling until its gap
resolved, and it starved the coding lanes of independent (differently
workspaced) work in the same stroke — measured live 2026-08-14/15, one
branch held the sole focus slot for ~24h across 6 "continue" verdicts.
BRAINOS_PAPER_FOCUS_MAX_CANDIDATES is now 3, and
PublicationSteeringService.CONTINUE_MAX_ATTEMPTS (3) forces a branch that
keeps failing readiness for the same reason to pivot/stop instead of
re-asking forever. Separately, CodingCycleService._run_once_bounded adds a
coding_job_hard_ceiling_seconds (3h) backstop around each lane's job: every
external call inside a job is already individually wall-clock-bounded, but
one measured job still ran ~11h past its own 2h implementation ceiling,
apparently hung after implementation succeeded, and blocked its lane's
admission the whole time; on timeout the job is simply abandoned mid-flight
for the ordinary stale-lease recovery path to reclaim.
The real per-process worst case is the sum of two separate budgets, not
either one alone — coding_cycle_drain_seconds (115 min) only bounds how
long coding-cycle keeps admitting new jobs, checked between jobs, never
while one is running; a job admitted right at that boundary can then still
run the full coding_job_hard_ceiling_seconds (3h) before the backstop
above fires. Worst case is therefore ~4h55m, not 115 minutes — nothing
states that combined figure anywhere else, which made a real, otherwise
unremarkable 2026-08-16 case (a job legitimately still inside its 3h
allowance, at 3h15m elapsed) look like a hang until traced through both
settings. It wasn't one: the job was slow because it had landed on
muse-glimmer under the pre-fix role assignment (see the dated
structured_output note earlier in this section) and was killed manually to
unstick the lane under the corrected code rather than wait out the
remaining ~1h40m of its own legitimate allowance.
The 3-way cap above stopped one candidate monopolizing the focus slot; it did
not stop that candidate resetting itself, found 2026-08-15 after five days
with zero rendered manuscripts despite correct decomposition into exactly the
concrete experiments six review cycles kept demanding.
PublicationSteeringService matched a continue verdict to its live gap only
when the area chair's next_research_question was byte-identical (modulo
whitespace/case) to the previous verdict's — the area chair is an LLM, not a
deterministic string generator, so it restated two real questions eight ways
across one 22-hour window, and two consecutive gaps differed by exactly one
character in 523 (an em-dash where the next run wrote an ASCII hyphen). Each
restatement minted a new PublicationGap, whose _resolve_replaced_gaps
superseded the previous work module and cancelled its never-attempted coding
jobs — the ONNX fixed-tolerance-baseline and fault-injection-dataset modules
were superseded 93 minutes after creation with every job still at
attempt_count 0. _questions_are_equivalent now compares content-word
overlap instead of exact prose (0.6 threshold, calibrated against that
incident's own data: rewordings scored 0.92–1.00, the genuinely different
prior question 0.25–0.27), and ProblemTreeService.supersede_branch now
shields a branch whose coding jobs have never been attempted for
supersede_unattempted_grace_hours (12h, bounded so a genuinely starved queue
can still be retired; an owner-approved mission change can still opt out with
protect_unattempted=False).
Once that direct cause was fixed, a second one surfaced inside the same focus
branch: next_actionable's only tiebreak beyond the publication-focus tag
itself was raw priority_score, and score() rewards cheap and certain
work — exactly literature/taxonomy/provenance scaffolding, never the
experiment a focus branch exists to produce. The candidate's literature child
scored 0.6175 with 7 live children while all nine implementation children
(the pinned runtime substrate, the baseline, the fault-injection dataset)
scored 0.43–0.57 with zero children each, so next_actionable for the entire
tree returned a citation-extraction leaf three levels inside the literature
branch. requires_implementation now breaks the tie between the focus tag
and raw priority, scoped to exactly the node whose children are being
ordered when that node itself carries the tag — deliberately not a global
scoring change, since literature-before-implementation is the correct
default for the rest of the tree.
Neither bug would have been caught by the twice-daily review that exists
specifically to catch this failure class, for an unrelated third reason:
ImprovementCycleService._activate_safe_tasks capped new self-improvement
tasks against a count of every task the "BrainOS continuous improvement"
project had ever held, completed and failed included, so the cap only ever
grew — 20 completed + 15 failed + 12 proposed + 2 blocked + 1 running was
exactly 50/50 against task_limit_per_project, and both of 2026-08-15's
review cycles, including the one covering this exact outage, completed
successfully and returned "portfolio_limit_reached", discarding whatever
either found. Now excludes COMPLETED/FAILED/CANCELLED from the count.
Verified backups are scheduled only by the host-user
brainos-backup.timer installed through make service-install; the matching
PostgreSQL scheduler row is forced disabled so the non-root container does not
race the host timer or fail on /opt/brain/backups. Compose-only operators must
schedule the documented host CLI backup command separately — this host is
Compose-only and has not, so as of 2026-08-08 there is no verified backup at
all. Giving backup exactly one owner is correct; assuming that owner exists was
not. The daily owner summary now reports the age of the newest verified backup
and names a missing one as a fault (RuntimeDispatcher._backup_freshness,
bounded by BRAINOS_BACKUP_MAX_AGE_DAYS, default 2), so a schedule with no
installed owner cannot stay silent again.
See the owner coding workflow, the autonomous purpose loop, and CLAUDE.md, which holds the provider-neutral engineering contract for every agent working in this repository and defines exactly which tier "the AI" refers to in each rule.
Enterprise HTTPS proxies
BrainOS can add organization-managed CA roots to every Python HTTPS client
without replacing the normal trust store. Place PEM certificates or bundles in
the Git-ignored certs/ directory, or set BRAINOS_EXTRA_CA_CERT to a PEM path
inside that read-only mount, then recreate the application containers. With no
extra certificates configured, startup is a no-op and normal public PKI trust
is unchanged.
See the enterprise proxy deployment guide and implementation report. TLS verification must never be disabled.
Production start
cd /opt/brain
cp -n .env.example .env
# Set POSTGRES_PASSWORD and the owner-only BRAINOS_MATRIX_* values in .env.
# Leave BRAINOS_TELEGRAM_ENABLED=false.
make setup
docker compose up -d postgres redis searxng
make migrate
./scripts/run-brain.sh brain mission --adopt-default --owner owner
./scripts/run-brain.sh brain scheduler --initialize
./scripts/docker-build.sh api scheduler worker matrix
docker compose --profile matrix up -d api scheduler worker matrix
curl --fail --silent http://127.0.0.1:18000/ready
If the package index is temporarily unreachable but a previously verified
BrainOS image is present, use the documented offline overlay procedure in
docs/OPERATIONS.md; it replaces application source only and explicitly
reports optional dependency degradation.