On this page
HANDOFF.md
Handoff
Aegis Terminal — Handoff
Last rewritten: 2026-09-25 Project root: ~/Documents/Aegis-Terminal (git repo, branch master) Machine: ambassadorcrimson-node — Ubuntu 26.04, kernel 7.0, GNOME on Wayland
This file holds the stable facts: what Aegis is, how it fits together, the rules, and how to run it. Live facts (memory, services, git state, doc counts, recent logs) come from ./aegis-handoff, which bundles this file with a fresh system snapshot. Any AI working on Aegis should read the bundle, not just this file.
Related docs:
AI_PROMPT.md— instructions for an outside AI (copy-paste workflow).BUGS.md— the real changelog: every bug/feature with repro and fix, newest first.OPEN_ITEMS.md— short list of known open items with stable IDs.TRANSFER.md— filesystem layout, external pointers, relocation procedure.CHANGELOG.md— stale since 2026-09-13; do not rely on it. UseBUGS.md.
1. What Aegis is
A local-first question-answering engine with a desktop app and voice:
- Backend (
main.py, FastAPI): answers questions from a local DuckDB document store, expands to the web (DuckDuckGo search + page fetch) when local data is thin, extracts the best sentences, and optionally has a small local LLM (Ollamaqwen2.5:1.5b) rewrite them into a natural answer. It also has fast paths for math, unit conversion, stocks, weather, currency, time zones, and a teach/correction loop. - Desktop (
desktop.py, GTK3 + WebKit2): fullscreen app themed "Shield of Zeus". Pages: home/launcher, query engine (/), SOZ trainer (/soz). Every page has an Exit button that shuts down the whole stack. - Voice:
- Output: Kokoro TTS server (
kokoro_server.py, port 5050). - Input: Vosk offline speech recognition (
voice_stt.py). - "Artemis": always-on wake word listener (
artemis_listen.py) that runs voice commands and spoken queries, and reads answers aloud. - Terminal clients:
./aegis-lite(REPL, one-shot query, SOZ TUI) — no GTK/WebKit, safe on low RAM.
2. Hardware limits and the 2026-09-25 crash (read this first)
- 7.8 GB RAM, 4 cores, 4 GB swap (measured 2026-10-02; this line used to say 3.2 GB). Memory is still the constraint for everything: with Cursor open, about 1.6 GB is free and swap is full.
- 2026-09-25 14:02:
./aegis(desktop) was launched while Cursor was open. The machine froze by 14:04 and had to be hard-rebooted. No OOM kill was logged — it was swap thrashing. Treat this as a hard rule: never run the desktop alongside Cursor or a web browser. - Likely trigger:
- At 14:03:35 the session log shows
LLM warmed. The desktop setsAEGIS_QUERY_ALLOW_LLM=1, and at startup the backend preloads the model (synthesis.warm_async(), gated byram_ok()). ram_ok()only requiredAEGIS_LLM_MIN_FREE_MB= 900 MB available, but the model needs ~1.3 GB. So the preload passed, loaded, and pushed the host into swap.- Mitigated 2026-09-25: the default is now 1600 MB (
synthesis.pyandsettings.py). It and the warm-up itself can be changed on the Handshake page (section 14). The warm-up still doesn't check for Cursor or a browser, so the "never alongside Cursor" rule stands.
Measured memory per component (2026-09-25):
| Component | Memory | Notes |
|---|---|---|
| Kokoro TTS server | ~950 MB | Resident from boot (systemd user unit); mostly swapped when idle |
Ollama model qwen2.5:1.5b loaded | ~1.2–1.5 GB | 940 MB on disk; stays loaded AEGIS_OLLAMA_KEEP_ALIVE (5m; the Handshake "AI keep-alive" setting) after last use |
| Ollama daemon idle | ~20 MB | System service, starts at boot |
| Backend (uvicorn, idle) | ~105 MB | Grows with queries / DuckDB cache |
| Artemis worker (Vosk, 2 recognizers) | ~160 MB | |
| Desktop GTK + WebKit processes | ~200–350 MB | Estimate, not measured (measuring it is what crashed the host) |
| Cursor (all processes) | ~2.5 GB | Enough on its own to push the host into swap |
What that means:
- The full desktop stack with the LLM (Kokoro + model + backend + Artemis + WebKit) is ~2.8–3.0 GB — essentially the whole machine.
- Before
./aegis, runfree -mand check theavailablecolumn: - With LLM answers (the desktop default): need ≥ 2000 MB available.
- Without LLM (
AEGIS_QUERY_ALLOW_LLM=0 ./aegis): need ≥ 800 MB available. - To free the LLM's memory:
curl -s localhost:11434/api/generate -d '{"model":"qwen2.5:1.5b","keep_alive":0}' ./aegis-liteneeds only the backend (~105 MB+) and is the right tool for testing when Cursor or a browser is open.- The
./aegislauncher prints memory and whether Cursor is running, but it no longer refuses to start (removed at the user's request 2026-09-25). - Backend login service (2026-10-03): the backend runs all the time as the user unit
aegis-backend(about 105–120 MB idle, capped at 800 MB, low CPU and I/O priority, no AI warm-up). Stop it with./aegis-service pause CMD(for the length of CMD) or./aegis-service uninstall. The desktop reuses it, so the never run the desktop alongside Cursor rule is unchanged (section 17).
3. Processes and ports
| Process | Port | Runs from | Started by |
|---|---|---|---|
Backend uvicorn main:app | 8000 | .venv (Python 3.12) | systemd user unit aegis-backend.service at login (./aegis-service); otherwise desktop.py (or aegis-lite, or manual) |
Kokoro TTS kokoro_server.py | 5050 | .venv-kokoro (Python 3.12, CPU torch) | systemd user unit kokoro-server.service; ./aegis falls back to spawning it |
| Ollama | 11434 | system install | system service ollama |
Desktop desktop.py | — | /usr/bin/python3 (system; GTK/WebKit bindings) | ./aegis |
Artemis worker artemis_worker.py | — | .venv (vosk) | desktop.py subprocess; mic via arecord -D plughw:0,7 |
Kokoro endpoints: POST /speak (plays via aplay, blocks until done), POST /synth (returns WAV bytes). Backend KOKORO_URL must be /synth.
Two Python environments on purpose: .venv (backend, vosk) and .venv-kokoro (torch/kokoro, pinned). The desktop uses system Python because GTK/WebKit introspection bindings live there. Do not merge venvs.
4. Entry points
| Command | What it does |
|---|---|
./aegis | Full desktop session: pre-flight, ensures Kokoro, prints memory, launches desktop.py (which starts the backend and Artemis). Logs to logs/aegis-<ts>.log + logs/latest.log. |
./aegis-lite | Terminal REPL against the backend (starts backend if down). |
./aegis-lite "what is X" | One-shot query. |
./aegis-lite --soz | SOZ trainer TUI (curses). |
./aegis-lite --health | Backend health. |
./aegis-activate | Voice-first activation sequence (greeting, Kokoro, backend). |
./aegis-handoff | Build the AI handoff bundle (see AI_PROMPT.md). |
./aegis-check | Requirements check: OK / WARN / MISSING with a fix per item. --fix runs user-level fixes, --quick, --json. ./aegis runs the quick check at start (AEGIS_SKIP_CHECK=1 skips it). |
./aegis-share | Export the knowledge (all DuckDB tables as Parquet + mobile pack + settings) and serve it once on the LAN; --out DIR --no-serve writes it to a folder (section 16). |
./aegis-import SOURCE | Rebuild data/aegis.duckdb from an aegis-share URL, .tar.gz or folder, with a backup (section 16). |
./aegis-transfer | Build ~/Desktop/aegis-transfer/, the hand-carried folder that updates another laptop (section 16). |
./aegis-update | Run on the other laptop (from the transfer folder): sync an old install to the bundle's commit, with backup and rollback. |
http://127.0.0.1:8000/handshake | Handshake page: handoff bundle, live tests, storage inventory, settings and permissions (section 14). Red button on the launcher; voice "open handshake" / "open settings". |
mobile/run-desktop.sh | Aegis Mobile in a phone-shaped window on the laptop (section 15). |
mobile/build-apk.sh | Build the Android APK for the Tank 3 Pro. Close Cursor first (section 15). |
./aegis-service | The backend login service: install, uninstall, status, pause CMD (stop, run CMD, start), logs [N] (section 17). |
./handshake.sh | Short read-only briefing (git, backend, DB, bug headers). |
~/aegis_start.sh, ~/aegis_shutdown.sh, ~/aegis_full_test.sh | Older lifecycle scripts in $HOME (see TRANSFER.md). |
/usr/local/bin/speak "text" | Say text through Kokoro from any shell. |
5. Query pipeline (main.py → POST /query)
Request: {"prompt": "...", "sense"?: ..., "session_id"?: ...}. Order of handling:
- User corrections lookup — a user-taught answer wins instantly.
- Terminal sense resolution (req.sense or multi-token subject).
- Per-query response cache (5 min).
- Disambiguation gate (whitelist in
config.AMBIGUOUS_SUBJECTS) →status: "ambiguous"+ options. - Teach short-circuit ("teach me X").
- Fast paths: math, units, stock, weather, currency, time zone.
- Wiktionary / definition seed (
definition_path.py). - Local BM25 search over DuckDB FTS (
local_search.py). - Web expansion (DDGS search + trafilatura/Jina fetch, Firecrawl fallback) —
web_ingest.py. Runs only when local hits are missing, cover ≤ 50% of the query words, or the question is "fresh". A definition with ≥ 90% coverage skips it, and so does a "short question" (≤ 4 words, no current-info terms) whose local hits answer it:local_first.local_answer_sufficeswants ≥ 90% coverage and a top local sentence holding every query word plus the kind of value asked for (date, number, name). If extraction then finds nothing, the forced web search still runs. - Extractive QA (
extractive_qa.build_answer): sentence scoring;strip_markers()removes[N]citation markers, date prefixes, source notes, and leading paragraph numbers. - LLM synthesis (
synthesis.py), only ifAEGIS_QUERY_ALLOW_LLMis on andsynthesis.ram_ok(): - Conversational system prompt; top 3 extracts (280 chars each) as "Source: Title". - Token budget: 320 for how-to, 110 otherwise; hard time capAEGIS_LLM_CAP_S(15 s) with socket-level cancel. - Rejects answers containing numbers not present in any retrieved text (grounding = extracts + local bodies + web snippets). - "NO ANSWER" or ungrounded → honest no-answer line (answer_qtype: no_answer). - Timeout / low RAM → natural fallback: best 1–2 clean extract sentences. _sanitize_query_outputdecorator strips any leftover markers (skips computed/stock/units qtypes).
Response shape: answer, answer_qtype, results, local_hits, web_hits, session_id; ambiguous responses carry status + options[]. The UI shows answer text without numbers and keeps the source list.
6. Voice and Artemis
- Answers are silent unless the user clicks Speak on the answer card (
AEGIS_SPEAK_ANSWERS=0). Spoken queries are the exception — their answers are read aloud. - Artemis phrases (always listening while the desktop runs; header mic button pauses/resumes):
- "Artemis" → "Your wish is my command" (armed for
AEGIS_ARTEMIS_ARMED_S, 8 s). - "Artemis, activate Aegis" → opens the query engine, says "Enter question or query to continue.", then listens for a spoken question.
- "Artemis, run query, <question>" — one breath, with pauses, or without the wake word → navigates to the query engine, says "Searching.", runs the query, and reads the answer (or "I found no direct answer. The top result is: …").
- Commands: "go home", "open shield" / "activate shield", "open handshake", "open settings" / "permissions", "mute", "stop"/"cancel", "exit"/"quit".
- How it works:
- Two Vosk recognizers get the same audio: a grammar-constrained one (wake word + commands) and a free-form one (questions). Both use word timings.
- Replies are deferred until the user stops talking (mic energy vs. noise floor,
AEGIS_ARTEMIS_SPEECH_RMS), so TTS doesn't cut the user off. - Spoken questions are collected in pieces until
AEGIS_ARTEMIS_QUERY_END_S(1.2 s) of silence, maxAEGIS_ARTEMIS_QUERY_S(20 s). desktop.pyhas a speech queue; the mic stays paused until the queue drains.- The page posts the final answer back through the WebKit script message handler
artemisAnswer;desktop.pystrips[N]and speaks it. - A second handler,
aegisControl, takes JSON from the Handshake page:artemis_enable/artemis_disable(start/stop the worker),artemis_pause/artemis_resume(stop the worker during a mic test soarecordcan open the device, then restart it),settings_changed(re-read settings, refresh header buttons, restart Artemis on a mic device change). - If the microphone permission is off, the desktop doesn't start Artemis, push-to-talk records nothing, and the backend's
/voice/transcribeand/voice/command(audio) return 403. The header mic button says "Microphone blocked in Settings". - Artemis log:
/tmp/artemis.log(AEGIS_ARTEMIS_LOG).
7. Key modules
| File | Lines | Role |
|---|---|---|
main.py | ~3760 | FastAPI app: routing, /query pipeline, ~70 routes (query, ingest, trainer, facts, voice, exit, debug) |
synthesis.py | ~830 | Ollama synthesis: prompt, budgets, grounding checks, ram_ok(), streaming with cancel |
extractive_qa.py | ~510 | Sentence splitting/scoring, strip_markers(), build_answer() |
local_search.py | ~260 | DuckDB BM25 search |
web_ingest.py | ~1050 | Web search/fetch/ingest, Wiktionary/Wikipedia seeding |
definition_path.py | ~265 | Definition-shaped queries |
query_expand.py | ~170 | LLM search-query expansion |
query_log.py | ~245 | Structured query log (@query_log.wrapper) |
extract.py | ~690 | Regex fact extraction |
teach.py, tutorials.py | ~880, ~900 | "Teach me" tutorials |
config.py | ~125 | Runtime config (reads AEGIS_* env) |
desktop.py | ~1220 | GTK/WebKit wrapper, backend start, Artemis bridge, speech queue, answer and control channels |
artemis_listen.py | ~560 | Wake word + command/question state machine |
artemis_worker.py | ~50 | Subprocess wrapper: JSON lines out, pause/resume on stdin |
voice_commands.py | ~190 | Phrase → action parsing (parse, parse_artemis_command, parse_run_query) |
voice_stt.py | ~110 | Vosk model loading / transcription |
kokoro_server.py | ~135 | Resident TTS server |
narration.py | ~200 | Shield of Zeus narration lines |
app_exit.py | ~160 | Full stack exit (backend, desktop, voice, mic) |
aegis-lite.py | ~155 | Terminal client |
soz_tui.py | ~330 | SOZ trainer TUI |
settings.py | ~215 | User settings: data/settings.json, metadata, startup precedence, live apply (stdlib-only; imported by backend and desktop) |
system_report.py | ~630 | Handshake data: live status, storage inventory, query/trainer stats, query and trainer self-tests, mic devices and mic test |
handshake_routes.py | ~180 | APIRouter for /handshake, /system/, /settings (mounted in main.py) |
templates/handshake.html, static/aegis-handshake.{css,js} | Handshake page | |
templates/index.html | Query engine page | |
templates/soz.html, static/ | SOZ pages, theme CSS/JS, fonts (local, no CDN) | |
mobile/ | Aegis Mobile: standalone Android app, engine and pack exporter (section 15) |
Clutter to ignore: .bak, .pre-.<digits>, main.py.debug., main.py.gates-disabled-, the empty file named =, aegis-fix-*.sh (old one-off patches), archive/.
8. Environment variables (defaults)
Backend / answers:
AEGIS_QUERY_ALLOW_LLM(false; the desktop sets it to 1)AEGIS_QUERY_ALLOW_WEB(true)AEGIS_LLM_CAP_S(15)AEGIS_LLM_MIN_FREE_MB(1600; was 900 until the 2026-09-25 crash)AEGIS_OLLAMA_MODEL(qwen2.5:1.5b)AEGIS_OLLAMA_URL(http://localhost:11434)AEGIS_OLLAMA_KEEP_ALIVE(5m; was 30m until 2026-09-27)AEGIS_ENABLE_WEB_SEARCH(true)AEGIS_WEB_SEARCH_RESULTS(5)AEGIS_FETCH_TIMEOUT(8)AEGIS_MIN_BM25_SCORE(1.2)AEGIS_MAX_BODY_CHARS(40000)AEGIS_ENABLE_VERIFIER(false)AEGIS_DATA_DIR(./data)AEGIS_HOST(127.0.0.1)AEGIS_PORT(8000)
Desktop:
AEGIS_FULLSCREEN(1)AEGIS_NO_BROWSER(1)AEGIS_LOGOAEGIS_DESKTOP_KILLAEGIS_URL
Voice:
AEGIS_SPEAK(1)AEGIS_SPEAK_ANSWERS(0)AEGIS_KOKORO_VOICE(af_heart)AEGIS_MIC_DEVICE(plughw:0,7)AEGIS_LISTEN_SECS(5)AEGIS_VOICE_DEBOUNCE(0.30)AEGIS_VOICE_SHUTDOWN_WAIT(20)AEGIS_VOSK_MODEL(models/vosk-model-small-en-us-0.15)
Artemis:
AEGIS_ARTEMIS(1; 0 disables)AEGIS_ARTEMIS_ARMED_S(8)AEGIS_ARTEMIS_QUERY_S(20)AEGIS_ARTEMIS_QUERY_END_S(1.2)AEGIS_ARTEMIS_REPLY_DELAY_S(1.4)AEGIS_ARTEMIS_SPEECH_RMS(300)AEGIS_ARTEMIS_LOG(/tmp/artemis.log)
./aegis-handoff prints the live list grepped from the code.
Several of these are also user settings (section 14). Startup precedence: env var set at launch → data/settings.json → default.
9. Data and logs
data/aegis.duckdb— the document store:- Tables include
documents,query_log,user_corrections,facts,reports. - DuckDB is single-writer: stop the backend (
fuser -k 8000/tcp) before opening it in write mode from another process. - Read-only access:
duckdb.connect(path, read_only=True)— fails while the backend holds the lock. - The DB file is ~28 MB for ~17 MB of text. It was ~2.8 GB (space DuckDB never reclaimed) until the 2026-09-25
aegis-importrun rebuilt it from Parquet, which compacted it. The Handshake Storage tab shows the live numbers. - Old copy:
data/aegis.duckdb.bak.1789876094(~630 MB, 2026-09-19). The four older Sep 19 copies and.aegis-doctor/backups/import-20260925-190231/(3.7 GB, the pre-import snapshot ofdata/) were deleted 2026-09-27, after a row-by-row check showed the live DB holds every row of the snapshot's DB. data/settings.json— user settings from the Handshake page (git-ignored withdata/).data/jobs.sqlite— background jobs.data/aegis.log*— backend app log (rotated).logs/aegis-<ts>.log,logs/latest.log—./aegissession logs.logs/backend.log,logs/activate.log./tmp/artemis.log,/tmp/aegis-backend.log— ephemeral (cleared on reboot).journalctl --user -u kokoro-server --no-pager -n 50— Kokoro log.- Backups of patched files:
.aegis-doctor/backups/<feature>-<timestamp>/(git-ignored). data/,logs/,models/vosk-,.venvare git-ignored.
10. Safe operations
Check memory first:
free -m
Backend as the login service (installed 2026-10-03; section 17):
./aegis-service status # state, memory, /health systemctl --user restart aegis-backend # or ./restart.sh ./aegis-service pause CMD ... # stop it for CMD (DuckDB writes, fix scripts), then start it ./aegis-service logs 100
Backend only, by hand (when the service is not installed):
cd ~/Documents/Aegis-Terminal fuser -k 8000/tcp 2>/dev/null; sleep 1 nohup .venv/bin/uvicorn main:app --host 127.0.0.1 --port 8000 > /tmp/aegis-backend.log 2>&1 & for i in $(seq 1 30); do curl -sf -m 1 localhost:8000/health && break; sleep 1; done
Stop a backend started by hand (never the service: systemd would restart it after 30 s):
fuser -k 8000/tcp
Test a query without the desktop:
./aegis-lite "what is the capital of france" curl -s -X POST localhost:8000/query -H 'Content-Type: application/json' -d '{"prompt":"what is hydrogen"}' | head -c 600
Kokoro:
systemctl --user status kokoro-server --no-pager | head -5 systemctl --user restart kokoro-server
Full shutdown: the Exit button in the desktop, or curl -s -X POST localhost:8000/app/exit.
Tests:
.venv/bin/python full_test.py # needs backend running (git pre-commit hook runs it if up)
Do not use pkill -f <pattern> with broad patterns — it can match and kill the shell running the command. Use fuser -k 8000/tcp or exact PIDs.
11. Non-negotiables
- Check
free -mbefore starting anything heavy. Never run./aegiswith Cursor or a browser open. - Stop the backend before opening DuckDB in write mode (single writer).
- Verify port bind /
/healthafter every restart. - Back up every file before patching it:
.aegis-doctor/backups/<feature>-<timestamp>/. - Patch with exact-match replacement (assert the old text occurs exactly once); no blind
sed -ionmain.py. - Verify every patch:
python3 -m py_compile <file>/bash -n <script>, then a targeted test. - One patch per step; commit after each verified fix (
git commit), sogit diff/git revertwork. - Sample before delete. Never delete data without a backup.
- Log every fix/feature in
BUGS.mdusing its existing format (newest at top). - Don't print or paste secrets: never
cat .env, never dumpenv, never paste fullpsargs.
12. Recent work (from BUGS.md)
- 2026-10-03:
- Phone: Signal Walk is merged into the Aegis APK (one app). The selection page card opens its activity from
mobile/libs/signalwalk.aar, built from~/UNM/android/walklib. Artemis opens it and starts, stops and checks walks by voice. The Aegis Engine answers questions about the walks, and the walk screen has an Ask Artemis button. See section 19. - Shared archive (section 18): a PHP + MySQL server in
server/that every Aegis phone downloads checked facts from; phones whose users agree contribute Wikidata numbers, and the server re-checks each fact itself. - Curiosity engine on the laptop and the phone (section 17): finds subjects in what Aegis reads, ingests them directly (article + Wikidata facts, confirmed when both agree) and asks checked questions for what's missing.
- The backend runs as the
aegis-backendlogin service (./aegis-service). - 2026-09-27:
- Correction matching is symmetric and question-type aware ("who is Marie Curie" no longer answers "1867").
- Nonsense gate v3 re-enabled.
- Ollama keep-alive 30m → 5m, with a Handshake slider.
- Old DB backups deleted (~6.2 GB).
- 2026-09-25:
- Launcher no longer refuses to start when Cursor is open.
- Desktop + Cursor then crashed the host (section 2).
- New handoff system: this file,
AI_PROMPT.md,./aegis-handoff. - Baseline commit
7a1adb6. - Handshake page (section 14): handoff bundle in the UI, live tests, storage inventory, settings and permissions.
AEGIS_LLM_MIN_FREE_MBdefault raised to 1600. - Aegis Mobile rebuilt as a standalone offline Android app in
mobile/(section 15); old prototypes archived. - Updating the second laptop (section 16):
aegis-check,aegis-share/aegis-import,aegis-update,aegis-transfer;.envnow git-ignored. - 2026-09-24:
- Natural LLM answers with grounding checks and honest fallbacks.
- Stray
[N]/ paragraph numbers removed from answers. - "Artemis, run query …" flow (one breath, paused, without wake word) with answers read aloud.
- Artemis wake-word fixes (deferred replies, energy-based talk detection).
query_expandUnboundLocalError fix.- 2026-09-23:
- Artemis wake word.
- Silent-unless-Speak answers.
- SOZ UI restyle.
- Exit button (full stack).
- Voice commands (Vosk STT).
- Kokoro wired into the desktop.
- Desktop kill-race / fake boot / CDN fonts fixes.
extract.pyNameError fix.- 2026-09-19 to 09-21:
- Table extraction for fact queries.
- Kokoro TTS server + systemd unit.
- Transfer to this machine (see
TRANSFER.md).
13. Open issues
- See
OPEN_ITEMS.md: - 3: nonsense queries. The gate is back on; the definition path and "define <junk>" still slip through.
- 4: tallest-building list reject has no web fallback.
- 5: invalid ticker APPL falls through.
- 7: planned deterministic formatter.
- 8:
/teachinvents a tutorial for made-up topics, even with AI and web switched off. - Crash risk (mitigated): the AI memory gate is now 1600 MB, but the warm-up still doesn't detect Cursor or a browser. Keep the "never alongside Cursor" rule (section 2).
- ~~"who is Marie Curie" answers "1867"~~ — fixed 2026-09-27. The correction keyword fallback scored only the query's words, so "who is marie curie" fully matched the stored "what is the birth year of marie curie". It now scores both prompts' words and never matches across question types (
query_corrections.question_class). - Fact extractor on "Paris is the capital of France." yields
france / country = franceandfrance / definition = capital of Franceinstead offrance / capital = Paris(seen in the trainer dry-run test). - LLM answers are slow or fall back to extracts under memory pressure (~9 tok/s when healthy).
- Definition path gives wrong answers for "baking soda formula" and "tallest mountain".
BUGS.mdopen (not FIXED) entries:extract.pydoesn't parse table-formatted docs.- No hedge-word penalty in sentence scoring.
- Desktop memory footprint not measured (section 2).
- Clutter files listed in section 7 could be archived.
- Older docs (
HANDOFF.md.pre-transfer.*,SESSION-NOTES-2026-09-19.md,CHANGELOG.md) are historical only.
14. Handshake page, live tests, settings
Open it from the red Handshake button on the launcher, by voice ("open handshake", "open settings"), or at http://127.0.0.1:8000/handshake (works in a normal browser too; Artemis switches only work inside the desktop). Tabs (URL hash selects one, e.g. /handshake#settings):
| Tab | What it shows / does |
|---|---|
| Handshake | Builds the ./aegis-handoff bundle (optionally with a DB and storage section), shows leak-check result, size and parts, sectioned viewer, Copy all / Copy part N / Open folder. |
| System | Memory, swap, load, AI-ready gauges; services (backend, Kokoro, Ollama, Artemis, desktop, heavy apps); biggest processes; versions/git; warnings. Refreshes every 5 s. |
| Query Engine | Live self-tests (math, conversion, correction, fact, definition, local search, optional web and AI) with answer, stage and time; the 12-stage pipeline with how many logged questions each answered; speed by answer type (typical/avg/p95/max); questions per day; recent questions. |
| Training | Read-only trainer tests (queue, draft-time estimate, fact extraction dry run, correction hit/miss, fact lookup); the report → draft → review → train → facts → corrections flow; queue counts, categories, top corrections, recently trained. |
| Storage | Every file group Aegis keeps (DB, WAL, jobs, corpus, logs, models, venvs, HF cache, Ollama models, backups, handoff output, git, source) with sizes; per-table rows and text bytes with descriptions; docs by source; facts by attribute; the DB-bloat finding. Cached 10 min; "Re-measure" refreshes. |
| Settings & Permissions | Switches and sliders from settings.py (below), mic device picker, 3-second mic test with level meter and Vosk transcript. |
Settings (data/settings.json; env var at launch wins at startup):
| Key | Env var | Default | Applies |
|---|---|---|---|
mic_allowed | — | on | now (backend 403s, desktop stops Artemis) |
mic_device | AEGIS_MIC_DEVICE | plughw:0,7 | when Artemis restarts (desktop restarts it) |
voice_output | — | on | now |
speak_answers | — | off | now |
internet | AEGIS_QUERY_ALLOW_WEB | on | now |
local_ai | AEGIS_QUERY_ALLOW_LLM | on | now |
artemis | AEGIS_ARTEMIS | on | now (desktop only) |
ai_warmup | — | on | next launch |
ai_min_free_mb | AEGIS_LLM_MIN_FREE_MB | 1600 (600–3000) | now |
ai_cap_s | AEGIS_LLM_CAP_S | 15 (5–60) | now |
ai_keep_alive_min | AEGIS_OLLAMA_KEEP_ALIVE | 5 (0–30 minutes; 0 unloads after each answer) | now |
curious | — | on | now (section 17) |
curious_min_free_mb | — | 800 (300–3000) | now |
fullscreen | AEGIS_FULLSCREEN | on | next launch |
desktop.py passes settings.env_for_backend() to the backend it starts. A backend started by hand (section 10) doesn't set AEGIS_QUERY_ALLOW_LLM, so AI answers are off there unless you switch "Local AI model" on.
Endpoints (handshake_routes.py):
GET /handshake— the page.POST /handshake/bundle{include_db}→{ok, leak_check, text, chars, path, parts}. Runs./aegis-handoffwithAEGIS_HANDOFF_NO_CLIP=1; the DB section is passed viaAEGIS_HANDOFF_EXTRA.GET /handshake/part/{n},POST /handshake/copy{part}(xclip),POST /handshake/open-folder.GET /system/status,GET /system/storage?refresh=1,GET /system/query-stats,GET /system/trainer-stats.POST /system/tests/query{include_web, include_ai},POST /system/tests/trainer.GET /settings,POST /settings{changes: {key: value}}.GET /settings/mic/devices,POST /settings/mic/test{device, seconds}(403 if the mic is off).GET /system/requirements?deep=0|1: theaegis-checkresult, shown in the System tab's requirements card. Quick takes about 250 ms; deep imports packages and takes about 7 s.POST /system/knowledge/export{include_query_log}: Parquet export through the backend's DuckDB connection intohandoff-out/share/export-<ts>/.GET /curious/status(inmain.py): the curious worker's counts, the gate holding it, limits, last 20 questions. The Training tab shows itsline.
Test safety:
- Query tests use session
aegis-selftest(excluded from stats), clear the 60 s response cache first, and only run the AI test with ≥ 2000 MB available. - Trainer tests write nothing (corrections are read with a direct SELECT because
query_corrections.get()bumpsused_count). - One test run at a time (lock).
15. Aegion, the Aegis app (formerly Artemis / Aegis Mobile)
Refreshed 2026-10-03 for the one-app merge (section 20 has the details). Aegion is the same Kivy app (mobile/main.py) on Android, iPhone/iPad (kivy-ios, mobile/BUILD-IOS.md) and the Linux, macOS and Windows desktops (./aegis, mobile/RUN-DESKTOP.md). The display name is Aegion; the package id stays online.mindflux.aegis and the App class stays AegisMobile, so updates keep the data folder (.../aegismobile). Artemis is now only the wake word. Version 0.2.0 (buildozer.spec, app_version.txt).
Target phone: Unihertz Tank 3 Pro. It runs Android 15 (API 35), has 16 GB RAM, a MediaTek Dimensity 8300 (arm64-v8a) and a 1080x2460 screen at density 3.0 (360x820 dp; status bar and camera hole 110 px, no bottom inset with gesture navigation).
What it is:
- The offline engine runs inside the app: no server, no account, no Termux requirement.
- Answers come from your teachings, a bundled SQLite knowledge pack (or a newer one synced from the laptop into the data folder,
engine/pack.py choose), U.S. places, companies, the shared archive (section 18), Signal Walk logs, and the web when Search the web is on. Weather, exchange rates and city times are answered live (engine/livedata.py: Open-Meteo, open.er-api.com, zoneinfo; never stored). With a laptop link, the laptop engine answers what the phone misses. - Pipeline:
mobile/engine/answer.py; Library > "How an answer is chosen" lists all 16 steps. - Tabs: Ask, Library, Markets (watchlist and portfolio import; replaced StockTrack), Laptop (desktops: backend, Kokoro, Artemis worker), Settings (voice, web, Auto-train, archive, local AI, LAPTOP LINK with Sync now, style, Logs). The Choose screen opens the Aegis Engine, Shield of Zeus (trainer, plus the Guardian scan on Linux), Handshake, tutorials, Signal Walk (recorder on Android, viewer elsewhere) and SignalDub Studio (desktops where it is installed).
- Background: on Android the Shield of Zeus service keeps learning with the app closed. It stops at the trainer's session limit or day cap, when Auto-train is off, when the app is back, and before Android 15's 6-hour data-sync allowance runs out; the app does not restart it on a day that already hit the cap (
engine/shield_bg.py). - Edge-to-edge padding: the app reads Android's real system-bar and cutout insets a moment after it opens and on every resume (
engine/plat.py android_insets,safe_area: inset + 6 dp), with 40 dp / 24 dp fallbacks (48 dp below with button navigation), iPhone 44 / 34 dp, desktop 12 / 10 dp. The log line "safe area …" shows what it used. Check this on the device. - Optional local AI (Ollama on
127.0.0.1:11434) is off by default and falls back to the library answer.
Files (mobile/):
| File | Role |
|---|---|
main.py | Kivy UI (Shield of Zeus crimson/gold theme) |
engine/mathx.py, units.py, extractive.py | Copies of math_compute.py, units.py, extractive_qa.py; build-apk.sh re-copies them before every build |
engine/factq.py | Mirrors main._parse_fact_query / _format_fact_answer and definition_path.classify; change both sides together |
engine/pack.py | Pack reader: corrections, facts (+aliases), definitions, BM25 over packed postings |
engine/textutil.py | Tokenizer shared by the exporter and the engine |
engine/llm.py | Optional Ollama client (urllib) |
export_mobile_pack.py | Laptop only: DuckDB (read-only) → assets/aegis-pack.sqlite |
buildozer.spec | arm64-v8a only, android.api = 35, minapi = 26, accept_sdk_license = True, requirements = python3,kivy==2.3.1,sqlite3,plyer, permission INTERNET (local AI only) |
build-apk.sh | Guards (refuses with Cursor running or under 2000 MB free), engine sync, optional export, laptop self-test, buildozer android debug with Gradle capped at 1 GB and one worker; copies the APK to mobile/dist/ |
run-desktop.sh [--shots] | Runs the app in a 408x906 window; --shots saves screenshots of every screen via desktop_shots.py |
make-spec-html.py | Builds handoff-out/aegis-mobile-build.html (copied to ~/Desktop), a self-contained page with every mobile/ file, the pack, an extractor, build/verify/install steps and a Cursor prompt, so another machine can build the APK; --no-pack leaves the private pack out |
Knowledge pack (assets/aegis-pack.sqlite, git-ignored, rebuilt on demand):
- Contents: facts, fact aliases, corrections, and documents (bodies capped at 6,000 characters, zlib-compressed) plus a BM25 term index stored as compressed 16-bit postings. It uses only the built-in
sqlite3(no DuckDB, no FTS5). - 2026-09-25: 5.1 MB from the DuckDB (2.8 GB at the time, ~28 MB since the import rebuilt it). It holds 2,091 docs, 918 facts, 962 corrections and 34,538 terms, and was built in about 2 s using about 170 MB of RAM.
- The export needs the backend stopped (DuckDB single writer):
fuser -k 8000/tcp .venv/bin/python mobile/export_mobile_pack.py
- To update the phone's knowledge, re-export and rebuild.
Build (on this laptop):
- The Kivy/buildozer venv is
aegis-mobile/.venv(Python 3.11, Kivy 2.3.1, buildozer 1.6.0; git-ignored). JDK 17 is installed. libncurses-devis missing; the script prints theaptline.- The first build downloads the Android SDK/NDK (~3–4 GB of disk, one time) and can take 30–60 minutes.
- Steps:
- Close Cursor and browsers.
- Free RAM:
systemctl --user stop kokoro-server; fuser -k 8000/tcp. - Run
mobile/build-apk.sh(add--exportto refresh the pack first).
- If the build fails on API 35 with the default python-for-android, try adding
p4a.branch = developto the spec. - To build on a bigger machine instead, run
python3 mobile/make-spec-html.py, upload the page (basic auth or an unlisted URL, since it contains the pack) and paste its "For Cursor" prompt there. Regenerate after any change tomobile/.
Install on the Tank 3 Pro:
- Connect USB and choose "File transfer".
- Copy the newest
mobile/dist/aegis-mobile-<version>-arm64-v8a-debug.apk(the version is inmobile/buildozer.spec) to the phone's Download folder. - On the phone, open Files and tap the APK. When Android 15 asks, allow Install unknown apps for the Files app (Settings → Apps → Special app access → Install unknown apps).
- Tap Install, then open Aegis.
Optional local AI on the phone (Termux from F-Droid):
pkg install ollama ollama serve & ollama pull qwen2.5:1.5b
Then in Aegis go to Settings, switch on "Use local AI", and tap "Test AI connection".
Performance on the laptop (the phone should be similar or faster):
- Pack load: 133 ms.
- Deterministic answers: under 1 ms.
- Library search: about 35 ms.
- Self-test: 7/7 in about 37 ms.
Tutorials page (2026-09-27; see BUGS.md):
- Choose → Tutorials, or say "teach me X" / "tutorial on X" in the shell, or "open tutorials".
engine/tutorial_sources.pyreads Wikipedia, Wikibooks, the Stack Exchange API (keyless: 300 requests/day per network address, gzip, backoff) and robots-allowed docs pages found through the web search tiers.engine/tutorial.pyassembles sourced sections extractively and saves them totutorial_library.jsonfor offline reading.ui/tutorial.pyis the page and the reader.- Search the web off: saved tutorials only. Test:
test_tutorial.py. - Accuracy rules (2026-09-30):
- A how-to searches Wikipedia for its object ("build a company" → Company).
- A concept queued from a tutorial carries that tutorial's subject (
context), and only articles that the subject's Wikipedia article links to are accepted ("Preparation" in bread → Meal preparation, never Preparation H). - Stack Exchange searches without a tag sort by relevance and judge questions by title and tags only.
web.title_matcheskeeps one-letter words.- Tests:
test_tutorial.pytest_accuracyandtest_tutorial_train.pytest_context. - Network deadlines (2026-09-30): every engine HTTP request goes through
engine/netopen.pyurlopen. - Its timeout covers the whole connect: at most 4 addresses, IPv6 and IPv4 alternating. Plain
urllibgave each of Bing's 14 addresses the full timeout, so a 12 s search took 84 s. - Use
netopen.urlopenfor any new request code. Test:test_ingestion_part1.pytest_connect_deadline. - Shared stores (2026-09-30): the app and the background service both write the JSON stores, so each save goes through a per-process temporary file (
<name>.<pid>.tmp) and thenos.replace. Keep that pattern for any new store. Test:test_shield_bg.pytest_two_writers.
Tutorial trainer, navigation and tutorial facts (2026-09-27, 0.1.16; see BUGS.md):
- Shield of Zeus builds tutorials by itself:
engine/tutorial_train.pyruns as a "tutorial" job inside Auto-train's thread under every Auto-train gate, plus theauto_tutorialssetting. - Limits: 3 min apart, 10 a session, 30 a day, and no Stack Exchange subject while its allowance is used up.
- State:
tutorial_train.json. Seeds:assets/tutorial_seeds.json, rebuilt withpython3 mobile/build_tutorial_seeds.py. - Each subject gives a full tutorial (
origin: "auto") and a teach-me article (teach.from_tutorial) filed through the ingester. - Library caps: 60 user and 150 auto-built. Keep makes an auto-built tutorial the user's. Deleting an auto-built one bans its subject.
- Tutorials page: search, area groups, and a Teach me articles group. Reader: Contents jump row, Previous/Next, Related tutorials, concept links, and "Build a tutorial on this".
- Query engine:
engine/tutorial_facts.pyTutorialIndexfeeds a "Saved tutorials" step inanswer._lookup(after stored answers, before saved web pages). - It returns qtype
tutorial_savedand cites the original sources. - A number is verified only when two sites in the tutorial agree.
- "That's wrong" marks the block rejected.
- Tests:
test_tutorial_train.py,test_tutorial_facts.py,test_tutorial.py. build-apk.shoverwritesengine/extractive.pyfrom the laptop'sextractive_qa.py. The year-split fix was restored there on 2026-09-27;test_missespasses 92/92 inmobile/as of 0.1.17.
Go deeper, tutorial levels (2026-09-27, 0.1.16; see BUGS.md):
- The reader has a "Go deeper · build Level N" button in the header and a GO DEEPER card at the end. Each press runs
engine/tutorial_expand.pyexpand()off the UI thread. There is no last level. tutorial_busy()includes the reader'sexpandingflag, so Auto-train waits.- What a level reads:
- Wikipedia: the tutorial's own article again, for sections and subsections not used yet (h3 at level 2, h4 from level 3), plus up to 2 linked sub-articles;
- Wikibooks: the next 3 unread chapters, in book order;
- Stack Exchange: the next page of top-voted questions, skipping ids already seen;
- the web: a deeper search ("… advanced", "… examples", …), skipping pages already used.
- Stored in
tutorial_library.jsonwith the tutorial: level, andlevels[](at, by user/auto, section ids, blocks, new sources);used(URLs read or tried, Stack Exchange ids, Stack Exchange page, the Wikipedia article);deeper(the last try, including "none").- Sections are named
level{N}-concepts/-start(how-to only) /-examples/-problems, titled "Level N · …", and placed before Further reading. The Contents row shows "L2 Concepts" and so on. - Limits: 20 blocks per level, and a tutorial over 400 KB of JSON gets no more levels. A rebuild carries the levels over (
tutorial.carry_levelsinTutorialLibrary.save), unless it settled on a different Wikipedia article (tutorial.main_article); then the old levels are dropped. - Sense (2026-09-30):
tutorial_expand.sense()= the tutorial's buildcontextplus its article's qualifier ("finance" in "Security (finance)"). The Wikibooks search and the web query include it. A how-to with no article searches Wikipedia for its object ("company").build()savescontexton the tutorial. - Going deeper as the user makes a Shield of Zeus tutorial the user's (like Keep). The trainer may add one level a week to tutorials the user took deeper before; this counts toward its daily cap.
- Test:
test_tutorial_expand.py.
Shield of Zeus in the background (2026-09-27, 0.1.17; see BUGS.md):
- With Auto-train on, going to the background starts a python-for-android foreground service. It runs
mobile/shield_service.pyin its own process:service_shield, with no Kivy. - The spec entry is
services = shield:shield_service.py:foreground:foregroundServiceType=dataSync(not sticky since 2026-10-03). p4a generatesonline.mindflux.aegis.ServiceShield, which extendsShieldServiceBase(mobile/java/online/mindflux/aegis/ShieldServiceBase.java, specandroid.add_src = javaandandroid.service_class_name). - Permissions:
FOREGROUND_SERVICE,FOREGROUND_SERVICE_DATA_SYNC,POST_NOTIFICATIONS,WAKE_LOCK. ShieldServiceBasekeeps it running after a swipe-away (onTaskRemoveddoes nothing). A refusedstartForeground()and Android 15'sonTimeoutstop it instead of crashing. The notification reads "Shield of Zeus is learning" with the live status.- Turning it off:
- Turning Auto-train off stops it within about 2 s. The service re-reads
settings.json. - Auto-build tutorials off stops tutorials only.
- It also stops itself when the app comes back and after 5.75 h in one run.
- Android 15 data-sync allowance (
shield_bg.Allowance,shield_allowance.json): - 6 h of time awake in all, over every run. It starts over only when a run starts 24 h after the previous run started, or after a reboot. - Opening Aegis does not reset it: Android checks the:service_shieldprocess, which is never on screen. - The service stops at 6 h less 15 min. On Android 15+,main._servicedoes not start it with under 10 min left; the stop line says when it starts over. - One writer:
engine/shield_bg.py. - An exclusive
flockonuser_data_dir/shield.lock. Handoff.give()runs on pause: stop the trainer, let the job in progress and the user's own ask finish (≤ 90 s), save, release.Handoff.take()runs at start and on resume: writeshield.stop, wait ≤ 40 s, else stop the service. Reload the stores if the files' fingerprint changed, and absorb the service's[bg]log lines.ServiceLoopis the service's side. Status and summary go toshield_service.json, shown as the "Background:" line on the AUTO-TRAIN card.- User-queued tutorials ("Build a tutorial on this", Add subjects, Ask miss offers):
- First in, first out, 25 s apart (
USER_GAP_S). Not held by the 3-min gap, the session/day caps or the charger. Network, Wi-Fi, rest, busy and the Stack Exchange allowance still apply. - Each shows its state and why it waits: the button follows it, and the TUTORIALS card has a "Your queue" list. Built rows open on tap.
- Self-chosen subjects keep the old limits.
- Data dir:
main.pypassesuser_data_diras the service argument. With no argument it falls back toANDROID_PRIVATE(the same files dir). The OS no longer restarts the service. - Risks to watch on the phone:
- The cached-app freezer while
give()waits. If the wait runs out, the app keeps the data and trains in-process while Android lets it. - Time spent waiting for the charger counts against the 6 h allowance.
- Memory (1.3 GB RSS seen on 0.1.16).
- Tests:
test_shield_bg.py, plus the new cases intest_tutorial_train.pyandtest_autotrain.py.
Styles, restart to apply, honest boot (2026-09-27, 0.1.17; see BUGS.md):
- Seven styles in
engine/themes.py(no Kivy). This is the only place colours, radii, flags and fonts live: - Classic (the default; the look before styles), Obsidian Pro, Parchment Desk (light), Olympus Night, Aurora Glass, Daylight Clean (light), Signal Terminal.
- The mockups are in
mobile/design/themes/(reference only, not packed). - Setting:
themeinsettings.json. An unknown value falls back to Classic (themes.resolve). - How it is applied: once per run, before any KV loads.
main._apply_stylecallsthemes.activate(saved), setsWindow.clearcolor = C.BG, and sets the app'sdisplay_font/ui_font/head_font/mono_font.ui.load_kvthen putsthemes.kv_header()in front oftheme.kvand every module's KV. It holds the#:setconstants (BG, INK, EMBER…,*_HEX, R_CARD…, GRADIENT / MEANDER / CAPS / HEAD_BOLD, WORD, DISPLAY_SIZE) and the importsbackdrop(),gradient(),meander()andbtn_radius().- Python code reads
themes.C.EMBER/C.hex("DIM")at use time. - Nothing binds to the style, so a new pick needs a restart.
- Picker: Settings, the first card (STYLE). Each row shows swatches (bg, surface, accent, text), the name, a one-line description, and IN USE on the current style.
- Tapping another style opens
StyleConfirm, drawn in the current style: "Switch to <name>? Aegis needs to close and restart…" with Cancel and Restart now. - Nothing changes until Restart now.
- Restart (
engine/restart.py;main.restart_app): StyleSwitch.confirm()savestheme.set_settingnow fsyncs the file beforeos.replace.- "Restarting…" shows. Speech is stopped and cleared, Artemis is disabled, and device polling is unscheduled.
- On a worker thread: with Auto-train on and the service available,
Handoff.give()stops the trainer, waits for the job, saves and releases the lock; the service carries on and is not killed. Otherwisetrainer.stop(wait=30)runs, thenshield_bg.save_all(). - Then
restart.relaunch()runs. - Relaunch on Android:
- On the main thread, while the app is still on screen: post the "Aegis is restarting · Tap to open Aegis with the new style" notification (channel
aegis_restart, id 7102), callstartActivity(Intent.makeRestartActivityTask(launch component)), wait 0.1 s,os._exit(0). Android starts the new task in a fresh process. The next start's_buildcallsrestart.clear_notice(). - The first version (0.1.17) used an
AlarmManageralarm fired after the exit. It never reopened the app on the Tank 3 Pro: Android 10+ blocks activity starts from an app that is not on screen, and the API 34 creator opt-in does not help once the app is gone. - The service is in its own process (
:service_shield), so it neither blocks nor dies with the exit. - Fallback: if the new task is refused, the notification opens the app; otherwise the style applies the next time it is opened. The dialog says so.
- Service class lookup:
ServiceShieldand the activity are resolved once in_build(main thread,_resolve_service). jnius on other threads uses the system class loader and raisedClassNotFoundException: online.mindflux.aegis.ServiceShieldfrom the startup and restart threads. - Relaunch on desktop:
App.stop(), then__main__runsrestart.exec_self()(os.execv(sys.executable, [sys.executable] + sys.argv), after flushing stdout). - Boot screen:
ui/boot.pyhas one layout per style, fed by real steps. The oldBOOT_STEPS0.42 s timers are gone. main.BOOT_STEPSare(key, label, weight): data (take the files back from Shield of Zeus, 10), saved (the phone's stores, 45), library (the pack, 35), start (Auto-train, 10)._load_enginemarks them as they happen.shield_bg.open_stores(progress=…)reports saved and library with counts; a failure marks that stepfailand shows the error.- The screen stays at least
BOOT_MIN_S= 0.9 s, and "Ready" holdsREADY_HOLD_S= 0.3 s. - Fonts:
- Added
LiberationSans-Regular.ttf(410,820 B) andLiberationSans-Bold.ttf(414,568 B), Liberation 2.1, SIL OFL 1.1. The licence is inassets/fonts/LiberationSans-OFL.txt;txtwas added tosource.include_exts, and nothing else inmobile/is.txt. - They are registered as "Sans" and "SansBold". Classic keeps Sans Narrow / C059.
- Contrast: body and muted text are at least 4.5:1 on every surface (translucent panels blended over BG). The lowest is Parchment MUTED on PANEL_HI at 4.95.
- Daylight's bubble is
#2A62F0, not the mockup's#2F6BFF, so white text reaches 4.5. - Aurora's on-accent text is dark
#0B0E1A, not white. - Olympus bubble text is ink, not dark.
- Shots:
aegis-mobile/.venv/bin/python mobile/desktop_shots.py --themes /tmp/aegis-themesruns every style in a throwawayXDG_CONFIG_HOME, with web, Artemis and Auto-train off. It saves boot, Ask, a tutorial and Settings for each, then the confirm dialog and the first frames after the re-exec. - Adding a style: add an entry to
THEMES(17 base colours; the rest are derived in_palette), a boot layout inui/boot.pyVIEWS, and runtest_themes.py. - Test:
test_themes.py. Underaegis-mobile/.venvit also loads every KV once per style.
Carrier phrases and clause superlatives (2026-09-27; see BUGS.md):
assets/intent_phrases.jsonis the one catalogue of question wordings, for the phone and the laptop.engine/phrases.pymatches it (stdlib).qparse.parserewrites through_carrybefore its own patterns; the hand-written patterns stay as the fallback.- The laptop's
intent_phrases.pyloads the same module by path, andmain.py's@_carrier_phraserewritesreq.promptfor the call. - Adding a wording: add it to its category, either as a string (read as the category canonical) or as
[phrase, canonical]. Then runtest_phrases.py: it rejects duplicates and slot mismatches, and checks each phrase's parse. - A canonical must be a form
parsealready handles, and must map to itself. - Never rewritten: reserved phrases (tutorials, about-app, greetings, clock), anything
teach.detectclaims, live data, arithmetic, digits, pronoun subjects. The laptop also skips stock/weather/time/currency words and prompts with a saved correction. - Superlatives: first/last/-est + noun + clause ("the first satellite put in orbit") parse as superlative, with the clause in
detail.checks.superlative_okwants the clause said in the first sentences. - Tests:
test_phrases.py,test_misses.py(test_first_records),test_aegis.py§4b.
Local first (2026-09-27, 0.1.17; see BUGS.md):
answer._lookuporder: definitions → stored answers → saved tutorials → saved web pages → offline library (_from_library) → web → trimmed-definition fallback → miss. Every local answer passes the sameacceptscheck a web answer does, and "that's wrong" rejections are honoured._WEB_FIRSTintents (define, who, age, population, holder, superlative, count, overview) still ask the web before the library. The library is a dated snapshot of scraped pages: it says Jupiter has 95 moons (the web says 115), its "define" passages often just mention the word, and an overview wants the article lead plus key facts. For these the library answers only when the web has nothing or is off.- Live questions skip everything saved, as before. A number or date from the library carries "single source, not saved" and is never marked verified.
- Logs: a
localtrace line lists the sources checked ("checked stored answers, saved tutorials, saved web pages, offline library → none passed; going online");replaynames the source that answered. - Test:
test_local_first.py(the web fetcher andurlopenare instrumented; no network).
Out of scope for now:
- wake word and mic on the phone
- updating the pack without rebuilding
- web search
- trainer features
The old prototypes (a Kivy UI over a toy HTTP bridge with a 3-document corpus and phi3) are in archive/mobile-prototype/.
16. Updating another machine (transfer folder)
The user's second laptop has an older install. It is updated from a folder carried by hand. The user copies it to ~/updates/aegis-transfer there, and an agent follows its START-HERE.md.
Build it here (the repo must be committed; it takes about 25 s, ~24 MB, ~120 MB RSS; the backend can stay up):
./aegis-transfer # recreates ~/Desktop/aegis-transfer/ cp -r ~/Desktop/aegis-transfer /media/$USER/<drive>/
Contents:
| Path | What |
|---|---|
START-HERE.md | Step-by-step guide for an agent that knows nothing; rendered from transfer/START-HERE.md |
repo/aegis.bundle, repo/aegis-code.tar.gz | git bundle --all (every ref and tag) and git archive HEAD |
knowledge/ | aegis-share --out: one Parquet file per table, manifest.json, extras/ (mobile pack, settings), aegis-import |
python/ | pip freeze of .venv / .venv-kokoro (used as -c constraints) and interpreter versions |
system/, mobile/, handoff/, tools/ | Kokoro unit and speak; APK build page with BUILD-GUIDE.md and extract_aegis.py; a fresh aegis-handoff --db with the docs; aegis-update, aegis-import, aegis-check, the pre-commit hook |
MANIFEST.json, SHA256SUMS | Source commit, time, sizes, row counts; checksums of every file |
Safety:
- The folder never contains
.env, venvs, models, the live DuckDB, logs or backups. The builder checks this. - The builder secret-scans everything: text files, the mobile pack, the tarball, and every blob in the git history.
aegis-share --outscans the Parquet text columns. Any hit aborts the build. aegis-handoff --dbcan't open the DB while the backend holds it. The builder passes the backend's storage view viaAEGIS_HANDOFF_EXTRA, as the Handshake page does.
On the other laptop, START-HERE.md runs these steps in order:
sha256sum -c.- Find the install (default
~/Documents/Aegis-Terminal; otherwise ask the user). - Stop Aegis.
aegis-update --dry-run, then the real run. It backs up the code and.gitto.aegis-doctor/backups/update-<ts>/with arollback.shand moves git to the source commit. Old-only files stay unless the user chooses--delete-old-only, which moves them into the backup.- pip installs with the freeze as constraints.
./aegis-import "$T/knowledge", which replaces the DB after moving the old one into a backup../aegis-check --fix, asking before each fix. Sudo fixes go to the user.- The
system/files, with approval. - Verify: health,
full_test.py, the Handshake tests. - The APK (Python 3.11, buildozer 1.6.0, Cython 0.29.37, Kivy 2.3.1; WSL2 on Windows).
Regenerate the folder after any commit that should reach the other laptop. MANIFEST's source_commit must equal HEAD.
17. Curiosity engine (learns by itself, laptop and phone)
Aegis finds subjects in everything it reads, ingests each real one directly, and asks checked questions only for facts still missing. Shared logic: mobile/engine/curiosity.py (stdlib only; the laptop loads it by path). Tests: mobile/test_curiosity.py, mobile/test_autotrain.py, test_curious.py.
Relevance rule, no LLM: a question is asked only when
- the subject is real: an English Wikipedia article with a Wikidata item (disambiguation pages, lists, years and dates are dropped by one batched
wbgetentitieslookup per 50 titles); - the fact exists: one question per property the item has (
PROPERTY_QUESTIONS: born, died, capital, population, elevation, area, founded, founder, inventor, creator, country, mass, orbital period), plus "what is X"; - Aegis found it: in a page it saved, an answer, a tutorial, a Wikipedia lead section, or an item-valued fact it kept, at most 2 steps from your questions or what it already knows;
cross_checkagrees with the Wikidata value (years equal, numbers within 2%, population 10%, names named). A disagreeing answer is not kept and is logged.
Sources: the Wikipedia API (extracts, lead links), the Wikidata API and the free Wikipedia daily feed. No keys, no paid APIs. Seeds: your questions, your misses (laptop: query_log and pending /reports), the feed (only while fewer than 20 topics wait).
Direct ingestion first: each real subject's article is compared claim by claim with its Wikidata facts. Confirmed (the article states it) is stored with both sources; pending (the article doesn't say) waits for review on the laptop and is not kept on the phone; contradicted is dropped and logged. Only the properties still missing are asked.
Laptop (curious.py, a thread in the backend):
- Gates: setting
curious(on) and Internet access; 10 minutes since your last/query; free memory ≥curious_min_free_mb(800) and swap under 90%; direct ingestion 60/hour, 600/day; questions 20/hour, 200/day, 60 s apart; a rest (2, 4, 8 … 60 min) after network errors. - Articles go in through
web_ingest.ingest_url(url, text=, title=); facts throughfacts.insert_pending+confirm(confirmed_by="wikidata+wikipedia")(asked and agreeing:"wikidata+web"); FTS rebuilt once per batch, at most every 30 minutes. - Questions run
query(QueryRequest(prompt, origin="curious")): no local AI (synthesis.no_llm, also in the expansion thread), sessionaegis-curiousinquery_log(left out of the Handshake statistics), not spoken. The web pages such a question reads are saved like any other question's. - State
data/curious.json;GET /curious/status; one line on the Handshake Training tab. - It runs whenever the backend runs, so in practice inside the
aegis-backendlogin service:systemd/aegis-backend.service, managed by./aegis-service.AEGIS_SERVICE=1skips the AI warm-up; boot stages still wait for the first page. Anything that opens DuckDB for writing goes through./aegis-service pause CMD(handoff.shdoes;build-apk.shpauses it for the whole build). The desktop's full Exit stops the service through systemd; it starts again at login.
Phone (Auto-train, needs 0.1.19): a curious job after follow-ups, every second question, under every Auto-train gate and cap (so only while charging in the background by default). Setting auto_curious (on). Direct ingestion keeps confirmed facts and the article's lead as auto records; ingest.MAX_AUTO is 10,000 (about 5 MB), shown as "of which Auto-train facts" in Storage and cleared from Settings. Trainer card: "Curious: N topics waiting · K facts kept, D disagreed".
18. Shared archive (every phone, one server)
Every Aegis phone downloads one archive of checked facts. Phones whose users agree help build it. Server: server/ (PHP 8, PDO MySQL; server/README.md has install, cron and the API). Phone: mobile/engine/archive.py (stdlib only). Tests: server/tests/test_check.php, server/tests/test_api.php (--mysql DSN USER PASS on the real database), mobile/test_archive.py.
Phones point, the server fetches. A contribution is {qid, title, pids, about} plus a random device id and token from v1/register. Questions, answers, teachings and anything typed are never sent. verify.php (cron, every few minutes) fetches the Wikidata entity and the Wikipedia intro itself and runs lib/check.php, a port of curiosity.check_article that must stay in step with it (tests/make_vectors.py rebuilds the vectors from the phone's Python). Only values from the server's own fetch are published; a value that later fails a re-check (after 90 days) is withdrawn, and the withdrawal syncs.
Downloading is private: v1/facts?since=SEQ (delta sync, every 6 h under Auto-train's gates) and v1/bucket/XXXX for a name not on the phone. XXXX is the first 4 hex digits of the sha256 of curiosity.norm(name), so the server never learns the name or the question.
Trust: refs are accepted, rejected (contradicted, not a real subject, no such claim) or unconfirmed (no article, a redirect, not stated); agreement counts devices. A device with at least 50 refs, over 60% of them rejected, is suspended (403 on the phone stops contributing). Rate limits are in config.php limits. admin.php withdraws facts and suspends or restores devices.
Phone:
ARCHIVE_URLinarchive.pyis empty. Set it before a build, or the user sets Settings > Shared archive > Archive address (archive_url).- Settings:
archive_download(on),archive_contribute(off until the one-time dialog or Settings),archive_max_mb(50),archive_asked. Turning Contribute off drops the queue. - Answer order: the archive comes right after stored answers, before saved tutorials, pages, the library and the web. Chip
SHARED ARCHIVE. - Auto-train: an
archivejob (sync or send, at most every 15 min). Direct ingestion and agreeing curious answers queue contributions. Subjects the archive has are not read again. With consent, up to 12 leases a day while fewer than 20 topics wait. - Files:
shared.sqlite(trimmed oldest-first to the size cap) andarchive_queue.json, beside the other data.
Deploying:
- Copy
server/to the web host and create the database withschema.sql. - Copy
config.sample.phptoconfig.phpand fill in the database,salt,cron_keyandadmin_password_hash. - Add the cron line for
verify.php. - Run
php tests/test_api.php --mysql ...once. - Put the URL into
ARCHIVE_URL, or into the phone's setting.
19. Signal Walk inside Aegis (one APK)
Signal Walk logs Wi-Fi, Bluetooth and cell towers along a GPS walk. It is native Kotlin, and p4a cannot compile Kotlin, so it ships as a prebuilt Android library that buildozer merges.
- Source:
~/UNM/android.app/is the standalone app (packageorg.unm.signalwalk).walklib/is a library module that builds the same sources and resources (../app/src/main) with its own manifest:MainActivitywithout a launcher entry andWalkService. Edit the code inapp/only. - Rebuild the AAR after changing Signal Walk:
mobile/build-signalwalk-aar.sh. It runs offline with--no-daemon,-Xmx1gand in-process Kotlin, and writesmobile/libs/signalwalk.aar. Then build Aegis as usual (mobile/build-apk.sh, Cursor closed). - buildozer.spec:
android.add_aars, aflatDirrepository,android.gradle_dependencies(must matchwalklib/build.gradle.kts;test_shield_bgchecks this),enable_androidxandminapi = 29.libs/is not packaged as app source. The buildozer build downloads those dependencies, so it needs the network. - Opening it:
main.pyopen_signal_walk()startsorg.unm.signalwalk.MainActivityby class name in Aegis's task, so Back returns to the selection page. Aegis pauses meanwhile like for any other app (Artemis to the background, the Shield service holds the data). - Data: walks stay in
Download/signalwalk/walks.db. Aegis needs "All files access" (Signal Walk asks for it), plus location, Nearby devices and notifications for a walk. - Old app:
org.unm.signalwalkis no longer needed and can be uninstalled. Its walks are in the samewalks.db, so nothing is lost.
Voice (Artemis), with or without the wake word, or typed on the Ask page:
- "open Signal Walk", "start Signal Walk app": opens it (the same as the selection card).
- "start a walk", "start Signal Walk", "begin a new walk", "start walking": starts
WalkServicethroughAegisBridge.startWalk. If a permission, location or All files access is missing, it opens Signal Walk's Start walk flow instead (AegisBridge.openToBegin,EXTRA_BEGIN), and says what is needed. The walk starts once it is allowed. - "stop the walk", "end Signal Walk", "stop walking": stops it. A bare "stop" still only stops speech.
- "is the walk running", "walk status": the live counts from
WalkBus. - Parsing is in
engine/voice_commands.py(walk_command, thesignalwalktarget) andengine/artemis.py(eventssignal_walkandwalk).main.pywalk_command()callsorg.unm.signalwalk.AegisBridge(jnius, main thread) throughengine/signalwalk.walk_line. The spoken line is also put on the Ask page.
Aegis Engine questions (engine/signalwalk.py, stage "Signal Walk", qtype signalwalk, chip SIGNAL WALK). This stage comes right after your own teachings, before every other stage:
- "how many devices has Signal Walk found" (or Wi-Fi networks, Bluetooth devices, towers; "on my last walk" counts only that walk);
- "what's the strongest Wi-Fi nearby", "strongest Bluetooth", "strongest cell tower" (on the latest walk);
- "how far is <name>" (the latest sample's
estimated_distance_ftfromextra_json, or RadioEstimate's formula from its RSSI); - "summarize my last walk" (when, how long, how far, the radios and the strongest Wi-Fi);
- "how many walks"; "what is Signal Walk" (what it does and its commands).
- It only takes clearly Signal Walk questions. "how far is the moon" and "how many walks did Babe Ruth have" go on to the rest of the engine. "how far is X" is answered only when X is a radio in
walks.db, or when the question says Wi-Fi, Bluetooth, device or Signal Walk. walks.dbis opened read-only (file:...?mode=ro, 2 s timeout). Off the phone the stage readsAEGIS_SIGNALWALK_DBor~/Downloads/signalwalk/walks.db. If the file is missing or unreadable, the answer says to allow All files access. Nothing is filed, searched or sent.signalwalkis inautotrain.KNOWN_QTYPES, so these questions are never retried as misses.
Ask Artemis button: the walk screen's header shows ASK ARTEMIS only inside Aegis (AegisBridge.hosted). It sets AegisBridge.requestAsk() and finishes. If the walk screen is alone in its task, it first starts Aegis. main.py on_resume → _signal_walk_back() reads takeAsk(), opens the Ask page and calls listen(send=True). The question heard is asked, and its answer is read aloud.
Pause and resume: while Signal Walk is on top, Kivy is paused. on_pause stops Artemis's recognizer first, then any Voice-button dictation; on_resume turns Artemis back on. A walk runs in WalkService, a foreground service (location and connected device) in the app's main process. It is independent of the Shield of Zeus service (:service_shield process, dataSync), so handing the data to the Shield service does not touch it. Closing Aegis with "exit" may end the app's process, and with it a walk in progress (not tried on the phone); say "stop the walk" first.
After changing the Kotlin (AegisBridge.kt, MainActivity.kt, WalkFragment.kt, fragment_walk.xml in ~/UNM/android/app): run mobile/build-signalwalk-aar.sh, then mobile/test_shield_bg.py (checks AegisBridge.class and the button in the AAR) and mobile/test_signalwalk.py. Then build Aegis.
20. One app: Aegion everywhere (2026-10-03)
Aegion is the one Aegis app: mobile/main.py (Kivy 2.3.1) on Android, iPhone/iPad, and the Linux, macOS and Windows desktops. Section 15 describes the app itself; this section is the map of the merge. The BUGS.md entries for 3 Oct have the details and test counts.
Names.
- Aegion is the display name: the window title (
APP_NAMEinmain.py),titleinbuildozer.spec, each style'swordinengine/themes.py, the about answers (engine/about_app.py), the restart notice, the logs header and the iOSCFBundleDisplayName. - Keep the internal names: package id
online.mindflux.aegis(an update keeps the data and the permissions), the App classAegisMobile(Kivy derives the data folderaegismobilefrom it), the repo folder,./aegis,aegis-service, the module names, and the feature names Aegis Engine, Artemis (the wake word), Shield of Zeus and Signal Walk.
Platform layer.
engine/plat.pyanswers every "which device / what can it do" question:name(),is_android,is_ios,is_desktop,can_stt,can_background,can_restart,can_scan_radios,has_laptop, andsafe_area/android_insetsfor edge-to-edge padding.engine/plat_ios.pyholds the iOS voice, speech recognition and links (pyobjus, Mac build only).
Running it.
- Linux: Aegion in the app menu (
~/.local/share/applications/aegis-terminal.desktop, backed up in.aegis-doctor/backups/oneapp-20261003-175319/launchers/) or./aegis. This reuses the backend, starts Kokoro and the Artemis worker, and sets the window classAegion../aegis --classicopens the old GTK window. - macOS / Windows: from source,
mobile/RUN-DESKTOP.md. - Android:
mobile/build-apk.shwith Cursor closed (0.2.0 installs over 0.1.19 and keeps the data). - iOS:
mobile/BUILD-IOS.md(Mac with Xcode;buildozer --profile ios ios debug, thenpython3 ios-plist.py).
What was merged where.
| From | Into Aegion | Notes |
|---|---|---|
| Laptop backend answers | Laptop tab, "Laptop engine first" (engine/laptop.py, ui/laptop.py) | 127.0.0.1:8000 on the laptop; over the LAN with the pairing code |
| Weather / currency / time fast paths | engine/livedata.py | live, never stored; time works offline for built-in cities |
~/UNM/signalwalk (Python) | mobile/signalwalk/ (sync-signalwalk.sh), engine/walkview.py, ui/signalwalk.py | viewer everywhere; recorder stays the Android AAR |
| Guardian Security Center scanner | mobile/guardian/ (sync-guardian.sh), engine/guardian_scan.py, Shield screen card | Linux only, reads only |
| StockTrack / StockTrack Pro | Markets tab (engine/portfolio.py, ui/markets.py) | launchers retired, backups in retired-launchers/ |
SignalDub (~/signal-dub) | Choose screen card (engine/studio.py, port 8766) | desktop, when installed; FFmpeg missing |
LAN sync (laptop and phone).
- Laptop side:
lan_sync.py, routes inhandshake_routes.py: GET /sync/pairandPOST /sync/pair/new(local only);GET /sync/pack;POST /sync/teachings;GETandPOST /sync/walks.- The
lan_sync.guardmiddleware inmain.pydemands thex-aegis-pairheader from every non-local client on every route except/health. The code is indata/lan_pair.json(chmod 600) and shown on the Handshake page's Settings tab. - The backend unit still binds
127.0.0.1. For phones on the Wi-Fi, change it to--host 0.0.0.0(RUN-DESKTOP.md). - App side:
engine/lan_sync.run, behind Settings > LAPTOP LINK > Sync now: - teachings both ways (a taught answer always wins; web/auto answers only fill gaps);
- pending trainer prompts;
- walks (the phone sends, other devices fetch);
- the knowledge pack, saved as
aegis-pack.sqlitein the data folder and preferred over the bundled one when newer.
Android background (Tank 3 Pro findings, 3 Oct).
- The Shield service stops when the trainer is spent: its session limit or the day's cap, with no queued user tutorial. The app does not restart it on a capped day (
shield_bg.spent,idle_today). - The hidden browser reader is off in the service process (
browser_reader.in_service). - Insets are read at runtime (
plat.android_insets). <queries>for TTS engines and recognizers (android_manifest_extra.xml).
Building the APK on this network (0.2.0 / 0.2.1 builds, 3 Oct):
- A direct connection to dl.google.com / Maven times out, but the SOCKS proxy in
ALL_PROXYworks. sdkmanager and Gradle are Java and ignoreALL_PROXY, so the build hangs at "Fetch remote repository". Run it asJAVA_TOOL_OPTIONS="-DsocksProxyHost=10.98.59.9 -DsocksProxyPort=1080" mobile/build-apk.sh. Don't unset the proxy. curl localhost:8000/healthgoes through the proxy too and gets nothing back. Usecurl --noproxy '*'.android.minapi = 29(for the AAR) is aboveandroid.ndk_api = 26, so the spec passesp4a.extra_args = --allow-minsdk-ndkapi-mismatch. That avoids rebuilding every native recipe.- pyjnius picks overloads by argument type. For
Intent.setClassNamepassactivity.getPackageName(), not the activity.
After installing 0.2.x on the phone (user actions):
- Grant Location (Precise, while using), Nearby devices and All files access (for
Download/signalwalk/walks.db). - Set battery to Unrestricted, and exempt Aegion from the Agui "background power saving".
- Keep the standalone Signal Walk app until Aegion has recorded a walk. Don't record in both at once, then uninstall it (its walks stay in Download).
Tests.
- Each suite runs from
mobile/with../aegis-mobile/.venv/bin/python test_X.py. - New suites:
test_plat,test_laptop_link,test_fastpaths,test_signalwalk_ui,test_sync,test_guardian. - Screenshots:
mobile/desktop_shots.py --oneapp [DIR], offline, in a throwaway data folder with a made-up walk.
Left as they are (not merged):
- Personal and legal documents, the websites and PHP games, CoreDefense, agentic-analyzer.
- Duplicates: the zips, the "(Copy)" folders and the empty
thresholdfile. They were listed for the user, not deleted.
Session notes
(Append a dated entry here at the end of each work session: what changed, what was verified, what's next.)
2026-09-25 — Handshake page
- Changed:
- New
settings.py,system_report.py,handshake_routes.py,templates/handshake.html,static/aegis-handshake.{css,js}(section 14). - Red Handshake button on the launcher.
desktop.py:aegisControlchannel, saved settings at startup, mic-blocked state.main.py: settings applied at startup,LLM_CAP_Sread live, mic 403 gate.synthesis.py:ram_okdefault 1600 MB.aegis-handoff:AEGIS_HANDOFF_NO_CLIPandAEGIS_HANDOFF_EXTRA.- Verified (backend only, with Cursor open):
- Every endpoint returns 200.
- Bundle builds in ~2.5 s with the leak check passing.
- Query tests: 6 pass, 1 fail (Marie Curie, section 13), AI skipped because memory was low.
- Trainer tests: 6/6 pass, and the DB counts are unchanged afterwards.
- Mic test records and meters.
- A settings round-trip saves and applies live.
- All tabs render in a browser with no JS errors.
- Not verified: the desktop side (
aegisControl, Artemis stop/start around the mic test), because the desktop must not run alongside Cursor. Test it with Cursor closed. - Next: fix the loose correction match ("who is Marie Curie" → "1867").
2026-09-25 — Aegis Mobile standalone APK
- Changed:
- New
mobile/(section 15): the app, the offline engine, the pack exporter,buildozer.specfor the Tank 3 Pro (API 35, arm64-v8a), the build and desktop-run scripts. ~/aegis-mobile-uiand~/aegis-termuxmoved toarchive/mobile-prototype/(a copy is in.aegis-doctor/backups/mobile-*)..gitignore:mobile/.buildozer/,mobile/bin/,mobile/dist/,aegis-mobile/.- Verified on the laptop:
- Pack: 5.1 MB with the expected counts.
- Engine self-test 7/7, each answer well under 50 ms.
- Local-AI path tested against a stub Ollama, including the fallback when it's down.
- Desktop-window screenshots of Ask, Library and Settings.
- All mobile files compile under Python 3.11.
build-apk.shrefuses to run while Cursor is open.- Not done: the APK build itself (it needs Cursor closed) and on-phone testing, especially edge-to-edge padding and text-to-speech.
- Next: close Cursor, run
mobile/build-apk.sh, install on the Tank 3 Pro.
2026-09-25 — Updating the second laptop (requirements, knowledge, transfer folder)
- Changed:
- New
requirements_check.py/aegis-check,requirements-kokoro.txt,system/,knowledge_bundle.py,aegis-share,aegis-import,secret_scan.py,aegis-update,aegis-transfer,transfer/START-HERE.md(sections 4, 14, 16). - Launcher quick check.
- Handshake requirements card and
/system/requirements,/system/knowledge/export. .envgit-ignored.- Verified (backend only, Cursor open):
aegis-check: 24 OK, 3 WARN, 0 MISSING.- LAN share and download.
- Folder and URL imports into a temp data dir: 10/10 tables match, FTS 2,091 docs, test search OK.
- Transfer folder: checksums, bundle clone equals HEAD, and the update simulation from 7a1adb6 (with and without git,
--delete-old-only, rollback). - The protected files are unchanged throughout.
- Note: a test's
rollback.shonce stopped this machine's backend, because it usedfuser -k 8000/tcp. It now stops only a backend whose working directory is the target install, and so doesaegis-update. - Next: carry
~/Desktop/aegis-transferto the other laptop and give Cursor the first prompt: read~/updates/aegis-transfer/START-HERE.mdand follow it.
2026-09-27 — Desktop integrity and cleanup
- Changed:
query_corrections.py: Jaccard keyword score,question_class()guard, deterministic tie-break.main.py: nonsense gate v3 refusals re-enabled.config.py/synthesis.py/settings.py: keep-alive 5m, read live, and theai_keep_alive_minsetting.- Deleted four of the five Sep 19
data/aegis.duckdb.bak.*files. - Verified (all on DB copies, with the backend untouched):
- Corrections: 962/962 self-match, and the 627-prompt replay changed only the 2 Curie prompts (now right) and dropped 2 wrong matches.
- Nonsense probes: gibberish answered 10/20 → 3/20, real refused 0/20, real answers unchanged, with web on and off.
- Keep-alive values parse and cap correctly.
- The 3.7 GB import backup was deleted after a row-by-row check against the live DB.
- Not done: the backend was stopped and picks up the new code on its next start.
- Next: restart the backend and check
/health; then look at OPEN_ITEMS 8 (/teachfabrication).
2026-10-02 — Health audit fixes (BUGS.md, 2 Oct entries)
- Laptop:
- Every request that can load the Ollama model checks
synthesis.llm_allowed()first: the Local AI setting andram_ok(). Add the check to any new model call. The boot warm-up also needs theai_warmupsetting. Test:test_ai_guard.py. - The backend answers only its own pages:
config.OWN_ORIGINSfor CORS, the_refuse_foreign_pagesmiddleware, and an Origin check on the/ptyWebSocket. curl and scripts (no Origin) still work. Test:test_origin_guard.py. - Phone (needs a 0.1.18 build):
- While Shield of Zeus is handing the data back, saves go through
app.defer_until_data(fn, say)(UI:ui.widgets.after_data) and run once the data is back. Engine work that writes runs afterapp.wait_data(). Any new save from the UI should use one of the two. - Stores write their
<name>.<pid>.tmpinside their own lock. buildozer.specleaves everytest_*.pyout of the APK.- Test:
test_shield_bg.py. - Hardware line in section 2 corrected to 7.8 GB RAM, 4 GB swap.
2026-10-03 — Curiosity engine and the backend login service (BUGS.md, 3 Oct entry)
- New:
mobile/engine/curiosity.py,curious.py,systemd/aegis-backend.service,aegis-service, testsmobile/test_curiosity.pyandtest_curious.py. Section 17 has the rules. - The service was installed once (
./aegis-service install); the backend now starts at login. - Phone changes need a 0.1.19 build.
- Next: after a day of idle time, read
GET /curious/status(or the Handshake Training line) and spot-check a fewconfirmed_by = 'wikidata+wikipedia'facts.
2026-10-03 — Shared archive (BUGS.md, 3 Oct entry)
- New:
server/(PHP + MySQL API, verifier cron, admin page, tests),mobile/engine/archive.py,mobile/test_archive.py. Wired intoanswer.py,provenance.py,autotrain.py, Settings and a one-time consent dialog. Section 18 has the rules. - Verified: PHP check vectors 354/354, API 76/76 (in-memory store),
php -Ssmoke test, phonetest_archive70/70, and all phone suites. - Not done: no MySQL server on this laptop, so the
--mysqltest run waits for the real server. The archive address is still empty. - Phone changes need a 0.1.20 build (
buildozer.specbumped, not built). - Next: deploy
server/, runphp tests/test_api.php --mysql ...there, set the address, build 0.1.20 with Cursor closed.
2026-10-03 — One app: Aegion (sections 15 and 20, BUGS.md 3 Oct entries)
- Merged the laptop apps into the Kivy app, now called Aegion, version 0.2.0 (not built). Added the iOS target (Mac build only) and the Tank 3 Pro fixes (insets, idle service, browser reader in the service).
- Verified: all 33 app suites pass, the live backend sync routes work, and the
--oneappscreenshots are taken.full_test.pygave 18/28: network timeouts while the proxy failed, plus two qtype labels. - Next: build 0.2.0 with Cursor closed, install, grant the permissions, check the header padding ("safe area" log line) and that the Shield service stops at "Session limit".