Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,30 @@ new version heading in the same commit.

## [Unreleased]

## [0.447.0] - 2026-09-17
### Added
- **Focus check — a nudge when a session goes down a rabbit hole.** The progress verdicts could only see a
run's *shape* (`circling` = the same action repeated, `stuck` = silence), and a rabbit hole has neither:
it is busy, varied work on the wrong thing, so it read as `forward`. Now, once a run has taken ~20
actions (then every ~20 more, at most every 8 min), Haiku compares its recent work against the ask —
the original request as amended by every later human message, each paired with the agent proposal it
answered, so a bare "go" / "merge both" counts as approval. The judge runs out of band (the gate never
waits on it), and a `drifted` first vote is confirmed by two more (majority of 3, confidence ≥ 0.8)
before anything happens. A confirmed drift shows as a new **`drifting`** verdict on the session strip
(below `blocked`/`circling`, above `stuck`) and parks an advisory note on the agent's next tool call
(allow + `additionalContext`) suggesting it record the side-finding with `task_create` and return. If
the next check still says drifted, an unattended run's owner gets ONE Inbox card per streak;
interactive runs are never carded. Never blocks or stops anything. Backend: the workspace Anthropic key
(pinned to Haiku) when set, else a pooled `claude -p --model claude-haiku-4-5`. Modes in Settings →
Governance (`nudge` default · `observe` · `off`), per-agent opt-out `driftCheck: false`, process kill
switch `AOS_DRIFT=0`. Audited `drift.judged` (verdict, votes, outcome) / `drift.judge_failed` /
`drift.nudged` / `drift.escalated`. Tuned on a replay of 30 long live sessions (90 checkpoints): 2
genuine rabbit holes confirmed (one from the held-out half), 0 false positives at the shipped floor —
the replay also caught harness injections (`<task-notification>`, skill bodies) being read as human
messages, now filtered. Pinned by `scripts/drift-nudge-test.cjs`.
**For users:** when an agent wanders off what you asked for, its session now says **drifting** and the agent gets a gentle check-in to note the tangent and get back on track. Unattended runs that keep wandering send you one Inbox card.
**For admins:** the focus check can be set to Nudge, Observe only or Off in [Settings → Governance](#/settings/governance).

## [0.446.1] - 2026-09-17
### Fixed
- **The pause test raced the launcher and blocked the deploy.** `session-pause-test.cjs`'s HTTP section
Expand Down
77 changes: 77 additions & 0 deletions docs/drift-focus-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Focus check (drift nudge)

Code: `src/edge/drift.ts` · wiring: `TerminalManager.driftObserve` / `runDriftJudge` in `src/terminal.ts` ·
test: `scripts/drift-nudge-test.cjs`.

## Why

The progress verdicts (`src/state/session-progress.ts`) judge a run's shape: `circling` (the same action
repeated) and `stuck` (silence). A rabbit hole has neither: busy, varied, plausible work on the wrong
thing reads as `forward`. Catching it means comparing what the run is doing against what it was asked,
which takes a model.

## How

1. **Trigger (deterministic, free).** Count allowed top-level tool calls per session. First check at 20;
then every 20 more, at most once per 8 min. One judgement in flight per session; three consecutive
backend failures stop judging that session.
2. **Judge (Haiku, out of band).** Input: the original request, every later *human* message paired with
the agent message it replied to (head + tail, since proposals sit at the end), and the transcript tail.
Harness injections in the user role (`<task-notification>`, skill bodies, command plumbing) are not
human messages and are filtered (`isHumanText`). The model first states the `current_ask`, then a
verdict: `on_track` / `supporting` / `drifted`.
3. **Confirmation.** A `drifted` first vote (confidence ≥ 0.8) triggers two more votes in parallel; the run
counts as drifting only on a majority. The CLI lane has no temperature control and single votes
flipped on identical input in the replay.
4. **Delivery.**
- `detected` (first confirmed drift): an advisory note rides out on the agent's NEXT allowed tool call
via `additionalContext`. The copy is branded, says what it saw, allows "this is needed, carry on",
and suggests `task_create` for the side-finding.
- `escalated` (still drifting at the next check): one more note, plus ONE `sessionOwner` Inbox card, only
for an unattended, unclaimed run.
- `persisting`: record only. `cleared`: the streak resets.
- The strip shows `drifting` for 30 min after a drifted judgement unless a newer one clears it.
Precedence: `blocked` > `circling` > `drifting` > `stuck` > `forward`.

