ext.yml Manifest Reference

ext.yml Manifest Reference

ext.yml is an extension container's manifest, placed at the root of the container directory. It declares the container's metadata and, in the contributes section, lists the capabilities the container provides to the system. This is the authoritative reference for all fields.

To understand the overall model first, read the Extension System Overview. When writing a manifest, validate it with clacky ext verify — it reports errors field by field with locations.


Top-Level Fields

id: weather-panel          # unique container id (defaults to directory name); lowercase letters, digits, -, _
name: Weather Panel        # display name
description: Sidebar weather panel
version: "0.1.0"           # semantic version; used as-is on publish
author: Your Name          # credit, shown on the New Session card
origin: self               # source nature: self / marketplace / enterprise
contributes:               # contributions (see below)
  ...
Field Required Description
id No Unique container id; defaults to directory name. Overridden across layers by id (local > installed > builtin).
name Yes Display name.
title No Optional short title (used by some UIs).
description No One-line summary.
version Yes Semantic version string, e.g. "0.1.0". Used as-is when publishing a new version.
author No Credit, shown on the New Session card.
origin No Source nature: self (locally authored, default) / marketplace (commercial, encrypted) / enterprise (internal).
homepage No Homepage / repo URL.
license No License identifier, e.g. MIT.
license_required No Boolean; whether install / use requires a license.
public No Boolean; whether it may be listed publicly in the marketplace.
keywords No Array of strings; keywords for marketplace search.
contributes Yes Contribution section declaring what the container provides.

The contributes Section

contributes may hold any combination of the following eight keys. Detailed usage is in each linked doc; here we only list fields.

panels — Web UI Panels

contributes:
  panels:
    - id: weather                    # panel id, globally unique
      title: Weather                 # tab / title text
      title_zh: 天气                  # Chinese title (optional)
      view: panels/weather/view.js   # panel script path (relative to container root)
      attach: ["*"]                  # visibility: array of agent ids, or ["*"] for all
      order: 100                     # ordering when multiple panels share a slot (default 100)
Field Description
id Panel id, globally unique; other agents can reuse it via panels: [id].
title / title_zh Title text (EN / ZH).
description / description_zh Panel description (optional).
view Panel JS script path, relative to container root.
attach Visibility: array of agent-id strings, or ["*"] for all. Omit to let referencing agents' panels: decide mounting.
order Ordering number, default 100.
entry_points Optional. Array of { unit_id, slot } recording which unit mounts into which slot. Written by tooling (e.g. Extension Studio) as metadata; the loader does not consume it.

See Web UI Extensions.

api — HTTP Backend Endpoint

A single backend can be written inline:

contributes:
  api: api/handler.rb        # single handler, mounted at /api/ext/<id>/

handler.rb defines a Clacky::ApiExtension subclass declaring endpoints with a routing DSL. See HTTP API Extensions.

skills — AI Skills

contributes:
  skills:
    - id: triage             # skill id
      dir: skills/triage/    # directory holding SKILL.md (defaults to skills/<id>/)
      protected: false       # whether encrypted (for commercial extensions)

agents — Assistants

contributes:
  agents:
    - id: designer
      title: Designer
      title_zh: 设计师
      description: A demo agent that owns the canvas panel.
      description_zh: 拥有画布面板的演示 agent。
      order: 100
      prompt: agents/designer.md   # system prompt for personality / role
      avatar: agents/designer.png  # avatar (optional)
      panels: [canvas]             # which panels to mount (referenced by id)
      skills: [layout-tips]        # which skills to bind (referenced by id)
      tools: [hello]               # which extension tools to inject (referenced by id)
      disabled_skills: [triage]    # which skills to disable (referenced by id, blacklist)

panels: / skills: reference by id — they can reference contributions from this container or shared components elsewhere (e.g. the builtin git). tools: can only reference tools contributed by this container's contributes.tools. disabled_skills: is a blacklist complementing the skills: whitelist — besides the bound list, you can explicitly disable a globally visible skill. See Agent Configuration.

channels — IM Channel Adapters

