Skip to content

Jira platform adapter: talk to Archon inside each ticket (conversation point, not end-to-end resolver) #1750

@coleam00

Description

@coleam00

Summary

Add a Jira platform adapter to Archon so that each Jira issue becomes a live, persistent conversation with Archon — exactly the way each Slack thread is its own conversation today. You @-mention Archon in a ticket comment, talk to it, and it runs workflows and replies back as comments on that same ticket. Many tickets open at once = many parallel conversations, just like Slack threads.

This introduces a new adapter category — task-management — sibling to the existing chat/ and forge/ categories. Jira (and later Linear / Asana / ClickUp) are work trackers, not messaging platforms and not code forges, so they get their own category rather than being shoehorned under chat/.

The important framing: the adapter is the conversation point, not an end-to-end ticket-resolver. It does not "watch the backlog and auto-fix tickets." It routes a conversation into the orchestrator and posts the result back. Anything that actually resolves a ticket — explore → plan → implement → open a PR → transition the ticket from To Do → In Progress → Done — lives in the workflows we run from inside that conversation, with a separate Jira skill giving those workflows the ability to move the ticket around.

Motivation

Archon already triggers from GitHub, Slack, Telegram, and Discord. The teams we work with run their work out of Jira, not GitHub Issues. Today there's no way to talk to Archon from inside the place that work actually lives. This adapter closes that gap: a ticket is a conversation, and from that conversation you can drive any workflow Archon knows about.

The core reframe (please read before implementing)

There are two ways one could build "Jira + Archon," and we are deliberately choosing the first:

  1. Conversation point (this issue). A Jira issue = a conversation/thread. A human (or, later, a workflow) talks to Archon inside the ticket via comments. The adapter's only job is: receive a comment → hand it to handleMessage → post the reply back as a comment. The orchestrator picks the workflow off the message, exactly as it does for Slack/GitHub.

  2. End-to-end auto-pickup. A new ticket appears and Archon silently fixes it start-to-finish. Not what we're building. That behavior, when we want it, is a workflow concern, not an adapter concern.

The Jira adapter is its own task-management category. It borrows the conversation semantics of the chat family (conversation-per-ticket, @mention to talk, lightweight — no repos/codebases/clones/worktrees) and borrows the webhook transport of the forge family (Atlassian pushes events over HTTP rather than holding a socket open). It is not a forge — it hosts no code — and it is not chat — it's a work tracker, which is why it gets its own folder.

How the existing adapters work (grounding for the implementation)

Archon has two adapter families today, both implementing IPlatformAdapter (packages/core/src/types/index.ts:118): sendMessage, ensureThread, getStreamingMode, getPlatformType, start, stop (+ optional sendStructuredEvent, emitRetract). The new task-management category is a third sibling.

Chat adapterschat/slack, chat/telegram, community/chat/discord:

  • Transport: WebSocket / polling. start() opens the connection.
  • Lightweight — no repo cloning, no codebase rows, no isolation lifecycle.
  • conversationId is a thread identifier. Slack uses channel:thread_ts (adapter.ts:190-199); each thread is one conversation, so parallel threads are parallel conversations.
  • Pattern: the adapter exposes onMessage(handler); the server wires that handler to handleMessage inside lockManager.acquireLock (server/src/index.ts:423-458 for Slack).
  • Isolation hint is { workflowType: 'thread', workflowId: conversationId }.
  • Auth = a user allowlist (isSlackUserAuthorized).

Forge adaptersforge/github, community/forge/{gitea,gitlab}:

  • Transport: webhooks. start() is a no-op; the route is registered in the server (server/src/index.ts:483-511 registers /webhooks/github).
  • Heavy lifecycle — clone repos, create codebase rows, manage worktrees/isolation (ensureRepoReady, getOrCreateCodebaseForRepo in forge/github/adapter.ts).
  • conversationId is owner/repo#number (buildConversationId, adapter.ts:431).
  • The adapter calls handleMessage itself, from inside handleWebhook under a lock (adapter.ts:942-948).
  • Auth = HMAC signature verify (createHmac + timingSafeEqual, adapter.ts:267) + user allowlist.
  • Self-trigger guard: a hidden <!-- archon-bot-response --> marker is appended to every bot comment and ignored on the way back in (adapter.ts:51, :741-754).

The orchestratorhandleMessage (packages/core/src/orchestrator/orchestrator-agent.ts:643) is the single, platform-agnostic entry point. It creates/looks up the conversation, inherits thread context, lets the orchestrator agent pick a workflow (or answer directly), and routes the final result back to the triggering adapter via platform.sendMessage(conversationId, …) (orchestrator/orchestrator.ts). So once JiraAdapter.sendMessage exists, the comment-back is essentially free.

The task-management adapter takes the transport mechanics from the forge family (webhook handler + registered route + secret verification + self-trigger guard) and the conversation semantics from the chat family (conversation = ticket, workflowType: 'thread', no codebase/clone/worktree management).

Proposed design

New category + location: packages/adapters/src/community/task-management/jira/ — a new task-management category folder, sibling to community/chat/ and community/forge/. Add a community/task-management/README.md documenting the category conventions (mirrors the existing community/chat/README.md and community/forge/README.md). Files: adapter.ts, auth.ts, types.ts, index.ts, adapter.test.ts. Export JiraAdapter from packages/adapters/src/index.ts.

