OMP Workbook

Read the source. Follow the evidence.

Seed Desk: welcome and inventory

Mara’s first goal is modest: stop repeating the fictional seed desk’s opening hours. Her obstacle is that a friendly human command alone would leave the agent without a supported way to retrieve the same information.

She chooses two entrances to one small domain: a human command and a read-only tool.

Milestone: a welcoming desk for both readers

Starting state: no reservation state, no inventory mutation and no external service.

Files:

Terminal shell—launch stage 1:

Seed Desk: welcome and inventory · source excerpt 1; read surrounding instructions
omp --no-extensions --no-skills -e "$EXAMPLES/seed-desk/01-welcome/index.ts"

Human OMP slash commands—enter in the composer:

Seed Desk: welcome and inventory · source excerpt 2; read surrounding instructions
/seeds welcome
/seeds hours

The hours response is:

Expected local output:

Seed Desk: welcome and inventory · source excerpt 3; read surrounding instructions
Our fictional desk opens Saturday, 10:00-12:00. No booking has been made.

The machine interface is seed_welcome.

Model tool arguments—call seed_welcome:

Seed Desk: welcome and inventory · source excerpt 4; read surrounding instructions
{"op":"discover"}

Model tool arguments—call seed_welcome:

Seed Desk: welcome and inventory · source excerpt 5; read surrounding instructions
{"op":"inspect","topic":"hours"}

The first operation advertises discover and inspect, the topics, scope and quiet flag in structured details. The second returns the same hours text used by the slash command.

Important distinction: details is useful to SDK callers, hosts and renderers. It is not automatically model-visible. This stage’s ordinary text content tells the model which topics it can inspect; the quiet flag is in details, not duplicated into that text.

What the code changes

The async factory reads its description from a real adjacent file. It registers:

  • seed_welcome, with approval: "read" and loadMode: "essential";
  • the boolean flag seed-quiet;
  • a session_start notification;
  • /seeds, including argument completion.

It does not register a reservation operation.

Exact excerpt—completion behavior in stage 1’s entry; not a standalone replacement file:

Seed Desk: welcome and inventory · source excerpt 6; read surrounding instructions
getArgumentCompletions(prefix) {
    const matches = choices.filter(value => value.startsWith(prefix.trimStart()));
    // A completed sole match MUST release Enter to submit the command.
    if (matches.length === 1 && matches[0] === prefix.trim()) return null;
    return matches.length ? matches.map(value => ({ value: `${value} `, label: value })) : null;
},

Mara types /seeds wel. The completion offers the full argument text welcome .

When welcome is already the sole exact match, returning null lets Enter submit rather than continually reaccepting the completion. A completion-array equality test is helpful, but it is not a terminal-key-dispatch test.

Inspect progress

Observed:

  • wel produced welcome .
  • welcome and welcome produced no completion.
  • The tool’s greeting matched the headless command’s greeting.
  • Quiet startup produced zero startup notices.
  • A headless command produced one custom message.

The supplied Seed Desk proof simulated confirmation/notification UI. It did not exercise actual terminal autocomplete and Enter behavior.

Quiet is a startup choice, not a permission

Exit the stage and restart it with the flag.

Terminal shell—suppress the startup notice:

Seed Desk: welcome and inventory · source excerpt 7; read surrounding instructions
omp --no-extensions --no-skills -e "$EXAMPLES/seed-desk/01-welcome/index.ts" --seed-quiet

This suppresses only the startup notification. Commands and tools still work.

Register flag names without the leading --; use the leading -- in the shell. In this parser, a boolean flag’s presence means true. Do not assume --seed-quiet=false means false; omit the flag to use its false default.

Failure and mode boundaries

  • An inspect request without a topic throws an error.
  • An unknown human verb produces guidance and changes nothing.
  • The tool checks its AbortSignal before doing work.
  • With UI, the command uses notify().
  • Without UI, it uses sendMessage(..., { triggerTurn: false }), so the answer is not lost in an inert notification.
  • Reloading code is not accomplished by directly running the TypeScript file. Restart the explicit launch when testing an edit.

Exercise: ask the agent to reserve basil through seed_welcome.

Checkpoint: it should report that no stock-changing operation exists, not invent reserve or attempt to invoke /seeds.

Milestone: the desk gains a real query surface

