Let a spoken request reach the capability it names

a04c4fcf4708 · AtlantisPleb · · parent 8c60e763b9ec

Let a spoken request reach the capability it names

Asking the coder to read a conversation now runs the whole loop without
a detour through improvised shell: the capability tool's standing
description carries the installed catalog's names and first sentences,
the shell tool defers to an installed capability that covers the task,
a load answers with "call it now" rather than a bare confirmation, and
the scanner's manifest points content reads at read_conversation.
Capability loads are auto-approved by the owner's decision: the
installed, digest-pinned catalog is the trust boundary, and the load
still lands in the session's plugin events.

Closes #41.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E1mRkPGYmTVvMKqAzmQvy5
Co-Authored-By
Claude Fable 5 <noreply@anthropic.com>
Closes
#41

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified packages/openagents-cli/src/cli.ts
  • modified packages/openagents-cli/src/coder-capability.ts
  • modified packages/openagents-cli/src/coder-tools.ts
  • modified plugins/foreign-sessions/manifest.json

Diff

4 files changed, +39 -4

packages/openagents-cli/src/cli.ts modified +6 -1

@@ -2537,9 +2537,14 @@ const coderCommand = Command.make(

2537 2537
          },
2538 2538
        });
2539 2539
      };
2540
      // Auto-approval, by the owner's decision (2026-08-25): a capability
2541
      // in the installed, digest-pinned catalog loads without a prompt,
2542
      // whatever it declares. The catalog itself is the trust boundary —
2543
      // what is installed is what may run — and the load still lands in the
2544
      // session's plugin events, so the record says what ran and when.
2540 2545
      const capability = capabilityTool({
2541 2546
        catalog,
2542
        approval: new PluginApproval(),
2547
        approval: new PluginApproval({ ask: () => "allow" as const }),
2543 2548
        recordGap: defaultCapabilityGapRecorder(),
2544 2549
        onSelect,
2545 2550
      });
packages/openagents-cli/src/coder-capability.ts modified +28 -1

@@ -144,12 +144,31 @@ function catalogDescription(catalog: ReadonlyArray<PluginCatalogEntry>): string

144 144
 * Those appear only after a search with `query` returns the catalog and an
145 145
 * exact-name call with `name` loads the chosen plugin.
146 146
 */
147
/** A description's first sentence, for the one-line standing summary. */
148
const firstSentence = (text: string): string => {
149
  const at = text.indexOf(". ");
150
  return at > 0 ? text.slice(0, at + 1) : text;
151
};
152
147 153
export function capabilityTool(options: CapabilityOptions): CoderTool {
148 154
  const { catalog, approval, recordGap, onSelect, load = loadPluginFromManifest } = options;
155
  // The catalog's names and first sentences ride in the standing
156
  // description: a model that has never heard what is installed answers
157
  // "read that conversation back" with an improvised shell script, and the
158
  // sandboxed, bounded capability sits unused. One tool, but an honest one.
159
  const summary =
160
    catalog.length === 0
161
      ? ""
162
      : "Installed: " +
163
        catalog
164
          .map((entry) => `\`${entry.name}\` (${firstSentence(entry.description)})`)
165
          .join("; ") +
166
        ". When one of these covers the work, load and call it instead of improvising a script: it is sandboxed, bounded, and returns structured output. ";
149 167
  return {
150 168
    name: "capability",
151 169
    description:
152 170
      "Discover and load a local plugin capability from the installed catalog. " +
171
      summary +
153 172
      "No semantic embedding is available in this package, so `query` returns " +
154 173
      "the full catalog of installed capabilities and their descriptions for you " +
155 174
      "to choose from. Do not try to guess a name by substring or keyword. " +

@@ -197,7 +216,15 @@ export function capabilityTool(options: CapabilityOptions): CoderTool {

197 216
          return describeLoad(outcome);
198 217
        }
199 218
        onSelect(outcome, entry.manifestPath);
200
        return describeLoad(outcome);
219
        // The next move is named explicitly. A model that loaded a
220
        // capability mid-plan kept following the plan — improvising with
221
        // shell — while the tool it asked for sat ready.
222
        return (
223
          describeLoad(outcome) +
224
          `\n\nThe tool \`${entry.name}\` is available now. Call it directly for this work ` +
225
          "instead of a shell script: it is sandboxed, bounded, and returns structured JSON. " +
226
          "Its parameters are in its tool declaration."
227
        );
201 228
      }
202 229
203 230
      if (query !== undefined && query.length > 0) {
packages/openagents-cli/src/coder-tools.ts modified +4 -1

@@ -539,7 +539,10 @@ export function shellTool(cwd: string): CoderTool {

539 539
      "printed. Use it for anything you would type at a terminal: reading files, listing " +
540 540
      "directories, searching, git, running builds and tests. For the `openagents` CLI use the " +
541 541
      "`openagents` tool instead — it carries the list of commands, so running it through here " +
542
      "costs a turn finding out what exists. Prefer it over `delegate` for " +
542
      "costs a turn finding out what exists. When an installed capability covers the task — " +
543
      "the `capability` tool names what is installed — load and call it instead of scripting " +
544
      "the same thing here: it is sandboxed, bounded, and returns structured output. " +
545
      "Prefer it over `delegate` for " +
543 546
      "single commands -- a child agent is for work worth a whole agent, not for one line of " +
544 547
      "output. Both output streams come back together with the exit code. There is no terminal, " +
545 548
      "so a command that would prompt gets end-of-file instead of waiting; pass a flag that " +
plugins/foreign-sessions/manifest.json modified +1 -1

@@ -3,7 +3,7 @@

3 3
  "name": "foreign_sessions",
4 4
  "version": "0.1.0",
5 5
  "author": "OpenAgents",
6
  "description": "Discover recent Claude Code and Codex CLI sessions from their local state directories, mounted read-only: for each session its source, id, working directory, modification time, size, and record count. Metadata only \u2014 it never reads whole conversations back, never writes, and cannot resume anything. Use it when asked what foreign coding-agent sessions exist on this machine, optionally filtered to a working directory.",
6
  "description": "Discover recent Claude Code and Codex CLI sessions from their local state directories, mounted read-only: for each session its source, id, working directory, modification time, size, and record count. Metadata only \u2014 it never reads whole conversations back, never writes, and cannot resume anything. Use it when asked what foreign coding-agent sessions exist on this machine, optionally filtered to a working directory. To read a conversation's content back, load the read_conversation capability instead.",
7 7
  "artifact": {
8 8
    "path": "foreign_sessions.wasm",
9 9
    "digest": "sha256:5ba9c4265b7a18dbb1c1c175e10a0ffcff5b7d124c766536b582c6e33bde84bb"

This page updates live while a promote is in flight · changelog