On this page
Open bugs
2026-10-03 — Phone (Tank 3 Pro): tapping Signal Walk crashed Aegion (NoClassDefFoundError ViewBinding) — FIXED (0.2.2)
2026-10-03 — Phone (Tank 3 Pro): "Signal Walk" button does not open the built-in Signal Walk — FIXED (0.2.1)
2026-10-03 — APK build: minapi 29 above ndk_api 26 stopped python-for-android — FIXED
2026-10-03 — Phone (Tank 3 Pro): header under the status bar, the Shield service idling on Android's allowance, the hidden browser failing in the service — FIXED
2026-10-03 — One app everywhere: Aegion on Linux, macOS, Windows, Android and iPhone — ADDED
2026-10-03 — Phone: Signal Walk voice and engine integration (Artemis runs it, the Aegis Engine knows its walks) — ADDED
2026-10-03 — Phone: Signal Walk merged into Aegis (one app) — ADDED
2026-10-03 — Shared archive: every Aegis phone downloads checked facts, and phones that agree help build them (PHP + MySQL server) — ADDED
2026-10-03 — Phone: the Shield of Zeus background service crashed on Android 15 (data-sync time limit, sticky restarts) — FIXED
2026-10-03 — Curiosity engine: Aegis finds subjects in what it reads, ingests them and asks checked questions, by itself — ADDED
2026-10-02 — Phone: saves during the Shield of Zeus hand-back could be lost, saves raced on one .tmp, tests went into the APK — FIXED
2026-10-02 — Laptop backend answered any website: open CORS, no Origin check on writes or the terminal — FIXED
2026-10-02 — Laptop: the RAM check and the local-AI setting were skipped on some model loads — FIXED
2026-09-30 — Go deeper levels read the wrong sense, and a rebuild kept levels from the wrong article — FIXED
2026-09-30 — Tank 3 Pro log (0.1.17, Sep 28): trainer saves collided, harvested subjects lost context, a wrong "how much" answer, "distance between" missed, a game page in a tutorial — FIXED
2026-09-30 — Phone web requests ignored their deadline: a 12 s web search took 84 s — FIXED
2026-09-30 — Tutorials: "how to build a company" was built from Build-A-Bear Workshop; off-topic Stack Exchange threads; concepts lost their subject ("Preparation" → Preparation H) — FIXED
2026-09-27 — Local first: the phone checks its offline library before the web; the laptop skips web expansion for short questions it can answer — CHANGED
2026-09-27 — Carrier phrases: one catalogue of question wordings for the phone and the laptop — ADDED
2026-09-27 — Phone: "the first satellite put in orbit" found no answer — FIXED
2026-09-27 — Phone: style change never reopened the app; background service failed from threads — FIXED
2026-09-27 — Phone: selectable styles (7 themes), restart to apply, honest boot progress — ADDED
2026-09-27 — Phone: Shield of Zeus keeps building queued tutorials in the background — ADDED/FIXED
2026-09-27 — Phone: Go deeper, a new level on any saved tutorial — ADDED
2026-09-27 — Year fix restored in the laptop extractive_qa.py — FIXED
2026-09-27 — Phone: Shield of Zeus builds tutorials by itself, Tutorials navigation, answers from saved tutorials — ADDED
2026-09-27 — Phone: pause, resume, stop and play for answers read aloud — ADDED
2026-09-27 — Phone storage figure for data gathered on the device — ADDED
2026-09-27 — Phone Tutorials page: sourced tutorials built from the web, kept offline — ADDED
2026-09-27 — Phone 0.1.14 session fixes: dropped years, bare questions, how-to subjects, distance endpoints — FIXED
2026-09-27 — Phone auto-train waited for Wi-Fi on mobile data — CHANGED (0.1.14)
2026-09-27 — Aegis Mobile 0.1.13 crashed in the voice listener (SIGSEGV) — FIXED (0.1.14)
2026-09-27 — Nonsense queries got confident junk answers (gate was switched off) — FIXED
2026-09-27 — "who is Marie Curie" answered "1867" (loose correction match) — FIXED
2026-09-27 — Ollama keep-alive 30m → 5m, adjustable on the Handshake page — CHANGED
2026-09-27 — Disk: old DuckDB backups deleted (~6.2 GB) — CLEANUP
2026-09-25 — Updating a second laptop: requirements check, knowledge export/import, transfer folder — ADDED
2026-09-25 — Aegis Mobile standalone APK — ADDED
2026-09-25 — Handshake page, live tests, settings and permissions — ADDED
2026-09-25 — desktop + Cursor froze the host; AI handoff system — OPEN / ADDED
2026-09-24 — stray numbers in answers; stitched answers — FIXED
2026-09-24 — Artemis ignored wake word — FIXED
2026-09-23 — Artemis wake-word + silent answers + SOZ UI — ADDED
2026-09-23 — Exit button (full stack) — ADDED
2026-09-23 — voice commands (STT) — ADDED
2026-09-23 — desktop voice (Kokoro) not wired — FIXED
2026-09-23 — extract.py _sentences NameError (facts generate) — FIXED
2026-09-23 — desktop wrapper kill-race / fake boot / CDN fonts — FIXED
2026-09-19 — attribute-value misalignment in table-formatted sources — FIXED
2026-09-19 — preflight symbol match false positive — FIXED
2026-09-19 — extract.py doesn't parse table-formatted docs
2026-09-19 — no hedge-word penalty in sentence scoring

BUGS.md

Bug log

Open bugs

2026-10-03 — Phone (Tank 3 Pro): tapping Signal Walk crashed Aegion (NoClassDefFoundError ViewBinding) — FIXED (0.2.2)

Why: on 0.2.1 the intent fix worked (20:05:37 app opened Signal Walk), but the process died 3 s later. dumpsys activity exit-info showed APP CRASH (EXCEPTION), and dumpsys dropbox --print data_app_crash had the stack. logcat had already rolled past it, flooded by the vendor fingerprint SIGSEGV.

java.lang.NoClassDefFoundError: Failed resolution of: Landroidx/viewbinding/ViewBinding;
  at android.app.AppComponentFactory.instantiateActivity ... performLaunchActivity
