With `autolearn.autoContinue` on, the controller fires a synthetic
turn whose only user-role payload is `autolearn-nudge.md`. The old
prompt opened with "Before you finish:" and gave no terminal
contract, so after the `learn`/`manage_skill` call the agent read
its own unanswered prior question (e.g. "Want me to commit and
push?") as accepted and continued — pushing commits, running tools,
etc. — without the user ever answering.
Split the nudge into two prompts and pick at fire time:
- passive (rides the user's real next message) keeps additive
framing — "answer the user normally; the capture is in addition
to, not a replacement for, the work the user just asked for".
- auto-continue (`autolearn-nudge-autocontinue.md`) is explicitly
terminal — "not a user reply; do not treat this as approval or
acceptance of any pending action; capture, then stop; do not run
other tools, resume prior work, or answer your own pending
questions; wait for the user's next prompt".
Attribution stays `user` so llama.cpp keeps reusing the warm
prefix (#3456). Regression tests pin the load-bearing terminal
language in both branches.
Fixes#3504
- agent-loop: raise repetition-detection floor to 180 chars and clear thinking
replay anchors when collapsing a detected loop.
- providers/google: ignore empty text parts, retain terminal thoughtSignatures,
and stop function-call signatures clobbering the prior block.
- autolearn: capture goal-mode at the turn boundary; harden managed-skill writes
against hard-links/symlinks (O_NOFOLLOW + nlink); refuse minting managed skills
whose name an authored skill already claims.
- eager tasks: thread agentKind through the session so a custom top-level agentId
still gets always-mode delegation; split Eager Tasks prompt into hard vs soft.
- title-generator: race the online title model against a local tiny-model fallback.
- eager-todo: keep the soft reminder aligned with the todo init schema.
- mcp/stdio: keep close() detaching the read loop instead of awaiting it.
- stream loop: fix collapsing and tool-call thought-signature handling.
Grill findings on the auto-learn change-set — the controller nudge, the standing
guidance, and the actual tool availability could disagree:
- Guidance was rebuilt from live `autolearn.enabled`, so a mid-session enable (or
a subagent that filtered the tools out) injected guidance for tools the session
never built. `buildAutoLearnInstructions` now takes `{ manageSkill, learn }` and
is driven by the auto-learn BUILTINS that `createTools` actually built
(`builtInToolNames`) — provenance, so a same-named custom/extension tool can't
trigger it while auto-learn is off.
- The controller install reverted to gate on `autolearn.enabled && taskDepth === 0`:
the tool registry is built once at session start, so installing it while disabled
would nudge toward absent tools. The fire-time re-check still handles a
mid-session disable.
- Force-included auto-learn tools are now ACTIVATED for restricted top-level
sessions (mirroring the `yield` invariant), so a session with an explicit tool
whitelist actually exposes manage_skill/learn instead of building them inactive.
Tests: guidance gating by tool presence (none/manage-only/learn), restricted-session
activation via createAgentSession, and removal of the obsolete mid-session-enable
controller case.
The controller was only constructed when `autolearn.enabled` was true at
`createAgentSession` time. Because `newSession` reuses the session without
rerunning startup, enabling the setting via the UI mid-session never installed
the listener, so the feature stayed inert until the app was recreated. The
controller already re-checks the live flag at fire time, so install it for every
top-level session and let that check gate activation; when disabled it only
counts tool calls and returns.
Addresses review thread on PR #2542 (thread 18).
Two controller bugs from review:
- The post-stop nudge still queued a passive `nextTurn` message during goal
mode (goal mode only disabled `autoContinue`). That message rides the goal
continuation and can divert the goal loop into capture. Return early when
goal mode is active.
- `#suppressNext` was latched before the fire-and-forget `sendCustomMessage`.
It must arm synchronously (the synthetic turn's `agent_end` fires inside
`sendCustomMessage` before it resolves), but a rejected or *deferred* dispatch
(ACP clients downgrade `triggerTurn` to a queue) then produces no `agent_end`,
so the latch swallowed the next real stop. `sendCustomMessage` now returns
whether it actually started a turn; the controller disarms the latch when no
turn ran (rejection or deferral).
`task/executor.ts` widens its pending-message array to `Promise<unknown>[]` to
absorb the new return type (the resolved values are discarded).
Addresses review threads on PR #2542 (threads 5, 9, 12).
The `learn` tool previously required a `hindsight`/`mnemopi` backend. It now
also works when `memory.backend` is `local` (the file-based rollout backend):
lessons append to a `learned.md` under the project's memory root, kept separate
from the consolidation artifacts so a consolidation pass never clobbers them,
and are injected into future sessions alongside the memory summary.
- memories: `saveLearnedLesson` (newest-first, deduped, count- and per-field
size-capped, secret-redacted, injection-neutralized) with per-path write
serialization; `buildMemoryToolDeveloperInstructions` reads `learned.md` and
shares one injection budget with the summary; `redactSecrets` extended with
GitHub/npm/Slack/Google token prefixes.
- local backend: implements `save()`; status reports `writable: true`.
- learn tool: `local` execute branch; `createIf`/`isToolAllowed`/auto-include
and the standing guidance extended to `local`; local saves tier as a `write`
approval.
- read-path prompt: renders the learned-lessons block when present.
- Lessons are injection-neutralized and secret-redacted on BOTH write and read
(they render unescaped into the system prompt).
Also moves the auto-learn CHANGELOG entry out of the released [15.12.6] section
(a cherry-pick artifact) back under [Unreleased] and notes the local backend.
Tests: local storage (format, dedup, cap, redaction incl. provider/delimiter-
split tokens, concurrency), read-back (with/without summary, off-gating, raw
hand-edited file), tool gating + write-approval tiering.
Add a default-off "auto-learn" loop. When `autolearn.enabled` is set, after the
agent stops a session controller nudges it to capture reusable lessons: durable
facts go to long-term memory and repeatable procedures become "managed skills" —
SKILL.md files written to an isolated ~/.omp/agent/managed-skills directory that is
discovered and surfaced like authored skills but never overwrites them.
Two tools back this:
- `manage_skill` — create/update/delete managed skills.
- `learn` — record a lesson, optionally minting/enhancing a managed skill in the
same call (requires a hindsight/mnemopi memory backend).
The nudge is passive by default (a hidden reminder rides the next turn);
`autolearn.autoContinue` instead auto-runs one capture turn at stop, and
`autolearn.minToolCalls` (default 5) gates trivial turns. Plan/goal-mode turns and
subagents are never nudged, and the controller re-checks the live setting at fire
time so a mid-session opt-out takes effect.
Isolation & precedence: managed skills are a separate lowest-priority discovery
provider, so an authored skill of the same name wins across every provider and
custom directory regardless of third-party toggles; a disabled higher-priority
authored skill can never hide a managed one, and managed never masks an enabled
authored skill. Managed names and descriptions are sanitized on both write and
read (control/format chars, angle brackets, and Markdown fences) before they render
into the system prompt, and the SKILL.md byte cap is enforced on the final
serialized file.
Default off → zero footprint when disabled.