Never blocks, denies or stops anything.

## Controls

| Control | Where |
|---|---|
| Mode `nudge` (default) · `observe` · `off` | Settings → Governance (`settings: drift_nudge_mode`, owner/admin) |
| Per-agent opt-out | `driftCheck: false` in `agent.json` (for exploratory agents) |
| Process kill switch | `AOS_DRIFT=0` |
| Judge model | `AOS_DRIFT_MODEL` (default `claude-haiku-4-5`) |

Backend: the workspace Anthropic key when set (pinned to Haiku whatever the Q&A model is), else an
OpenAI-compatible router LLM, else a pooled `claude -p --model <haiku>`, the summarizer's lane
(`runClaudePrompt`).

## Audit

`drift.judged` {verdict, confidence, tangent, reason, outcome, mode, via, votes, ask} ·
`drift.judge_failed` {via, reason} · `drift.nudged` · `drift.escalated`. All four are NOISE for the
activity trail.

## Calibration (2026-09-17)

Replay over 30 long instapods sessions (≥145 gated actions each), 3 checkpoints per session, using the
production prompt and the CLI lane.

| Iteration | Change | Drifted / 42 (tuning set) |
|---|---|---|
| v1 | first prompt | 8, mostly false positives |
| v2 | filter harness injections; pair human replies with the proposal they answered | 3 |
| v3 | `current_ask` first; the fallout of the agent's own work counts as `supporting` | 2 |
| v4 | keep the tail of the approved proposal | 3 (single votes flicker run to run) |
| v5 | 2-of-3 confirmation, across 90 checkpoints (tuning + held-out) | 3 confirmed: 2 genuine, 1 FP at 0.75 |

The floor was then raised to 0.8, which removes that FP. The two genuine hits:
- An agent asked only questions about tooling shipped an instrumentation PR.
- An agent told "the PR is done, this needs a founder decision, not more engineering" re-implemented the
feature from scratch. This one came from the held-out set, 3/3 votes.

Cost: 102 model calls for 90 checks. CLI latency p50 ~14 s, which is irrelevant out of band.

