Nightshift

Thursday 23 July 2026 · 04:17 – 06:30 ACST · mycelium · autonomous
14branch commits
4branches
178tests green
10tasks filed
0prod changes

Read this first

Two of your background jobs have done nothing since May

cosmo-digest and cosmo-adversary both guarded main() with argv[1] === __filename. Under PM2 fork mode argv[1] is ProcessContainerFork.js, never your script — so the guard was always false. They never started. PM2's IPC channel held the event loop open, so both reported online, 0 restarts, 0% CPU, no logs, for two months.

They weren't failing. Nothing was wrong with the digest logic, the schedule, the bot token or the target chat — I verified all four. The job was simply never invoked. Fixed, tested, not restarted.

Blast radius checked and contained: I scanned every pm2 process, every com.cosmo.* launchd job, and the whole codebase for the same pattern. Only these two were affected. Two other files use that guard shape but are direct CLIs, where argv[1] genuinely is the script.

The dream pass has been closing tasks that weren't done — 8 of 10

I audited every auto-done it has ever made. Six cited evidence that explicitly says the work was not done. One literally read "still open in tasks/active/, not closed" and closed the task anyway.

All 8 reopened with the reason recorded. Because the pass commits as you, each one looked self-inflicted in git, which is why it went unnoticed.

Shipped

Model delegation systembuilt + proven

Nine lanes across three providers. Groq and Ollama added on your late steer; see the harness note below for what each can actually do.

A real executor for open-weights models, on the Agent SDK, with the two footguns that killed the last one designed out.

Built on query() with a scrubbed per-call env — verified that ANTHROPIC_BASE_URL passed that way routes cleanly to OpenRouter, so this is the SDK properly, not a wrapper around claude -p. No max_tokens cap and no silence-based stall detection anywhere in the path: both killed legitimate reasoning runs before, and silence is what a reasoning model normally does.

Nine lanes, all smoked live at the end of the session: groq-fast 0.2s · groq-qwen 0.5s · groq-compound 0.9s · gpt-oss 2.9s · nemotron 3.3s · glm 3.5s · qwen-coder 4.6s · kimi-k3 6.4s · ollama-local 31.2s. All three modes exercised for real: answer, read, and write.

That full smoke earned its keep — it caught groq-qwen leaking <think> reasoning blocks into its answers, which would have landed verbatim in any delegated file. Now stripped in all three transports, but only complete blocks: an unterminated one means the model was truncated, and quietly deleting the rest would hide that.

Safety is in code, not prose: ~/cosmo, ~/cosmo-memory, compass, .ssh and any directory holding a .env are refused as a working directory; ANTHROPIC_API_KEY is set empty rather than absent (the €34 cosmo-cloud leak); the child env is an allowlist; unknown model ids are refused rather than passed through.

Named open-models as its own skill rather than a page under delegate, since skills are selected on name and description — a sub-page would be invisible at the moment of choosing. delegate is now policy-only, codex is untouched.

node ~/cosmo/services/delegation/cli.mjs run --lane glm --prompt "..."
node ~/cosmo/services/delegation/cli.mjs smoke
branch delegation-system4 commits49 tests

Which lanes can actually run an agentyour Groq question, answered

The dividing line is the wire protocol, not the model.

So: Groq's edge is latency, not capability, and anything on both Groq and OpenRouter should use the OpenRouter lane when it needs tools. The shim is committed and working for whenever you decide about Groq Dev Tier.

Guided-access audit brokerbuilt + ran

Your steer was right — a static bundle wasn't enough. K3 now interrogates the server directly, without ever holding a credential.

