Skip to content
Open
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
1 change: 1 addition & 0 deletions docs/keybindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ app.history.search: []
| `app.clipboard.copyPrompt` | `Alt+Shift+C` | Copy the whole prompt |
| `app.clipboard.pasteImage` | `Ctrl+V` (`Alt+V` fallback on Windows) | Paste from the clipboard (image preferred, text fallback) |
| `app.stt.toggle` | Unbound (hold `Space`) | Toggle speech-to-text. By default there is no key chord — hold the space bar to record (push-to-talk) and release to transcribe; bind a chord here for a press-to-toggle alternative |
| `tui.input.newLine` | `Shift+Enter`, `Ctrl+J` | Insert a newline without submitting |

On Windows Terminal, `Ctrl+V` may be handled by the terminal paste command before `omp` sees it; use the `Alt+V` fallback when clipboard image paste appears to do nothing. When the clipboard holds no image, `app.clipboard.pasteImage` pastes the clipboard text instead, so hosts that deliver only this chord (VS Code's integrated terminal when configured to forward `Ctrl+V`, Windows clipboard history via `Win+V`) work for both payload kinds. Windows Terminal also swallows `Ctrl+Enter`, so the follow-up shortcut also binds `Ctrl+Q` — the same chord GitHub Copilot CLI uses. If your existing `keybindings.yml` already assigns `Ctrl+Q` to another action, that user remap wins and follow-up keeps `Ctrl+Enter` unless you explicitly bind `app.message.followUp`.

Expand Down
16 changes: 16 additions & 0 deletions docs/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ A value of `-1` means "use the provider/model default" — `omp` does not send t
| `presencePenalty` | number | `-1` | Presence penalty. |
| `repetitionPenalty` | number | `-1` | Repetition penalty. |
| `serviceTier` | enum | `none` | `none`, `auto`, `default`, `flex`, `scale`, `priority`, `openai-only`, `claude-only`. |
| `fastModeScope` | enum | `both` | `both`, `openai`, `claude`. Which providers `/fast on` and the fast-mode toggle target; `openai`/`claude` scope priority to one provider family. |
| `personality` | enum | `default` | `default`, `friendly`, `pragmatic`, `none`. |

### Retry and fallback
Expand Down Expand Up @@ -398,6 +399,21 @@ tools:

Individual built-in tools are toggled by their own keys, e.g. `bash.enabled`, `eval.py`, `eval.js`, `find.enabled`, `search.enabled`, `fetch.enabled`, `browser.enabled`, `astEdit.enabled`, `astGrep.enabled`, `web_search.enabled`, `inspect_image.enabled`, `renderMermaid.enabled`.

### Tasks and todos

```yaml
task:
eager: default # default, preferred, always

todo:
eager: default # default, preferred, always
```

| Key | Type | Default | Notes |
|---|---|---|---|
| `task.eager` | enum | `default` | `default`, `preferred`, `always`. How strongly to push delegating work to subagents. |
| `todo.eager` | enum | `default` | `default`, `preferred`, `always`. How strongly to push automatic todo-list creation after the first message. |

### Shell, eval, and LSP

```yaml
Expand Down
6 changes: 3 additions & 3 deletions docs/tools/irc.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
## Outputs
- Single-shot `AgentToolResult`; no streaming updates.
- `content` is one text block:
- `list`: `No other agents.` or `<n> peer(s):` bullets — `id [displayName · kind · status]` plus unread count, parent, and last-activity age; a footer notes that parked agents are revived automatically when messaged.
- `list`: `No other agents.` or `<n> peer(s):` bullets — `id [displayName · kind · status]` plus current activity (the peer's task), unread count, parent, and last-activity age; a footer notes that parked agents are revived automatically when messaged.
- `send`: per-recipient delivery receipts (`injected` / `woken` / `revived` / `failed — <error>`); with `await: true`, the reply body or a clean no-reply timeout note.
- `wait`: the consumed message as `[<msgId>] <from>: <body>` (with a reply-to tag), or `No message within <duration>.`
- `inbox`: `Inbox empty.` or `<n> message(s):` bullets.
Expand All @@ -40,7 +40,7 @@
## Flow
1. `IrcTool.createIf` constructs the tool only when `isIrcEnabled` passes and the session has both an `AgentRegistry` and `getAgentId`. There is no `irc.enabled` setting: availability is derived — true for every subagent (`taskDepth > 0`; a parent always exists) and for any session that can still spawn subagents through the task tool. Only a top-level session with task spawning unavailable has no peers, hence no irc.
2. `execute` resolves the registry and sender id; missing either returns a text error result instead of throwing.
3. `op: "list"`: `registry.list()` minus self and minus `aborted` agents — `parked` peers ARE listed. Each row includes the unread count from `IrcBus.unreadCount(...)` and last activity.
3. `op: "list"`: `registry.list()` minus self and minus `aborted` agents — `parked` peers ARE listed. Each row includes the peer activity (`ref.activity`, its current task), unread count from `IrcBus.unreadCount(...)`, and last activity.
4. `op: "send"` validates `to`/`message`, rejects self-sends, and rejects `await` with `to: "all"`.
5. Target resolution: broadcasts fan out to `registry.listVisibleTo(senderId)` (live peers only — `running`/`idle`; reviving every parked agent on a broadcast would be a stampede). Direct sends go through the bus unfiltered, so a parked recipient is revived.
6. `IrcBus.send(...)` is fire-and-forget — it never blocks on the recipient generating anything. Delivery by recipient status:
Expand All @@ -54,7 +54,7 @@
10. Timeouts resolve as `params.timeoutMs ?? irc.timeoutMs`, normalized: `0` disables the timeout, negative/non-finite values fall back to the default `120_000`, positive values are truncated and clamped to ≥ 1 ms.

## Modes / Variants
- `list`: enumerate peers with status (`running`/`idle`/`parked`), unread counts, and last activity.
- `list`: enumerate peers with status (`running`/`idle`/`parked`), current activity, unread counts, and last activity.
- `send` direct: one exact peer id; wakes idle peers, revives parked ones.
- `send` broadcast: `to: "all"` to every live peer; parked peers are skipped.
- `send` + `await: true`: round-trip convenience — send, then wait for the next message from that peer. Marks the send `expectsReply`, enabling the busy-recipient auto-reply path when async execution is disabled.
Expand Down
102 changes: 102 additions & 0 deletions docs/tools/learn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# learn

> Capture a reusable lesson into long-term memory, and optionally create or update a managed skill in the same call.

## Source
- Entry: `packages/coding-agent/src/tools/learn.ts`
- Model-facing prompt: `packages/coding-agent/src/prompts/tools/learn.md`
- Managed-skill collaborators:
- `packages/coding-agent/src/autolearn/managed-skills.ts` — validates names, writes managed `SKILL.md` files, enforces size and symlink safety.
- `packages/coding-agent/src/extensibility/skills.ts` — detects authored skills that would shadow a managed skill name.
- Memory collaborators:
- `packages/coding-agent/src/mnemopi/state.ts` — scoped Mnemopi retention path.
- `packages/coding-agent/src/memory-backend/local-backend.ts` — local file-backed lesson storage.
- `packages/coding-agent/src/hindsight/state.ts` — Hindsight queued retention path.

## Inputs

| Field | Type | Required | Description |
|---|---|---:|---|
| `memory` | `string` | Yes | Durable, self-contained lesson to remember. Include what, when, and why so a future session understands it without this conversation. |
| `context` | `string` | No | Optional source context for the lesson. Stored with the lesson when the active backend supports it. |
| `skill` | `object` | No | Also create or enhance a managed skill in the same call. Use only when the lesson is a repeatable procedure worth codifying. |
| `skill.action` | `"create" \| "update"` | Yes, when `skill` is present | Create a new managed skill or update an existing one. |
| `skill.name` | `string` | Yes, when `skill` is present | Kebab-case skill name. It is normalized to lowercase and must use only lowercase letters, digits, and hyphens, 1-64 chars, starting with a letter or digit. |
| `skill.description` | `string` | Yes, when `skill` is present | One-line description of when to use the skill. It drives discovery, so make it specific. |
| `skill.body` | `string` | Yes, when `skill` is present | `SKILL.md` body in Markdown. Do not include frontmatter; generated frontmatter uses `skill.name` and `skill.description`. |

## Outputs
Returns a single-shot tool result.

When only the lesson is captured:
- `content[0].type = "text"`
- `content[0].text = "Lesson stored."` for Mnemopi or local memory.
- `content[0].text = "Lesson queued for retention."` for Hindsight.
- `details = { skill: null }`

When a managed skill is also written:
- `content[0].text = "Lesson stored. Created managed skill \"<name>\"."` or `"Lesson stored. Updated managed skill \"<name>\"."`
- Hindsight uses `"Lesson queued for retention. ..."` instead of `"Lesson stored. ..."`.
- `details = { skill: "<name>" }`

When `skill.action = "create"` targets a name already claimed by a user-authored skill:
- the lesson has already been stored or queued;
- `content[0].text` explains that the managed skill was not created because managed skills cannot override authored skills;
- `isError = true`;
- `details = { skill: null, shadowed: true }`.

## Flow
1. `LearnTool.createIf(...)` exposes the tool only when `autolearn.enabled` is true and `memory.backend` is one of `"hindsight"`, `"mnemopi"`, or `"local"`.
2. `execute(...)` reads the active memory backend.
3. If the backend is `mnemopi`:
- it reads `session.getMnemopiSessionState()` and throws if the backend was not started;
- it calls `state.rememberScoped(...)` with source `coding-agent-learn`, importance `0.8`, bank scope, extraction enabled, `veracity = "tool"`, and `memoryType = "fact"`;
- it throws if Mnemopi does not return a memory id.
4. If the backend is `local`:
- it calls `localBackend.save(...)` with source `coding-agent-learn`, importance `0.8`, the current agent directory, and cwd;
- it throws if the sanitized lesson stores zero entries.
5. If the backend is `hindsight`:
- it reads `session.getHindsightSessionState()` and throws if the backend was not started;
- it calls `state.enqueueRetain(memory, context)` and reports the lesson as queued.
6. If `skill` is present:
- a `create` request first checks whether an authored skill already claims the normalized name;
- if the name is not shadowed, it writes the managed skill through `writeManagedSkill(...)`;
- write failures are surfaced after the memory operation, so the error message states that the lesson was stored or queued but the managed skill could not be written.

## Modes / Variants
- Memory-only mode records a reusable lesson without changing managed skills.
- Memory-plus-skill mode records the lesson and creates or updates an isolated managed skill.
- Managed skills are stored under `~/.omp/agent/managed-skills` and surface like normal skills in future sessions.
- Managed skills never touch user-authored skills and cannot override a user-authored skill with the same name.
- Capture sparingly and specifically: one strong reusable lesson is preferred over several vague lessons.

## Side Effects
- Memory
- Mnemopi: stores a scoped fact in the configured Mnemopi bank.
- Local: appends to the local backend's learned-memory store.
- Hindsight: queues a retention request for the backend.
- Filesystem
- When `skill` is present, creates or overwrites `~/.omp/agent/managed-skills/<name>/SKILL.md`.
- Approval
- Uses write approval when `skill` is present or when `memory.backend = "local"`; otherwise uses read approval.

## Limits & Caps
- Tool availability is gated by `autolearn.enabled`.
- Tool availability also requires `memory.backend` to be `"hindsight"`, `"mnemopi"`, or `"local"`.
- Managed skill names must match lowercase kebab-case: lowercase letters, digits, and hyphens, 1-64 chars, starting with a letter or digit.
- Managed skill content is capped at 64,000 UTF-8 bytes for the generated frontmatter plus body.
- `skill.action` supports only `"create"` and `"update"`; deletion uses `manage_skill`.

## Errors
- Throws `Mnemopi backend is not initialised for this session.` when `memory.backend == "mnemopi"` but no Mnemopi state exists.
- Throws `Mnemopi did not store the lesson (no memory id returned).` when scoped Mnemopi retention returns no id.
- Throws `Lesson was empty after sanitization; nothing stored.` when local storage rejects the sanitized lesson.
- Throws `Hindsight backend is not initialised for this session.` when `memory.backend == "hindsight"` but no Hindsight state exists.
- Returns an error result, after storing or queueing the lesson, when creating a managed skill whose name is already claimed by an authored skill.
- Throws `Lesson stored, but the managed skill could not be written: <reason>` or `Lesson queued for retention, but the managed skill could not be written: <reason>` when managed-skill writing fails.
- Managed-skill write failures include invalid names, empty descriptions, empty bodies, existing skills on `create`, missing skills on `update`, symlink safety failures, and the 64,000-byte size cap.

## Notes
- Use `learn` after solving something whose insight will pay off again: a non-obvious fix, a project convention, or a workflow that worked.
- Use the optional `skill` object only for repeatable procedures worth codifying as a skill, not for ordinary facts.
- The managed-skill `body` is the Markdown content below frontmatter; frontmatter is generated automatically from `name` and `description`.
96 changes: 96 additions & 0 deletions docs/tools/manage-skill.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# manage_skill

> Create, update, or delete an isolated managed skill for the auto-learn feature.

## Source
- Entry: `packages/coding-agent/src/tools/manage-skill.ts`
- Model-facing prompt: `packages/coding-agent/src/prompts/tools/manage-skill.md`
- Managed-skill collaborators:
- `packages/coding-agent/src/autolearn/managed-skills.ts` — validates names, writes and deletes managed `SKILL.md` files, enforces size and symlink safety.
- `packages/coding-agent/src/extensibility/skills.ts` — detects authored skills that would shadow a managed skill name.

## Inputs

| Field | Type | Required | Description |
|---|---|---:|---|
| `action` | `"create" \| "update" \| "delete"` | Yes | Operation to perform. `create` fails if the managed skill already exists; `update` fails if it does not exist; `delete` removes an existing managed skill. |
| `name` | `string` | Yes | Kebab-case skill name. It is normalized to lowercase and must use only lowercase letters, digits, and hyphens, 1-64 chars, starting with a letter or digit. |
| `description` | `string` | Required for `create` and `update` | One-line description of when to use the skill. It drives discovery, so make it specific. Not used for `delete`. |
| `body` | `string` | Required for `create` and `update` | `SKILL.md` body in Markdown. Do not include frontmatter; generated frontmatter uses `name` and `description`. Not used for `delete`. |

## Outputs
Returns a single-shot tool result.

When a skill is created:
- `content[0].type = "text"`
- `content[0].text = "Created managed skill \"<name>\" (managed-skills/<name>/SKILL.md)."`
- `details = { action: "create", name: "<name>" }`

When a skill is updated:
- `content[0].text = "Updated managed skill \"<name>\" (managed-skills/<name>/SKILL.md)."`
- `details = { action: "update", name: "<name>" }`

When a skill is deleted:
- `content[0].text = "Deleted managed skill \"<name>\"."`
- `details = { action: "delete", name: "<name>" }`

When `action = "create"` targets a name already claimed by a user-authored skill:
- `content[0].text` explains that managed skills cannot override authored skills;
- `isError = true`;
- `details = { action: "create", name: "<name>", shadowed: true }`.

## Flow
1. `ManageSkillTool.createIf(...)` exposes the tool only when `autolearn.enabled` is true.
2. The tool is backend-independent: it does not require an active memory backend.
3. For `action = "delete"`:
- `execute(...)` calls `deleteManagedSkill(name)`;
- the helper normalizes and validates the name, checks the managed-skills root, refuses symlinked skill directories, and removes `~/.omp/agent/managed-skills/<name>`.
4. For `action = "create"` or `"update"`:
- schema refinement requires both `description` and `body`;
- `create` checks whether an authored skill already claims the normalized name and returns an error result if so;
- `writeManagedSkill(...)` normalizes the name, sanitizes the description, trims the body, generates frontmatter, checks the byte cap, and writes `~/.omp/agent/managed-skills/<name>/SKILL.md`.
5. `create` uses atomic file creation and fails if the managed skill already exists.
6. `update` requires an existing plain managed `SKILL.md` and refuses symlinks or unsafe hard-linked files before overwriting.

## Modes / Variants
- `create` adds a new managed skill and fails if it already exists.
- `update` overwrites the generated frontmatter and body for an existing managed skill.
- `delete` removes an existing managed skill directory.
- Managed skills are stored under `~/.omp/agent/managed-skills` and surface like normal skills in future sessions.
- Managed skills are for repeatable procedures worth codifying: setup sequences, debugging recipes, and project-specific workflows.
- Managed skills never edit user-authored skills and cannot override a user-authored skill with the same name.

## Side Effects
- Filesystem
- `create` writes `~/.omp/agent/managed-skills/<name>/SKILL.md`.
- `update` overwrites `~/.omp/agent/managed-skills/<name>/SKILL.md`.
- `delete` removes `~/.omp/agent/managed-skills/<name>`.
- Approval
- Always uses write approval.
- Memory
- None. Use `learn` when a lesson should also be stored in long-term memory.

## Limits & Caps
- Tool availability is gated by `autolearn.enabled`.
- Managed skill names must match lowercase kebab-case: lowercase letters, digits, and hyphens, 1-64 chars, starting with a letter or digit.
- `description` must sanitize to a non-empty single line for `create` and `update`.
- `body` must be non-empty after trimming for `create` and `update`.
- Managed skill content is capped at 64,000 UTF-8 bytes for the generated frontmatter plus body.
- The generated file is always named `SKILL.md`; callers provide only the body, not frontmatter.

## Errors
- Schema validation rejects `create` and `update` calls that omit `description` or `body` with `"create" and "update" require both "description" and "body".`
- Throws `Invalid skill name "<raw>". Use lowercase letters, digits, and hyphens (1-64 chars, starting with a letter or digit).` for invalid names.
- Returns an error result when creating a managed skill whose name is already claimed by an authored skill.
- Throws `Managed skill "<name>" needs a non-empty description.` when the sanitized description is empty.
- Throws `Managed skill "<name>" needs a non-empty body.` when the trimmed body is empty.
- Throws `Managed skill is <bytes> bytes; the limit is 64000. Trim the body or description.` when the generated file exceeds the cap.
- Throws `Managed skill "<name>" already exists. Use action "update" to change it.` on duplicate `create`.
- Throws `Managed skill "<name>" does not exist. Use action "create" to add it.` on missing `update`.
- Throws `Managed skill "<name>" does not exist.` on missing `delete`.
- Throws symlink or hard-link safety errors when the managed-skills root, skill directory, or `SKILL.md` is unsafe to mutate.

## Notes
- `name` is the discovery key; choose a specific kebab-case name that will not collide with authored skills.
- `description` is discovery-facing. It should state when to use the skill, not merely what the skill is called.
- `body` should contain the reusable procedure in Markdown and must not include frontmatter.
Loading
Loading