Two hits is a small sample. Re-derive the floor from `drift.judged` rows after a tenant runs in `observe`.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "agent-os",
"version": "0.446.1",
"version": "0.447.0",
"description": "A generic, governed operating system for running autonomous agents safely across brands. Ships with a local web console.",
"license": "MIT",
"type": "commonjs",
Expand All @@ -27,7 +27,7 @@
"check-deps": "bash scripts/install-deps.sh --check",
"dev": "ts-node src/cli.ts serve",
"demo:dev": "ts-node src/demo.ts",
"test:governance": "node scripts/version-sync-test.cjs && node scripts/governance-conformance.cjs && node scripts/tier-a-policy-test.cjs && node scripts/policy-baseline-test.cjs && node scripts/heredoc-intent-test.cjs && node scripts/capability-registry-test.cjs && node scripts/composio-envelope-test.cjs && node scripts/composio-identity-test.cjs && node scripts/idle-reaper-test.cjs && node scripts/dm-continuity-test.cjs && node scripts/telegram-dm-lane-test.cjs && node scripts/cli-link-origin-test.cjs && node scripts/alert-staleness-test.cjs && node scripts/run-as-identity-test.cjs && node scripts/email-identity-guard-test.cjs && node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-usage-refresh-test.cjs && node scripts/keychain-credential-test.cjs && node scripts/credential-preflight-test.cjs && node scripts/runtime-login-test.cjs && node scripts/rotate-on-reload-test.cjs && node scripts/headless-resumable-test.cjs && node scripts/session-revive-gates-test.cjs && node scripts/session-pause-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs && node scripts/output-style-test.cjs && node scripts/session-cost-test.cjs && node scripts/chain-model-test.cjs && node scripts/task-workers-test.cjs && node scripts/tuning-patch-test.cjs && node scripts/task-runs-test.cjs && node scripts/task-pr-links-test.cjs && node scripts/task-draft-delete-test.cjs && node scripts/task-discussion-delivery-test.cjs && node scripts/task-resume-test.cjs && node scripts/task-unblock-test.cjs && node scripts/audience-session-access-test.cjs && node scripts/warm-chat-test.cjs && node scripts/poke-warm-caller-test.cjs && node scripts/wakeup-queue-test.cjs && node scripts/stranded-human-stop-test.cjs && node scripts/inject-submit-test.cjs && node scripts/blocked-routing-test.cjs && node scripts/self-dispatch-guard-test.cjs && node scripts/task-proposals-test.cjs && node scripts/npm-boundary-test.cjs && node scripts/agent-edit-guard-test.cjs && node scripts/per-agent-context-test.cjs && node scripts/goal-update-guard-test.cjs && node scripts/insights-signal-test.cjs && node scripts/outcome-derivation-test.cjs && node scripts/episode-quality-test.cjs && node scripts/memory-upkeep-test.cjs && node scripts/automem-health-test.cjs && node scripts/memory-store-switch-test.cjs && node scripts/memory-preload-test.cjs && node scripts/turn-lifecycle-test.cjs && node scripts/resume-seed-test.cjs && node scripts/outcome-vocabulary-test.cjs && node scripts/skill-presets-test.cjs && node scripts/skill-edit-proposal-test.cjs && node scripts/notify-hook-route-test.cjs && node scripts/review-notify-test.cjs && node scripts/turn-idle-background-guard-test.cjs && node scripts/waiting-brief-test.cjs && node scripts/runtime-death-alert-test.cjs && node scripts/github-per-member-test.cjs && node scripts/github-multi-org-test.cjs && node scripts/card-measurement-test.cjs && node scripts/scheduler-admission-test.cjs && node scripts/tick-liveness-test.cjs && node scripts/audit-mirror-test.cjs && node scripts/request-metrics-test.cjs && node scripts/tool-usage-test.cjs && node scripts/sessions-list-perf-test.cjs && node scripts/summarizer-degradation-test.cjs && node scripts/agent-history-scope-test.cjs && node scripts/webhook-ingress-test.cjs && node scripts/slack-content-filter-test.cjs && node scripts/slack-ingress-test.cjs && node scripts/discord-ingress-test.cjs && node scripts/chat-attachments-test.cjs && node scripts/clickup-task-bridge-test.cjs && node scripts/agentric-commands-test.cjs && node scripts/whats-new-test.cjs && node scripts/opencode-gate-test.cjs && node scripts/protected-path-guard-test.cjs && node scripts/attach-grace-test.cjs && node scripts/attach-file-liveness-test.cjs && node scripts/feed-smoke.cjs && node scripts/activity-classify-test.cjs && node scripts/goal-room-test.cjs && node scripts/secret-rotation-test.cjs && node scripts/update-watch-test.cjs && node scripts/runtime-update-watch-test.cjs && node scripts/setup-wizard-test.cjs && node scripts/md-pdf-test.cjs && node scripts/proposal-surfacing-test.cjs && node scripts/process-janitor-test.cjs && node scripts/detached-work-steer-test.cjs && node scripts/statusline-install-test.cjs && node scripts/docs-create-agent-test.cjs && node scripts/agent-stats-rollup-test.cjs && node scripts/task-discussion-rollup-test.cjs && node scripts/session-insights-stamp-test.cjs && node scripts/loop-stall-attribution-test.cjs && node scripts/session-progress-test.cjs && node scripts/goal-metric-review-test.cjs && node scripts/capability-gap-test.cjs && node scripts/workflow-proposal-test.cjs && node scripts/automation-edit-proposal-test.cjs",
"test:governance": "node scripts/version-sync-test.cjs && node scripts/governance-conformance.cjs && node scripts/tier-a-policy-test.cjs && node scripts/policy-baseline-test.cjs && node scripts/heredoc-intent-test.cjs && node scripts/capability-registry-test.cjs && node scripts/composio-envelope-test.cjs && node scripts/composio-identity-test.cjs && node scripts/idle-reaper-test.cjs && node scripts/dm-continuity-test.cjs && node scripts/telegram-dm-lane-test.cjs && node scripts/cli-link-origin-test.cjs && node scripts/alert-staleness-test.cjs && node scripts/run-as-identity-test.cjs && node scripts/email-identity-guard-test.cjs && node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-usage-refresh-test.cjs && node scripts/keychain-credential-test.cjs && node scripts/credential-preflight-test.cjs && node scripts/runtime-login-test.cjs && node scripts/rotate-on-reload-test.cjs && node scripts/headless-resumable-test.cjs && node scripts/session-revive-gates-test.cjs && node scripts/session-pause-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs && node scripts/output-style-test.cjs && node scripts/session-cost-test.cjs && node scripts/chain-model-test.cjs && node scripts/task-workers-test.cjs && node scripts/tuning-patch-test.cjs && node scripts/task-runs-test.cjs && node scripts/task-pr-links-test.cjs && node scripts/task-draft-delete-test.cjs && node scripts/task-discussion-delivery-test.cjs && node scripts/task-resume-test.cjs && node scripts/task-unblock-test.cjs && node scripts/audience-session-access-test.cjs && node scripts/warm-chat-test.cjs && node scripts/poke-warm-caller-test.cjs && node scripts/wakeup-queue-test.cjs && node scripts/stranded-human-stop-test.cjs && node scripts/inject-submit-test.cjs && node scripts/blocked-routing-test.cjs && node scripts/self-dispatch-guard-test.cjs && node scripts/task-proposals-test.cjs && node scripts/npm-boundary-test.cjs && node scripts/agent-edit-guard-test.cjs && node scripts/per-agent-context-test.cjs && node scripts/goal-update-guard-test.cjs && node scripts/insights-signal-test.cjs && node scripts/outcome-derivation-test.cjs && node scripts/episode-quality-test.cjs && node scripts/memory-upkeep-test.cjs && node scripts/automem-health-test.cjs && node scripts/memory-store-switch-test.cjs && node scripts/memory-preload-test.cjs && node scripts/turn-lifecycle-test.cjs && node scripts/resume-seed-test.cjs && node scripts/outcome-vocabulary-test.cjs && node scripts/skill-presets-test.cjs && node scripts/skill-edit-proposal-test.cjs && node scripts/notify-hook-route-test.cjs && node scripts/review-notify-test.cjs && node scripts/turn-idle-background-guard-test.cjs && node scripts/waiting-brief-test.cjs && node scripts/runtime-death-alert-test.cjs && node scripts/github-per-member-test.cjs && node scripts/github-multi-org-test.cjs && node scripts/card-measurement-test.cjs && node scripts/scheduler-admission-test.cjs && node scripts/tick-liveness-test.cjs && node scripts/audit-mirror-test.cjs && node scripts/request-metrics-test.cjs && node scripts/tool-usage-test.cjs && node scripts/sessions-list-perf-test.cjs && node scripts/summarizer-degradation-test.cjs && node scripts/agent-history-scope-test.cjs && node scripts/webhook-ingress-test.cjs && node scripts/slack-content-filter-test.cjs && node scripts/slack-ingress-test.cjs && node scripts/discord-ingress-test.cjs && node scripts/chat-attachments-test.cjs && node scripts/clickup-task-bridge-test.cjs && node scripts/agentric-commands-test.cjs && node scripts/whats-new-test.cjs && node scripts/opencode-gate-test.cjs && node scripts/protected-path-guard-test.cjs && node scripts/attach-grace-test.cjs && node scripts/attach-file-liveness-test.cjs && node scripts/feed-smoke.cjs && node scripts/activity-classify-test.cjs && node scripts/goal-room-test.cjs && node scripts/secret-rotation-test.cjs && node scripts/update-watch-test.cjs && node scripts/runtime-update-watch-test.cjs && node scripts/setup-wizard-test.cjs && node scripts/md-pdf-test.cjs && node scripts/proposal-surfacing-test.cjs && node scripts/process-janitor-test.cjs && node scripts/detached-work-steer-test.cjs && node scripts/statusline-install-test.cjs && node scripts/docs-create-agent-test.cjs && node scripts/agent-stats-rollup-test.cjs && node scripts/task-discussion-rollup-test.cjs && node scripts/session-insights-stamp-test.cjs && node scripts/loop-stall-attribution-test.cjs && node scripts/session-progress-test.cjs && node scripts/drift-nudge-test.cjs && node scripts/goal-metric-review-test.cjs && node scripts/capability-gap-test.cjs && node scripts/workflow-proposal-test.cjs && node scripts/automation-edit-proposal-test.cjs",
"test:alert-staleness": "node scripts/alert-staleness-test.cjs",
"test:deps": "node scripts/deps-freshness-test.cjs && node scripts/runtime-account-test.cjs && node scripts/runtime-account-misattribution-test.cjs && node scripts/runtime-login-test.cjs && node scripts/claude-config-seed-test.cjs && node scripts/claude-config-isolation-test.cjs",
"test:dm-continuity": "node scripts/dm-continuity-test.cjs",
Expand Down
Loading
Loading