getPlatformType()'jira' (the platform identifier is independent of the category folder). getStreamingMode()'batch' (comment back once, no streaming spam).

conversationId = the Jira issue key (e.g. DF-123). One key = one conversation. This is the direct analog of Slack's channel:thread_ts — parallel tickets are parallel conversations. ensureThread returns the id unchanged (a ticket is already its own thread).

handleWebhook(payload, rawHeadersOrSecret) (mirrors the forge shape, registered at /webhooks/jira in server/src/index.ts):

  1. Verify the request — Atlassian webhooks do not HMAC-sign the body the way GitHub does, so this is the real divergence from the forge template. Use a configured shared secret (query param / header) plus an accountId allowlist, not timingSafeEqual over a signature header. Document this clearly in auth.ts.
  2. Parse the event — we care about comment_created (and optionally issue_created / issue_updated later). Extract issue key, comment body, author accountId.
  3. Self-trigger guard — ignore comments authored by the bot account, and/or carry over the forge's hidden-marker trick so the bot never answers itself.
  4. @-mention gate — only act when the comment mentions the bot (mirror the chat/forge @mention activation; Jira mentions are ADF mention nodes, so the parse differs from a plain @Name regex).
  5. Resolve conversationId = issue key, gather light thread context (recent comments on the ticket → threadContext), and call handleMessage(this, conversationId, message, { threadContext, isolationHints: { workflowType: 'thread', workflowId: conversationId } }) under lockManager.acquireLock.

sendMessage(conversationId, message) — POST a comment to POST /rest/api/3/issue/{key}/comment, Basic auth (email + API token), body in Atlassian Document Format (ADF). Reuse splitIntoParagraphChunks for long messages, and append the self-trigger marker.

start() / stop() — effectively no-ops like the forge adapters (the route does the listening); start() can validate the API token.

Config (env): JIRA_BASE_URL, JIRA_EMAIL, JIRA_API_TOKEN, JIRA_WEBHOOK_SECRET, JIRA_ALLOWED_ACCOUNT_IDS, JIRA_BOT_MENTION. Gate adapter init in server/src/index.ts on the presence of these (same pattern as the other adapters).

What the adapter explicitly does NOT do

  • It does not clone repos, create codebases, or manage worktrees/isolation. (That's the forge lifecycle; a task-management conversation doesn't own a repo.)
  • It does not fix the ticket, open PRs, or transition status. Those are workflow actions.
  • It does not map a ticket to a repo end-to-end. Repo selection is a workflow/conversation concern. (A small optional project→repo hint could pre-link a fresh ticket conversation to a codebase, but that's a convenience, not the adapter doing the work.)

Companion deliverable: the Jira skill (separate, but worth scoping here)

For a workflow to actually "handle the ticket and move it around," it needs to act on Jira from inside a run. Archon workflow nodes/agents can already declare skills: string[] (packages/workflows/src/schemas/dag-node.ts:147). So we add a jira skill that wraps the Jira REST API for use inside workflows:

  • Transition status (To Do → In Progress → In Review → Done).
  • Comment / link the resulting GitHub PR back onto the ticket.
  • Read issue/subtask details, set labels, etc.

This keeps the separation clean: the adapter is the mouth (conversation in/out); the skill is the hands (mutating the ticket), invoked by workflows. Two ticket actions — comment-back and status-transition — could overlap; comment-back is the adapter's sendMessage, transitions belong to the skill.

Implementation checklist

  • New community/task-management/ category folder + README.md documenting conventions.
  • community/task-management/jira/{adapter,auth,types,index}.ts implementing IPlatformAdapter.
  • Webhook verification via shared secret + accountId allowlist (auth.ts).
  • handleWebhook — parse comment_created, @mention gate, self-trigger guard, key→conversationId, handleMessage under lock.
  • sendMessage — ADF comment via POST /rest/api/3/issue/{key}/comment, chunking + marker.
  • Register adapter + /webhooks/jira route in packages/server/src/index.ts, gated on env.
  • Export from packages/adapters/src/index.ts.
  • Lazy cachedLog/getLog() logger; {domain}.{action}_{state} log naming.
  • adapter.test.ts in an isolated bun test batch (per the community READMEs — mock.module() is process-global).
  • Companion jira workflow skill (transitions + PR-link-back) — can be a follow-up PR.

Open questions / decisions

  • Category name: going with task-management. Alternatives considered: tracker, issues, pm. Speak up if you prefer a shorter slug.
  • Webhook delivery in dev: tunnel vs. replaying a captured payload into /webhooks/jira.
  • @mention parsing: Jira mentions are ADF mention nodes (accountId), not @Name text — confirm the activation rule.
  • Bot identity for the self-trigger guard: dedicated bot accountId vs. hidden-marker-only.
  • Repo association: leave entirely to workflows, or allow an optional project→codebase pre-link on first contact?

Out of scope (follow-ups)

  • Auto-pickup of new tickets without a human in the loop.
  • Status transitions (belongs to the jira skill).
  • Multi-project / multi-site configuration.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions