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:
-
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.
-
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 adapters — chat/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 adapters — forge/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 orchestrator — handleMessage (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):
- 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.
- Parse the event — we care about
comment_created (and optionally issue_created / issue_updated later). Extract issue key, comment body, author accountId.
- 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.
@-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).
- 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
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.
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 existingchat/andforge/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 underchat/.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:
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.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-managementcategory. It borrows the conversation semantics of the chat family (conversation-per-ticket,@mentionto 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(+ optionalsendStructuredEvent,emitRetract). The newtask-managementcategory is a third sibling.Chat adapters —
chat/slack,chat/telegram,community/chat/discord:start()opens the connection.conversationIdis a thread identifier. Slack useschannel:thread_ts(adapter.ts:190-199); each thread is one conversation, so parallel threads are parallel conversations.onMessage(handler); the server wires that handler tohandleMessageinsidelockManager.acquireLock(server/src/index.ts:423-458for Slack).{ workflowType: 'thread', workflowId: conversationId }.isSlackUserAuthorized).Forge adapters —
forge/github,community/forge/{gitea,gitlab}:start()is a no-op; the route is registered in the server (server/src/index.ts:483-511registers/webhooks/github).codebaserows, manage worktrees/isolation (ensureRepoReady,getOrCreateCodebaseForRepoinforge/github/adapter.ts).conversationIdisowner/repo#number(buildConversationId,adapter.ts:431).handleMessageitself, from insidehandleWebhookunder a lock (adapter.ts:942-948).createHmac+timingSafeEqual,adapter.ts:267) + user allowlist.<!-- archon-bot-response -->marker is appended to every bot comment and ignored on the way back in (adapter.ts:51,:741-754).The orchestrator —
handleMessage(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 viaplatform.sendMessage(conversationId, …)(orchestrator/orchestrator.ts). So onceJiraAdapter.sendMessageexists, the comment-back is essentially free.The
task-managementadapter 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 newtask-managementcategory folder, sibling tocommunity/chat/andcommunity/forge/. Add acommunity/task-management/README.mddocumenting the category conventions (mirrors the existingcommunity/chat/README.mdandcommunity/forge/README.md). Files:adapter.ts,auth.ts,types.ts,index.ts,adapter.test.ts. ExportJiraAdapterfrompackages/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'schannel:thread_ts— parallel tickets are parallel conversations.ensureThreadreturns the id unchanged (a ticket is already its own thread).handleWebhook(payload, rawHeadersOrSecret)(mirrors the forge shape, registered at/webhooks/jirainserver/src/index.ts):timingSafeEqualover a signature header. Document this clearly inauth.ts.comment_created(and optionallyissue_created/issue_updatedlater). Extract issue key, comment body, author accountId.@-mention gate — only act when the comment mentions the bot (mirror the chat/forge@mentionactivation; Jira mentions are ADFmentionnodes, so the parse differs from a plain@Nameregex).conversationId= issue key, gather light thread context (recent comments on the ticket →threadContext), and callhandleMessage(this, conversationId, message, { threadContext, isolationHints: { workflowType: 'thread', workflowId: conversationId } })underlockManager.acquireLock.sendMessage(conversationId, message)— POST a comment toPOST /rest/api/3/issue/{key}/comment, Basic auth (email + API token), body in Atlassian Document Format (ADF). ReusesplitIntoParagraphChunksfor 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 inserver/src/index.tson the presence of these (same pattern as the other adapters).What the adapter explicitly does NOT do
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 ajiraskill that wraps the Jira REST API for use inside workflows: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
community/task-management/category folder +README.mddocumenting conventions.community/task-management/jira/{adapter,auth,types,index}.tsimplementingIPlatformAdapter.auth.ts).handleWebhook— parsecomment_created,@mentiongate, self-trigger guard, key→conversationId,handleMessageunder lock.sendMessage— ADF comment viaPOST /rest/api/3/issue/{key}/comment, chunking + marker./webhooks/jiraroute inpackages/server/src/index.ts, gated on env.packages/adapters/src/index.ts.cachedLog/getLog()logger;{domain}.{action}_{state}log naming.adapter.test.tsin an isolatedbun testbatch (per the community READMEs —mock.module()is process-global).jiraworkflow skill (transitions + PR-link-back) — can be a follow-up PR.Open questions / decisions
task-management. Alternatives considered:tracker,issues,pm. Speak up if you prefer a shorter slug./webhooks/jira.@mentionparsing: Jira mentions are ADFmentionnodes (accountId), not@Nametext — confirm the activation rule.Out of scope (follow-ups)
jiraskill).