Skip to content

Integrations

Many teams first capture requests in an external work system — GitHub Issues, Jira, Azure DevOps, Linear. Kaddo’s Integration Adapter Foundation connects to those systems through a single, provider-agnostic boundary and normalizes what it finds into a neutral model Kaddo can consume.

The rule that governs everything here:

External tools can originate work and contribute context. Kaddo normalizes that information, preserves its traceability, and keeps its own Work Item as the source of truth for development.

External Work System
↓
Integration Adapter (normalizes provider data)
↓
Normalized External Work Item
↓
Import Preview (read-only)
↓
Human Confirmation
↓
Kaddo Core (creates a canonical Draft)
↓
Canonical Work Item ← the source of truth

Integration Adapters vs Agent Adapters

Kaddo uses the word adapter in two unrelated places. Keep them distinct:

Projects toExamples
Agent Adaptersagent-native filesAGENTS.md, CLAUDE.md
Integration Adaptersexternal work systemsGitHub, Jira, Azure DevOps, Linear

This page is about Integration Adapters.

Architecture

@kaddo/core (domain: Work Items, Knowledge, Graph)
▲
│ normalized model
│
@kaddo/integrations (contract · registry · models · config · errors)
▲
┌───────────┼───────────┐
▼ ▼ ▼
GitHub Jira Azure DevOps (future concrete adapters)
  • @kaddo/core is provider-agnostic. It never imports a vendor SDK and never knows GitHub fields, Jira issue types or Linear states.
  • @kaddo/integrations owns the adapter contract, registry, normalized models, configuration and secret-reference model, error/status model, import-preview mapping and a reference adapter. It holds no Work Item domain rules, Graph semantics or lifecycle rules — those stay in Core.
  • The integration service (in the CLI/Admin layer) is the only place external reads cross into Core, and the only place an import materializes a Work Item.

Adapter contract

interface IntegrationAdapter {
readonly id: string
readonly metadata: IntegrationAdapterMetadata
readonly capabilities: IntegrationCapabilities
verifyConnection(context): Promise<ConnectionResult>
listWorkItems(request): Promise<ExternalWorkItemPage> // paginated
getWorkItem(request): Promise<ExternalWorkItem | null>
}

Adapters are not assumed equivalent. Each declares its capabilities so the UI can ask “what can this adapter do?” instead of assuming everything is supported. The VS-102 baseline requires verifyConnection, list, read and import; write, statusSync, comments and webhooks are future capabilities.

Providers are resolved through a registry — never a hardcoded switch (provider). Adding a new provider is a registration, not a change to Core, Admin or MCP.

Normalized model

Every provider item becomes a neutral ExternalWorkItem — what Kaddo needs, not a faithful copy of the provider model. Provider-specific detail may ride along in rawMetadata but never dominates.

Each item has a stable external identity — integration + externalId — used for duplicate detection and linking. It never relies on the visible title.

Configuration & secrets

Declare integrations in .kaddo/integrations.yml. Configuration carries only how to find a credential, never the credential itself:

integrations:
- id: github-dotear
adapter: github
enabled: true
config:
owner: trycatch-tv
repository: dotear
credentials:
token_env: GITHUB_TOKEN # a reference — resolved at runtime only
secrets:
apiKey: github-dotear.apiKey # VS-103: resolved via SecretProvider

An inline secret (token: ghp_…) is rejected by validation. Secrets are resolved from the environment only for the duration of a call and never appear in Work Items, Knowledge, the Graph, context packs, the Admin API, MCP output, logs or telemetry.

Secret management (VS-103)

Kaddo supports two mechanisms for managing secrets:

  1. Environment variables (VS-102): credentials reference an env var via token_env: VAR_NAME.
  2. SecretProvider (VS-103): secrets are stored in .kaddo/.secrets.json (gitignored, never committed) via a pluggable SecretProvider interface. The YAML stores only a logical reference (e.g. github-dotear.apiKey), never the value.

