Plugin Manifest Reference
Complete field-by-field reference for a kandev plugin's manifest.yaml, including the full event-subscription vocabulary.
manifest.yaml is the authoritative description of a plugin: identity,
runtime executables, capabilities, webhooks, agent tools, config schema, and
optional native UI or isolated web apps. Kandev parses and validates it
before any plugin code runs. See Authoring a plugin
for the build workflow and Plugins for install and operation.
Quick path
- Start from the annotated example.
- Set identity, version, and either a runtime executable or
ui.web_apps. Add only the capabilities the plugin needs. - Add config, webhooks, events, or UI fields only when the plugin uses them.
- Validate the manifest before packaging.
Annotated example
id: "kandev-plugin-slack" # ^[a-z0-9][a-z0-9._-]*$
api_version: 2 # use 1 or 2; v2 is recommended for new packages
version: "1.0.0"
display_name: "Slack Notifications"
description: "Post to Slack on task events, relay messages to agents"
author: "kandev"
categories: ["connector"] # connector | automation | tools | analytics
repo_url: "https://github.com/kdlbs/kandev-plugin-slack" # optional; "Repo" link in Settings > Plugins
runtime:
type: binary # only supported value today
executables:
linux-amd64: server/plugin-linux-amd64
linux-arm64: server/plugin-linux-arm64
darwin-amd64: server/plugin-darwin-amd64
darwin-arm64: server/plugin-darwin-arm64
windows-amd64:
server/plugin-windows-amd64.exe
# any subset; the host's <goos>-<goarch> key
# is required at install time
min_kandev_version: "0.91.1" # required for admin actions
capabilities:
events: ["task.created", "task.state_changed", "agent.completed"]
api_read: ["tasks", "agent_profiles"] # gates the Host data-reader RPCs
api_write: ["tasks", "messages"] # gates Host task and message writes
state: true
secrets: true
agent_invoke: true # gates Host.InvokeUtilityAgent
auth: true # gates external (OIDC/SAML) login; see ADR 0050
user_state: true # gates host.storage (per-user browser storage)
webhooks:
- key: "slack-events"
description: "Slack Events API webhook"
method: "POST" # informational only, not enforced
access: public # public (default) or authenticated
max_body_bytes: 4194304 # public maximum 4 MiB; authenticated maximum 16 MiB
actions: # authenticated manifest-declared actions; some are backend-invoked
- key: "connection.save"
scope: "workspace" # workspace | task | repository
access: admin # authenticated (default) | admin
max_body_bytes: 65536 # 1 through 1048576
- key: "repositories.inspect"
scope: "workspace"
max_body_bytes: 16384
- key: "repositories.branches"
scope: "workspace"
max_body_bytes: 16384
repository_providers: ["acme"] # optional provider ids owned while active
reference_sources: # optional dynamic composer sources
- source: "acme-pull-requests"
provider: "acme"
kind: "pull_request"
display_name: "Acme pull requests"
kind_label: "Pull request"
auth_providers: # login buttons (needs capabilities.auth)
- id: "google"
display_name: "Google"
initiate: "login-start" # names a webhook key above; the button navigates there
config_schema:
type: object
properties:
bot_token:
{
type: string,
secret: true,
title: "Bot Token",
description: "Slack bot OAuth token",
}
default_channel:
{ type: string, description: "Default channel for notifications" }
notify_on_task_created: { type: boolean, default: true }
utility_agent:
{
type: string,
format: utility-agent,
title: "Utility Agent",
description: "Agent used for plugin LLM calls",
}
required: ["bot_token", "default_channel", "utility_agent"]
agent_tools:
- name: add_tag
description: Add an existing tag to the current task.
surfaces: [kanban-task]
input_schema:
type: object
properties:
tag_id: { type: string }
required: [tag_id]
additionalProperties: false
output_schema:
type: object
properties:
task_id: { type: string }
required: [task_id]
additionalProperties: false
annotations:
read_only_hint: false
destructive_hint: false
idempotent_hint: true
ui: # optional native frontend plugin
bundle: "/ui/bundle.js" # root-relative
styles: ["/ui/plugin.css"] # optional, root-relative
keybindings: # optional, requires ui.bundle
- id: "open-panel" # plugin-local: ^[a-z0-9][a-z0-9-]*$
default: "mod+shift+j" # combo grammar, see field reference
description: "Open the Acme panel"
allow_in_editor: false # optional; true lets it fire while typingIsolated web applications
An isolated web application is a packaged static app that Kandev loads in a
sandboxed iframe. It does not need a backend executable. The package uses the
relative ./_kandev/v1 protocol and does not receive an injected JavaScript
API.
id: "acme-task-board"
api_version: 2
version: "0.1.0"
display_name: "Acme Task Board"
description: "A task board for Kandev canvases"
author: "Acme"
ui:
web_apps:
- key: main
title: Task board
entry: ui/index.html
placements: [task-canvas, workspace-canvas]
network_origins: [https://api.example.com]The package contains the declared entry document and its static files:
acme-task-board-0.1.0.tar.gz
├── manifest.yaml
└── ui/
├── index.html
├── app.js
└── styles.cssOmit runtime, base_url, and endpoints for a static web-app package. A
package can declare both runtime.type: binary and ui.web_apps when its web
app needs a managed backend. Use Authoring a plugin
for a complete authoring path.
Field reference
Security:
capabilities.authlets a plugin assert external login identities. Grant it only to trusted plugins whose identity provider verifies email ownership; a spoofed email claim can take over an account.
| Field | Required | Type | Notes |
|---|---|---|---|
id | yes | string | Must match ^[a-z0-9][a-z0-9._-]*$ (lowercase alphanumeric, dots, underscores, hyphens; must start with a lowercase alphanumeric). Directory name under ~/.kandev/plugins/. |
api_version | yes | int | Supported values are 1 and 2. Version 1 keeps the legacy public default for omitted webhook access. Version 2 uses authenticated access for omitted webhook access. |
version | yes | string | Free-form; used as the version directory name (~/.kandev/plugins/<id>/<version>/). |
display_name | no | string | Shown in Settings > Plugins. |
description | no | string | Shown in Settings > Plugins. |
author | no | string | Free-form. |
categories | no | string[] | Each entry must be one of connector, automation, tools, analytics. Unknown values are rejected. |
icon | no | string | Package-relative path (e.g. icon.svg) to an image the package ships, rendered on the marketplace card and in plugin lists. The registry index-build resolves it to an absolute icon_url; for an installed plugin it is served from the extracted package. Omit it and the card falls back to a letter tile. |
repo_url | no | string | Absolute http(s) URL to the plugin's source repository. Rendered as a "Repo" link in Settings > Plugins (both the installed list and the plugin detail). Any other scheme (e.g. javascript:) is rejected at registration. Distinct from the marketplace card's repo_url, which the registry derives from plugins.yaml; declare this in the manifest so sideloaded and directly-installed plugins also carry the link. |
runtime.type | conditionally | string | "binary" is the only supported value. Setting it (vs. leaving it empty) makes the manifest runtime-managed; see "Managed vs. legacy" below. |
runtime.executables | required when runtime.type: binary | map<string,string> | Key is <goos>-<goarch> (e.g. linux-amd64, darwin-arm64, windows-amd64); value is a clean, package-relative path under server/ (no leading /, no .. segments). At least one entry required; the running host's key must be present at install time. Windows values end in .exe. |
min_kandev_version | conditionally | string | Lowest released Kandev version this plugin supports. Required to be at least 0.91.1 when any action uses access: admin. Enforced at install time: installing onto an older release fails with requires kandev >= <version>, running <version> and nothing is registered. Already-installed plugins are never re-checked at load or start. Release values use up to three dotted numeric segments (0.78.0) and may have a leading v; malformed minimums are rejected. Non-release development and git-describe builds skip the compatibility gate. |
capabilities.events | no | string[] | Bus subjects (or wildcard patterns) this plugin subscribes to. See "Event subscription vocabulary" below. |
capabilities.api_read | no | string[] | Gates the Host data API's read-only accessors. Each entry is a resource name: tasks, sessions, messages, workspaces, workflows, agent_profiles, executor_profiles, repositories. Calling the matching Host accessor (e.g. Tasks()) without its resource declared returns gRPC PermissionDenied. See "Host data API resource vocabulary" below. |
capabilities.api_write | no | string[] | Gates Host writes independently of api_read. tasks permits Host.Tasks().Create and .Update; messages permits Host.Messages().Send. Undeclared writes return gRPC PermissionDenied. |
capabilities.state | no | bool | Gates Host.GetState/SetState/DeleteState/ListState. Calling any of them without this set to true returns gRPC PermissionDenied. |
capabilities.secrets | no | bool | Gates Host.RevealSecret/GetSecret/SetSecret/DeleteSecret. Calling any of them without this set to true returns gRPC PermissionDenied. |
capabilities.agent_invoke | no | bool | Gates Host.InvokeUtilityAgent: a one-shot completion run by the utility agent selected for this plugin. Declare a utility_agent config property with type: string and format: utility-agent; Settings > Plugins renders the picker. The selected utility agent resolves its own enabled ACP profile. Calling without this capability returns gRPC PermissionDenied; calling without a valid profile-backed selection returns gRPC FailedPrecondition. See ADR 0048. |
capabilities.auth | no | bool | Lets the plugin log a visitor in against an external IdP (OIDC/SAML). Its webhook validates the token, then asserts the identity to Kandev via the X-Kandev-Auth-Login response header ({provider, subject, email, display_name}); Kandev mints the session and sets the cookie, so the plugin never sees the token. Requires authentication enabled; new users are provisioned as members, and Kandev never creates an admin nor auto-links to an existing admin account. You MUST only assert an email the IdP verified as owned by the subject; a spoofed email claim is account takeover. Highest-privilege capability; grant only to trusted plugins. See ADR 0050. |
capabilities.user_state | no | bool | Gates host.storage (get/set/delete/list/subscribe), the authenticated per-user browser storage surface at /api/plugins/{id}/user-state/.... Unlike capabilities.state (the gRPC Host.SetState family, written by the plugin's own backend), this is reachable directly from the plugin's frontend bundle with no Go backend required; every read/write is scoped to the calling user. Calling the route without this capability returns 403. See Authoring a plugin and the per-user-plugin-storage decision record. |
webhooks[].key | yes | string | Must be unique within the manifest. Used in the relay path POST /api/plugins/{id}/webhooks/{key}. |
webhooks[].description | no | string | Free-form. |
webhooks[].method | no | string | Informational only: kandev does not validate or enforce the inbound HTTP method against this value. |
webhooks[].access | no | string | public (default) allows anonymous callers; authenticated requires a Kandev identity. Cookie-authenticated browser calls remain subject to Kandev's same-origin policy. |
webhooks[].max_body_bytes | no | int | Request-body limit for this key. Defaults to 4 MiB. Public webhooks cannot exceed 4 MiB; authenticated webhooks can request up to 16 MiB. Plugins using this field should declare the first supporting min_kandev_version. |
actions[] | no | object[] | Authenticated, manifest-declared browser actions. They are distinct from public webhooks: the host validates the action key, limits the JSON body, authorizes the declared resource, and passes only a verified context to the plugin. |
actions[].key | yes* | string | Unique non-empty action key, used at POST /api/plugins/{id}/actions/{key}. |
actions[].scope | yes* | workspace | task | repository | Resource selector the host authorizes. A task-scoped invocation may additionally name one repository attached to that verified task. Use the canonical field name scope; resource_scope is only a legacy read-compatibility spelling and must not be authored. |
actions[].access | no | authenticated | admin | Defaults to authenticated. admin is enforced before the action envelope is read and requires min_kandev_version: "0.91.1" or later so older hosts cannot silently treat it as authenticated. |
actions[].max_body_bytes | yes* | int | Maximum size of the decoded untrusted body, from 1 through 1,048,576 bytes. The whole HTTP envelope has a slightly larger hard cap. |
repository_providers | no | string[] | Provider IDs this plugin owns while active. An active provider can register native repository discovery/URL inspection and may supply transient Git credentials. Native first-use task creation requires a workspace-scoped repositories.inspect action; Kandev invokes the active owner on the server and validates its descriptor before persistence. Plugin-originated task creation must use the authenticated plugin task-create path so the plugin reauthorizes the descriptor before Host Tasks.Create. Duplicate ownership is rejected. |
reference_sources[] | no | object[] | Dynamic composer-reference source. Each entry declares source, provider, kind, display_name, and kind_label; order is optional. Candidate identity is not trusted: Kandev reauthorizes the canonical reference when it is submitted. |
auth_providers[] | no | object[] | Login buttons this plugin contributes to the pre-auth login screen (needs capabilities.auth). Each is { id, display_name, initiate }, where initiate names one of the plugin's webhooks[].key values; the button navigates to that webhook, which 302-redirects to the IdP. Surfaced anonymously in the boot payload as auth.ssoProviders. |
config_schema | no | object | JSON-Schema-like object driving the settings form at Settings > Plugins > <plugin> (GET /api/plugins/{id}/config and PATCH /api/plugins/{id}). See "Config schema validation and secret fields" below. |
agent_tools | no | object[] | Task-aware MCP tools implemented by the managed plugin's optional AgentToolPlugin SDK interface. Each declaration has a local name, description, surfaces, required input_schema, optional output_schema, and optional MCP annotation hints. At most 16 tools are allowed. |
agent_tools[].name | yes* | string | Plugin-local name matching ^[a-z0-9][a-z0-9_]{0,31}$. Kandev derives the global MCP name; authors cannot choose it. |
agent_tools[].surfaces | yes* | string[] | One or both of kanban-task and office-task. Plugin tools are not exposed to configuration or external MCP surfaces. |
agent_tools[].input_schema | yes* | object | Compiled object-root JSON Schema, at most 64 KiB serialized. Kandev rejects unknown top-level arguments before invoking the plugin. |
agent_tools[].output_schema | no | object | Optional compiled object-root JSON Schema applied to structured plugin results. |
agent_tools[].annotations | no | object | MCP hints: read_only_hint, destructive_hint, idempotent_hint, and open_world_hint. Omitted values use conservative host defaults. |
ui.bundle | no | string | Root-relative path (must start with /, e.g. /ui/bundle.js) to the plugin's native UI ES module, served at GET /api/plugins/{id}/bundle. |
ui.styles | no | string[] | Root-relative CSS paths (each must start with /), served at GET /api/plugins/{id}/ui/* and injected as <link> tags on load. |
ui.pages | no | object[] | Optional declarative metadata accepted by the manifest model. The current frontend does not render these entries; a native bundle registers its supported routes/nav/slots at runtime, so most plugins omit ui.pages. |
ui.pages[].key | yes* | string | Stable identifier for the page (*required when a page entry is present). |
ui.pages[].title | yes* | string | Display title. |
ui.pages[].path | yes* | string | Route path for the page. |
ui.pages[].surface | yes* | string | Metadata enum (settings · task-panel · main-nav) validated by the manifest parser. It is not a current frontend mount; use the registry hooks in the authoring guide. |
ui.web_apps | no | object[] | Packaged static web apps. Each entry declares one key, title, package-relative HTML entry, and one or more placements. Supported placements are task-canvas and workspace-canvas. |
ui.web_apps[].key | yes* | string | Unique plugin-local key. It starts with a lowercase letter or digit, then uses lowercase letters, digits, ., _, or -, with a maximum length of 64 characters. |
ui.web_apps[].title | yes* | string | Non-empty web-app title with a maximum length of 200 bytes. |
ui.web_apps[].entry | yes* | string | Clean, package-relative path to the entry HTML document. It cannot start with / or contain a .. path segment. |
ui.web_apps[].placements | yes* | string[] | Non-empty list with no duplicates. Each value is task-canvas or workspace-canvas. |
ui.keybindings | no | object[] | Declares plugin keybindings bound at runtime via registerKeybinding. Requires ui.bundle. |
ui.keybindings[].id | yes* | string | Stable, plugin-local slug (_required when a keybinding entry is present). Must match ^[a-z0-9][a-z0-9-]_$and be unique within this plugin's ownui.keybindingslist, not globally; the effective shortcut is namespacedplugin:{pluginId}:{id}. |
ui.keybindings[].default | yes* | string | Default combo string. +-separated tokens: zero or more modifiers from mod, ctrl, cmd, meta, alt, option, shift (mod = ⌘ on macOS, Ctrl elsewhere) plus exactly one non-modifier key. shift may not combine with a digit or symbol key; the browser reports the shifted glyph for those keys, so the combo could never match. |
ui.keybindings[].description | yes* | string | Non-empty, human-readable label shown on the installed plugin's detail page at Settings > Plugins > <plugin>, where each user can configure a personal shortcut override. |
ui.keybindings[].allow_in_editor | no | boolean | Lets this binding fire while an input, textarea or contenteditable holds focus. Defaults to false, so a plugin cannot shadow ordinary typing. Only accepted on a combo carrying a ctrl/cmd/mod/alt modifier; shift alone does not count. Kandev re-applies that requirement to the effective combo, so a user override that drops the modifier reverts to skip-while-typing behavior. |
ui.pages is declarative manifest metadata only and is not currently rendered
by the frontend. A native bundle's runtime nav items, icons, routes, named
slots, and per-route title-bar chrome (registerNavItem, registerRoute, and
registerComponent) are the supported JS SDK surface with no corresponding
manifest.yaml field, see Authoring a plugin.
ui.web_apps is the manifest surface for isolated static web apps. Kandev
owns the host label, placement, route, and canvas controls. The web app owns
the content inside its frame. It uses packaged scripts and styles, relative
Kandev protocol paths, and only the capabilities and network origins that the
host grants. Declare exact HTTPS origins in network_origins when the app
needs external network access. Paths, credentials, wildcards, query strings,
and fragments are rejected.
Static web app network origins
ui.web_apps[].network_origins is optional. Each value must be an exact HTTPS
origin with no path, credentials, wildcard, query string, or fragment. Kandev
reviews each declared origin as a separate grant before the app can contact
it.
The runtime sets form-action 'none', so packaged forms cannot submit to an
external origin. Requests to an approved network_origins value go directly
from the browser. The host tears down the app iframe immediately after a
release, grant, scope, archive, disable, or removal authority change, then
requires a fresh runtime binding before mounting it again.
Managed vs. legacy manifests
Setting runtime.type: binary makes a manifest runtime-managed: kandev
spawns and supervises the declared executable itself. A managed manifest
must not set base_url or an endpoints block (health/events/
webhooks paths on a remote service); those describe the old
remote/operator-hosted tier, and validation rejects a managed manifest that
sets them.
A manifest with an empty runtime.type still parses as a legacy remote
manifest (with base_url/endpoints instead) and passes
manifest.Validate() on its own, but the installer rejects it: pkgtar .Install requires manifest.IsManaged() to be true (runtime.type: binary), so a legacy manifest can never actually be installed via POST /api/plugins/install or a filesystem sideload. The remote tier is
effectively removed in practice, even though the manifest schema still
recognizes its shape.
Host data API resource vocabulary
capabilities.api_read gates the read-only Host data accessors (ADR 0043,
ADR 0047): each entry must be one of tasks, sessions, messages,
workspaces, workflows, agent_profiles, executor_profiles, repositories. Declaring a
resource grants the matching Host accessor (Tasks(), Sessions(),
Messages(), Workspaces(), Workflows(), AgentProfiles(),
Repositories()), or the optional pluginsdk.ExecutorProfiles(host) extension;
see Authoring a plugin. Calling an
accessor for an undeclared resource returns gRPC PermissionDenied.
capabilities.api_write currently accepts tasks and messages. tasks
enables Host.Tasks().Create and .Update; messages enables
Host.Messages().Send. The host applies the same first-party task service and
message-delivery path as its own UI/API, emits the normal events, stamps created
rows/messages as plugin:<id>, and does not let a plugin supply that provenance.
A plugin may declare a read resource without its write capability, or vice
versa. Calling a write without the matching declaration returns gRPC
PermissionDenied.
messages reads historical conversation content (Messages().List):
one user/agent message per row (id, session_id, task_id, turn_id,
author_type, content, type, created_at), filterable by session ids,
task ids, a created_at time range (since inclusive / until exclusive,
RFC3339), and message types. Content is sanitized; kandev-injected
<kandev-system> blocks are stripped, exactly like the message.added bus
event, so raw system prompts are never exposed. author_type is user or
agent (there is no system author).
Config schema validation and secret fields
config_schema is not an arbitrary, purely descriptive JSON Schema; kandev
validates submitted config against a specific subset of it before
persisting:
required(an array of property names) is enforced; aPATCHmissing a required property is rejected.type(string,boolean,number, orinteger) is checked against the submitted value.enummembership is checked when present.- A string property with
format: utility-agentis rendered as a picker of configured built-in and custom utility agents. The UI displays agent names but persists the selected agent's stable ID. Add the property torequiredwhen the plugin must always have a selection; optional fields include a Not set choice. - A property with
secret: true, orformat: "password", is treated as a secret field and must betype: string(or untyped); a non-string secret is rejected. Secret values are moved into kandev's encrypted vault;GET /api/plugins/{id}/configreturns the literal mask"********"in their place, and resubmitting that mask unchanged is treated as "keep the stored value" rather than overwriting it with the literal string. titleis read by the settings-page renderer as a display label override for the property (falling back to the property name); it has no backend validation effect.
The plugin process itself always sees real, unmasked values (secrets
included) via the GetConfig Host RPC; masking only applies to the
operator-facing API/UI.
Event subscription vocabulary
capabilities.events entries are bus subjects (e.g. task.created) or
wildcard patterns using * as a single dot-segment wildcard (e.g.
task.*, agent.*, github.*). A pattern segment of * matches exactly
one subject segment; every other segment must match literally; and the
pattern and subject must have the same number of dot-separated segments
to match at all. task.* matches task.created (2 segments each) but does
not match a three-segment subject such as shell.output.<sessionId>
(shell.* would not match it either; 2 vs. 3 segments).
Any subject kandev publishes on its internal event bus is a valid
subscription target; this is not a closed list scoped to any one feature
area. The table below groups every subject defined in
internal/events/types.go by domain (some are further suffixed per-session,
e.g. shell.output.<sessionId>, git.event.<sessionId>; subscribe to the
literal wildcard segment count that matches, e.g. shell.output.*).
A delivered event's event_type (pluginsdk.Event.EventType) is the
concrete subject it was published on, i.e. the same string your pattern
matched, so a plugin subscribed to shell.output.* reads the session id off
event_type as its last segment. For an unsuffixed subject that string is
just the subject itself (task.created).
| Domain | Events |
|---|---|
| Tasks | task.created, task.updated, task.state_changed, task.deleted, task.moved, task.tree_hold_created, task.tree_hold_released |
| Workspaces | workspace.created, workspace.updated, workspace.deleted |
| Workflows | workflow.created, workflow.updated, workflow.deleted |
| Workflow steps | workflow_step.created, workflow_step.updated, workflow_step.deleted, workflow.step_completion_signaled |
| Comments / messages | message.added, message.updated, message.deleted, message.queue.status_changed |
| Task sessions | task_session.state_changed |
| Task plans | task_plan.created, task_plan.updated, task_plan.deleted, task_plan.revision.created, task_plan.reverted |
| Task walkthroughs | task_walkthrough.created, task_walkthrough.updated, task_walkthrough.deleted |
| Session turns | turn.started, turn.completed |
| Repositories | repository.created, repository.updated, repository.deleted, repository.script.created, repository.script.updated, repository.script.deleted |
| Executors | executor.created, executor.updated, executor.deleted, executor.profile.created, executor.profile.updated, executor.profile.deleted, executor.prepare.progress, executor.prepare.completed |
| Users | user.settings.updated |
| System | system.job.update |
| Environments | environment.created, environment.updated, environment.deleted |
| Agent profiles | agent_profile.created, agent_profile.updated, agent_profile.deleted |
| Agents | agent.started, agent.running, agent.boot_ready, agent.ready, agent.completed, agent.failed, agent.stopped, agent.context_reset, agent.acp_session_created, agentctl.starting, agentctl.ready, agentctl.error |
| Agent stream | agent.stream (per-session: agent.stream.<sessionId>), agent.turn.message_saved |
| Agent prompts | permission_request.received (per-session: permission_request.received.<sessionId>) |
| Clarification | clarification.answered, clarification.primary_answered, clarification.cancelled, clarification.stale_dismissed |
| Git / workspace status | git.event (per-session), git.ws (per-session), file.change.notified (per-session) |
| Shell I/O | shell.output (per-session), shell.exit (per-session) |
| Dev server I/O | process.output (per-session), process.status (per-session) |
| Session context | context_window.updated (per-session), available_commands.updated (per-session), session_mode.changed (per-session), agent_capabilities.updated (per-session), session_models.updated (per-session), session_info.updated (per-session), session_todos.updated (per-session), session_prompt_usage.updated (per-session) |
| Automations | automation.triggered, automation.run.created |
| GitHub | github.pr_feedback, github.pr_state_changed, github.new_pr_to_review, github.new_issue, github.task_pr.updated, github.task_ci_options.updated, github.watch.event, github.rate_limit.updated |
| GitLab | gitlab.mr_feedback, gitlab.mr_state_changed, gitlab.new_mr_to_review, gitlab.new_issue, gitlab.task_mr.updated, gitlab.watch.event |
| Jira | jira.new_issue |
| Linear | linear.new_issue |
| Sentry | sentry.new_issue |
| Office (autonomous agents) | office.agent.created, office.agent.updated, office.agent.status_changed, office.skill.created, office.skill.updated, office.project.created, office.project.updated, office.approval.created, office.approval.resolved, office.comment.created, office.cost.recorded, office.run.queued, office.run.processed, office.run.event_appended (per-run), office.routine.triggered, office.inbox.item, office.task.status_changed, office.task.updated, office.task.decision_recorded, office.task.review_requested, office.provider.health_changed, office.route_attempt.appended, office.routing.settings_updated |
| Cross-plugin | plugin.<plugin_id>.<name>: published by Host.EmitEvent; subscribe with plugin.<other-plugin-id>.* to react to another plugin's events. |
Event list current as of 2026-07-16; regenerate from
apps/backend/internal/events/types.goif this page drifts from the code.
Runtime-managed fields (do not author these)
Once installed, kandev writes several fields onto the stored record
alongside the parsed manifest. These are kandev-owned runtime state, not
author-supplied manifest fields: do not include them in manifest.yaml;
they have no effect there and are overwritten on install:
| Field | Meaning |
|---|---|
status | Current lifecycle state: registered, active, error, disabled, or uninstalled. |
install_path | Absolute path the package was extracted to (~/.kandev/plugins/<id>/<version>/). |
signed | Whether the package's checksums.txt.sig was cryptographically verified. Signature verification is not currently wired, so this is always false today (every package is reported unsigned). |
installed_at | Install timestamp. |
restart_count | Best-effort restart bookkeeping used by the supervision loop. |
Related: Plugins, Authoring a plugin.
