Weekly upstream sync 2026-05-03 (8 commits) - #2
loganbronstein wants to merge 15 commits into
Conversation
Auto-regenerated by `cortextos ecosystem`. Switches from path.join helpers to inline absolute paths plus process.env overrides for PM2 runtime flexibility. max_restarts raised so crash loops have more headroom before PM2 gives up. Daily auto-commit snapshot. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Add a silent auto-reset tier that fires BEFORE the graceful handoff tier, so agents get snapshotted and force-restarted at e.g. 55 percent without user-facing Telegram noise. - New ctx_autoreset_threshold in AgentConfig (0 or absent = disabled) - FastChecker.checkContextStatus grows a Tier 0 check that fires once per session, takes a synchronous best-effort snapshot, resets context_status.json pre-emptively, and routes through forceContextRestart so the existing circuit breaker + hardRestart + sessionRefresh path runs - scripts/snapshot-agent.sh factored from pre-compact-snapshot.sh with a --silent flag; daemon always invokes silent, ops can invoke with --notify - cortextos bus auto-compact-agent <name> manual ops hatch (silent default) - Idempotency: .restart-planned present and <2min old blocks Tier 0; stale markers (>2min) are ignored so a leaked marker cannot permanently disable the tier - Unit tests for Tier 0 selection (disabled/fired/deadline precedence, malformed pct, marker idempotency) and integration tests for autoCompactAgent (silent vs notify, snapshot failures, missing script) - AGENTS.md Context Discipline section documents the four tiers and the manual hatch Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three fixes from the adversarial review pass: 1. NON-BLOCKING SNAPSHOT: runAutoresetSnapshot now uses spawn with detached+unref instead of execFileSync. The 1s FastChecker poll loop is no longer held for up to 10s while the snapshot runs, which was delaying Telegram delivery, inbox checks, and typing-indicator updates. 2. BOOT-WINDOW GUARD: Tier 0 refuses to fire within 60s of session start. Without this, an agent that boots at or above the autoreset threshold (bloated CLAUDE.md, pre-loaded handoff doc, heavy bootstrap) would enter a restart loop — each fresh session crosses the threshold within the first tick and trips Tier 0 again. We do NOT latch the fired-at flag during the boot window so later polls can reconsider. 3. CLOCK-SKEW-SAFE MARKER STALENESS: the .restart-planned marker age check now treats negative ages (mtime in the future, from system clock jumps) as stale instead of fresh. Previously a backward clock jump could silently disable Tier 0 by making a stale marker look fresh. Defense-in-depth: forceContextRestart now also zeros ctxAutoresetFiredAt alongside the other per-session context flags, so a future soft-refresh path that reuses the same FastChecker instance would not latch Tier 0 off forever. New tests cover boot-window gate (0s/59s/60s/10min boundary), clock-skew marker staleness (negative age, 2min boundary), and legacy no-session_id fallback. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Four functional issues from the codex adversarial pass, all blocking silent / correct behavior of Tier 0: 1. AUTORESET-ONLY CONFIG WAS A NO-OP: the action gate in checkContextStatus bailed whenever ctx_handoff_threshold was undefined, so an operator who configured ONLY ctx_autoreset_threshold (the documented enablement) got observe-only mode. Gate now arms the monitor if any of the three thresholds are set. 2. BOOT TELEGRAMS AFTER TIER 0: the post-Tier-0 fresh session ran the normal cold-boot prompt which tells the agent to send "Booting up..." and "back online" messages. That violated the silent-auto-reset contract. Added .silent-restart marker armed by Tier 0 and consumed by agent-process.ts; when present, the boot prompt gets a SILENT AUTO-RESET override that suppresses both Telegram messages. Marker is unlinked after consumption so the effect lasts one restart. 3. MANUAL auto-compact-agent DID NOT ACTUALLY RESTART: writing .force-fresh + .restart-planned is not enough on its own. The daemon only consumes those markers at the next start(). CLI now sends IPC restart-agent to the daemon after arming markers, matching the self-restart pattern. --no-ipc flag preserves the old arm-only behavior for scripted use. 4. CROSS-AGENT ENV LEAK: when agent A ran auto-compact-agent for agent B, execFileSync inherited A's CTX_AGENT_DIR / CTX_AGENT_NAME, and snapshot-agent.sh prefers those env vars over the positional name — so A's memory marker and Telegram got written instead of B's. autoCompactAgent now scrubs those env vars and overrides CTX_AGENT_NAME to the target; CTX_AGENT_DIR is intentionally omitted so the script falls back to the positional-argument derivation. autoCompactAgent also now writes .silent-restart alongside the restart markers so the manual hatch matches Tier 0's silent contract. Tests cover: env scrubbing (caller CTX vars do not leak to stub), .silent-restart marker presence after silent auto-compact. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Upstream PR: grandamenium#206 Fleet-wide silent auto-reset at configurable ctx_autoreset_threshold (typical 55%). Fires BEFORE the existing 70/80 tiers; non-blocking snapshot via spawn+detached; .silent-restart marker suppresses boot Telegrams; cortextos bus auto-compact-agent manual hatch with IPC restart-agent. Two adversarial passes caught 7 bugs total; all fixed before merge: blocking poll loop, cold-boot restart loop, clock skew, autoreset-only config no-op, silent-mode Telegram leak, manual hatch no-restart, cross-agent env leak.
…lection ingest Root cause: ingestKnowledgeBase passed timeout:120000 to execFileSync. When incremental ingest ran against a collection with 1000+ chunks, the python mmrag.py child had to dedup existing doc IDs, call the Gemini embedding API (network-bound, rate-limited), and upsert to ChromaDB. That work routinely exceeds 2 minutes, so Node's timer fired and SIGTERM'd the healthy child. Direct python invocation worked because no Node-imposed timer existed. Fix: remove the default timeout (ingest is interactive, stdio:inherit, user sees progress and can Ctrl-C). Add optional CORTEXTOS_KB_INGEST_TIMEOUT_MS env override for CI/automation that needs to bound runtime. Read from the merged env so .env / secrets.env overrides work the same way MMRAG_CONFIG and other KB vars do. Tests: 5 new vitest cases covering no-default-timeout, positive-number override, invalid-value handling, merged-env override, and regression guard against a simulated 5-minute subprocess. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…on kb-ingest Fixes SIGTERM kill on large (1000+ chunks) collection ingest. Root cause was execFileSync timeout:120000 firing on healthy network-bound work; direct python invocation worked because no Node timer. Adds optional CORTEXTOS_KB_INGEST_TIMEOUT_MS env override for automation. PR: grandamenium#208
…n Vault
New `cortextos bus publish-to-vault <file> --vault-dir <subdir>` command so
any agent can land strategic deliverables in the Vault with provenance
frontmatter (published_by, published_at, source_task, source_path, summary,
tags) and a wikilink back to the originating task. Bridges the gap between
ChromaDB-searchable agent output and Logan's daily Obsidian workflow.
- src/bus/vault.ts — core module with hardened path + source-file handling:
* vault-dir containment: per-segment .. / . rejection, realpath check that
the resolved target stays under the canonical vault root
* source read: lstat preflight (rejects FIFO/device/dir), then a single
openSync + fstat + bounded readSync so symlink swaps can't reach us
* size cap 10 MB, NUL-byte binary heuristic in first 8 KB
* collision-safe write: temp file + linkSync (EEXIST retries -v2, -v3, …)
so two parallel publishers never clobber each other via rename TOCTOU
* task-id requires task-dir + regular-file stat to block fabricated
provenance from planted dirs / symlinks named <id>.json
- src/cli/bus.ts — wiring with --vault-dir / --task-id / --summary / --tags
/ --vault-root flags; resolves $VAULT_ROOT → org context.json →
hardcoded default; emits output_published_to_vault event
- templates/agent/AGENTS.md — "Publishing to the Vault" section +
output_published_to_vault row in the event-logging table
- tests/unit/bus/vault.test.ts — 32 tests covering happy path, collisions,
frontmatter merge, and adversarial inputs (traversal, symlink escape,
binary files, oversized files, TOCTOU pressure, fabricated task-ids)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Defends against a planted directory, symlink, or FIFO named `<task_id>.json` in the taskDir. existsSync() returns true for all of those, so the publish would record fabricated provenance. lstat + isFile() closes the hole. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the "merged but never shipped" gap we hit today on 4 OAuth Edge Functions. Adds two automations wired into git hooks, plus docs + tests. - scripts/setup-upstream-pr.sh: one-time setup. Verifies gh auth, ensures fork exists, wires origin=fork / upstream=grandamenium/cortextos. - scripts/auto-pr-upstream.sh: pre-push delegate. Opens a PR to upstream when a branch touches src/, dashboard/, templates/, .claude/hooks/, or scripts/. Idempotent; never blocks the push on any failure path. - scripts/auto-deploy-supabase.sh: post-merge delegate. Deploys every supabase/functions/<name>/ touched by the merge via `supabase functions deploy <name> --project-ref baidaaansxrfdislmgyx`. - scripts/hooks/pre-push: extended to schedule auto-PR-upstream in background. - scripts/hooks/post-merge: new hook delegating to auto-deploy-supabase. - scripts/setup-hooks.sh: installs both hooks. - CONTRIBUTING.md: new "Contributing to upstream" section documenting the flow, kill switches, and failure modes. - tests/unit/scripts/: 19 new integration tests covering the happy path and adversarial cases (unsafe branch names, missing upstream remote, behind upstream, gh not authed, network failure, duplicate PR, supabase CLI missing, AUTO_DEPLOY_SKIP kill switch, path-like function names, deleted function dirs, continue-on-failure for partial deploys). All fake binaries PATH-prepended — no real gh / supabase calls. macOS bash 3.2 compatible (no mapfile/readarray). All failure paths exit 0. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tional now optional Option B migration of the evaluate-experiment CLI. Previously, passing `--score N` overwrote result_value AND baseline_value with the 1-10 rubric, discarding the actual measured number. The quantitative workflow (pricing autoresearch) silently lost every measurement. The original behavior had a purpose: qualitative metrics where the agent passed 0 as a placeholder measuredValue and --score 7 as the real value. Eight skill docs taught that pattern. A naive fix would ship a new bug onto every qualitative cycle. This PR resolves both: - Add `score: number | null` to the Experiment interface; initialize to null on create; normalize to null on load for legacy JSONs that predate the field (loadExperiment + listExperiments). - CLI `evaluate-experiment` `<value>` becomes optional. At least one of positional value or --score must be provided (throws otherwise). --score validates as integer 1-10 at the CLI boundary. - evaluateExperiment(): when both value and --score are provided, result_value tracks the positional value and score lives in its own field. When only --score is provided (qualitative workflow), the score doubles as result_value so decision logic has a number to compare, and baseline_value rolls forward to the score on keep. - results.tsv gets a new 'score' column (header + row); learnings.md adds a 'Score: N/10' line when present. - Update CLI help text to reflect the new contract. - Update 5 autoresearch skill docs (templates/agent, analyst, orchestrator + community/skills/autoresearch + community/agents/ security) to drop the placeholder '0' — examples now use `evaluate-experiment <id> --score 7 --justification ...`. - Add 5 regression tests covering: --score stored without overwriting measured value, score defaults to null, qualitative path with only --score, throw on both-missing, legacy-JSON normalization. All 656 tests pass (43 test files, --exclude dashboard since the unrelated comms/routes test failure predates this branch). Found during 2026-04-22 autoresearch backfill of 6 pricing iterations; the wrong behavior silently stored scores (5,8,9) where measured values (76.9,80.6,92.4) should have landed. Backfilled files were patched by hand at the time; this PR prevents recurrence. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…re-field-v2 fix(bus): evaluate-experiment --score stored in own field; value positional now optional
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a1840485bf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const onlineMessage = isHandoffRestart || isSilentRestart | ||
| ? '' | ||
| : ' After setting up crons, send a Telegram message to the user saying you are back online.'; | ||
| <<<<<<< HEAD |
There was a problem hiding this comment.
Resolve merge markers in daemon source
The merge conflict markers left in buildBootPrompt() make this TypeScript file unparsable (TS1185: Merge conflict marker encountered), which blocks tsc/builds and prevents shipping any runtime changes from this commit. This is a hard failure for all environments that compile or run the daemon code.
Useful? React with 👍 / 👎.
| args: '--instance ' + (process.env.CTX_INSTANCE_ID || "default"), | ||
| cwd: "/Users/loganbronstein/cortextos", | ||
| env: { | ||
| <<<<<<< HEAD |
There was a problem hiding this comment.
Remove conflict markers from ecosystem config
Unresolved merge markers are committed directly in ecosystem.config.js, so Node cannot parse the PM2 config (Unexpected token '<<'). Any deployment path using pm2 start ecosystem.config.js will fail before the daemon can launch.
Useful? React with 👍 / 👎.
| import { join } from 'path'; | ||
| import { tmpdir } from 'os'; | ||
| import { execSync } from 'child_process'; | ||
| <<<<<<< HEAD |
There was a problem hiding this comment.
Clean merge markers from bus system test import
This test file still contains conflict markers around the import list, which makes the spec syntactically invalid and prevents Vitest from parsing/running it. Even after fixing production code, CI test execution will remain broken until these markers are removed.
Useful? React with 👍 / 👎.
…build-pin disclosure corrected + #2 collection-level fidelity Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018pcr1gUvn3fSvCoCxGYk56
Weekly upstream sync (2026-05-03)
Upstream
grandamenium/cortextosadded 8 commit(s) since the last run.Each one is summarized in plain English below.
What's new
New feature in bus: hooks framework — Day-1 stub + Day-2 per-handler wiring + telemetry (feat(bus): hooks framework — Day-1 stub + Day-2 per-handler wiring + telemetry grandamenium/cortextos#272)
da437c3(grandamenium@da437c3)Bug fix in telegram: validate BOT_TOKEN and CHAT_ID against Telegram API before enable + setup writes .env (fix(telegram): validate BOT_TOKEN and CHAT_ID at add-agent and setup time grandamenium/cortextos#235)
eb119a9(grandamenium@eb119a9)Bug fix in daemon: extend gap detection to cron-expression crons (fix(daemon): gap detection skips cron-expression crons — only interval-based crons monitored grandamenium/cortextos#169) (fix(daemon): extend gap detection to cron-expression crons (#169) grandamenium/cortextos#184)
b85cb69(grandamenium@b85cb69)Bug fix in telegram: switch to HTML parse mode — eliminates silent content drops (fix(telegram): switch to HTML parse mode — eliminates silent content drops grandamenium/cortextos#181)
5f1943e(grandamenium@5f1943e)Bug fix in daemon: use CronCreate directly on boot to skip /loop cloud-prompt (fix(daemon): use CronCreate directly on boot to skip /loop cloud-prompt grandamenium/cortextos#210)
b6e4515(grandamenium@b6e4515)Bug fix in bus: hard-restart now sends IPC restart-agent to terminate the session (fix(bus): hard-restart now sends IPC restart-agent to terminate the session grandamenium/cortextos#217)
a38ef7a(grandamenium@a38ef7a)Bug fix in daemon: guard worker PTY null-write + add crash visibility (fix(daemon): guard worker PTY null-write + add crash visibility grandamenium/cortextos#223)
a803002(grandamenium@a803002)Bug fix in test: use relative timestamps in channels route test (fix(test): use relative timestamps in channels route test grandamenium/cortextos#226)
3420b5b(grandamenium@3420b5b)How to merge
merge allto take everything.merge 1 3 5(numbers from the list above) to cherry-pick those.skipto dismiss this whole PR.This PR was opened automatically by boss every Sunday at 6:30pm Chicago time.