Caused by: java.lang.ClassNotFoundException: Didn't find class "androidx.viewbinding.ViewBinding"

walklib builds with buildFeatures { viewBinding = true }. That adds androidx.databinding:viewbinding:<AGP version> to walklib's own build implicitly, so it is not an implementation(...) line. An AAR carries no dependencies, and android.gradle_dependencies copied only the explicit ones. The generated *Binding classes in the AAR implement ViewBinding, so MainActivity could not even be instantiated.

Checked and fine: the merged activity has android:theme="@style/Theme.SignalWalk" (parent Theme.MaterialComponents.DayNight.NoActionBar), and the style resource is in the APK. The AAR's classes refer only to kotlin, recyclerview, fragment, lifecycle, activity, core, appcompat, material, annotation (all covered by the declared dependencies) and viewbinding.

Fixed:

Verified:

2026-10-03 — Phone (Tank 3 Pro): "Signal Walk" button does not open the built-in Signal Walk — FIXED (0.2.1)

Why: on the installed 0.2.0, aegis-log.txt shows Signal Walk not opened: TypeError: Invalid instance of 'org/kivy/android/PythonActivity' passed for a 'java/lang/String'. main.py open_signal_walk() calls intent.setClassName(activity, SIGNAL_WALK_ACTIVITY). Intent.setClassName has both (Context, String) and (String, String) overloads, and pyjnius picks the (String, String) one, so it rejects the activity. The activity itself is in the APK: org.unm.signalwalk.MainActivity and WalkService merged into the manifest from libs/signalwalk.aar.

Fixed: intent.setClassName(activity.getPackageName(), SIGNAL_WALK_ACTIVITY), and test_shield_bg.py checks the new call. Version 0.2.1. The other jnius calls added this session were checked and need no change:

Verified: test_shield_bg 102/102, test_signalwalk 125/125, test_signalwalk_ui 41/41, test_plat 60/60, test_themes 120/120.

2026-10-03 — APK build: minapi 29 above ndk_api 26 stopped python-for-android — FIXED

Why: buildozer android debug for 0.2.0 failed at packaging: "--minsdk argument does not match the api that is compiled against … You must pass --allow-minsdk-ndkapi-mismatch". android.minapi was raised to 29 for libs/signalwalk.aar, while android.ndk_api (and the cached dist's native libraries) stayed at 26.

Fixed: mobile/buildozer.spec sets p4a.extra_args = --allow-minsdk-ndkapi-mismatch. Native code compiled for API 26 runs on 29+. Raising ndk_api to 29 instead would rebuild every recipe (Python, Kivy, SDL2, OpenSSL) on a 7.8 GB laptop.

Also: sdkmanager and Gradle are Java and ignore ALL_PROXY. On this network a direct connection to dl.google.com times out and the SOCKS proxy works, so sdkmanager hung at "Fetch remote repository". The build ran with JAVA_TOOL_OPTIONS="-DsocksProxyHost=10.98.59.9 -DsocksProxyPort=1080" (no script change). Likewise curl localhost:8000/health needs --noproxy '*' while ALL_PROXY is set.

Verified: test_plat 60/60, test_shield_bg 102/102, test_guardian 38/38, test_themes 120/120, test_phrases 139/139. Build OK in about 8 min (mobile/dist/aegis-mobile-0.2.0-arm64-v8a-debug.apk, 44 MB). adb install -r on the Tank 3 Pro succeeded and shows versionName 0.2.0. The app starts and logs "safe area (110, 0) px, nav 2: top 128 px, bottom 48 px", with no crash in logcat.

2026-10-03 — Phone (Tank 3 Pro): header under the status bar, the Shield service idling on Android's allowance, the hidden browser failing in the service — FIXED

Why: a read-only analysis of the Tank 3 Pro (0.1.19, /tmp/tank3pro-analysis/REPORT.md) found:

  1. main.py padded the top by dp(28) = 84 px, but the status bar and camera hole take 110 px (37 dp at density 3.0), so 26 px of the header sat under them. The spec page claimed 36 dp / 28 dp, which matched neither.
  2. shield_service.json had read "Session limit · 120 questions" since 17:40, yet the dataSync foreground service kept running and counting against Android 15's 6-hour allowance. ServiceLoop._watch stopped only for app back, Auto-train off, allowance used up or stretch time. Idle days like this used up the allowance on 30 Sep, which led to the crashes in the entry below.
  3. aegis-log.txt had 57 browser-reader failures in 30 minutes, all ClassNotFoundException: org.jnius.NativeInvocationHandler, from [bg] lines. pyjnius cannot load its proxy class in the :service_shield process, and there is no Activity there to host a WebView anyway.

Fixed:

Verified:

2026-10-03 — One app everywhere: Aegion on Linux, macOS, Windows, Android and iPhone — ADDED

Why: the user asked to merge the Ubuntu system's apps into one application "for desktop and android and apple devices", then to call it Aegion.

Added (details in HANDOFF.md section 20):

  1. Platform layer. engine/plat.py and engine/plat_ios.py. The latter uses pyobjus for AVSpeechSynthesizer, AVAudioRecorder + SFSpeechRecognizer and openURL:options:.
  2. Desktop app. ./aegis opens the Kivy app with the laptop backend, Kokoro (engine/speech.py) and the Artemis worker as the microphone (engine/desktop_voice.py). It adds a Laptop tab (ui/laptop.py, engine/laptop.py). ./aegis --classic keeps the GTK window for one release.
  3. Fast paths. Weather, exchange rates and city times (engine/livedata.py, live, never stored).
  4. Signal Walk everywhere.
  1. LAN sync.
  1. Merged apps.
  1. iOS.
  1. Packaging.
  1. Name.

Verified:

2026-10-03 — Phone: Signal Walk voice and engine integration (Artemis runs it, the Aegis Engine knows its walks) — ADDED

Why: the user asked for Aegis's voice operation in Signal Walk as well, "a total system". Signal Walk was only a screen opened from Aegis: Artemis couldn't open it or run a walk, and the engine knew nothing about the walks.

Added:

Verified:

2026-10-03 — Phone: Signal Walk merged into Aegis (one app) — ADDED

Why: the user asked for Signal Walk (~/UNM/android, native Kotlin, until now its own APK org.unm.signalwalk) to be part of Aegis as one app, with no second install. This replaces the same-day card that opened the separate app (android_queries.xml, the "not installed" note).

Added:

Verified:

2026-10-03 — Shared archive: every Aegis phone downloads checked facts, and phones that agree help build them (PHP + MySQL server) — ADDED

Why: the user asked for every Aegis phone to work together on one archive of facts, coordinated by an API on their own web server and SQL database. Each phone learned alone, so the same subjects were researched again on every phone.

Choices (the user's): PHP with MySQL. Anyone who installs Aegis can join. The server re-checks every contribution against Wikidata and Wikipedia itself and counts how many devices agree. Every phone downloads automatically; contributing needs the user's consent.

Design: phones point, the server fetches.

Added:

Verified:

2026-10-03 — Phone: the Shield of Zeus background service crashed on Android 15 (data-sync time limit, sticky restarts) — FIXED

Why: a read-only log pull from the Tank 3 Pro (Android 15, 0.1.17, dropbox crash records) showed :service_shield crashing three times on 30 Sep:

  1. 21:39:06, after a 4 h run: ForegroundServiceDidNotStopInTimeException: A foreground service of type dataSync did not stop within its timeout.
  2. 21:39:08: ForegroundServiceStartNotAllowedException: Time limit already exhausted for foreground service type dataSync.
  3. 22:09:08: ForegroundServiceStartNotAllowedException: Service.startForeground() not allowed due to mAllowStartForeground false.

Cause:

Options weighed:

Fixed:

Verified:

2026-10-03 — Curiosity engine: Aegis finds subjects in what it reads, ingests them and asks checked questions, by itself — ADDED

Why: the user asked for Aegis to keep gathering factual knowledge without being prompted, from relevant questions only: nothing invented, everything traced to something Aegis actually read. Auto-train on the phone asked only from a fixed list; the laptop had only one-off batch scripts.

Relevance rule (no LLM anywhere): a question is asked only when all four hold:

  1. The subject is real: an English Wikipedia article with a Wikidata item, not a disambiguation page, list, year or date.
  2. The fact exists: one question per Wikidata property the item actually has (born, died, capital, population, elevation, area, founded, founder, inventor, creator, country, mass, orbital period), plus "what is X".
  3. Aegis found it: in a page, answer, tutorial, lead section or fact it read, at most 2 steps from your questions or from what it already knows.
  4. cross_check agrees: the answer's year, number (2%, population 10%) or name matches the Wikidata value.

Added:

Verified:

2026-10-02 — Phone: saves during the Shield of Zeus hand-back could be lost, saves raced on one .tmp, tests went into the APK — FIXED

Why: found in the 2 Oct health audit.

  1. Coming back from the background, the app waits up to about 50 s for the service to hand the data files back (shield.take). During that wait the user can already tap. Trainer saves (starter pack, paste, Use this / Wrong on an auto-learned answer, review commit and skip), keeping or deleting a tutorial, queueing a subject, and the review draft's engine call all wrote straight away. The service could then save over them, or the app could reload the service's files and drop them.
  2. autotrain, tutorial_train, market.SnapshotStore, sports.SportsLog and local_corrections built the JSON inside their lock but wrote <name>.<pid>.tmp outside it. Two threads in one process share that name, so one could rename the other's file away (the same "No such file or directory: ….tmp" as the Sep 28 Tank log). local_corrections had no lock at all.
  3. The hand-back fingerprint, which decides whether the app reopens its stores, left out sports_log.json and sports_cache.json. The background engine fills both.
  4. buildozer.spec excluded only 3 of the 24 test_*.py files, so the other 21 shipped in the APK, along with make-spec-html.py.

Fixed:

Verified:

2026-10-02 — Laptop backend answered any website: open CORS, no Origin check on writes or the terminal — FIXED

Why: found in the 2 Oct health audit. main.py allowed CORS from *, and nothing checked who was calling. The backend has no login, so any page open in a browser on this machine could call http://127.0.0.1:8000: change settings, ingest pages, delete data, or open the /pty terminal WebSocket (a shell).

Fixed:

Verified:

2026-10-02 — Laptop: the RAM check and the local-AI setting were skipped on some model loads — FIXED

Why: found in the 2 Oct health audit. Answer synthesis checked config.QUERY_ALLOW_LLM and synthesis.ram_ok() (free memory against AEGIS_LLM_MIN_FREE_MB) before loading the Ollama model. Other paths did not: the boot preload (boot._stage_preload and main._preload_model), query expansion, /teach (one Ollama call with a 600 s timeout), the tutorial chat, the trainer's drafts, the verifier and the device benchmark. Any of them could load the model with swap already full, which is how the 2026-09-25 freeze started (LLM warmed).

Fixed:

Verified:

2026-09-30 — Go deeper levels read the wrong sense, and a rebuild kept levels from the wrong article — FIXED

Why: I read every Go deeper level in the phone's tutorial_library.json (a read-only copy in /tmp/tank-logs-145630). Most levels were accurate. Two were not:

  1. "How to build a company", Level 2, was four Build-A-Bear blocks (Happy Meal, Build-A-Bearville, Walmart, specialty stores). Go deeper reads the tutorial's own Wikipedia article again, and that was Build-A-Bear Workshop, from before today's how-to object fix. A rebuild would pick "Company" now, but carry_levels copied every old level into the rebuilt tutorial, so the Build-A-Bear level would have stayed.
  2. "Securities", Level 3 Examples, used Wikibooks "Basic Computer Security/Introduction". wikibooks_deeper searched for the bare word "Securities" and threw away the sense the tutorial already had, its article "Security (finance)". A how-to with no article also fell back to a Wikipedia search on the whole phrase ("build company") rather than its object.

Fixed:

Verified:

2026-09-30 — Tank 3 Pro log (0.1.17, Sep 28): trainer saves collided, harvested subjects lost context, a wrong "how much" answer, "distance between" missed, a game page in a tutorial — FIXED

Why: I read the phone's aegis-log.txt over USB, read-only (copied to /tmp/tank-logs-*). It covers four sessions on Sep 28. Logcat shows no Aegis crash; all the SIGSEGVs come from the vendor fingerprint service (android.hardware.biometrics.fingerprint@2.1). Findings:

  1. tutorial trainer progress not saved: [Errno 2] … tutorial_train.tmp -> tutorial_train.json. The app and the background service each save the same JSON stores through one .tmp name, so one process renamed the other's half-written file away. Overlapping writes could also leave a store that mixes the two. shield_bg.py already used a per-process name; the other ten stores did not.
  2. Subjects the trainer harvested from its own tutorials were built with no subject context. From the Security tutorial: "Collateral" became the film, "Market" became Darknet market, "Securities services" became Federal Security Service, "DRS securities" became Department of Intelligence and Security, and "Stockholders" became the artist Jessica Stockholder. Section labels also became subjects ("1830s–1880s: Before celluloid", "Standardisation astronomical tables", "Advantages and disadvantages").
  3. "how much food is in the united states" was answered from the offline library with "One United States customary cup is equal to 236.5882365 milliliters…". The "how much" check lets one of three key words go missing, and the missing word was "food". With local-first (not yet on the phone), the web would never have been asked.
  4. "what is the distance between the earth and the sun" found nothing, while "how far is earth from the sun" answered 149.6 million km. quantity_lookup only builds keywords for "how far / how many", so the first wording searched Wikipedia as a whole sentence and got Moon and Earth, not Astronomical unit.
  5. "How to build fire" was built from Wikipedia "To Build a Fire" (the story) and crazygames.com "BuildNow GG". The Wikipedia side was already fixed today by the how-to object rule, which now searches for "fire". The game page matched on the words "build" and "fire".

Fixed:

Verified:

2026-09-30 — Phone web requests ignored their deadline: a 12 s web search took 84 s — FIXED

Why: with the network unreachable, webscan.search raised WebSearchUnavailable after 84.4 s and 84.1 s against a 12 s deadline, and never got past Bing. The cause is urllib.request.urlopen(timeout=…): socket.create_connection tries every address the name resolves to, one after another, and gives each the full timeout. www.bing.com resolves to 14 addresses (5 IPv6, 9 IPv4), and 14 × 6 s = 84 s. The deadline was only checked before the request started. Every engine request had the same flaw: Wikipedia, Wikidata, the topic APIs, Stack Exchange, ESPN and Yahoo. A phone on poor Wi-Fi, or with a dead IPv6 route, could hang the same way. (The laptop's sandboxed shell has only all_proxy set, which urllib ignores, so there every direct connect times out. That is how this reproduced.)

Fixed:

Verified:

2026-09-30 — Tutorials: "how to build a company" was built from Build-A-Bear Workshop; off-topic Stack Exchange threads; concepts lost their subject ("Preparation" → Preparation H) — FIXED

Why: the Tank log showed "teach me how to build a company" filed as a Build-A-Bear Workshop tutorial. There were three causes:

Fixed:

Verified:

2026-09-27 — Local first: the phone checks its offline library before the web; the laptop skips web expansion for short questions it can answer — CHANGED

Why: the phone asked the web before its offline library (2,091 pack documents). Web answers took about 3–8 s and library answers about 60 ms, and the home screen already said "offline library … then a web search when online". Git, BUGS.md and past sessions give no deliberate reason for the order. The library was the last step of the offline-only engine (it also built the NOT FOUND reply), and 0.1.12 put the web in front of it. There is a real reason for some intents, though. With the web off, the library was wrong on 5 of 7 "define" questions (a sentence that only mentions the word), gave a WHO health-worker sentence for "how many people are in the world", missed "how old is the earth", and answered "who invented the airplane" with a headline. It is also dated: 95 moons of Jupiter against the web's 115. On the laptop, _needs_fresh_info flags every question of four words or fewer as "short question", so those always went to DDGS/Jina even with full local coverage; only definitions skipped the web.

Changed:

Verified:

2026-09-27 — Carrier phrases: one catalogue of question wordings for the phone and the laptop — ADDED

Why: each new wording ("fill me in on X", "do you know who X was", "X vs Y", "which ocean is the largest") needed its own hand-written pattern, and many fell through to the generic lookup. The laptop had its own separate patterns.

Added:

Verified: mobile/test_phrases.py 139/139. It fills every phrase with 3 subjects and checks category and subject, the parser intent, that the canonical maps to itself, and the re-parse. It also covers wrapper stacks, 44 guard cases, the satellite questions, and the pack answer. All 23 phone suites pass. test_aegis 66/76: the 14 new checks pass; the 10 failures are baseline (backend not running, old log errors). Live: "hey artemis do you know what the first satellite put in orbit was" gives Sputnik 1 and "fill me in on black holes" gives the Black hole lead. "cats vs dogs" is still no_answer (it was before too; the difference route needs singular subjects).

2026-09-27 — Phone: "the first satellite put in orbit" found no answer — FIXED

Why: on 0.1.17, "tell me about the first satellite put in orbit" and "what was the first satellite put in orbit" gave no_answer. The superlative parser needed a -est word or first/last + noun with a narrow shape. The overview parser took the "tell me about" form whole. The answer checks refused Sputnik's lead because "put in orbit" is in its second sentence.

Fixed:

Verified: test_misses 120/120 (new test_first_records: both questions, Sputnik's two sentences accepted and the lead alone refused, Explorer 1 and Aryabhata refused, Armstrong accepted and Gagarin refused, guard parses). Live (temp library): both questions give "Sputnik 1 was the first artificial satellite to orbit Earth." (sovietspaceprogram.com, Wikipedia: Satellite).

2026-09-27 — Phone: style change never reopened the app; background service failed from threads — FIXED

Why: on the Tank 3 Pro (0.1.17, Android 15, targetSdk 35), picking a style closed Aegis but it never reopened; the user had to open it by hand (app log: "restarting for the new style" at 17:17:01, next start 17:17:13). The alarm-based reopen fires after the process is gone, and Android 10+ blocks an app that is not on screen from starting an activity (the API 34 creator opt-in only helps while the app is visible). The same log showed Shield of Zeus background service not started/stopped: ClassNotFoundException: online.mindflux.aegis.ServiceShield every time it was called from the restart handoff thread or the startup thread: jnius in a non-main thread uses the system class loader, which cannot see the app's classes. From the main thread (on_pause) it worked.

Fixed:

Verified: test_themes 120/120 (relaunch order notice → new task → exit, failures still exit, no AlarmManager, service lookup on the main thread, notice cleared at start); test_shield_bg 37/37, test_speech_controls 74/74, test_storage 44/44, test_tutorial 77/77. Needs the next APK on the phone to confirm the reopen.

2026-09-27 — Phone: selectable styles (7 themes), restart to apply, honest boot progress — ADDED

Why: the user wants the six mockup styles (mobile/design/themes/), plus the current look, as choices in Settings. A new style should show only after the app restarts: the app says it must close, closes, and reopens in the new style. The boot bar was fake (six 0.42 s timers).

Added:

Verified:

2026-09-27 — Phone: Shield of Zeus keeps building queued tutorials in the background — ADDED/FIXED

Why: the user pressed "Build a tutorial on this" about 24 times (Tank 3 Pro, 0.1.16, logs pulled read-only on 2026-09-27). The button said "Queued in Shield of Zeus", but only 3 were built in 8 minutes, exactly 3 minutes apart, and none after the app went to the background or was swiped away. The user wants Shield of Zeus always running in the background unless it is turned off, processing whatever the buttons queue.

What the phone's logs showed:

Added:

Fixed:

Not fixed (out of scope):

Verified:

2026-09-27 — Phone: Go deeper, a new level on any saved tutorial — ADDED

Why: the user wants to keep expanding a tutorial ("There's always gotta be another level as well, right?").

Added:

Verified:

Backup: .aegis-doctor/backups/tutorial-expand-20260927-154340/

2026-09-27 — Year fix restored in the laptop extractive_qa.py — FIXED

Why: the 0.1.15 fix for years cut from the end of sentences ("released in 1995." became "released in.") was made only in mobile/engine/extractive.py. mobile/build-apk.sh copies the laptop's extractive_qa.py over that file on every build, so the fix was lost: mobile/test_misses.py dropped to 89/92 and today's APK does not have it. The laptop engine had the same bug.

Fixed:

Verified: py_compile is clean. The new year check fails with the old regex ("released in.") and passes with the new one. test_aegis.py: all section 1–6 checks pass, including both new ones; the 10 failures are the live-API section (backend not running) and the log's "no recent errors". test_accuracy.py 26/26, test_extractors.py 31/31, test_disambiguate.py all passed. In a temp copy of mobile/ with the laptop files copied in the way build-apk.sh does: test_misses.py 92/92, test_phone_questions.py 74/74.

2026-09-27 — Phone: Shield of Zeus builds tutorials by itself, Tutorials navigation, answers from saved tutorials — ADDED

Why: the user wants the phone to make its own list of tutorials and "teach me" articles and gather the material for them, to move between the tutorials already saved, and to have the query engine answer from what the tutorials say.

Added:

  1. subjects the user adds (Shield of Zeus "Add subjects", the reader's "Build a tutorial on this", a missed how-to's offer);
  2. the user's own activity: tutorial requests that found nothing or came out short, and how-to and programming questions from Auto-train's history;
  3. tutorials due again;
  4. Key-concept titles, Stack Exchange question titles and Wikipedia/Wikibooks Further-reading pages found in built tutorials (one level deep, filtered with trainable);
  5. the seed list.

Verified:

2026-09-27 — Phone: pause, resume, stop and play for answers read aloud — ADDED

Why: the user wants to pause or stop the voice reading of responses at any time, resume a paused reading, and have Play after Stop start the same reading again.

Added:

Verified:

2026-09-27 — Phone storage figure for data gathered on the device — ADDED

Why: the user asked for a calculated number showing how much phone storage the scraped and assembled local information takes. The only existing figure was Handshake's raw per-file KB list from AutoTrainer.sizes(), which had no total and left out caches, logs and corrections.

Added:

Verified:

2026-09-27 — Phone Tutorials page: sourced tutorials built from the web, kept offline — ADDED

Why: in the 0.1.14 session (/tmp/tank3-1456/), "teach me about the ubuntu terminal" failed ("couldn't build a tutorial … from what's on this phone"). "teach me the basics of python / php / html / css" each gave a short one-source Wikipedia "step by step". The user wants a tutorial page: name a subject and get a comprehensive tutorial built from the internet, especially programming forums.

Added:

Verified:

Not verified:

Backup: .aegis-doctor/backups/mobile-tutorial-20260927-150044/ and …-151440/ (docs)

2026-09-27 — Phone 0.1.14 session fixes: dropped years, bare questions, how-to subjects, distance endpoints — FIXED

Why: the 0.1.14 session pulled to /tmp/tank3-1457/ showed five problems. (1) Years were cut from the end of sentences: "released in 1995. The PHP ..." became "released in." because _NUMBERED_ITEM_RE read "1995." as a numbered-list item. (2) A cut-off voice question, "how do i", ran a 9 s search and was queued for an auto-train retry. (3) "how do i work in the terminal on ubuntu" got the subject "in terminal on ubuntu", which produced the junk follow-up "what is in terminal on ubuntu". (4) Distance answers could skip one endpoint. "from the moon of Earth to Mars" got an Earth–Mars figure (56 million km), and "moon to mars" got Phobos's orbit, because the word check allows one missing word and counts the title "Moons of Mars". (5) A one-site answer said "Checked against search results from different sites."

Fixed (0.1.15):

Verified: over the 147 saved pages in web_library.json, the old regex cut off 275 numbers on 70 pages: 244 years, plus 31 short numbers such as "HTML 4." and "count to 67.". None of them was a real list item. The new regex cuts off 0. Sentences ending in a year went from 18 to 255. Sentences ending in "in." / "since." / "of."-style words went from 170 (60 pages) to 11 (8 pages), and all 11 are natural endings ("in between.", "and so on."). The sentence total is unchanged at 4303. With the phone's Moon–Mars pages minus WhiteClouds, the old check answered "56 million km" (Earth–Mars); it now gives an honest miss. With WhiteClouds it answers from its "minimum distance from Mars to the Moon" sentence. Suite results: test_misses.py 92/92, test_phone_questions.py 74/74, test_autotrain.py 59/59, test_provenance.py 20/20, test_companies.py 75/75, test_ingestion_part1.py 91/91, test_ingestion_part2.py 113/113, test_overview.py 84/84, test_sports.py 113/113, test_sources.py 22/22, test_common_questions.py 14/14 offline, test_ingest.py 11/11. test_artemis.py passes and test_tutorial.py passes 66/66. The new tests use real sentences from the phone data, and they fail on the old code. Not yet verified on the phone: it needs the 0.1.15 APK.

Backup: .aegis-doctor/backups/mobile-answer-fixes-20260927-150409/

2026-09-27 — Phone auto-train waited for Wi-Fi on mobile data — CHANGED (0.1.14)

Why: the phone was on T-Mobile LTE with Wi-Fi on but not connected, so auto-train stayed on "Waiting for Wi-Fi". "Only on Wi-Fi" (auto_train_wifi) was on by default. Ordinary web searches already used mobile data. The user wants auto-train to use whatever network the phone has.

Changed:

Verified: test_autotrain.py passes 57/57. The new checks: no network waits for a network (with Wi-Fi-only on and off), Wi-Fi-only on mobile data waits for Wi-Fi, the default runs on mobile data, and main.py DEFAULTS has the flag off. test_provenance.py passes 17/17 (its miss now says "once it has a network and the charger"). test_misses.py 74/74, test_phone_questions.py 61/61, test_artemis.py and test_companies.py 75/75 also pass. The phone's settings.json was read back after the write. The code default ships with the 0.1.14 APK.

Backup: .aegis-doctor/backups/mobile-autotrain-network-20260927-144250/ (includes the phone's original phone-settings.json)

2026-09-27 — Aegis Mobile 0.1.13 crashed in the voice listener (SIGSEGV) — FIXED (0.1.14)

Symptom: on the Tank 3 Pro, about 2½ minutes after the last question, the app died and restarted. aegis-log.txt showed AttributeError: 'SpeechListener' object has no attribute '__javamethods__' in jnius.PythonJavaClass.invoke. Logcat then showed Fatal signal 11 (SIGSEGV) in SDLActivity, with PyObject_ClearWeakRefs in libpython3.14.so under android.speech.SpeechRecognizerImpl$InternalRecognitionListener.handleMessage.

Cause: the Voice button's start (_patient_start in mobile/engine/artemis.py) and plyer's own Android _start (used by Artemis) both created the SpeechListener as a local variable. They also made a new SpeechRecognizer on every listen without destroying the old one. pyjnius frees a PythonJavaClass once Python drops it, but Android still holds it, so a late recognizer message landed in freed memory.

Fixed (mobile/engine/artemis.py):

Verified: test_phone_questions.py passes 61/61. The new G checks: Artemis gets no silence extras, the previous recognizer is destroyed, and the listeners stay referenced. test_artemis.py passes, test_companies.py 75/75, test_misses.py 74/74. Not yet verified on the phone: it needs the 0.1.14 APK.

Also found in the same device log (OPEN, not fixed):

Backup: .aegis-doctor/backups/mobile-voice-listener-20260927-144032/

2026-09-27 — Nonsense queries got confident junk answers (gate was switched off) — FIXED

Why: main.py already had the nonsense gate v3 (early, after the local/web fetch, and late, before synthesis). It refuses when fewer than half of the query's tokens appear in the top local title/body or the top 5 web titles. Both refusals had been switched off with if False and ... ("temporary, for pilot"), and there was no record of why. So "blorptastic quantum marshmallow" returned a "Found: 5 local" dump, "how tall is the zibbledorf tower" got a refinery tower, and "what color is the number seven" got pH strips.

Fixed: removed the two if False and guards and the stale comment. No thresholds changed.

Verified (20 gibberish + 20 real prompts, on copies of the live DB, AI answers off):

rungibberish answeredreal refusedreal answers changed
gate off, web off10/200/20—
gate on, web off3/200/200
gate off, web on10/200/20—
gate on, web on3/200/200
patched live code, web off4/200/20—

Backup: .aegis-doctor/backups/nonsense-gate-20260927-135230/

2026-09-27 — "who is Marie Curie" answered "1867" (loose correction match) — FIXED

Why: the keyword fallback in query_corrections.get() divided the shared keywords by the query's keywords only. "who is marie curie" has the keywords {marie, curie}, and both appear in the stored "what is the birth year of marie curie", so that scored 1.0 and the birth year won over local search. Question words are stopwords, so "who" vs "what year" never counted.

Fixed (query_corrections.py):

Verified (on a copy of the live DB):

Backup: .aegis-doctor/backups/corrections-match-20260927-133716/

2026-09-27 — Ollama keep-alive 30m → 5m, adjustable on the Handshake page — CHANGED

Why: on this 3.2 GB host the model (~1.3 GB) stayed resident for 30 minutes after every AI answer, and that memory is what pushes the machine into swap.

Changed:

Verified: the default gives "5m", setting 0 gives "0", 99 is capped to "30m", and env "2h" wins at startup. py_compile is clean.

Backup: .aegis-doctor/backups/ai-keepalive-20260927-133920/

2026-09-27 — Disk: old DuckDB backups deleted (~6.2 GB) — CLEANUP

Why: data/ held five full copies of the DB from 2026-09-19 (~630 MB each) next to a live DB that is 28 MB since the Sep 25 import compacted it.

Done: the live DB was checked first (documents 2,091, facts 918, corrections 962, query_log 7,340, reports 1,084; FTS "hydrogen" returns 189 hits). Then aegis.duckdb.bak.1789875371, .1789875844, .1789875906 and .1789875952 were deleted, which freed ~2.5 GB. The newest, aegis.duckdb.bak.1789876094, is kept.

Also deleted (user asked: "Do we need it? If not get rid of it"): .aegis-doctor/backups/import-20260925-190231/ (3.7 GB), the snapshot of data/ that aegis-import set aside. It held the Sep 20 DB, the same five Sep 19 copies, logs and small files.

2026-09-25 — Updating a second laptop: requirements check, knowledge export/import, transfer folder — ADDED

Why: the user's second, more powerful laptop has an older Aegis install. It had to be brought to exactly this machine's code and knowledge. There was no way to list what a machine is missing, no way to move the knowledge without copying the 2.8 GB DuckDB, and no safe code sync.

Added:

Verified (this machine, with Cursor open):

Not verified: the real update on the other laptop, pip installs against its Python version, and the APK build there.

2026-09-25 — Aegis Mobile standalone APK — ADDED

Why: the old phone version was split across ~/aegis-mobile-ui (a Kivy chat UI) and ~/aegis-termux (a toy HTTP bridge with a 3-document corpus and phi3). It only worked with Termux and Ollama running on the phone, had no real knowledge, and there was no APK build.

Added (mobile/, see HANDOFF section 15):

  1. exact corrections
  2. math and units
  3. facts
  4. definitions
  5. BM25 search plus the laptop's extractive answer picker
  6. optional local AI (Ollama qwen2.5:1.5b, off by default, falls back when unreachable)

Verified: engine self-test 7/7 in about 37 ms. Stub-Ollama AI path and fallback. Desktop screenshots of every screen.

Not verified: the APK build (needs Cursor closed and sudo apt install libncurses-dev) and on-device behaviour: edge-to-edge padding on Android 15, text-to-speech, and Termux Ollama.

2026-09-25 — Handshake page, live tests, settings and permissions — ADDED

Added:

Changed: AEGIS_LLM_MIN_FREE_MB default is now 1600 (was 900) in synthesis.py. This mitigates the 2026-09-25 freeze. The value is adjustable on the Settings tab.

Found by the new tests (OPEN):

Not verified: the desktop control channel, because the desktop can't be run alongside Cursor. Check it with Cursor closed.

2026-09-25 — desktop + Cursor froze the host; AI handoff system — OPEN / ADDED

Symptom: After the launcher's Cursor refusal was removed, ./aegis was started with Cursor open. The host froze within ~2 minutes (hard reboot, no OOM kill logged, so it was swap thrashing).

Likely cause: Startup warm-up (synthesis.warm_async) loaded qwen2.5:1.5b (~1.3 GB) because ram_ok() only requires AEGIS_LLM_MIN_FREE_MB = 900 MB available. Kokoro holds ~950 MB from boot and Cursor ~2.5 GB. Open: raise the threshold and/or skip the warm-up when Cursor or a browser is running. Measured memory table is in HANDOFF.md section 2.

Added:

2026-09-24 — stray numbers in answers; stitched answers — FIXED

Symptom: Answers showed [1]..[13] bubbles, copied Wikipedia footnotes (1970[a][I], follows:[14]), and snippet prefixes (4 days ago - ). Answers were 4 stitched page sentences, often off-topic.

Fix: extractive_qa.strip_markers() cleans source text and every /query answer (_sanitize_query_output in main.py); no [N] markers are added any more. Desktop backend now runs with AEGIS_QUERY_ALLOW_LLM=1: synthesis.py writes 1-3 conversational sentences, rejects answers whose numbers appear in no retrieved text, skips the LLM when free RAM is low (AEGIS_LLM_MIN_FREE_MB, default 900) and cancels generation past AEGIS_LLM_CAP_S (default 15). Without the LLM the best 1-2 sentences are used; when nothing answers the question the reply says so and background research starts (answer_qtype: no_answer).

Also: query_expand.expand referenced seen before assignment when the LLM call failed, silently dropping search variants.

Known slow case: under memory pressure (e.g. Cursor open, swap in use) the 1.5B model misses the 15s cap and the extractive fallback is shown.

2026-09-24 — Artemis ignored wake word — FIXED

Symptom: Saying “Artemis” produced no response.

Cause: Free-form Vosk (small en-us model) transcribes “Artemis” as “art amiss” / “artem s” / “art artem mess”, which the wake regex missed. Worker stderr went to /dev/null, so nothing was visible.

Fix: artemis_listen.py feeds audio to a grammar-constrained recognizer (wake word + command phrases) alongside the free-form one; grammar decides wake/commands, free-form carries spoken questions. Logs to /tmp/artemis.log (worker stderr → /tmp/artemis-worker.err); the desktop prints artemis heard: ... to the session log.

2026-09-23 — Artemis wake-word + silent answers + SOZ UI — ADDED

Artemis: Always-on wake word via artemis_worker.py (Vosk). Say “Artemis” → “Your wish is my command.” Then “Activate Aegis” → Query Engine + “Enter question or query to continue.” Answers are silent; Speak button on the answer card reads them.

UI: Query Engine restyled with SOZ bronze/midnight (aegis-engine.css).

Env: AEGIS_ARTEMIS=0 disables continuous listen; AEGIS_SPEAK_ANSWERS=0 default.

2026-09-23 — Exit button (full stack) — ADDED

Feature: Fixed Exit control on launcher, Aegis (/), and SOZ (/soz). Confirms, then POST /app/exit → stop mic/Kokoro, backend, and close the GTK desktop window.

Files: static/aegis-exit.{css,js}, app_exit.py, desktop poll of /tmp/aegis-exit.request.

2026-09-23 — voice commands (STT) — ADDED

Feature: Push-to-talk mic in desktop header. Local Vosk STT (models/vosk-model-small-en-us-0.15) + command router.

Commands: mute / unmute · home / launcher · open shield / soz · open aegis · ask/query/what/who/… → Query Engine (spoken answers via existing Kokoro path).

Env: AEGIS_MIC_DEVICE (default plughw:0,7 DMIC16k), AEGIS_LISTEN_SECS (default 5), AEGIS_VOSK_MODEL.

API: POST /voice/command (WAV), POST /voice/transcribe, GET /voice/stt.

Verify: mic button in ./aegis; or curl -s -X POST --data-binary @/tmp/aegis-ptt.wav http://127.0.0.1:8000/voice/command

2026-09-23 — desktop voice (Kokoro) not wired — FIXED

Gap: Desktop never started/verified Kokoro (:5050). Query auto-speak was removed; WebView had enable-media=False (host aplay only).

Fix (2026-09-23):

Verify: curl -sf :5050/health; ./aegis (quit Cursor first).

2026-09-23 — extract.py _sentences NameError (facts generate) — FIXED

Symptom: Backend log flooded with [Aegis] generate: extract failed on web::…: name 'body' is not defined (~143 hits). SOZ fact generation / facts_pending population broken.

Root cause: A pasted AEGIS-FIX block inside _sentences() called _sentences(body) / facts.extend(...) with undefined names (and would have recursed forever if body existed). Concept/attribution/formula matchers were also never registered in _MATCHERS.

Fix (2026-09-23):

Verify: .venv/bin/python -c "from extract import _sentences, extract_facts; \ print(_sentences('Hydrogen is a chemical element. It has symbol H.')); \ print(extract_facts('Hydrogen','Hydrogen is a chemical element. Symbol is H.','t1'))"

2026-09-23 — desktop wrapper kill-race / fake boot / CDN fonts — FIXED

Issues: desktop.py always killed :8000 then spawned main.py; ./aegis pkill'd healthy uvicorn; launcher used DEMO timed boot; Google Fonts CDN + CSS glitch in loading.html.

Fix (2026-09-23):


2026-09-19 — attribute-value misalignment in table-formatted sources — FIXED

Repro: curl -s -X POST http://127.0.0.1:8000/query \ -H 'Content-Type: application/json' \ -d '{"prompt":"what is the boiling point of nobelium"}'

Expected: "Unknown" (RSC says boiling point is undetermined) or empty. Actual: "Nobelium – Melting Point ... Melting point | 827°C ..."

Source doc: web::900b7ad6883332ba https://periodic-table.rsc.org/element/102/Nobelium Body: "Melting point | 827°C, 1521°F, 1100 K | ... Boiling point | Unknown"

Two gaps:

  1. Extractor produced nothing from this doc — facts_pending has no rows for either melting_point or boiling_point. RSC's table format (| Attribute | Value |) isn't parsed.
  1. build_answer picked the melting-point row to answer a boiling-point question. Even if a fact existed, the picker doesn't pair the queried attribute with the value slot.

Root cause (diagnosed 2026-09-19):

Attempted fix (2026-09-19): patches/picker-attr-alignment.md. Applied, tested, reverted. Does not work. Three findings:

  1. The filter was gated on is_qty, which _question_type returns only for 'how many' / 'how much'. 'what is the X of Y' queries never hit the block. Guard change alone is insufficient (see 2).
  2. Even without the guard, the RSC table candidate contains Boiling point | Unknown. _attr_value_aligned accepts it because _UNKNOWN_VALUE_RE matches 'unknown'. The candidate survives with its ~8.06 score and wins anyway.
  3. Structural: the picker scores whole sentences. A table row with multiple attr | value cells cannot be disambiguated by any filter over whole sentences. The value must be extracted first.

Correct fix: structured table extraction.

Reverted: extractive_qa.py restored to the state before the attempt. The patch file remains in patches/ for reference.

Not urgent: known, reproducible, doesn't corrupt data.

2026-09-19 — preflight symbol match false positive — FIXED

Log line on every element query: [Trainer] preflight symbol match: 'the boiling point of nobelium' -> 'th'

'th' matched the substring 'the' because the symbol fallback fired on the whole prompt (which the subject extractor returned instead of the bare element name). Both trainer.py call sites (preflight and canonical_lookup) now guard the fallback with:

if len(k_clean) <= 3 and k_clean.isalpha():

Real cases ('al' -> aluminum, 'c' -> carbon) still work. Multi-word subjects skip straight to canonical name lookup.

Fixed in commit 97863b4. Verified: grep of /tmp/aegis-backend.log for 'symbol match' returns nothing after a batch of element queries.

Resolution (2026-09-19): Fixed in extractive_qa.py (2b19936). _expand_table_pairs rewrites | A | B | C | D | lines as A of <subj> is B. C of <subj> is D. before scoring, so each table cell becomes its own candidate. build_answer limits fact-shaped quantity queries to one sentence.

Verified: what is the boiling point of nobelium -> 'Boiling point of Nobelium is Unknown.' (previously: RSC melting-point 827°C table row)

2026-09-19 — extract.py doesn't parse table-formatted docs

Observation: RSC nobelium page (web::900b7ad6883332ba) contains Melting point | 827°C and Boiling point | Unknown in a table, but no facts_pending rows were created for either.

Impact: The picker can still answer from the doc body via the new _expand_table_pairs path (fixed in 2b19936), but the fact-table preflight never gets populated for these sources. Every query re-runs the picker instead of hitting a confirmed fact.

Fix location: extract.py — teach extract_facts() to detect | Attr | Value | lines and emit candidate facts with attribute names normalized against the canonical attribute vocabulary.

Not urgent: picker path works; this is an optimization.

2026-09-19 — no hedge-word penalty in sentence scoring

Observation: extractive_qa._score_sentence has no penalty for "predicted", "estimated", "theoretical", "calculated". A sentence with a predicted value scores the same as one with a measured one.

Impact: For synthetics (Z >= 100) most published values are predictions. The picker can surface a predicted number without distinguishing it from a measured one. This is the class of problem that started the whole session.

Fix location: extractive_qa._score_sentence — apply a multiplier (0.5?) when the sentence contains any hedge word and no "measured"/"observed" qualifier.

Not urgent: canonical refuses when only a prediction exists. This would tighten the web-doc path.