Agent! 1.0.92.186
Agent! v1.0.92 (186)
Agentic Harness & Guards
- Add HarnessGuardTests — eval suite for the agentic harness guards (e0b5aff)
- 32 deterministic tests covering isToolFailure status-line semantics, toolCallFingerprint determinism + repeat exemptions, and routeStopReason (malformed tool call, max_tokens, open criteria, action claims, retry cap)
- Also covers ToolErrorClassifier codes, GoalStateStore evidence gating, PlanStateStore surfacing rules, and critic-gate diff plumbing — all passing
- Add critic review gate — opt-in one-shot LLM diff review before task_complete (2ce20ac)
- New CriticGate.swift: when the toggle is on and the task edited files, completionGateBlocker runs a one-shot reviewer call (PASS/ISSUES) on
git diff HEADand blocks completion once with the found issues - Runs at most once per task (reset at task start in both main and tab loops) so it can never loop completion; toggle lives in Coding Preferences
- New CriticGate.swift: when the toggle is on and the task edited files, completionGateBlocker runs a one-shot reviewer call (PASS/ISSUES) on
- Add ToolErrorClassifier — typed error codes + recovery hints on failing tool results (1d16827)
- recordToolOutcomes appends a stable
[error_code: ...]annotation with an actionable hint (old_string_not_found, file_not_found, permission_denied, build_failed, timeout, etc.) - The model gets structured errors instead of bare strings, directly on the failing tool_result content
- recordToolOutcomes appends a stable
- Extend typed-error annotations to the tab-task path (55a8d19)
- processTabResponseContent now runs the same ToolErrorClassifier as the main loop on each failing tool result
- The
[error_code: ...]+ recovery hint is appended before the stuck-file and broken-record guards see the result
- Add generic repeated-tool-call (broken record) guard (6dc2454)
- StuckGuard.toolCallFingerprint() fingerprints every call (name + sorted-key JSON of input); polling/waiting tools are exempt via repeatExemptTools
- Nudges on the 2nd identical call, hard-stops on the 3rd+; wired through ToolLoop.processTabResponseContent and TabTask
- Nudge once before accepting a tool-less turn as completion (7bbb2eb)
- A text-only turn is ambiguous (done, narrated plan, dropped malformed tool call, or truncation) but was silently reported as "✅ Completed"
- First tool-less turn now appends a nudge to call task_complete or continue; a second consecutive one completes as before, so no ping-pong
- Match guard tool results by tool_use_id instead of toolResults.last (5bcd81d)
- toolResults.last is only correct for single-tool turns; on batched turns build failures were scored against unrelated output and edit failures attributed to the wrong file
- Adds resultContent(for:in:) id-matched lookup plus an id-targeted appendNudge so rewind/stuck nudges land on the tool_result that actually triggered them
- Route main-loop task_complete through the completion gates (c021bf3)
- The four verification gates (goal criteria, auto-build, evidence, physical files) were unreachable from the main loop, which handles task_complete inline before dispatch
- Extracted into completionGateBlocker() and called from both paths; when blocked, the refusal is fed back as that tool_use's tool_result and the loop continues
- Fix false-positive stuck nudges on successful edits (77543c7)
- appendStuckFileNudgeIfNeeded searched the ENTIRE tool output for "error:"/"failed"/"not found", so editing any file whose source contains those words logged phantom failures
- Now checks only the first (status) line, matching FileTools.swift convention, and drops the bare "failed" substring
- Fix the same false-positive stuck nudge in the overnight coding guard (025deed)
- runOvernightCodingGuards had a byte-identical copy of the whole-output-scanning bug — and this is the live path that fired on every task
- Same fix: status-line-only check, no bare "failed" substring; stops phantom 2-failure recovery nudges and 4-failure "stop editing this file" stuck-outs
- Extract shared isToolFailure() helper (ac8ada4)
- The same 8-line failure-detection block existed in two places, both with the identical bug needing the identical fix — a divergence risk on every future tweak
- AgentViewModel.isToolFailure(output:) is now the single source of truth, called from both appendStuckFileNudgeIfNeeded and runOvernightCodingGuards
- StuckGuard: unify give-up threshold with Guards.swift (6d51583)
- The tab path gave up after 6 failures on a file while the main loop gave up at 4, despite a comment claiming the thresholds were unified
- Both now give up at 4
Goal State & Planning
- Feature #1: persistent goal state + self-verifying autonomy loop (57b5b1f)
- GoalStateStore: file-backed goal + verification criteria that survive restarts, with a prompt block injected into Claude/Ollama system prompts
- New goal_state tool (set/get/mark/clear) in all provider tool lists; task_complete bounces back while criteria remain unverified
- Wire goal state prompt block into all LLM providers (835f427)
- GoalStateStore.promptBlock was only injected for Claude/Ollama
- OpenAI-compatible and Codex providers now also see the active goal and its success criteria, so goal-driven execution works on every backend
- Require evidence when marking a goal criterion done (171b139)
- The gate only checked criteria were marked done — pure self-reporting; a criterion could be checked off with zero proof
- GoalCriterion gains an
evidencefield; goal_state(mark, done:true) rejects missing/blank evidence; task_complete blocks on unevidencedCriteria; 4 new tests
- Add PlanStateStore — surface active plan checklist in every system prompt (6cda83f)
- Injects the most recent non-stale plan's step status alongside the GoalStateStore/ToolOutcomeStore blocks
- Works across Claude, OpenAI-compatible, Ollama, and Codex prompt builders — the model sees plan progress each turn without calling plan_mode(read)
- Add self-verification pass + edit summary to task_complete (03a5dc1)
- Physical evidence check: every file snapshotted this task must still exist and be non-empty, else completion is blocked with a rewind hint
- task_complete output now appends taskEditSummary() so the blast radius of a task is always visible
Context & Compaction
- Scale the compaction threshold to the model's real context window (b97e336)
- CompactionState ignored its contextWindow argument and always used a hardcoded 30K threshold — far too late for a 4K Foundation Models session, far too early (cache-destroying) for 1M Claude
- Threshold is now ~55% of the window clamped to 2K–400K; the provider→window mapping moved into AgentViewModel.contextWindow(for:) so the token meter and compactor share one source of truth
- Always run structural compaction; only AI summarization respects the toggle (bcc2827)
- With "Token Compression" off there was no truncation path at all: the conversation grew append-only until the provider rejected it, and context-length recovery was a no-op
- Structural recovery now always runs; the toggle gates only Tier 1 Apple Intelligence summarization; adds a cheap chars/4 pre-check and demotes orphaned tool_result blocks so pruning can't cause an Anthropic 400
- Prompt-cache-stable context + stop_reason-driven loop control (8fda92f)
- Tier 1: messages are append-only between compaction events — the per-turn sliding window that rewrote the conversation middle (defeating prompt caching) is gone; the newest user message carries a cache_control breakpoint; PrefixStabilityTests locks in byte-identical prefixes
- Tier 2: routeStopReason routes turns on the API-reported stop_reason — malformed tool calls re-issue, max_tokens continues instead of completing, end_turn with open goal criteria gets a specific nudge
- Add ToolResultCache: disk spill for tool results before compaction (b258882)
- Compaction rewrites old tool_result blocks to a 3-line preview, so the agent's own earlier reads vanished mid-task and it re-read the same files
- Full text is written to .agent/toolcache/.txt before truncation so it stays recoverable
- Compression: spill tool results to disk before truncating (0ed2557)
- Both lossy sites (compressMessages 3-line preview, microcompact [cleared]) now write the full content to ToolResultCache first
- Compaction becomes recoverable rather than destructive
- Add restore_tool_result tool (04f42ee)
- Exposes the spill cache to the model: pass the tool_use_id from a truncated block to get the full text back, instead of re-reading a file
- Schema registered in ClaudeService.tools(), which also points ToolResultCache at the current project folder
- Compression: key summary cache on SHA-256 instead of hashValue (2bd7452)
- _summaryCache was keyed on content.hashValue — per-process seeded and collision-prone
- A collision served another tool result's summary as if it were this one; SHA-256 keys eliminate the silent wrong-context risk
LLM & Sub-agents
- Extended thinking for Claude + reasoning effort pass-through (0b39191)
- New persisted Reasoning setting (off/low/medium/high); ClaudeService maps it to an extended-thinking budget (2K/8K/16K), sends the interleaved-thinking beta flag, drops temperature, and floors max_tokens above the budget
- Streams thinking/redacted_thinking blocks (with signatures) so they pass back unmodified across tool turns; OpenAI-compatible providers get reasoning_effort opt-in; retry paths preserve thinking blocks and compaction never splits a message
- Sub-agent upgrades: model override, file-based results, tiered caps (f7bb1bb)
- spawn_agent accepts an optional model id from the active provider's list, so cheap/fast models can run search agents while the parent keeps its own — honored across Claude, LM Studio, and both Ollama paths
- Findings longer than the notification cap spill to {project}/.agent/subagents/.md with the path in the notification; write-capable agents stay capped at 3 concurrent, read-only research agents fan out to 6
- Tool-outcome learning: in-task advisories + chronic-failure flags (7753e40)
- ToolOutcomeStore tracks per-tool success/failure per task and persists per-project counts to {project}/.agent/tool_outcomes.json; after 3 same-tool failures in one task a one-shot advisory tells the LLM to change approach
- Tools with 5+ persisted failures and no successes are listed in a CHRONICALLY FAILING TOOLS system-prompt block, frozen at task start for prompt-cache stability; any success clears chronic status
Hooks, Rewind & Backups
- Wire post-tool hooks into tool dispatch (96b3857)
- runPostToolHooks was defined in HooksService but never called anywhere
- finishStep() now feeds each appended tool result through the postToolUse hooks and substitutes the transformed output, so hooks can react to / rewrite results without model tokens
- Fire taskStart and taskComplete event hooks (6a566af)
- runEventHooks was dead code
- The tab task loop now emits .taskStart at the beginning of a run and .taskComplete on task_complete, passing prompt/summary/tab/projectFolder as context
- Fire buildFailure event hook on failed xcode_build (d05e2ac)
- Completes the hook wiring: .buildFailure fires with projectPath, projectFolder and the tail of the build output
- Hooks can auto-react to a broken build without spending model tokens
- Route file edit backups through FileBackupService.snapshot (87a9ae5)
- snapshot() was dead code — the live write/edit paths called backup() directly, so per-task snapshot grouping never happened
- The three call sites (FileManager.swift write_file/edit_file, File.swift) now use snapshot(), giving task-scoped rewind real data
- Add task-scoped rewind on top of FileBackupService (2664587)
- snapshot() recorded per-task versions but nothing consumed them; a failed multi-file attempt could only be undone one edit at a time
- New FileBackupService.snapshottedFiles()/rewindTask() roll every file touched this task back to its version-1 snapshot as a unit; exposed as rewind_task / task_edits tool actions and file(action:"rewind"|"task_edits") aliases
- Offer task rewind after 3 consecutive build failures (022d0b2)
- rewind_task existed but only fired if the model chose to call it
- consecutiveBuildFailures was already tracked, so the rewind offer now hooks in at 3 failures — before the hard auto-stop at 5
Xcode & Tooling
- Xcode build: verify status before reporting success (c16b0a8)
- buildProject only polled
completed— a cancelled, failed, or errored build with zero collectible issues was reported as "Build succeeded" - Now requires status == .succeeded; adds an isXcodeRunning() guard so ScriptingBridge can't cold-launch Xcode mid-build, waits for workspace.loaded before build(), and includes status in the timeout message
- buildProject only polled
- Infer build target from path in xcode add_file (fdfded1)
- addFileToProject always inserted into the FIRST PBXSourcesBuildPhase (the app target), so files under AgentTests/ broke the build with "unable to resolve module dependency: 'Testing'"
- New sourcesPhaseID(for:in:) matches the file's parent directory against each PBXNativeTarget name and returns that target's Sources phase, falling back to first-phase for non-target-shaped paths
- Stop logging git/xcode tool output twice (d19a938)
- Tools shelling out through executeViaUserAgent streamed output live into the activity log, then dispatchTool logged the returned string a second time — every commit block appeared twice
- executeViaUserAgent records what it streamed in lastStreamedOutput and both logging sites skip the log when the return value matches
- ToolBatch: make read_file parallel and actually consume pre-executed results (d935d30)
- precomputedResults was write-only: the TaskGroup pre-executed read-only tools, then the dispatch loop re-ran every tool anyway — every parallel batch executed twice
- The read_file dedup guards are now nonisolated (lock-protected), so read_file joins the parallel group; multi-file reads — the agent's most common operation — run concurrently
- Tab tasks: run the same overnight-coding guard battery as the main loop (e436bda)
- runTabTask is called from 7 UI sites but ran exactly one guard, while the main loop runs the full runOvernightCodingGuards battery (build enforcement, edit-cycle detection, build-failure budget)
- processTabResponseContent now takes the three guard counters as inout params, calls the full battery per tool result, and ends the task cleanly when the error budget trips
Tests
- Add unit tests for the agentic core (cb8e401)
- GoalStateStoreTests: criteria trimming/filtering, the allCriteriaDone gate, empty-criteria semantics, case-insensitive lookup, persistence across instances, clear() reopening the gate, promptBlock rendering
- StuckGuardFingerprintTests: fingerprint determinism, key-order normalization, changed value/name/extra-key detection, and the repeatExemptTools membership contract
- Fixture-replay tests for the task loop's decision layer (919ffd0)
- Extracts turnDecision (pure completion detection) so it and routeStopReason are testable without UI or stores
- LoopReplayTests drives scripted fixtures through the real decision order: happy path, malformed-tool recovery, max_tokens truncation, premature end_turn against open criteria; also locks in that sanitizeMessagesForContinuation leaves no orphaned tool_use blocks
- Add regression tests for isToolFailure() (e36891c)
- Failure detection had zero coverage despite being pure string logic that was wrong twice — both times from scanning the whole output instead of the status line
- Pins that a successful edit echoing "error:"/"failed"/"not found" in its body is NOT a failure, real status-line failures still are, and bare "failed" is not a trigger
- Gate live-network web automation tests behind an env var (b21cc4b)
- WebAutomationTests drives real Safari against Google/LinkedIn/GitHub; results depend on network, login state and third-party markup, so the suite could never exit clean
- Opt in with AGENT_RUN_NETWORK_TESTS=1; default
xcodebuild testnow passes: 57 tests, 6 suites, 0 failures
- Make the test target compile so the suite can actually run (96dc0f1)
- DiffToolsTests.swift had 35 missing-
tryerrors on throwing helpers; AgenticCoreTests needed @mainactor for main-actor-isolated members - xcodebuild test now runs to completion: 26 tests across GoalStateStoreTests, StuckGuardFingerprintTests and DiffTools all pass
- DiffToolsTests.swift had 35 missing-
Full Changelog: v1.0.90.184...v1.0.92.186