The next Saturday, Mara’s fictional desk has three seed varieties. Repeating a prose list has become awkward: the agent needs stable IDs, family filters and a clear error for an invented ID.

She replaces stage 1 with stage 2. These stages are independent, not cumulative imports.

Files:

Terminal shell—after exiting stage 1:

Seed Desk: welcome and inventory · source excerpt 8; read surrounding instructions
omp --no-extensions --no-skills -e "$EXAMPLES/seed-desk/02-catalog/index.ts"

Human OMP slash commands:

Seed Desk: welcome and inventory · source excerpt 9; read surrounding instructions
/seeds query
/seeds query herb
/seeds inspect basil-genovese

Model tool arguments—call seed_catalog:

Seed Desk: welcome and inventory · source excerpt 10; read surrounding instructions
{"op":"query","family":"herb"}

Model tool arguments—call seed_catalog:

Seed Desk: welcome and inventory · source excerpt 11; read surrounding instructions
{"op":"inspect","id":"basil-genovese"}

Stage 2 supports query and inspect only. It does not have a discover operation merely because stage 1 had one.

One shared domain, two entrances

This is the complete small helper used by both interfaces.

TypeScript source—stage 2’s catalog.ts:

Seed Desk: welcome and inventory · source excerpt 12; read surrounding instructions
import fixture from "./inventory.json";

export interface Seed {
    id: string;
    name: string;
    family: string;
    packets: number;
}

export const inventory: readonly Seed[] = fixture;

export function inspect(id: string): Seed {
    const seed = inventory.find(item => item.id === id);
    if (!seed) throw new Error("Unknown seed ID. Query the catalog for valid IDs. Nothing changed.");
    return seed;
}

export function query(family?: string): readonly Seed[] {
    return family ? inventory.filter(seed => seed.family === family) : inventory;
}

export function summary(seeds: readonly Seed[]): string {
    return seeds.length
        ? seeds.map(seed => `${seed.id}: ${seed.name}, ${seed.packets} fictional packets (${seed.family})`).join("\n")
        : "No fictional seeds match that family.";
}

Expected output for the herb query:

Seed Desk: welcome and inventory · source excerpt 13; read surrounding instructions
basil-genovese: Genovese basil, 8 fictional packets (herb)

The tool returns that summary in content, and records in:

  • details.scope: "fictional-fixture"
  • details.seeds

This is better than asking the agent to scrape a notification. The tool gives it stable targets, while the host gets structured records.

Inspect progress and failure

Observed: the real loader, runner and intercepted tool adapter returned only basil-genovese for the herb query. Slash/tool summaries matched. Unknown inspection IDs threw. A pre-aborted call was refused through the intercepted path.

The family match is exact and case-sensitive. An unknown family gives an empty query result; an unknown inspect ID is an error.

No read appends reservation state.

Exercise: compare these two requests:

Model tool arguments—call seed_catalog, one request at a time:

Seed Desk: welcome and inventory · source excerpt 14; read surrounding instructions
{"op":"query","family":"unknown-family"}
Seed Desk: welcome and inventory · source excerpt 15; read surrounding instructions
{"op":"inspect","id":"unknown-seed"}

Answer: the query reports no matches. The inspection fails because it promises to identify one existing target.

What changes next?

Mara can now ask, “Which herbs are available?” But the eight packets are still a static fixture. To represent a reservation, she needs a state model, an ownership scope and a concurrency check—not merely another button.

That is the next chapter.

Source, snapshot 2026-08-29: the linked Seed Desk files; packages/coding-agent/src/extensibility/extensions/types.ts, ToolDefinition; packages/coding-agent/src/extensibility/extensions/wrapper.ts, RegisteredToolAdapter, ExtensionToolWrapper.

Extensions inside those boundaries · Source chapter: extensions/seed-desk-welcome-and-inventory. Original evidence remains scoped to its recorded snapshot.

Read this chapter as Markdown

Your lesson ticks

A self-reported reading checklist, not proof of real OMP behavior. Only these ticks are saved in this browser. Reading a milestone does not resume, fork, reset or export a session.

Chapters I have worked through
Start here 1
Sessions, resets, and reviewable history 19
Memory and reusable knowledge 14
Tangent work and live control 17
Tool permissions and approvals 15
Extensions inside those boundaries 23
Connections and next steps 8
0 of 97 checked

Checklist saving needs JavaScript and available browser storage.