Skip to content

ctx.agents.profiles beta ​

Read the user's Agent Profiles and start one — optionally with an opening prompt. A profile is the user's own named recipe for launching a coding agent (a label, a command line like claude-work, and an optional config directory for running a second account), defined on Settings → Agents → Profiles.

ts
ctx.agents.profiles: AgentProfilesService

A profile is a way to start a terminal, not a way to talk to an agent. What comes up is a PTY running a real agent CLI, exactly as if the user had typed the command themselves — there is no agent-agnostic messaging layer here, and there is not meant to be one. Talking to an agent that is already running is out of scope.

There is deliberately no pick() — build one from list() and ctx.ui.showMenu — and no get(), which is list().find(). list()'s return is read-only and deeply frozen, recomputed only when the profile list actually changes — safe to call on every render, same contract as ctx.agents.catalog().

Example ​

Let the user pick a profile and start it on a task ​

ts
function startOn(ctx: ExtensionContext, profileId: string): void {
  const result = ctx.agents.profiles.launch({
    profileId,
    prompt: "Fix the failing test in src/foo.test.ts",
  });
  if (result.ok) {
    ctx.ui.notify("info", `Started in terminal ${result.terminalId}.`);
  } else {
    ctx.ui.notify("warn", `Couldn't start it: ${result.refusal}`);
  }
}

export const extension: Extension = {
  id: "my.start-task",
  activate(ctx) {
    ctx.subscriptions.push(
      ctx.registerCommand({
        id: "my.startTask",
        label: "My: Start a task",
        run: () => {
          const profiles = ctx.agents.profiles.list();
          if (profiles.length === 0) {
            ctx.ui.notify(
              "warn",
              "No agent profiles yet — add one in Settings → Agents.",
            );
            return;
          }
          void ctx.ui.showMenu({
            items: profiles.map((p) => ({
              label: p.isDefault ? `${p.label} (default)` : p.label,
              run: () => startOn(ctx, p.id),
            })),
          });
        },
      }),
    );
  },
};

Only offer a prompt where one is possible ​

Whether an agent can take an opening prompt is a static fact about that agent, not about a particular launch — so list() tells you up front and you never have to discover it from a refusal:

ts
const promptable = ctx.agents.profiles.list().filter((p) => p.acceptsPrompt);

launch() only starts a Terminal profile ​

A profile's interface says which arm it uses — "terminal" (a PTY the agent draws its own TUI in) or "chat" (an Agent Client Protocol child driven through ctx.agents.sessions). The two are started by different services, so filter before you offer: launch()ing a Chat profile refuses, and connect()ing a Terminal profile rejects.

ts
const all = ctx.agents.profiles.list();
const launchable = all.filter((p) => p.interface === "terminal");
const connectable = all.filter((p) => p.interface === "chat");

It is the same list and the same ids — only the verb differs.

Show a profile's brand mark before connecting ​

AgentProfileSummary.agentId is the catalog agent this profile resolves to, if any — the same id ctx.agents.catalog() keys on, and the same resolution AgentSessionHandle.agentId reports once a Chat session is actually live. Look it up against the catalog to show a picker row's brand mark before you've connected anything:

ts
const icon = profile.agentId
  ? ctx.agents.catalog().find((a) => a.id === profile.agentId)?.icon
  : undefined;

undefined when the profile resolves to no known agent — render the picker row without one rather than guessing.

Start an agent without stealing the user's place ​

activate defaults to true (activate the workspace, focus the new terminal). Pass false to launch quietly — including into a background workspace, which spawns its session eagerly since no panel will mount to do it. cwd defaults to the workspace folder; pass it to start somewhere else in a multi-root workspace:

ts
ctx.agents.profiles.launch({
  workspaceId: someOtherWorkspaceId,
  cwd: `${workspaceFolder}/packages/api`,
  prompt: "Run the migration and report what changed",
  activate: false,
});

Opening prompts ​

The prompt is delivered as a literal. Silo composes it into the launch line so the shell cannot interpret it — $HOME, backticks, $(…), quotes, backslashes and newlines all arrive as text and execute nothing. You do not need to escape anything.

If Silo cannot deliver a prompt exactly, it refuses rather than mangling or silently dropping it: nothing is typed, no terminal is created, and launch() returns the reason.

RefusalMeaning
no-agentThe profile matches no agent Silo knows, so there is no way to tell how its CLI takes a prompt
agent-takes-noneThat agent has no way to accept an opening prompt while staying interactive
unsupported-shellSilo has no exact quoting rule for the shell this terminal would run — bash, zsh and fish work
too-largeOver the 2 KiB limit — about a page. See "How long can a prompt be?" below
no-profileThe named profile does not exist, or there are none at all
no-workspaceThe named workspace does not exist, or none is open

How long can a prompt be? ​

2 KiB — roughly a page of prose. Longer is refused with "too-large" before anything is typed.

The ceiling is lower than you might expect, and the reason is worth understanding: the prompt is typed into the user's shell, exactly as if they had entered it. A shell running syntax highlighting or autosuggestions re-parses its whole input buffer on every keystroke batch, so it consumes a long paste slowly — and measurably fails to keep up past a few KiB. On an unadorned shell the same text arrives fine, so the real ceiling depends on your user's setup, not on Silo.

Silo therefore refuses well short of where delivery actually degrades. That is deliberate: a caller who gets "too-large" can trim and retry, whereas a prompt that silently lost its last paragraph reaches the agent looking complete, and the agent acts on it.

If you have more context than fits, put it somewhere the agent can read — a file in the workspace, an issue it can fetch — and use the prompt to point at it.

Two things worth knowing before you put text in a prompt:

  • It is visible. The composed line is typed into the user's own interactive shell, so it appears in scrollback and enters shell history exactly as if they had typed it. That is the product's model — a launch is something the user can see, edit, and re-run — so don't put a secret in one.
  • It is an opening prompt. It rides the launch line of a new terminal. There is no way to send a second prompt to an agent that is already running.

See also ​