contributes:
  channels:
    - id: slack
      platform: slack              # platform identifier (optional)
      adapter: channels/slack.rb   # adapter script path

See Custom Channel Adapter.

patches — Runtime Patches

contributes:
  patches:
    - target: "Clacky::Tools::Terminal#execute"  # method to prepend onto
      file: patches/audit.rb                     # patch file path
      fingerprint: "a1b2c3..."                   # optional: source fingerprint of the target
      on_mismatch: disable                       # on fingerprint mismatch: disable / warn

Omitting fingerprint = trust the patch and require it directly; providing one lets the loader act via on_mismatch when upstream source drifts. See Runtime Patches.

hooks — Tool-Call Interception

contributes:
  hooks:
    - event: before_tool_use     # event name (see table below)
      file: hooks/audit.rb       # callback script path

Valid events: before_tool_use, after_tool_use, on_tool_error, on_start, on_complete, on_iteration, session_rollback. See Declarative Shell Hooks.

tools — Extension Tools

Add a brand-new tool to the AI's model schema (a new API, a new system-level operation). tools/<id>.rb defines a Clacky::Tools::<Camelized id> subclass — the file name IS the class-name mapping (tools/hello.rbClacky::Tools::Hello), so no extra registration code is needed:

contributes:
  tools:
    - id: hello
      file: tools/hello.rb   # defines Clacky::Tools::Hello < Clacky::Tools::Base
Field Description
id Tool id; agents reference it via tools: [id].
file Ruby file path, relative to container root. The file name maps to the class name: tools/<id>.rbClacky::Tools::<Camelized id>.

A tool is injected only into agents that declare it — an agent mounts the tool into its schema only when its agents: entry lists tools: [hello]. A broken tool file is logged and skipped at startup; it never blocks the agent.

A tool instance receives the host agent at registration (via the agent accessor), giving it access to the Ruby-layer public API — this is what sets a tool apart from a Skill:

Method Purpose
agent.skill_loader Inspect installed skills
agent.fork_subagent(model:, forbidden_tools:, system_prompt_suffix:) Fork a subagent inheriting the parent conversation
agent.fan_out_labeled(jobs, max_concurrency:, timeout:) Run a batch of [{ label:, run: }] in parallel, UI handled for you
agent.final_reply(subagent) Get a subagent's final reply text
agent.absorb_subagent_cost(result, notify_ui: false) Merge a subagent's spend into the parent (thread-safe)

fan_out_labeled returns an array strictly aligned to the input order, each entry carrying ok? / value / error / duration. One failing job never drags down its siblings, so check ok? per entry when aggregating. The Web UI folds each job into its own card; the CLI collapses them into a single shared progress line.

Three hard rules when orchestrating parallel subagents: fork_subagent must complete serially on the calling thread (it deep-copies the parent config and history, so concurrent forks race) — only the blocking run belongs in the run: lambda; ② add the parallel tool itself to the subagents' forbidden_tools, or it will recursively spawn threads; ③ never call execute_skill_with_subagent from a thread — that's the serial path, which writes the parent's history and opens a UI phase, and is not thread-safe. Don't spawn your own Thread.new for subagents either: you'd lose the UI phase attribution and the task epoch (the marker used to discard events after a task is interrupted).


Full Example

A "Slack suite" contributing five capabilities from one container:

id: slack-suite
name: Slack Suite
description: Slack integration + support agent + inbox panel
version: "0.1.0"
author: Your Name
origin: self
contributes:
  channels:
    - id: slack
      adapter: channels/slack.rb
  agents:
    - id: support
      title: Support
      prompt: agents/support.md
      panels: [inbox]
      skills: [triage]
  panels:
    - id: inbox
      view: panels/inbox/view.js
      attach: [support]        # only appears in the support agent's sessions
  skills:
    - id: triage
      dir: skills/triage/
  api: api/handler.rb

Validate with clacky ext verify: it checks field validity, id uniqueness, and reference integrity (do the inbox/triage that support references exist, do the view/prompt files exist), reporting locatable errors.