Skip to content

Latest commit

 

History

History
204 lines (147 loc) · 9.06 KB

agent-manifest.md

File metadata and controls

204 lines (147 loc) · 9.06 KB

Agent Manifest — Design Draft (RFC)

Status: Draft · Open for feedback · Not yet implemented Target milestone: P0 in the Roadmap Authors: @gurudin · open for PRs

AnyCoding's north star is to be the unified mobile front-end for any pty-based AI coding agent. To scale beyond the two hardcoded agents (Claude Code, Codex), we need a declarative way to add new agents without changing core code. This document proposes that mechanism.


Goals

  1. Adding a standard pty-based agent should be a manifest PR, not a code PR.
  2. Users can install custom / private agents locally by dropping a file into ~/.any-coding/agents/.
  3. The CLI core stays small. wrapper.js handles generic spawn + pty plumbing; agent-specific knowledge lives in manifests.
  4. Forward-compatible. The Android app should tolerate unknown agents gracefully (fallback icon, generic rendering).

Non-Goals

  • Not a plugin system that executes arbitrary code. Manifests are data only. No scripts, no dynamic module loading.
  • Not a marketplace. Discovery happens via the git repo and user-local files.
  • Not for relay-side agents. This is CLI-only. Agents that need a closed-source backend live elsewhere.
  • Not hot-reloading. Manifest changes take effect on next CLI invocation.

Manifest format

Manifests are JSON files (one agent per file). JSON keeps the CLI dependency-free. YAML support is a possible v2 addition.

Minimal example

{
  "schema_version": 1,
  "name": "aider",
  "description": "AI pair programmer in your terminal (paul-gauthier/aider)",
  "binary": "aider",
  "install_hint": {
    "default": "pip install aider-chat",
    "darwin":  "brew install aider || pip install aider-chat"
  },
  "args_mode": "passthrough",
  "prompt_marker": "^> $",
  "icon": { "kind": "initials", "text": "AI", "color": "#B3001E" }
}

Full field reference

Field Type Required Description
schema_version integer ✅ Currently 1. CLI rejects unknown versions.
name string ✅ Subcommand name. Invoked as any-coding <name> [args...]. Must match ^[a-z][a-z0-9_-]{1,31}$.
description string ✅ One-line description shown in the interactive picker.
binary string ✅ Command to execute. Resolved against $PATH. May be absolute.
install_hint object — Map of platform → shell command. Shown when binary is not found. Keys: default, darwin, linux, win32.
env object — Extra env vars to inject into the spawned process.
args_mode enum ✅ How CLI args after the subcommand are forwarded. See below.
prompt_marker string (regex) — Matched against the tail of the output buffer to detect "agent finished replying". Used by the message-counter feature.
icon object — Rendering hint for the Android app tab strip. See "Icon rendering".
min_cli_version semver string — Manifest refuses to load on older CLI versions.

args_mode values

Value Behavior
passthrough All args after the subcommand go verbatim to the binary. (Claude, Codex, Aider — the common case.)
positional Args are collected and passed as positional only. Flags are rejected at CLI level.
none No args forwarded. Used for agents that take no CLI args.

Icon rendering

Android uses this order of preference:

  1. Built-in: { "kind": "builtin", "id": "claude" | "codex" } — renders the bundled app asset.
  2. Initials fallback (recommended for new agents): { "kind": "initials", "text": "AI", "color": "#B3001E" } — app draws a rounded square with the text. No asset bundling needed.
  3. Remote URL (v2, not in initial release): { "kind": "url", "src": "https://..." } — app fetches and caches.

New manifests should use initials unless there's a strong reason to ship a built-in asset (which requires an Android PR).


Discovery & precedence