A CompositeResolver tries the local SecretProvider first, then environment variables. This means both mechanisms work together — local secrets take priority, env vars serve as fallback.

Admin shows secrets as “Configured” or “Not configured” — values are never sent back to the browser after storage. Adapters declare what secrets they need via secretSchema metadata but never know where or how secrets are stored.

Admin management

Kaddo Admin provides full CRUD management of integrations — no need to edit YAML by hand:

  • Create — choose an adapter type, fill dynamic forms driven by the adapter’s configSchema and secretSchema, and save.
  • Edit — update configuration or replace secrets on an existing integration.
  • Delete — remove an integration and its stored secrets.
  • Enable / Disable — toggle an integration without deleting its configuration.
  • Verify — test the connection with a single click.

Dynamic forms are rendered from adapter metadata — no hardcoded provider-specific forms. The YAML file remains the single source of truth: Admin reads and writes .kaddo/integrations.yml directly, and CLI and Admin always see the same state.

Status & connection

Two axes that must never be conflated: an integration connection status and a Work Item lifecycle status. verifyConnection() distinguishes “adapter installed” from “integration actually usable”:

configured · available · unavailable · unauthorized · invalid-config · disabled

Errors are normalized (INTEGRATION_UNAUTHORIZED, INTEGRATION_RATE_LIMITED, INTEGRATION_TIMEOUT, INTEGRATION_UNAVAILABLE, …). Raw provider messages are never surfaced.

Import semantics

Reading is not importing. Viewing EXT-001 does not create a Work Item — import is an explicit, human-confirmed action:

  1. Preview (read-only) shows the source, the captured intent and “No project files have been modified yet.”

  2. Confirm. You choose the Kaddo Work Item type — it is never inferred from the external type.

  3. Import reuses Core’s createWorkItem, producing a Draft (regardless of the external status). The external origin is recorded as provenance, not as the truth:

    source:
    type: external
    provider: github
    integration: github-dotear
    id: "231"
    url: https://github.com/trycatch-tv/dotear/issues/231

Re-importing the same external identity does not create a duplicate — Kaddo returns the existing Work Item. After import, refinement and impact analysis happen on the Kaddo Work Item, and it keeps working even if the external provider becomes unavailable.

CLI

Terminal window
kaddo integrations list # configured integrations + capabilities
kaddo integrations status # verify each and report connection status
kaddo integrations verify <id> # verify one integration
kaddo integrations work-items <id> # list external items (paginated)
kaddo integrations work-item <id> <ext-id> # read one external item
kaddo integrations import <id> <ext-id> --type <feature|fix|…> # preview → confirm → Draft

Read-only commands support --json. Import always previews and asks for confirmation before Core creates anything.

MCP

@kaddo/mcp exposes read-only tools — kaddo_integrations_list, kaddo_integrations_status, kaddo_integrations_work_items, kaddo_integrations_work_item. Reading an external item through MCP never materializes a Kaddo Work Item; import stays a human-confirmed action.

Reference (mock) adapter

Kaddo ships a deterministic, offline mock adapter that exercises the whole contract — registry, connection, listing, pagination, read, normalization and error simulation — with no network or credentials. It is the reference a custom adapter can be checked against.

integrations:
- id: mock-work-source
adapter: mock
enabled: true
config:
simulate: available # or unauthorized · rate-limited · unavailable · timeout

Writing a custom adapter

  1. Implement the IntegrationAdapter contract.
  2. Declare your capabilities honestly.
  3. Normalize provider data into ExternalWorkItem (keep extras in rawMetadata).
  4. Register the adapter in the registry.
  5. Validate configuration; reference secrets by environment variable, never store them.
  6. Never write Kaddo artifacts directly — return normalized data and let the integration service and Core own materialization.

External Work Item Discovery & Filtering

VS-104 adds discovery — the ability to query all enabled integrations and see their external work items in a unified view, without importing any of them. This is the “browse before you buy” layer.

Discovery

discoverExternalWorkItems queries every enabled integration that supports list in parallel. Partial failures are isolated: if one integration errors, the others still return their results.

Terminal window
kaddo integrations discover # discover items from all integrations
kaddo integrations discover --types Bug,Feature # filter by type
kaddo integrations discover --search billing # text search

Admin exposes the External Items view, which shows items grouped by integration with type badges, status indicators, labels, and assignees. Each item has an Import action (the same human-confirmed flow from VS-102) and an Open link to the provider’s URL.

Load More — the discovery endpoint supports per-integration cursor-based pagination. When an integration reports hasMore, the UI shows a Load more button below that integration’s section. Clicking it fetches the next page using the integration’s cursor and appends the results.

Integration Filters vs UI Filters

Filters come in two flavors:

Persisted in YAMLApplies to
Integration FiltersYes — .kaddo/integrations.ymlEvery query to this integration
UI FiltersNo — temporary, client-side onlyThe current discovery session

Integration filters set the scope of what Kaddo queries from a provider (e.g. “only bugs from the backend label”). UI filters narrow that further at runtime (e.g. “only the ones assigned to Alice”).

Both share the same ExternalWorkItemFilters shape:

integrations:
- id: github-dotear
adapter: github
enabled: true
filters:
statuses:
- Open
- In Progress
labels:
- backend

The service merges them before calling the adapter — overlay fields (UI) take precedence over base fields (integration) when present.

Filter Capabilities

Each adapter declares which filter fields it supports via filterCapabilities in its metadata. Admin uses this to render only the filter controls the adapter can actually handle — unsupported filters are not shown, not silently ignored.

Provider-Driven Integration Setup

VS-103A introduces a provider-driven creation flow: instead of manually filling adapter-specific fields, Admin generates the entire form from adapter metadata — no hardcoded provider logic.

Provider Catalog

The creation flow starts with a visual grid of available providers, sourced directly from the Integration Registry. Each tile shows the adapter’s icon, display name, description and capabilities. Selecting a provider moves to the configuration step.

Adding a new adapter to the registry automatically makes it appear in the catalog — no Admin changes needed.

Schema-based forms

Each adapter declares a configSchema and secretSchema in its metadata. Admin renders a dynamic form from these schemas at runtime. Supported field types:

TypeRenders as
stringText input
urlURL input (validated)
passwordMasked input
numberNumber input
booleanCheckbox
selectDropdown from options
multi-selectToggle buttons from options

Each field carries required, label, description, placeholder, options and defaultValue. Validation runs against the schema before saving — required-field checks, URL format, type coercion, option membership.

Verify on create

The creation flow includes a Verify & Save step: after filling configuration and secrets, the integration is created, secrets are set, and verifyConnection runs automatically. If verification fails, the integration is removed and the error is shown — so no broken integrations linger.

Provider icons

Adapters declare an icon field in metadata. Admin maps this to a visual icon via ProviderIcon — a simple emoji-based mapping that is extensible without external assets.

Multiple instances

The same adapter can back multiple integrations — each with independent configuration, secrets and connection status. For example, two separate GitHub integrations pointing at different repositories.

Adapter unavailable

If an adapter is no longer registered but its configuration persists, Admin shows the integration with an “Adapter unavailable” label and hides the Verify button. Configuration is preserved — the adapter can be re-registered later without data loss.

External Work Item Import & Refinement Handoff

VS-105 adds a human-initiated import action that converts a discovered External Work Item into a native Kaddo Draft Work Item — preserving full provenance and an original snapshot of the external state at import time.

