Releases: l33tdawg/mynah
Release list
Mynah 1.6.3
Searching works again.
Every question that reached for the web stopped dead. Mynah said it was looking, and then nothing came back until the six-minute stop fired and it admitted it had given up — three times in a row on one afternoon.
The page load and its own twenty-second timeout were being raced against each other in a way that reads correct and is not: the machinery holding the race cannot finish until both sides finish, so when the timeout won it cancelled the page load and then sat waiting for it. And a page load waiting on the browser engine does not stop because it was cancelled. Nothing ever came back.
It logged nothing at all while this happened, which is why it looked like the model hanging rather than the search. The only reason there was ever an answer is the stop added in 1.6.1.
The same mistake was in two other places, and one of them was in Google sign-in — where it would have hung for good rather than timing out. It has never been reported, and only because nobody had left the consent screen open long enough to find it. That is luck, not correctness.
All of them now let the timeout end the wait rather than race it: one waiter, one place that finishes it, whoever gets there first. There is a check that walks every piece of concurrency in the codebase looking for the pattern and fails the build if it reappears.
The update bar no longer offers "What's new".
It says one thing and offers one verb. The release notes are still a click away in the install window, which is where anybody actually after them is already looking.
And two tests that were not telling the truth.
One read the real clock against a fixture dated for one o'clock in the afternoon. It passed all morning, and at one o'clock it started failing — then crashed rather than failed. A test whose result depends on the hour it is run is not a test.
The other failed the last release build in CI by two and a half thousandths of a second on a machine we do not own. A red build that means "the runner was busy" teaches everybody to ignore red builds, and the next one will be real.
1775 tests, 0 failures. Notarized and stapled.
Mynah 1.6.2
The task list has its days written in.
Every day now carries its own heading down the left column — today and tomorrow by name, the rest by weekday and date — and the days with nothing on them say so.
The empty days are the point. The dates were already legible on the cards; what a list of cards cannot tell you is where the gaps are, and that is the question the list actually gets asked: when can this go? So the stretch after the last appointment is named too, which is the part nobody would have thought to look for and the part a plain grouping would have said nothing at all about.
Bounded to the week ahead. Past that a day is named only when it holds something — eleven consecutive "free" rows before a hotel check-in on the nineteenth would bury the two things that are genuinely on. Days that have already gone keep their heading, because work past its deadline stays on this board on purpose, but they are never offered as free: a gap that has gone is not an opening.
A calendar event now says where in the world it is.
Mynah wrote the title, the day, the start, the end, the alerts, the link and a note explaining itself — and never once said which timezone any of that was in.
An event with no timezone is not "local", it is undefined. iCloud re-reads it on whichever device opens it, so one o'clock written on a Mac in Kuala Lumpur could be shown as one o'clock four hours away. Same number, different moment, missed appointment. On screen it looks exactly like the calendar having been set to GMT.
All-day events keep floating, and must. A day is not an instant: pin "Sunday 9 August" to a timezone and it becomes midnight-to-midnight in that timezone, which one country east is a hotel check-in that starts on the Saturday.
This was found while checking a screenshot that turned out not to have come from Mynah at all — the calendar mirror has never been granted access on that Mac, and has never written a single event. The fault was real regardless, and was waiting for the first time permission is given.
1772 tests, 0 failures. Notarized and stapled.
Mynah 1.6.1
Mynah knows what day it is.
Asked over Signal on Tuesday morning what was on the list for today, it answered for Monday — with every date on the list correct, and only the question of which one was today wrong.
There was a piece of this app written to prevent exactly that, and nothing had ever called it. No prompt on either surface carried the date, so the model took "today" from the only date it could see: a message Mynah itself had sent eight minutes earlier, naming a Monday task that had just come off the list. The window answered the same question correctly at the same minute, and not because it knew — the owner had happened to type "it's already Tuesday" into it an hour before.
Every turn now arrives stamped with the date and the time. It cannot go stale, because it is not stored anywhere: the appliance runs for days at a stretch, and anything written down at start-up is wrong by the following morning in the way that is hardest to notice — confidently, in prose, about a day that has passed.
A wait that never came back now comes back.
The appliance answers Signal one message at a time, so a turn that hangs stops it answering at all. There has been a stop on that since 1.5.4, and it had never once fired.
It was built to race the work against a timer and take whichever finished first. But the mechanism it used cannot return until both have finished — so when the timer won, it cancelled the work and then sat waiting for it, which is precisely what it existed to avoid. Against the one thing that hangs in practice, a read from the memory node that never answers, cancelling changes nothing. It was incapable of firing in the only situation it was for, while looking armed from the outside.
Caught in the log this morning: a question asked at 10:42 was still unanswered at 10:51, three minutes past a six-minute ceiling, with the rest of the appliance ticking along beside it.
The test that covered this passed for the wrong reason — it used a sleep that stops the instant you cancel it, so it measured a quick cancellation while claiming to measure a stubborn one. Rewritten against work that genuinely cannot be interrupted, it fails the old version by fifty times its deadline.
And the update bar is green.
Twice now it has been asked for and twice delivered white. Both times it painted a solid white surface and tinted it ten percent, which is white with a hint in it; the second attempt corrected the order of the layers and made it about four percent greener. Translucent means the window shows through, so the band cannot have a surface of its own at all. It no longer has one.
Also: removals stop being announced. "That came off the list" cannot say whether something was finished, dropped, or simply had its day pass — and every one of those is something you already knew, because you did it. The exception is a task that leaves while nobody is touching it, which is a list quietly losing something, and that one still speaks up.
And Mynah no longer reads your own edits back to you. Changing the list in the window, or dragging a card on the board, used to come back over Signal a quarter of an hour later as though a stranger had done it.
1757 tests, 0 failures. Notarized and stapled.
1.6.0 — SAGE 11.17.1, and sources you can press
Mynah now carries SAGE 11.17.1.
The memory node inside the app moves up from 11.16.4 to the version the SAGE team have just shipped — a major release on their side. Nothing to install and nothing to migrate: the node travels inside Mynah, signed as part of it, and this build simply has the newer one in it.
The check that decides whether a node is fit to ship passed every gate — the version floor, the companion bootstrap it has to read, the readiness gate, the governance string, and the bundle identity. The whole test suite was then re-run against it.
The sources in a PDF are links you can press.
A model writing a references section writes the address on its own line, because that is what a citation looks like. Pandoc leaves those as plain characters unless told otherwise, so the last page of a generated report was a list of addresses to retype — while the rarer [name](url) form linked properly and had been styled blue since the day the template was written. Both work now.
The tests for this check the PDF's link annotations rather than its text, because those two come apart in exactly the way that matters: an address can be on the page, in the right colour, and still be nothing but characters.
And the update banner is green.
It was supposed to be in the last release and the tint never appeared, because the opaque surface behind the band was being painted over the top of it. The icon turned green — it was not behind that surface — and nothing else did. Fixed, and composed so the order written is the order drawn.
One note: this release's banner is drawn by the version you already have, so it will be white one more time. Green from the update after this.
1733 tests, 0 failures. Notarized and stapled.
1.5.5 — asking the app for a PDF now works too
Asking the app for a PDF now works, not just asking your phone.
"Can you make me an infographic pdf please" came back as a plain note and an apology about a temporary file. The note itself was fine — converted afterwards, by hand, with the very same converter shipped inside Mynah, it produced the document on the first attempt with its diagram drawn.
What gave it away was the record. Every PDF this appliance has ever successfully made was made by the part of it that answers your phone. The window had never made one, not once, in any log going back. That is not a difference in what was asked for; it is a difference in where the work was happening.
A program the app starts inherits the app's own working folder, and for an app you launched from the Finder that folder is the root of your disk, which nothing may write into. The part that answers Signal is started differently and never had the problem. The converter needs somewhere to put a file mid-way through, so it now works inside the scratch folder it already creates for itself and deletes afterwards.
Said plainly: that is the best explanation the evidence supports, not a proven one — because the one thing that would have proved it had been thrown away.
Which is the other half of this release. The converter and the note-writer both report what went wrong through a channel that was, in the app, connected to nothing. The same failure explained itself in the log on Signal and said nothing at all in the window. So an hour went into reconstructing a sentence the program had already written and discarded. Both are connected now, and a failed conversion records everything the converter said rather than its last line — the last line is the readable half and the right thing to show you, and the wrong thing to keep as the only copy.
1730 tests, 0 failures. Notarized and stapled.
1.5.4 — a question that never comes back now says so
A question that never comes back now comes back and says so.
The bug looked like two messages where there should have been one:
MYNAH >> On it — give me a couple of minutes and I'll come back when it's all done.
MYNAH >> Searching for that now.
The real one was underneath. No answer ever arrived. Two more messages sent a minute later were received and never answered either, and the appliance stayed like that — alive, using no processor at all, having stopped answering Signal entirely without a word in any log to say so. Turns are handled one at a time, so a single turn that never finishes takes the whole thread with it.
Three waits in this app had no end. A network call has a timeout, a search has a timeout, and the agent loop has its own five-minute brake — but that brake is checked between steps and never during one, the memory node is read down a pipe with no deadline of any kind, and a provider asking to be retried in an hour was simply obeyed. None of the three writes anything down while it waits, which is why fourteen minutes of silence left no trace to read afterwards.
So a turn is now bounded from outside, a minute past the loop's own brake — high enough that it only ever fires when that brake has already failed, low enough that nothing is ever silent for more than about six minutes. When it fires you are told, in a sentence that takes back the promise the first message made and says what to do instead, and the queue is released so the next thing you send is answered. It is written to the log as an error, because it means some wait still has no end of its own and that is where it will be found. A provider's retry request is now capped at thirty seconds.
This does not fix any one hang. It makes every hang finite, which is the property that was missing.
And the two messages. All the deduplication here works by comparing wording, which is right for catching a stutter — "having a look online" then "Looking online" — and useless for two sentences that say different things. Nothing counted how many times one turn may speak. Now it may speak once.
The update banner is green, using the same translucent tint the app already uses for inline notes.
1730 tests, 0 failures. Notarized and stapled.
1.5.3 — the scrollbar stops sitting on your tasks
The scrollbar stops sitting on your tasks.
The task list is inset from the edge of its column, and that inset was applied to the whole column — so the scrolling area ended at exactly the point the cards ended, and macOS had nowhere to draw the scroller except on top of them. It cut through the right border of a card, and through the outline of the "at the same time" box, which has a visible edge to spoil.
The scrolling area now extends into the margin the column already had, and its contents are inset by the same amount. Every card is exactly where it was. The only thing that moved is the scroller, out into the gap.
1719 tests, 0 failures. Notarized and stapled.
1.5.2 — Mynah tells you when there is a newer Mynah
Mynah tells you when there is a newer Mynah.
All of it already worked — asking GitHub, comparing versions, fetching the release, checking who signed it, putting it in place, restarting into it. Every part of that was behind Settings, on the screen nobody opens when nothing is wrong. Six releases went out in a day and the owner's report was the honest one: "haven't even seen our version of the update banner." There wasn't one. There was a settings row.
So there is now a strip under the header, on whichever of the four screens you happen to be on. It names the version, links to that release's own notes, and has one button. Press it and Mynah fetches the build, checks the signature, and puts it in place while it carries on answering your phone — the only interruption is the restart, and that is the same strip changing its verb once the file is down.
No dismiss. It stays until you take the update. There is no "later", no close button, and nowhere for a dismissal to be recorded — a control whose only function is to hide the fact that the app is out of date is not a kindness. It goes away by itself the moment the newer version is the one running.
A failed check draws nothing. Being offline is not an update, and a band across the top of the window is far too loud a way to say a background request did not come back. Settings still says so plainly.
And it now asks every fifteen minutes rather than once a day. The argument for a day was that every check tells a third party this Mac exists. That is true, and it was beside the point on an appliance that stays running for days — it meant a release could sit unnoticed for most of one, on a Mac that was awake the whole time. Four requests an hour, against GitHub's limit of sixty, and the switch on the Privacy screen still turns the whole thing off.
Under it, the check, the download and the restart moved into one object that both the banner and the Settings row look at. That is not tidying. Two things each holding their own install task can each start one, and two copies of Mynah replacing the same bundle at the same moment is the single failure in this feature that would leave somebody with no working app.
1719 tests, 0 failures. Notarized and stapled.
One note on seeing it: this is the release that adds the banner, so it cannot show you itself. Install 1.5.2 and it will be there the next time anything newer ships.
1.5.1 — ask for a PDF and get a PDF
Ask for a PDF and get a PDF.
Asked to research EDR and SIEM tools and write a report, Mynah spent two minutes searching, then said it had the research but the file was not built yet — and asked whether you would like it. Say yes and it did the same thing again. And again.
It was not stalling and it was not refusing. It called the tool that writes the file eight times in that turn, and every one of those calls arrived empty.
A document travels inside the tool call. That is the whole bug. When Mynah writes you a file, the entire report is an argument to write_note — so the cap on how much a model may generate in one turn is not bounding the reply, it is bounding the document. Run past it and the call is cut off mid-sentence, and a half-written call is not a short document. It is no document at all: the arguments will not parse, so the tool sees a request to write a file with nothing in it, and says so. Eight times. Then the turn hit its iteration limit and fell through to the wrap-up — which is run with the tools deliberately taken away — and the only thing a wrap-up can do is describe what happened and ask whether to keep going.
Two separate faults put the wrong number there, and either alone was enough.
The first was one setting holding two halves of two different answers. The turn's configuration took its writing instructions from "you are being read on a screen" and its token ceiling from "you are being read aloud" — a 40-word spoken reply's budget, handed to something writing a two-page report. The window inherited both. They now come from one place, and a test asserts they agree.
The second was a floor that existed and was never copied. The Anthropic connection has raised any too-small ceiling since the day it was written, because a hosted model that reasons before it answers will spend a small allowance thinking and return nothing at all. Nothing did that for DeepSeek, so DeepSeek got the small number unopposed. An Anthropic brain would have survived this. The one you were using could not.
The guard that should have caught this was written for this exact bug and still missed it. The same mistake happened once before, in the Signal daemon, and the test written afterwards named the file it had happened in. It passed for three releases while the window — a different target, the same mistake — repeated it. It now reads every source file in the project. Putting the bug back makes it fail, which is the only version of that test worth having.
Also in this release
A turn that dies because it ran out of room now says so, instead of suggesting you rephrase. The old sentence — try asking a different way, it does better with a whole sentence — is good advice for a two-word question and useless after a full one; the length of the answer was the problem, not the question.
And when a document does arrive empty, the tool now names truncation as a possible cause and suggests writing it shorter or splitting it in two. The model cannot see that its own output was cut off, which is why it tried the identical call eight times rather than a smaller one.
The waiting line on a long turn no longer calls it "a big recall".
1709 tests, 0 failures. Notarized and stapled.
1.5.0 — its own calendar, and macOS does the reminding
Mynah has its own calendar now, and macOS does the reminding.
Anything on your task list with a date in it appears in a calendar called Mynah, in your iCloud account. Which means the alert reaches your phone and your watch, fires whether or not this Mac is awake, and survives it being shut — none of which a sixty-second tick in a daemon could ever manage.
It is a mirror, not a second list. SAGE stays the source of truth, the board stays exactly as it is, and only dated items go across. Nothing is ever read back out of the calendar as fact: two writable stores with no arbiter is how an appointment ends up existing in one and cancelled in the other. Delete the Mynah calendar in Calendar.app and everything it put there goes with it — that is the whole uninstall.
The split. iCal owns it is happening soon; Mynah owns it did not happen, what now. So the run-up nudges over Signal stop for anything the calendar is already holding, and the overdue check-in stays — "that was yesterday, move it or drop it?" is a question, and a calendar alert has nowhere to put the answer.
It asks for calendar access the first time you actually date a task, never at launch, and a refusal changes nothing else: the reminders carry on exactly as they did.
Two things found by running it against a real task list rather than an invented one.
Taking a date out of a sentence leaves behind whatever led into it — "Call with Credence at on Microsoft Teams", "Call with Daniel & Tenzai —, on Google Meet", "and TII,, Malaysian time". That never mattered while these were being said mid-sentence over Signal. A calendar puts them on your lock screen in bold, so they are cleaned up.
And a Teams invite URL contains 19%3ameeting_, so looking for a time in a sentence that still has the link in it finds "3am" inside the address — which mangles the link and could date the event from a query string. The link now comes out first and goes in the event's own link field, where Calendar makes it a button.
Also in this release
sage-voiced calendar --plan prints every event it would write and touches nothing. Worth running before granting anything: it answers "is it reading the right tasks, and do these read properly" without asking for permission at all. Without --plan it does one real sync; --undo removes the calendar and everything in it.
1700 tests, 0 failures. Notarized and stapled.