K3 runs with no tools at all and emits JSON requests for the commands it wants. The broker vets each against a read-only allowlist (not a denylist — that's unwinnable), refuses shell metacharacters so an allowed binary can't become a launchpad, and refuses credential paths even to allowed readers so cat can't become an exfiltration primitive. Output is scrubbed before it goes back. Every command, allowed or refused, is logged.

It worked on the first round: K3 asked for apt list --upgradable 2>/dev/null and the broker refused the redirection. Full run: 5 rounds, 40 commands, 15 refused, nothing mutated.

The live run also caught a bug in my own allowlist that the unit tests couldn't: the ls rule contained Cyrillic characters I'd typed by accident, visually identical to Latin, so every directory listing was refused. Fixed, with a test asserting every rule is pure ASCII.

branch delegation-system21 broker testsread-only by construction

The delegate doctrine got tested for realunplanned

I asked Codex to red-team my own safety.mjs and audit-broker.mjs — find the bypasses before an attacker does. It refused: "flagged for possible cybersecurity risk… join the Trusted Access for Cyber program". Reviewing your own security code on your own server, at your own request.

That is precisely the case your delegate skill already describes: K3 is the "security-work fallback when frontier models over-refuse legitimate dual-use". Written on 21 Jul from the bake-off, never actually exercised until tonight. Re-routed to K3 through the new lane, with the authorisation context stated in the prompt.

Worth knowing as an operating fact: Codex is unusable for security review of your own infrastructure, so that work has a single lane rather than a choice.

And it earned its keep immediately. K3 found three critical RCE bypasses in my own broker. The worst: awk -F: 'BEGIN{system("id")}' /etc/passwd — I never put quotes in the metacharacter blocklist, so a quoted awk program survives vetting and the remote shell re-parses it into arbitrary root code. find … -exec cat {} + and grep -r . /root/.ssh read anything; /etc//shadow slips past the path denylist on a double slash. My "read-only allowlist" had three write/exec primitives wearing a reader's name.

Tonight's audits were safe only because I drove them — the broker is not safe against a hostile model, which is its whole reason to exist. Filed as critical, do-not-reuse-until-fixed. This is the doctrine working as designed: delegated output is a claim, and the claim to verify hardest is your own code.

Dream pass no longer auto-closes tasksfixed

Adds DREAM_AUTO_DONE with three modes, defaulting to suggest: proposals now land in the brief with their evidence and the command to confirm them, and nothing closes on its own. verify mode gates each close behind a second LLM call that gets the whole task file and must first work out the task's terminal action — you can't tell progress from completion without knowing the done-state.

I didn't just reword the prompt. It already said "done ONLY when a fact line EXPLICITLY states the work was completed", and the model ignored it 8 times out of 10.

branch dream-autodone-fix9 new tests55 green in memory-engine

Starbase entry-point fixfixed

New shared isEntryPoint() used by both jobs. It matches on either pm_exec_path or argv[1] — my first version preferred pm_exec_path, and a smoke run caught that this breaks direct runs, because PM2 exports pm_* to child processes and everything launched from cosmo-agent inherits pm_exec_path=src/agent.js. That would have been the same silent no-op from the other side.

Includes a regression test that runs a real PM2 fork container and asserts the old spelling is false while the new one is true.

branch starbase-entry-guard2 commits8 tests

Weekday/date verifier Stop hookbuilt by GLM

Blocks a turn asserting a weekday that doesn't match the real calendar — the "Tue 15 Jul 2026" bug, where 15 July was a Wednesday and the correct calendar was already in context. Detection is pure code, no LLM in the path; the model is engaged only to correct.

Built by GLM-5.2 through the new lane, in an isolated worktree with no credential access. I verified it independently rather than taking its word: its own 40 tests pass, plus 15 adversarial cases I wrote separately against the false-positive surface (inline and fenced code, bare dates, weekday words with no date, URLs, sha-like tokens, relative phrases, correct leap day, correct ranges). No false positives.

branch date-verifier-hook40 + 15 testsnot wired in

Decisions waiting on you

1 · Merge the four branches?

All four are worktree-isolated, committed, and green. Nothing is merged and nothing touches production. I re-checked at the end of the session: all four still merge clean onto current main, which other sessions moved twice tonight. My call: yes, all four.

Until you merge, none of it is reachable. The open-models skill lives on the branch, so Claude Code can't load or select it, and services/delegation/cli.mjs isn't on main either. Tonight's delegation work is real and tested but effectively invisible to any future session until this merge happens — which is why it's decision 1.

git -C ~/cosmo log --oneline main..delegation-system

2 · Restart cosmo-digest and cosmo-adversary?

The fix is in but I deliberately didn't restart them. Starting the digest begins sending you an 08:00 Telegram every day, and the adversary begins its Sunday review — you should decide whether you still want either, two months on. My call: start the digest, decide on adversary after one week of digests.

3 · Leave auto-done on suggest, or move to verify?

Default is now suggest, which never closes anything. My call: stay on suggest for a fortnight. The verifier is built and tested, but given an 8-in-10 error rate on the thing it's gating, I'd want to watch it reject in the brief before trusting it to close.

4 · Wire the date hook into settings.json?

Built and tested but not installed — a Stop hook can block your turns, so that switch is yours. My call: yes, it fails open on every error path.

5 · Two reopened tasks may actually be done

I reopened them as uncertain and said so on each. Cala Pola waterpass: the evidence covers item creation but not the non-resident payment path or the Uplaan/Nerea clause. HK scroll fix: evidence claims a fix plus tests, but the task's own note ends "needs on-device confirm". Close either in one command if they're genuinely finished.

6 · Credentials for delegated work — still undecided

§7 of the delegation spec. The clean-room denies all credentials by design, and the broker is one answer for the SSH case, but there's no general answer yet. Until you decide, I'm keeping credential-touching work on the Anthropic lane rather than improvising.

Server audit

Two WordPress installs are years out of date, in writable trees

All three sit in trees writable by www-data, and PHP runs as www-data. So an outdated plugin gives code execution, which can then rewrite the WordPress core itself and persist. With no HTTP-layer fail2ban jail, nothing is watching the attempts either.

This is the most realistic intrusion path on the box — more so than the sudo issues, because it needs no local access to start. Decide per install: update, or take the heckler archive offline if it's genuinely dead.

Three Firebase admin private keys are world-readable on prod

Mode 644, in world-traversable directories, inside the deploy tree at /var/www/kmzapi/controllers/sa/:

-rw-r--r--  h2os-global-firebase-adminsdk-h3lan-8874fcd477.js
-rw-r--r--  partners-h2os-global-firebase-adminsdk-h3lan-4336f932bd.js
-rw-r--r--  my-kmzero-firebase-adminsdk-r9um8-042fc6a03d.js

Any local user, and any process running as www-data — which means a bug in any of the 40+ sites on the shared PHP pool — can read full Firestore admin credentials for both h2os-global and my-kmzero. Given h2os-global's history (the Jan–Feb API key abuse, the May Gemini case), that's the one I'd act on first.

chmod 600 stops the bleeding tonight. The real fix is moving them out of the deploy tree and rotating the service accounts, since they've been broadly readable for an unknown period. I haven't touched either — one is a prod change, the other is a credential rotation.

Critical: the scoped sudo policy on the work server is decorative

/etc/sudoers.d/ holds two files for the agent user. One is a careful ~60-line scoped NOPASSWD list. The other, agent-full-access, contains exactly one line:

agent ALL=(ALL) NOPASSWD: ALL

sudo applies the last match, so agent already has unrestricted passwordless root and the scoped file does nothing. Every mitigation that assumed the narrow grant list was a boundary is void.

Decide which one you meant. If full access is intended, delete the scoped file because it's actively misleading. If it isn't, delete the override and fix whatever needed it. Worth knowing either way: even the "scoped" list is root-equivalent, via node, python3, docker, sed, tee, vi, chmod, chown.

K3 found this and I didn't. I ran the same check hours earlier and piped it through head -15, which cut off exactly that line. Don't truncate security output — I'd already written up the narrow grants as if they were the boundary.

Critical: every node app on the box runs as root

All 16 node processes and the PM2 daemon itself run as root. Thirteen of those services listen on all interfaces. So a remote-code-execution bug or a webshell in any one of them is immediately root — no privilege-escalation step needed, no exploit required.

This is the single highest-value thing on the list. It almost certainly grew out of the sudo pm2 habit: sudo pm2 start launches the daemon as root and every app it spawns inherits that.

K3 raised this at Medium confidence as "verify immediately, likely Critical". It was right to, and the verification came back worse than it guessed.

K3 got its top finding wrong — worth knowing before you read its reportverified

K3's headline Critical was that the box is past EOL with a dead security feed, so "0 security updates" means nothing is being patched. It rated that High confidence and built a kill chain on it.

It's wrong. Ubuntu Pro is attached and both esm-infra and esm-apps are enabled, so the security feed is alive and the zero is real: no pending security updates, no pending kernel update, running the newest installed kernel.

It made the same mistake twice: it also called PHP 7.4 "EOL since Nov 2022, unpatched". The installed build is 7.4.3-4ubuntu2.29+esm3, compiled January 2026 — the +esm3 suffix is ESM actively patching it.

But I got one wrong in the other direction, and it's the important one. I first refuted its claim that app directories are writable by www-data. That refutation was wrong: I searched only for group-writable directories at depth 2 and missed owner-writable ones deeper down. The correct number is 7,533 directories under /var/www writable by www-data, including the WordPress cores. K3 was right and I was hasty.

To its credit it named ESM status as "the single most important missing fact in this bundle" — it knew exactly which unknown its conclusion rested on. That is the model behaving well; the conclusion was still wrong, which is why nothing from a delegated model reaches you unchecked.

One real residual: livepatch reports the running kernel is past its active window, so a kernel refresh and reboot is still owed even though nothing is pending today.

Guided beat static, and beat memethod note

Two runs, same model. The static one reasoned over a bundle I chose; the guided one drove its own investigation through the broker over 5 rounds and 40 commands, 15 of which were refused.

The guided run found the root-user problem by itself as its own finding #1, and turned up four things my bundle never contained: the docker group membership, TLS 1.0/1.1, PHP version, and that the wpadmin SFTP chroot is undermined by its own group memberships. Worth knowing for next time — letting it ask beat handing it a curated summary.

Critical: webadmin and agent are in the docker group

Docker group membership is root equivalence, unconditionally — docker run -v /:/host mounts the whole filesystem into a container you control. No password, no sudo, no exploit.

It also makes the NOPASSWD sudo list nearly moot: all three interactive users are in sudo anyway (%sudo ALL=(ALL:ALL) ALL), so the narrow grants were never the boundary they looked like.

Confirmed directly by me

Genuinely good, and verified rather than assumed: ufw default-deny confirmed in netfilter, fail2ban actively banning on SSH (2 tries, 24-hour ban), password auth off, Ubuntu Pro attached with ESM applying, kernel current, .git blocked on every vhost I tested (403).

Root has no usable SSH keys. Its authorized_keys is 69 bytes holding only a comment — ssh-keygen -lf finds no key in it — with a .bak-170726 alongside, so someone stripped them deliberately on 17 July and kept a backup. That refutes the audit's concern about root key login. agent has exactly two keys and both are accounted for (webadmin@heckler-inc-fra-ubuntu and zero@cosmo-cloud).

Eight tasks filed for the verified server findings, each carrying its evidence and the specific fix. Nothing on the server was changed — every one is read-only research plus a written task.

full K3 report: session-docs/do-audit-230726/read-only throughout

7 · Consultancy name — SheOak, conditionally

Both methods landed there independently. My call: SheOak, but only if you'll do the two pieces of work it obliges — write the colonial-naming line before the 20 Aug talk, and ask your Ngadjuri collaborators before using it commercially. If you won't, take Inhere and accept the Victorian namesake, or reopen the shortlist.

Before spending anything: IP Australia trade mark search on the winner, classes 42 and 35. That's the one check neither of us could do.

8 · Take root away from the node apps

The biggest security win available and not a five-minute job, so I've left it entirely alone. Roughly: pm2 kill as root, re-establish pm2 under a non-root app user via pm2 startup systemd -u <user>, fix file ownership, restart. It will drop services while you do it, so it wants a chosen window — this is production.

9 · Compact the dream-pass question backlog

127 open questions are really 33 decisions — 73% duplicates, because they were deduped by episode line rather than by decision. That's why the same routing questions have reappeared every night since 16 Jul: answering one instance left the other thirteen open.

Fix committed. Run the compaction after merging, or tonight's pass re-inflates it.

node ~/cosmo/src/memory-engine/compact-questions.mjs          # dry run
node ~/cosmo/src/memory-engine/compact-questions.mjs --write

AI consultancy — naming

SheOak vs Inhere — Codex says SheOakdone

You asked for this last thing, so it got the tail of the session. Codex is doing an adversarial decision between the two, reading the positioning brief, the 353-line business plan and all six prior naming rounds first, and writing to plan/naming-round7-sheoak-vs-inhere.md. It must recommend one, not fence-sit.

One thing I made it confront rather than leaving to chance: in Australian colonial botany the "she-" prefix marked a species as inferior to the "true" one — she-oak meant lesser-oak, and the gendered diminutive is the whole construction. For an audience primed to notice exactly that kind of language, it's either a fatal flaw or an asset if named openly, and it shouldn't be discovered after the domains are bought. I also made it address the tree's significance to Aboriginal peoples concretely, given you work with Ngadjuri collaborators.

Its argument, condensed: the first encounter with the brand isn't a philosophy seminar, it's a name on a slide, in an intro, or under a winery proposal. Inhere asks the reader to decode a rare verb before it earns any warmth, and most will read "in here" or a typo. SheOak is concrete, living, local and speakable, and it sounds nothing like an AI startup — which is worth a lot in a room primed to dislike AI consultancies. It also gives you South Australian texture without putting "ethical" or "green" in the name.

It attaches a condition rather than hand-waving: the colonial naming must be acknowledged and advice sought on the cultural side before launch, and in its words, if you're unwilling to do that work then SheOak shouldn't be used. It's a 38KB document; that's the spine of it.

It's told not to claim domain or trademark checks it cannot perform; those come back as TO VERIFY. If the run didn't finish before the 06:30 stop, the brief is at /tmp/codex-naming-brief.md and re-runs in one command.

Verification layer — and a correction to my own workread this bit

I first checked domains with dig and concluded Inhere was clean. That was wrong. A registered domain with no nameservers configured looks identical to an unregistered one under dig. Re-running through your own namegen/check-name.mjs — which queries auDA and Identity Digital RDAP properly — reversed the finding. I built a worse check by hand before looking for the tool already in the repo.

What actually decides it, stated narrowly: Inhere already has an Australian namesake selling consulting to organisations — Inhere Consulting, Newstead VIC, doing organisational and leadership development. Same country, same word, same buyers. SheOak's Australian namesakes are a psychologist, a theatre company, a film company and a wellness brand. None of them are in this business.

Crowding across unrelated sectors is noise. A same-sector namesake in your own country is the one that costs you.

The inHere® trademark (Fidelity National Financial, attached to an AI assistant) is real but appears US-only — no Australian registration surfaced, and marks are territorial. That's an SEO and .com problem, not a likely enforcement one. Still needs an IP Australia search, which I can't do for you.

Your two screenshots match: sheoak.ai and inhere.com.au are both genuinely available. So domains don't decide this either way.

plan/naming-round7-verification-230726.mdcorrected 05:35

Both methods landed on SheOak, independently

Codex picked it on positioning grounds with no network access. The evidence layer landed there separately on the same-sector-collision argument. Convergence from two different methods is the strongest signal available tonight.

But you flagged the Indigenous question yourself, and that's the right instinct — it's the one input neither of us can supply. Codex treats consulting your Ngadjuri collaborators as a required pre-launch action rather than optional, and I agree. Given you already have that relationship through the White Ochre work, asking is cheaper than reasoning about it in the abstract.

The other thing to decide on purpose: in colonial botany "she-" marked a species as inferior to the true one. Either own that line publicly — the tree they called lesser, that fixes nitrogen and holds degraded ground, is a genuinely good story for this positioning — or don't pick the name. It shouldn't surface first in Q&A after the talk on Thu 20 Aug.

Not done

The substance of the routing backlog — actually answering the 33 decisions about where facts belong and which topics to create — is untouched. I fixed why they keep repeating and measured what's really there, but the answers are yours: they're judgements about your own memory taxonomy, and that's the one thing that shouldn't be guessed at while you're asleep.

Nothing else on the approved list was dropped.

Undo

Everything is reversible. No production change, no deploy, no deletion, no credential touched, no outward message sent. The only meter that moved is OpenRouter.

Do not reset main to the nightshift-start-230726 tag. I left that anchor early in the session and other sessions have committed to main since — resetting would destroy their work too, not just mine. Nothing of mine is on main except session docs and the audit artefacts, so there is nothing there to undo. Drop the branches instead.

# discard the four branches and their worktrees
for b in delegation-system dream-autodone-fix starbase-entry-guard date-verifier-hook; do
  git -C ~/cosmo worktree remove --force "$HOME/cosmo-wt/${b%%-*}" 2>/dev/null
  git -C ~/cosmo branch -D "$b"
done

# undo the reopened tasks (single commit, safe to revert)
git -C ~/cosmo-memory revert 20acbf4

# the naming docs and filed tasks are additive — delete the files if unwanted