Import pipeline

  1. Disabled check — if the integration is disabled, the import is rejected immediately.
  2. Concurrent lock — an in-process lock prevents two simultaneous imports of the same item (guards against the TOCTOU race between duplicate check and Work Item creation).
  3. User selects an External Item in the discovery view and chooses a Kaddo Work Item type.
  4. Kaddo re-reads the item from the adapter for freshness.
  5. Duplicate check runs against integrationId#externalId — if already imported, the existing Work Item is returned (idempotent import, no duplicate created).
  6. A Draft Work Item is created through the standard createWorkItem Core boundary.

The pipeline is provider-neutral: once an item is normalized to ExternalWorkItem, Core has no knowledge of the original provider.

Original snapshot

At import time, the external item’s title, description, type, status, labels, assignee and timestamps are captured as an original_snapshot in the Work Item frontmatter. This snapshot is immutable — it records what the external item looked like when it was imported, regardless of later changes on either side.

Provenance

Each imported Work Item carries full traceability in its source metadata:

FieldValue
typeexternal
providerAdapter id (e.g. mock, github)
integrationKaddo integration id
idExternal item id
urlLink back to the external item
imported_atISO timestamp of import
external_updated_atExternal item’s last update at import time

Imported badge in discovery

After importing an item, the discovery view shows an Imported badge with a link to the Kaddo Work Item. The Import button is hidden for already-imported items.

Rich provenance in Work Item detail

The Work Item detail view shows an External provenance section (provider, external ID, integration, timestamps, and an “Open in provider” link) and an Original snapshot section (title, description, type, status, labels, assignee).

Refinement handoff

Imported Work Items enter the standard Kaddo refinement pipeline. The snapshot and provenance survive refinement — no provider-specific refinement logic exists.

Resilience

  • Deleting the source integration does not affect imported Work Items.
  • If the adapter becomes unavailable, imported Work Items remain fully functional.
  • Import is a one-time snapshot, not continuous sync.

Jira Adapter (VS-106)

The first production adapter connects Kaddo to Jira Cloud using email + API Token (Basic Auth). It is fully provider-neutral from Kaddo’s perspective — Admin, Core and the refinement pipeline need no Jira-specific logic.

Configuration

integrations:
- id: my-jira
adapter: jira
enabled: true
config:
baseUrl: https://mycompany.atlassian.net
email: user@example.com
secrets:
apiToken: my-jira.apiToken
FieldSchema typeRequiredDescription
baseUrlurlYesJira Cloud instance URL
emailstringYesAtlassian account email
apiTokenpassword (secret)YesJira API Token (never stored in YAML)

Capabilities

CapabilitySupported
listYes
readYes
importYes
writeNo (future)

JQL filter generation

The adapter translates the normalized ExternalWorkItemFilters into JQL automatically:

Filter fieldJQL clause
projectsproject IN (...)
typesissuetype IN (...)
statusesstatus IN (...)
labelslabels IN (...)
assigneesassignee IN (...)
updatedAfterupdated >= "..."
search(summary ~ "..." OR description ~ "...")
providerQueryRaw JQL appended directly

Values are properly escaped. When no filters are provided, the adapter defaults to updated >= -30d ORDER BY updated DESC (the Jira enhanced search endpoint requires bounded JQL).

The providerQuery capability is labeled “JQL” in Admin, so users know they can write raw Jira Query Language when the normalized filters are not enough.

ADF to Markdown

Jira Cloud stores descriptions in Atlassian Document Format (ADF) — a rich JSON structure. The adapter converts ADF to Markdown at normalization time so the rest of Kaddo works with plain text.

Supported ADF nodes: doc, paragraph, heading (levels 1–6), bulletList, orderedList, listItem, blockquote, codeBlock (with language), rule, hardBreak, text with marks (strong, em, code, strike). Media nodes are safely skipped.

Error normalization

HTTP statusIntegration error code
400 (JQL)INVALID_QUERY
401UNAUTHORIZED
403FORBIDDEN
404NOT_FOUND
410UNAVAILABLE
429RATE_LIMITED
500+UNAVAILABLE
AbortErrorTIMEOUT

