Junior’s Slack output path is still mostly a slack-mrkdwn pipeline with an optional footer block. That was a reasonable baseline for correctness and delivery stability, but Slack’s rendering surface has moved well beyond that.
As of April 16, 2026, Slack officially supports new Block Kit blocks including alert, card, and carousel, alongside newer agent-oriented surfaces like plan and task_card. Work Objects are also GA and provide a separate path for durable, actionable entity previews inside Slack.
This issue proposes a concrete design plan for taking Junior from “good plaintext in Slack” to “strong native Slack presentation,” without letting the model emit raw Block Kit JSON or weakening the current reply-delivery contract.
Problem
Today, Junior’s final visible Slack replies are effectively constrained to:
- finalized thread posts
- top-level text fallback
- optional
section + context block usage for footer metadata
That leaves a lot of product value on the table:
- the agent cannot deliberately choose richer layouts for different result shapes
- plugins cannot define reusable domain-specific Slack presentations
- durable entities like incidents/issues/tickets have no first-class Work Object path
- the current prompt actively biases the model away from richer layouts like tables
- the local Slack boundary does not yet reflect the capabilities already available in Slack itself
The result is that Junior is reliable in Slack, but not especially native to Slack.
Current state
Relevant local constraints:
- final visible replies are standardized around finalized thread posts, not visible streamed prose
packages/junior/src/chat/slack/footer.ts narrows SlackMessageBlock to section | context
packages/junior/src/chat/slack/reply.ts only attaches blocks for the final footer-bearing chunk
packages/junior/src/chat/prompt.ts still instructs the model to emit slack-mrkdwn and avoid tables unless explicitly requested
- the plugin model is capability/credential/skill oriented today, not presentation oriented
Relevant local opportunity:
- the installed
chat / @chat-adapter/slack stack already supports useful rich surfaces today, including:
- cross-platform
Card(...)
- native Slack
table
- native Slack
plan and task_card
- structured progress chunks like
task_update and plan_update
- Slack-only
stopBlocks
This means our first improvement should not be “invent a new rendering DSL.” It should be “stop collapsing everything back to plain text plus a footer.”
Goals
- add a concrete, code-owned rendering architecture for rich Slack replies
- let the agent choose high-level render intents, not raw Block Kit JSON
- support plugin-owned templates for domain objects like Sentry issues, Linear tickets, and GitHub PRs
- preserve top-level fallback text and the current finalized-reply contract
- use Work Objects for durable entities instead of forcing everything through message blocks
- roll this out in phases, starting with surfaces our installed stack already supports
Non-goals
- do not reintroduce visible freeform text streaming as the default reply path
- do not let the model author arbitrary Slack block payloads directly
- do not make plugin manifests carry raw presentation JSON as static metadata
- do not treat logs, status telemetry, or tracing output as rendering contracts
- do not try to solve all Slack surfaces at once before we have a render-intent layer
Design principles
- keep the rendering contract code-owned and reviewable
- keep the model at the level of choosing presentation intent, not composing transport payloads
- preserve accessibility and notification fallbacks with top-level text
- treat final replies, in-flight progress, and durable entities as different product surfaces
- make plugin-owned rendering possible without turning plugin manifests into UI configuration blobs
- use Slack-native richness only behind the Slack boundary
- degrade gracefully when block support, SDK support, or payload limits get in the way
Proposal
1. Introduce a Slack render-intent layer
Add an internal render contract between assistant output and Slack delivery.
Representative intent types:
plain_reply
alert
summary_card
comparison_table
result_carousel
progress_plan
work_object_reference
The model should decide among these intents at a high level. Core code should build the actual Slack view model and fallback text.
This is the main boundary change: the model stops “writing Slack,” and instead starts “choosing how the answer should be presented.”
2. Separate three rendering lanes
Treat these as distinct product surfaces with different policies:
- final reply surfaces:
plain_reply
alert
summary_card
comparison_table
result_carousel
- in-flight progress:
- assistant status
- optional
plan
- optional
task_card
- durable entities:
- Work Objects for records users will revisit, search, expand, and act on
This prevents us from mixing incompatible goals into one abstraction.
3. Add a plugin-owned renderer registry
Extend the plugin system with a code-owned registry for presentation builders.
Each renderer should provide something like:
match(result, context): score
buildIntent(entity, context): SlackRenderIntent
buildFallbackText(entity): string
buildActions(entity, context): Action[]
buildWorkObject?(entity, context): WorkObjectPayload
This gives plugins ownership over domain presentation while keeping the Slack runtime in control of final rendering, fallbacks, and safety rules.
4. Prefer existing SDK support first
Phase 1 should use capabilities that already exist in our installed stack before we add new Slack-specific block abstractions in Junior.
That means using:
Card(...) for better single-object summaries
- native
table when compact comparison is the right surface
- native
plan / task_card for structured progress
- final-message actions where follow-up workflows matter
This gives us a meaningful first step without waiting for new local type support for every newer Slack block.
5. Add Slack-native block support behind a Slack-only renderer
For blocks that are newer than our current local abstractions, add a Slack-specific renderer behind the outbound boundary.
Initial targets:
This renderer must always emit top-level fallback text and must degrade cleanly to existing card/text layouts when support is unavailable.
6. Use Work Objects for durable records
Use Work Objects for records where persistent identity matters more than one-off message formatting.
Likely first candidates:
- Sentry incident
- Sentry issue
- Linear task
- GitHub issue or PR
Work Objects should be plugin-owned and should include actions where appropriate.
Layout policy
Use one hero surface per message.
Recommended selection rules:
alert
- warnings
- blockers
- partial failures
- auth required
- destructive decisions
summary_card
- one important object
- a few key fields
- 1-3 next actions
comparison_table
- compact numeric or categorical comparisons
- dense but still scannable data
result_carousel
- 2-5 comparable objects the user should browse
- good when the user needs to pick or inspect one item from several
progress_plan
- multi-step, long-running work
- investigation progress
- structured tool execution
work_object_reference
- durable record that should expand into richer detail and remain actionable in Slack
For chart-like content, continue to treat Slack as image + concise textual takeaway + accessible download path until Slack ships an official chart surface.
Native agent behavior
We should explicitly teach the agent when rich presentation is the right answer.
Examples:
- use
alert instead of prose when the main point is urgency, risk, or a required human decision
- use
summary_card when the answer is best understood as one record with status, owner, priority, and actions
- use
result_carousel when the user asked for “top few” objects and should be able to browse them
- use
comparison_table when the answer is driven by small structured comparisons
- use
progress_plan for long-running investigations or multi-step tool work
- use
work_object_reference when the object is durable and should exist in Slack as a first-class entity, not just as a formatted message
This should eventually replace the current narrow “emit slack-mrkdwn” prompting bias.
Plugin-defined templates
Plugins should define reusable templates for domain objects, not raw block JSON.
Examples:
Sentry
- issue summary card
- incident summary card
- regression alert
- issue search-results carousel
- incident Work Object
- issue Work Object
Potential card fields:
- title
- level or severity
- status
- assignee
- affected users
- last seen
- release
- service or project
Potential actions:
- open
- assign to me
- resolve
- summarize
- escalate
Linear
- issue summary card
- project or initiative carousel
- task Work Object
Potential card fields:
- state
- priority
- assignee
- cycle
- project
- due date
Potential actions:
- open
- assign to me
- move to in progress
- add comment
GitHub
- PR summary card
- issue summary card
- review queue carousel
- issue or PR Work Object
Potential card fields:
- repo
- state
- author
- assignee
- checks
- review status
- labels
Potential actions:
- open
- assign
- review
- summarize
- close
Work Objects strategy
Work Objects should not be treated as “fancier cards.” They are a separate surface with different product value.
Use them when we want:
- richer previews from shared links
- persistent object identity in Slack
- expandable detail in a flexpane
- cross-conversation context
- searchable structured metadata
- in-context actions on durable records
Recommended first Work Object target:
Reasoning:
- incident is a strong fit for Slack’s Work Object model
- incident state is durable and collaborative
- the value of flexpane detail is obvious
- actions are meaningful and time-sensitive
Recommended second target:
- Sentry issue or Linear task
Rollout plan
Phase 1: Foundation
- introduce
SlackRenderIntent
- introduce a richer internal reply model for Slack
- decouple final-reply blocks from the current footer-only shape
- preserve the current finalized-reply contract
Deliverable:
- the final reply path can carry rich presentation intents without changing visible delivery semantics
Phase 2: Existing-capability uplift
- enable richer final replies via
Card(...)
- enable native
table for compact structured comparisons
- enable native
plan / task_card where useful for progress and possibly finalized summaries
- add support for final-message actions where they improve follow-up workflows
Deliverable:
- Junior can produce at least one strong rich final reply surface using capabilities already present in the installed stack
Phase 3: Slack-native richness
- add explicit support for
alert, card, and carousel in the Slack renderer
- keep graceful fallbacks for unsupported cases
- ensure fallback
text remains first-class
Deliverable:
- Junior can deliberately choose richer Slack-native surfaces without degrading accessibility or notification behavior
Phase 4: Plugin templates
- ship first-party templates for Sentry, Linear, and GitHub
- add scoring and selection rules so the system can choose template vs plain text
- keep template ownership inside plugin code
Deliverable:
- at least one domain template is used automatically when it is clearly the best presentation for the result shape
Phase 5: Work Objects
- implement plugin-owned Work Object mappings for the highest-value durable entities
- start with one incident-type object and one issue/task-type object
- add action handling and detail presentation rules
Deliverable:
- at least one durable plugin entity is represented as a Work Object rather than only as a formatted message
Phase 6: Prompting and evals
- replace the current “emit
slack-mrkdwn” bias with “choose the best render intent”
- remove the blanket anti-table guidance
- add evals for:
- layout choice
- fallback behavior
- block-limit handling
- accessibility
- template selection
- action correctness
- graceful degradation
Deliverable:
- layout choice becomes deliberate and testable rather than incidental
Acceptance criteria
- Junior can choose among code-owned render intents instead of only plain Slack markdown
- final replies can render at least one richer surface beyond footer context
- plugin templates can own domain-specific rendering without emitting raw Block Kit JSON
- Slack-only richness stays behind the Slack boundary and does not leak into generic reply contracts
- top-level fallback text remains present for accessibility and notifications
- at least one domain template ships for Sentry
- at least one domain template ships for Linear or GitHub
- at least one Work Object-backed entity exists
- prompting and evals are updated so layout choice is deliberate and testable
Risks
- SDK/type lag:
- Slack docs may expose blocks before our installed stack types do
- product regression risk:
- richer layouts can accidentally weaken the current finalized-reply delivery guarantees
- accessibility risk:
- blocks without good top-level fallback text will degrade notifications and screen-reader experience
- overuse risk:
- if everything becomes a card/carousel, Slack output becomes noisy rather than better
- plugin sprawl risk:
- presentation ownership can become fragmented if the contract is too loose
- fallback complexity:
- block limits, unsupported surfaces, and partial support can create brittle UX if not handled centrally
Open questions
- should
plan / task_card remain progress-only, or also be available for finalized investigation summaries?
- should plugin renderers live in plugin code only, or should core provide shared primitives for common record patterns?
- what is the minimum viable action model for cards and Work Objects without creating unsafe side effects?
- which entity should be the first Work Object: Sentry incident, Sentry issue, or Linear task?
- how much presentation choice should be model-directed versus policy-directed?
- do we want one cross-platform render-intent layer with Slack-specific lowering, or a Slack-first layer that later generalizes?
References
External Slack docs:
- New Block Kit blocks and Streaming API method updates
- Alert block
- Card block
- Carousel block
- Table block
- Apps can now display thinking steps to users
- Developing an agent
- Introducing Work Objects
- Implementing Work Objects
- Work Objects overview
- Designing with Block Kit
Examples in the wild:
- Slack Work Objects partner examples
- PagerDuty Slack Work Objects GA
- Box enhanced integration with Slack Work Objects
- Linear Slack integration
Relevant local code/spec references:
packages/junior/src/chat/slack/footer.ts
packages/junior/src/chat/slack/reply.ts
packages/junior/src/chat/slack/outbound.ts
packages/junior/src/chat/slack/output.ts
packages/junior/src/chat/prompt.ts
specs/slack-agent-delivery-spec.md
specs/slack-outbound-contract-spec.md
specs/plugin-spec.md
packages/junior/src/chat/plugins/types.ts
Junior’s Slack output path is still mostly a
slack-mrkdwnpipeline with an optional footer block. That was a reasonable baseline for correctness and delivery stability, but Slack’s rendering surface has moved well beyond that.As of April 16, 2026, Slack officially supports new Block Kit blocks including
alert,card, andcarousel, alongside newer agent-oriented surfaces likeplanandtask_card. Work Objects are also GA and provide a separate path for durable, actionable entity previews inside Slack.This issue proposes a concrete design plan for taking Junior from “good plaintext in Slack” to “strong native Slack presentation,” without letting the model emit raw Block Kit JSON or weakening the current reply-delivery contract.
Problem
Today, Junior’s final visible Slack replies are effectively constrained to:
section+contextblock usage for footer metadataThat leaves a lot of product value on the table:
The result is that Junior is reliable in Slack, but not especially native to Slack.
Current state
Relevant local constraints:
packages/junior/src/chat/slack/footer.tsnarrowsSlackMessageBlocktosection | contextpackages/junior/src/chat/slack/reply.tsonly attaches blocks for the final footer-bearing chunkpackages/junior/src/chat/prompt.tsstill instructs the model to emitslack-mrkdwnand avoid tables unless explicitly requestedRelevant local opportunity:
chat/@chat-adapter/slackstack already supports useful rich surfaces today, including:Card(...)tableplanandtask_cardtask_updateandplan_updatestopBlocksThis means our first improvement should not be “invent a new rendering DSL.” It should be “stop collapsing everything back to plain text plus a footer.”
Goals
Non-goals
Design principles
Proposal
1. Introduce a Slack render-intent layer
Add an internal render contract between assistant output and Slack delivery.
Representative intent types:
plain_replyalertsummary_cardcomparison_tableresult_carouselprogress_planwork_object_referenceThe model should decide among these intents at a high level. Core code should build the actual Slack view model and fallback text.
This is the main boundary change: the model stops “writing Slack,” and instead starts “choosing how the answer should be presented.”
2. Separate three rendering lanes
Treat these as distinct product surfaces with different policies:
plain_replyalertsummary_cardcomparison_tableresult_carouselplantask_cardThis prevents us from mixing incompatible goals into one abstraction.
3. Add a plugin-owned renderer registry
Extend the plugin system with a code-owned registry for presentation builders.
Each renderer should provide something like:
match(result, context): scorebuildIntent(entity, context): SlackRenderIntentbuildFallbackText(entity): stringbuildActions(entity, context): Action[]buildWorkObject?(entity, context): WorkObjectPayloadThis gives plugins ownership over domain presentation while keeping the Slack runtime in control of final rendering, fallbacks, and safety rules.
4. Prefer existing SDK support first
Phase 1 should use capabilities that already exist in our installed stack before we add new Slack-specific block abstractions in Junior.
That means using:
Card(...)for better single-object summariestablewhen compact comparison is the right surfaceplan/task_cardfor structured progressThis gives us a meaningful first step without waiting for new local type support for every newer Slack block.
5. Add Slack-native block support behind a Slack-only renderer
For blocks that are newer than our current local abstractions, add a Slack-specific renderer behind the outbound boundary.
Initial targets:
alertcardcarouselThis renderer must always emit top-level fallback
textand must degrade cleanly to existing card/text layouts when support is unavailable.6. Use Work Objects for durable records
Use Work Objects for records where persistent identity matters more than one-off message formatting.
Likely first candidates:
Work Objects should be plugin-owned and should include actions where appropriate.
Layout policy
Use one hero surface per message.
Recommended selection rules:
alertsummary_cardcomparison_tableresult_carouselprogress_planwork_object_referenceFor chart-like content, continue to treat Slack as image + concise textual takeaway + accessible download path until Slack ships an official chart surface.
Native agent behavior
We should explicitly teach the agent when rich presentation is the right answer.
Examples:
alertinstead of prose when the main point is urgency, risk, or a required human decisionsummary_cardwhen the answer is best understood as one record with status, owner, priority, and actionsresult_carouselwhen the user asked for “top few” objects and should be able to browse themcomparison_tablewhen the answer is driven by small structured comparisonsprogress_planfor long-running investigations or multi-step tool workwork_object_referencewhen the object is durable and should exist in Slack as a first-class entity, not just as a formatted messageThis should eventually replace the current narrow “emit
slack-mrkdwn” prompting bias.Plugin-defined templates
Plugins should define reusable templates for domain objects, not raw block JSON.
Examples:
Sentry
Potential card fields:
Potential actions:
Linear
Potential card fields:
Potential actions:
GitHub
Potential card fields:
Potential actions:
Work Objects strategy
Work Objects should not be treated as “fancier cards.” They are a separate surface with different product value.
Use them when we want:
Recommended first Work Object target:
Reasoning:
Recommended second target:
Rollout plan
Phase 1: Foundation
SlackRenderIntentDeliverable:
Phase 2: Existing-capability uplift
Card(...)tablefor compact structured comparisonsplan/task_cardwhere useful for progress and possibly finalized summariesDeliverable:
Phase 3: Slack-native richness
alert,card, andcarouselin the Slack renderertextremains first-classDeliverable:
Phase 4: Plugin templates
Deliverable:
Phase 5: Work Objects
Deliverable:
Phase 6: Prompting and evals
slack-mrkdwn” bias with “choose the best render intent”Deliverable:
Acceptance criteria
Risks
Open questions
plan/task_cardremain progress-only, or also be available for finalized investigation summaries?References
External Slack docs:
Examples in the wild:
Relevant local code/spec references:
packages/junior/src/chat/slack/footer.tspackages/junior/src/chat/slack/reply.tspackages/junior/src/chat/slack/outbound.tspackages/junior/src/chat/slack/output.tspackages/junior/src/chat/prompt.tsspecs/slack-agent-delivery-spec.mdspecs/slack-outbound-contract-spec.mdspecs/plugin-spec.mdpackages/junior/src/chat/plugins/types.ts