# Release v0.16.15 - Changelog

Released: 2026-02-19

## Overview

Agent persistence, reliability, and decision transparency improvements.

## Security

### Socket.IO event handlers now validate all input with Zod
- **Previously**, all Socket.IO event handlers accepted raw client input without validation, allowing malformed or malicious data to reach business logic (PM2 process spawning, shell sessions, error recovery)
- **Now uses Zod schemas** for every event that accepts data: `detect:start`, `standardize:start`, `logs:subscribe`, `error:recover`, `shell:input`, `shell:resize`, `shell:stop`
- **Validation helper** (`validateSocketData`) returns parsed data on success or emits `${event}:error` with structured issue details on failure, matching the pattern used by HTTP route validation
- Schemas enforce type safety on path traversal inputs (`path`, `repoPath`), shell session IDs, terminal dimensions (`cols`/`rows` as positive integers), and process names

### Provider API responses no longer expose secrets to clients
- **Previously**, all GET endpoints (`/providers`, `/providers/:id`, `/providers/active`, `/providers/samples`) returned full provider objects including `apiKey` values and `secretEnvVars` values in plaintext -- anyone with network access to the API could read all API keys
- **Now sanitizes all provider GET responses** -- `apiKey` is stripped and replaced with `hasApiKey: boolean`, secret env var values are redacted to `***`
- **PUT endpoint preserves existing secrets** -- when the client omits `apiKey` or sends `***` for secret env vars, the server merges with existing values instead of overwriting
- **Client form updated** -- API key field starts empty on edit with a placeholder indicating whether a key is already set; only sends a new key value when the user explicitly types one

### Error context sanitized before Socket.IO broadcasts
- **Previously**, the error handler broadcast the full `context` object to all connected clients via `error:occurred` events, potentially including API keys, tokens, and other secrets from the error context
- **Now filters sensitive keys** (`apiKey`, `token`, `secret`, `password`, `credential`, `authorization`, `bearer`, `envVars`, `secretEnvVars`) from the context before emitting to clients; full context is still logged server-side