A 400 response whose body contains JQL/query/field keywords is mapped to INVALID_QUERY so the UI can show a specific message about filter syntax. Other 400 errors fall through to PROVIDER_ERROR.

Projects filter

VS-106 adds a projects field to the normalized filter model (ExternalWorkItemFilters). This is a common concept across providers (Jira has projects, GitHub has repos, Azure DevOps has projects) and avoids forcing users to resort to providerQuery for basic project scoping.

External Content Import (VS-109)

VS-109 adds a second import path — raw text or Markdown — independent of any Integration Adapter. Content from ChatGPT, Claude, a wiki, a Notion export, or a plain .md file enters Kaddo through the same Core boundary as an adapter import, producing a canonical Draft Work Item with full provenance.

The critical invariant: import never executes a Work Item. The content is data — parsed, normalized and registered — but implementation requires a separate, explicit action.

Raw content (text / Markdown / Kaddo-format Markdown)
↓
detectFormat() (kaddo-frontmatter | markdown-frontmatter | markdown | plain-text)
↓
parseContent() (extracts title, type candidate, sections, candidate fields)
↓
normalizeImportFields() (validates type, builds provenance, captures original snapshot)
↓
computeContentHash() (SHA-256 of trimmed input)
↓
findDuplicateByHash() (walks existing WIs — returns existing WI if same hash)
↓
createWorkItem() (Core — always Draft, always new ID)
↓
buildRefinementHandoff() (agent-agnostic refinement instructions)

Supported formats

FormatDetected when
kaddo-frontmatterYAML frontmatter with type: work-item or id: WI-NNN
markdown-frontmatterYAML frontmatter without Kaddo markers
markdown## headings, no frontmatter
plain-textEverything else

Lifecycle-controlled fields

External content may carry fields like status: completed or id: WI-001. Kaddo discards these lifecycle-controlled fields during normalization — they never override the internal lifecycle:

id, status, phase, knowledge_level, refined_by, implemented_by, closed_by, ready_at, generated_by, template_version, implementation_status, validation_status, release_status.

The imported Work Item is always created as a Draft with a Kaddo-generated ID.

Duplicate detection

A SHA-256 hash of the trimmed input is stored in source.source_hash. Before creating a Work Item, the pipeline walks all existing Work Items and checks their source_hash. If a match is found, the existing Work Item is returned — no duplicate is created.

Provenance

Each content-imported Work Item carries traceability in its source metadata:

FieldValue
typechat (MCP/conversation) or external (CLI/Admin)
imported_atDate of import
source_formatDetected format (kaddo-frontmatter, markdown, etc.)
source_hashSHA-256 of the original content

An original_snapshot captures the external title, description, type and status at import time.

CLI

Terminal window
kaddo work-item import ./feature.md # import from a file
kaddo work-item import --text "Add CloudWatch alert" -y # import from inline text, skip confirmation
kaddo work-item import ./spec.md --type bugfix # override the detected type

The command previews the detected format, title and type before asking for confirmation. Pass -y or --yes to skip the prompt.

MCP

The kaddo_work_item_import tool accepts raw content and returns the created Work Item with executed: false and an optional refinement handoff:

{
"content": "## Add CloudWatch alert\n\nMonitor API latency p99 and alert when > 500ms.",
"type": "feature",
"source": "chat"
}

The response includes workItemId, path, status: "draft", sourceFormat, and refinementHandoff with the recommended agent and skill for refinement.

Safety boundary

  • Content is data, never instructions — the parser extracts structure, never executes commands.
  • Input is capped at 100 KB.
  • The result always includes executed: false — a signal to any consumer that the Work Item has been registered but not acted upon.

Out of scope (built on this foundation later)

Bidirectional sync, polling, webhooks, status/comment/attachment sync, pushing or updating external issues, and external OAuth UI are not part of the foundation. They build on top of it — without redesigning the integration model.

Created by Julian Dario Luna Patiño · v3.100.0