On startup the CLI loads manifests from, in order:

  1. Built-in, bundled with the npm package (any-code-cli/agents/*.json). Covers default agents (Claude, Codex, and eventually Gemini / Aider / goose / Open Interpreter).
  2. User: ~/.any-coding/agents/*.json.
  3. Project: ./.any-coding/agents/*.json (if present in cwd).

Collision rule: higher-precedence sources win on name collision. Project overrides user overrides built-in. The CLI logs a note when an override happens (visible with any-coding list-agents --verbose).


Proposed new CLI commands

Command Description
any-coding list-agents Print available agents (name, description, binary found/missing, icon).
any-coding list-agents --verbose Include source (built-in / user / project) and manifest path.
any-coding show-agent <name> Pretty-print a single manifest with resolved values.

Runtime flow

argv[2] = <subcommand>
  │
  ▼
load manifests (built-in + user + project)
  │
  ├── if subcommand matches a manifest name:
  │     1. check `binary` exists on $PATH
  │        · missing → print install_hint, exit 1
  │     2. build spawn() argv per args_mode
  │     3. merge env (process.env + manifest.env)
  │     4. hub.createSession({ agent: name, cwd, args }) → pty.spawn
  │     5. hub pushes agent + icon metadata on /v1/agent sessions frame
  │
  └── else if subcommand is a legacy alias (e.g. bare claude flags):
        fall back to claude manifest (backward compat)

Relay protocol addition

The sessions text frame (agent → client) currently looks like:

{ "t": "sessions", "list": [ { "id": "...", "project": "...", "cwd": "...", "owner": "desktop", "args": [...], "createdAt": 123 } ] }

Proposed addition: each session object gets an agent field (string) and an icon field (object, mirror of manifest icon).

{ "id": "...", "agent": "aider", "icon": { "kind": "initials", "text": "AI", "color": "#B3001E" }, ... }

The app uses agent for tab labeling and icon for rendering. Unknown agents fall back to { "kind": "initials", "text": "?" }.


Worked example: adding Gemini CLI

Contributor workflow after this ships:

  1. Install Gemini CLI locally to verify it behaves like a standard pty TUI.

  2. Write any-code-cli/agents/gemini.json:

    {
      "schema_version": 1,
      "name": "gemini",
      "description": "Google Gemini CLI",
      "binary": "gemini",
      "install_hint": { "default": "npm install -g @google/gemini-cli" },
      "args_mode": "passthrough",
      "prompt_marker": "^\\s*>\\s*$",
      "icon": { "kind": "initials", "text": "Ge", "color": "#4285F4" }
    }
  3. Manual test: any-coding gemini --help spawns correctly.

  4. Add screenshot of the app tab strip showing the new icon.

  5. Open PR.

No changes to wrapper.js, no changes to the Android app, no changes to the relay.


Security notes

  • Manifests are data only. They cannot execute arbitrary code. The only thing they can cause is spawn(binary, args, env) with values the user's own config supplied.
  • User-local manifests (~/.any-coding/agents/) have the same trust boundary as the user's own $PATH — if an attacker can write files into your home directory, they already have a shell.
  • Built-in manifests ship with the npm package and go through normal code review.
  • install_hint is not executed; it is only printed as text for the user to run manually.

Open questions

  1. Multi-binary fallback — should binary be a list, trying each in order? (Useful for agents with multiple install methods — aider vs aider-chat.)
  2. Prompt marker complexity — a static regex may miss streaming-partial cases. Do we need a small state machine, or is "matched on last N bytes after idle timeout" enough?
  3. Manifest versioning policy — when schema_version bumps, do we break old manifests or translate? Current lean: translate where trivial, reject otherwise.
  4. Agent-specific UI affordances — some agents benefit from quick-action buttons (e.g. "accept all edits"). Out of scope for v1; could become a P2 feature where manifests declare actions.
  5. Windows compatibility — install_hint.win32 and binary resolution on Windows needs real validation during implementation.

Feedback welcome on any of the above — open an issue labeled manifest-design.


Alternatives considered

  • Hardcode every agent — status quo. Works for 2–3 agents, becomes a bottleneck beyond that. Every community contribution is a core PR.
  • Lua / JS plugin scripts — maximally flexible, but introduces a runtime security model and breaks "manifests are data". Rejected for v1.
  • Inline manifests inside package.json — couples to Node / npm. Rejected: manifests should be usable even if we later ship a non-Node CLI (e.g. a Go rewrite).
  • Remote manifest registry — too much infrastructure. Git-based discovery (PR to this repo, or drop into ~/.any-coding/) is sufficient.

Changelog

  • 2026-04-17 — Initial draft.