### PTY shell sessions no longer leak environment secrets
- **Previously**, `pty.spawn()` spread the entire `process.env` into shell sessions, exposing API keys, tokens, and other secrets to anyone running `env` in the terminal
- **Now uses an explicit allowlist** of safe environment variable prefixes (`PATH`, `HOME`, `SHELL`, toolchain vars like `NVM_`, `GOPATH`, etc.) so only standard/non-sensitive variables are passed through
- Secrets like `OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, and any other `*_KEY`/`*_TOKEN`/`*_SECRET` vars are no longer accessible from spawned shells

## Fixes

### Async mutex deadlock bug in memory service
- **`withMemoryLock` deadlocked on error** -- the promise-based mutex in `memory.js` called `resolve()` after `await fn()` with no `try/finally`, so if `fn()` threw, the lock was never released and all subsequent memory operations would hang forever
- **Extracted shared `createMutex()` utility** -- both `cos.js` and `memory.js` had identical inline mutex implementations; moved to `server/lib/asyncMutex.js` with guaranteed `try/finally` release

### Agent history now persists across state resets
- **Completed agents no longer disappear** -- previously, all agent history was stored only in `state.json`; if the state file was reset or corrupted, all completed agents vanished from the UI despite metadata files existing on disk
- **Added disk-backed agent cache** -- `getAgents()` now merges in-memory state (running agents) with persisted metadata files from `data/cos/agents/{id}/metadata.json`, so completed agents always survive state resets
- **Lazy-loaded cache** with ~1987 historical agents loaded on first access and kept in sync as agents complete
- **`getAgent()` falls back to disk** -- individual agent lookups now check the cache when not found in state
- **`deleteAgent()` now cleans up disk files** -- previously only removed from state, leaving orphaned metadata directories; now removes the agent directory entirely
- **`clearCompletedAgents()` now cleans up disk files** -- previously only cleared state, leaving metadata files that would reappear on next cache load; now removes all completed agent directories

### Agent cache load made resilient to individual file errors
- **JSON parse errors no longer crash the entire cache load** -- a single corrupt `metadata.json` file previously caused `Promise.all` to reject, leaving the cache permanently empty (showing only agents from `state.json`). Now uses `Promise.allSettled` and per-file try/catch so bad files are skipped while all others load
- **Failed cache loads can now be retried** -- the cache Map is built locally and only promoted to the module-level variable after successful loading; if `readdir` or another system call fails, the cache stays `null` and the next API call will re-attempt the load
- **Diagnostic logging** -- cache load now reports count of skipped/unreadable files alongside recovered count

### Full agent recovery from disk
- **38 agents with missing metadata recovered** -- agent directories created before metadata persistence was added now get reconstructed `metadata.json` files on first cache load, using directory timestamps as completion dates
- **8 agents with legacy metadata format normalized** -- older metadata files using `agentId` instead of `id` and missing `status` field are now properly loaded into the cache
- **Zombie-cleaned agents now persist to disk** -- `cleanupZombieAgents()` previously only updated in-memory state; now writes `metadata.json` and updates the cache so zombie-cleaned agents survive state resets

### "Done" metric now reflects actual completed agent count
- **Stats counter drifted from reality** -- `state.stats.tasksCompleted` is an in-memory counter that resets when the state file is reset or corrupted; the "Done" stat on the CoS dashboard showed only completions since the last state reset (e.g., 6) instead of the actual count (e.g., 1991)
- **Now derived from disk** -- `getStatus()` counts unique completed agents from the disk-backed cache plus in-memory state, using `Math.max` with the counter to never undercount

### Completed agents now fully visible in UI
- **Replaced hard limit of 15 with paginated "Show More"** -- previously the Agents tab showed only the 15 most recent completed agents with a static "+ N more" message and no way to see the rest; now shows 50 at a time with a "Show more (N remaining)" button to load the next batch
- **Search results also paginated** -- filtered results use the same incremental loading, and visible count resets when the search query changes
- **Fixed missing `socket.off('cos:agent:updated')` cleanup** -- the WebSocket event listener was never unregistered on component unmount

## Improvements

### Extracted validation boilerplate to `validateRequest` helper
- **Previously**, every route that validated input repeated a 5-6 line block: call `validate()`, check `success`, throw `ServerError` with details -- duplicated 70+ times across 12 route files with minor style variations (some used destructuring, some used `res.status(422)`, some used `context: { details }` vs `context: { errors }`)
- **Added `validateRequest(schema, data)`** to `server/lib/validation.js` -- validates against a Zod schema, returns parsed data on success, throws a consistent `ServerError` (400, VALIDATION_ERROR) on failure
- **All route files now use the one-liner** -- replaced inline validation blocks in apps, brain, digital-twin, genome, memory, socialAccounts, agentTools, moltworldTools, moltworldWs, agentPersonalities, automationSchedules, and platformAccounts
- **Error shape is now consistent** -- all validation failures produce the same `{ status: 400, code: 'VALIDATION_ERROR', context: { details } }` structure instead of varying between `422`/`400` and `{ errors }` vs `{ details: errors }`

### CoS now records every no-action decision with reasons
- **Capacity decisions logged** -- when max concurrent agents (global or per-project) prevents task spawning, the reason is recorded to the decision log with full context (which limit was hit, how many agents running)
- **Cooldown decisions logged** -- when a system task is skipped because its target app is still in cooldown, the decision log captures the app ID and cooldown window duration
- **Idle decisions logged** -- when CoS has nothing to spawn after a full evaluation, it records whether that's due to tasks awaiting approval, all tasks on cooldown, or genuinely no work available
- **New decision types** -- added `CAPACITY_FULL` and `IDLE` to the decision type vocabulary; started using the previously-defined but unused `COOLDOWN_ACTIVE` type
- **Dashboard surfacing** -- capacity and cooldown decisions now appear in the impactful decisions summary on the dashboard, making it easy to spot bottlenecks at a glance

### Learning system error categorization improved
- **Broadened usage-limit pattern** -- session limits like "You've hit your limit · resets 6am" were falling through as "unknown" because the pattern only matched "hit your usage limit" (with "usage"); now catches "hit your limit", "plan limit", "daily limit", and "session limit" variants
- **Added startup-failure detection** -- agents that exit immediately with <50 chars of output now get categorized as `startup-failure` instead of `unknown`, distinguishing instant provider failures from real task errors
- **Added turn-limit and billing-error patterns** -- new error categories for agents hitting turn budget limits and provider billing/subscription issues
- **Wait time extraction improved** -- now parses "resets Xam/pm" format in addition to "try again in..." for usage limit cooldown times

### Cron parser no longer CPU-spins on invalid expressions
- **Previously**, an invalid cron expression (e.g., month=13) caused the minute-by-minute search loop to iterate ~1 million times with no match, spinning the CPU for seconds
- **Added upfront field validation** -- before entering the loop, each cron field is checked against its valid range (minutes 0-59, hours 0-23, day 1-31, month 1-12, day-of-week 0-7); out-of-range values return `null` immediately
- **Added iteration safety cap** -- even for valid-but-unmatchable expressions, the loop now breaks after 525,960 iterations (1 year in minutes) instead of running for the full 2-year window

### Protected all external-data JSON.parse calls from crashing the server
- **Previously**, 15 `JSON.parse` calls on external data (AI responses, file reads, PM2 output, user imports) were unprotected -- a single malformed response or corrupted file would throw and crash the process
- **Now uses `safeJSONParse`** from `fileUtils.js` with appropriate fallbacks: `null` for AI response parsing (graceful degradation to raw-response paths), `[]` for PM2 process lists, `{}` for script metadata, default state for CoS state file
- **Files fixed**: `digital-twin.js` (11 locations across 7 AI-response parser functions and Spotify import), `cos.js` (4 locations: state loading, PM2 parsing, script metadata, report reading)

### npm dependency CVEs patched
- Ran `npm audit fix` in `server/` -- 12 packages added, 59 removed, 25 changed; fixed vulnerabilities in systeminformation (command injection), lodash (prototype pollution), and qs (DoS). Only remaining advisory is a pm2 ReDoS with no upstream fix available

### Added explicit radix to all parseInt calls
- `parseInt` calls in `agents.js`, `git.js`, and `productivity.js` now pass radix 10 explicitly, preventing accidental octal/hex parsing from string prefixes

### Fixed byModelTier data corruption in task type reset
- **Model tier metrics drifted from reality** -- `resetTaskTypeLearning()` subtracted counts from `byTaskType`, `errorPatterns`, `routingAccuracy`, and `totals`, but never touched `byModelTier`; after rehabilitation resets, the heavy tier showed 653 completed / 3 succeeded (0.46% success) due to accumulated historical data that was cleared from other structures but not from tier metrics
- **Now subtracts routing accuracy data from byModelTier** -- when a task type is reset, its per-tier succeeded/failed counts are decremented from the corresponding `byModelTier` entries, with duration estimated proportionally from the task type's average
- **Empty tiers cleaned up** -- if a tier's completed count reaches zero after subtraction, the tier entry is removed entirely

## Features

### Redesigned CoS Schedule page with per-app granular config
- **Segmented control** replaces three stacked sections -- pill toggle at top switches between Self-Improvement and App Improvement panels, stored as `?view=self|app` query param for deep-linking
- **Per-app interval overrides** -- each app can now customize its task interval (Inherit, Rotation, Daily, Weekly, Once, On Demand) instead of binary enable/disable only
- **Inline per-app controls** -- expanding an app-improvement task row shows both global defaults and per-app overrides in one place, eliminating the disconnected bottom table
- **Coverage badges** -- collapsed app-improvement rows show `5/8 apps enabled` at a glance
- **Data model migrated** -- `app.disabledTaskTypes` (string array) replaced with `app.taskTypeOverrides` (object with `enabled` + optional `interval` per task type); existing data auto-migrates on first load
- **Eliminated N+1 fetch** -- schedule status API now returns `appOverrides`, `enabledAppCount`, and `totalAppCount` per task type, removing the need for separate per-app API calls on mount
- **Removed PerAppOverrides component** -- replaced by inline `PerAppOverrideList` within each task row

### Updated README with comprehensive feature documentation
- **Restructured features** into organized sections: App Management, Chief of Staff, Digital Twin, Brain, Developer Tools, and Infrastructure
- **Added Digital Twin section** covering identity documents, behavioral testing, enrichment, taste profiling, genome visualization, and import/export
- **Added Brain section** covering thought capture, AI classification, knowledge links, memory system, and digest generation
- **Expanded Chief of Staff section** with multi-agent orchestration, task learning, goal tracking, decision transparency, and error recovery
- **Added Developer Tools section** covering shell, AI runner, process monitor, CyberCity, and browser control
- **Added Tech Stack section** listing frontend/backend/data/AI technologies
- **Consolidated screenshots** from 7 images down to 1 hero image
