kiro-discord-bot

A2A Protocol Model #

This page explains how kiro-discord-bot implements its A2A-like integration. It is the conceptual companion to Enable A2A with NATS. Operators can use this page to understand the keywords shown by /doctor, /a2a peers, /a2a status, and the built-in bot-tools MCP tools.

This project does not expose a public A2A HTTP server. It implements a custom internal NATS binding that preserves A2A task concepts where they matter: agent cards, skills, tasks, status, artifacts, cancellation, input/auth replies, durable state, and explicit authorization.

Design Summary #

AreaDecision
TransportNATS plus JetStream.
Public HTTP A2ANot implemented.
Routing identityRuntime agent ID, not Discord bot account.
DiscoveryRuntime AgentCards through JetStream KV; heartbeats through NATS liveness subjects.
Correctness boundaryJetStream plus SQLite task/policy stores.
Delivery defaultSame Discord channel/thread uses conversation collaboration. A different or unknown channel lets the receiver work in its own Discord channel/thread and return the result.
Security defaultA2A disabled while NATS_URL is empty. Remote work requires policy and usually confirmation.

Mental Model #

Discord bot process/account = transport host and runtime container
Discord bot + guild + channel/thread + A2A policy = runtime agent
NATS-visible AgentID = runtime_agent_id

One bot process can publish multiple runtime peers, one per enabled/discoverable channel or thread policy. A remote peer should delegate to the runtime peer, not to the bot process as a whole.

Normal User Flow #

First run /a2a peers in the relevant channel and copy the bot/channel ID shown for the channel you want.

Receiver-side consent is simple: in the receiving channel, run /a2a allow peer_agent:<bot/channel ID from /a2a peers> or tell the bot to allow that bot/channel to delegate. Normal consent applies immediately for that exact bot/channel runtime when it does not widen an existing skill restriction; it is not a wildcard bot-prefix grant and not bidirectional trust.

Sender-side delegation is also high level: run /a2a ask peer_agent:<bot/channel ID from /a2a peers> message:<task> or ask the bot to delegate the task in natural language. Normal delegation does not require a capability ID, target channel label, or reply-mode fields. If multiple targets match, the bot asks for disambiguation instead of guessing.

Same Discord guild+channel/thread means conversation collaboration: both sides can share the same transcript when receiver policy and Discord permissions allow it. A different channel means the receiver starts work in its own channel/thread and the result returns to the requester. A queued delegation is not acceptance or completion; check /a2a status or bot_a2a_task_status for authoritative state.

Keyword Glossary #

KeywordMeaning
A2AOptional cross-bot delegation layer implemented by this project.
NATSExternal message system used as the transport between bot processes.
JetStreamNATS persistence layer used for tasks, controls, events, peer KV, and object artifacts.
A2A_AGENT_IDStable bot/process base identity. It namespaces runtime IDs and credential ownership; it is not the main routing identity in runtime mode.
Runtime agentA Discord channel or thread runtime that can expose skills and receive delegated work.
runtime_agent_idStable NATS-visible identity for one runtime agent. Used in task/control/event/card/heartbeat subjects.
channel_refSubject-safe public alias for the channel runtime. Used for display, migration metadata, and skill context.
AgentCardPublic sanitized description of a runtime peer, its supported binding, skills, and display metadata.
AgentSkillA capability exposed by a runtime, such as task or a more specific skill ID.
PeerA discovered runtime AgentCard plus local trust/status metadata.
TrustLocal policy granting consent for a peer runtime. Receiver-side trust defaults to inbound delegation consent; outbound or bidirectional grants are explicit expert choices.
PolicyPer-channel A2A rules: enabled, discoverable, accepted senders, exposed skills, delegate targets, visibility, transcript mode, quotas, and tool policy.
delegate_targetsOutbound allowlist of {runtime_agent_id, skill_id} pairs.
accept_from_runtimesInbound allowlist of remote runtime IDs.
co_present_from_runtimesRuntimes allowed to share Discord context and co-present replies when other delivery gates pass.
Confirmation tokenSigned token returned by a plan step and required before applying policy or sensitive remote delegation.
TaskStoreLocal SQLite durable task and event state. /a2a status and bot_a2a_task_status read this state.
PeerStoreLocal SQLite view of known peers, trust display data, staleness, and skills.
Object storeJetStream Object Store bucket for larger A2A artifacts.
Nats-Msg-IdStable idempotency header on JetStream publishes. Prevents duplicate effects under redelivery.
proxySafe result mode where the requester bot relays the remote result.
transparentResult mode that exposes the remote result more directly while still enforcing policy.
co_presentTranscript mode where both bots may post in the same Discord channel or thread after policy and Discord permission checks.
legacyMigration mode that uses bot-level routing. Not the production target.
dualBounded drain mode that can consume legacy and runtime subjects. Use only during migration.
runtimeProduction target mode. New routing uses exact runtime IDs.

Identity Model #

Bot Base Identity #

A2A_AGENT_ID identifies the bot process and credential owner:

A2A_AGENT_ID=adam-n200

It must be stable and subject-safe. It is used to derive runtime IDs and to display bot host identity in audit/doctor output. In runtime mode it is not the user-facing peer route by itself.

Runtime Agent ID #

A runtime ID identifies one channel or thread runtime:

runtime_agent_id = <bot-prefix>-<public-channel-alias-slug>

If the alias is unsafe, too long, private, colliding, or contains raw Discord snowflake-like digits, the implementation falls back to a short hash:

runtime_agent_id = <bot-prefix>-rt-<short-hash>

Examples:

remote-bot-erp-support
remote-bot-backend
m5bot-main
m5bot-rt-4f8a9c01

Runtime IDs must remain stable across restarts. They must not include PID, boot timestamp, random suffix, raw Discord snowflake, private host path, or secret material.

Channel Reference #

channel_ref is an operator-readable, subject-safe alias for the runtime channel. It is not the primary durable route in runtime mode, but it appears in peer cards, policy displays, and skill context.

Allowed shape:

[A-Za-z0-9_-]{1,64}

Avoid dots, spaces, slashes, wildcard characters, and private channel names unless a manager intentionally makes the alias public.

Runtime ID Modes #

ModeBehaviorUse
legacyBot-level identity and legacy fields remain active.Migration compatibility only.
dualRuntime cards are published while legacy consumers may still drain old tasks.Short migration window.
runtimeNew routing uses exact runtime IDs. Legacy bot-level target asks are rejected or require migration.Production target.

New deployments should use A2A_RUNTIME_ID_MODE=runtime.

NATS Subject Schema #

All production task/control/event traffic uses JetStream. Subjects use the a2a.v1 prefix.

SubjectStreamPurpose
a2a.v1.task.<from_runtime>.<to_runtime>.<messageId>A2A_TASKSDelegator sends a task to a specific runtime.
a2a.v1.control.<from_runtime>.<executor_runtime>.<taskId>.<kind>A2A_CONTROLSCancel, input reply, auth reply, or other post-accept controls.
a2a.v1.event.<executor_runtime>.<delegator_runtime>.<taskKey>.<kind>A2A_EVENTSAccepted, rejected, status, result, and artifact events.
a2a.v1.card.<runtime_agent_id>KV or streamRuntime AgentCard update.
a2a.v1.heartbeat.<runtime_agent_id>.<instance>Core or KVEphemeral liveness signal.

Common event/control kinds:

accepted
rejected
status
artifact
result
cancel
input_reply
auth_reply

Old unversioned subjects such as a2a.task.{agent-id}, a2a.status.{task-id}, and a2a.announce are not used.

JetStream Topology #

The implementation uses these streams:

StreamSubjectsPurpose
A2A_TASKSa2a.v1.task.>Durable task submissions.
A2A_CONTROLSa2a.v1.control.>Durable control messages after acceptance.
A2A_EVENTSa2a.v1.event.>Durable accepted/rejected/status/result/artifact events.
A2A_PEERS KVruntime peer keysPeer cards and discovery metadata.
a2a-artifacts object storegenerated object keysLarger task artifacts.

Consumers are runtime-targeted. A runtime consumes only task/control/event subjects addressed to its exact runtime ID.

Task Lifecycle #

  1. The delegator validates local outbound policy.
  2. The delegator publishes a task message with a stable Nats-Msg-Id.
  3. The executor validates inbound subject, envelope, sender, policy, skill, quota, and runtime context.
  4. The executor stores the admitted task durably before acknowledging the JetStream message.
  5. The executor runs the task in its own Discord channel or thread runtime.
  6. The executor publishes accepted, status, artifact, and result events.
  7. The delegator stores received events and reports status through /a2a status or bot_a2a_task_status.

The system is at-least-once, not exactly-once. Durable idempotency prevents duplicate terminal effects when NATS redelivers messages.

Task States #

StateTerminalMeaning
TASK_STATE_SUBMITTEDnoDelegator queued the task.
TASK_STATE_WORKINGnoExecutor accepted the task or emitted progress.
TASK_STATE_INPUT_REQUIREDnoExecutor needs requester input.
TASK_STATE_AUTH_REQUIREDnoExecutor needs an authorization decision.
TASK_STATE_COMPLETEDyesTask finished successfully.
TASK_STATE_FAILEDyesRuntime or execution failure.
TASK_STATE_CANCELEDyesTask was canceled.
TASK_STATE_REJECTEDyesPolicy, auth, skill, quota, or validation denied the task.

An accepted event is an event and state transition into TASK_STATE_WORKING; it is not a distinct TaskStore state. A queued tool call is not a completed remote task. Always check task state before reporting completion.

Policy Model #

A2A policy is owned by the channel runtime. Important fields:

FieldMeaning
enabledAllows the runtime to participate in A2A.
discoverablePublishes a runtime card for discovery.
runtime_agent_idStable runtime route. Required before enabled/discoverable runtime policy can be saved.
accept_from_runtimesRuntime IDs allowed to send inbound tasks.
accept_skillsSkills accepted inbound.
expose_skillsLocal skills shown in the runtime card.
delegate_targetsRuntime and skill pairs this channel may delegate to.
result_visibilityProxy or transparent result behavior.
discord_transcript_modeDelegator, mirror, or co-present transcript behavior.
share_discord_contextAllows co-present context sharing only when transcript mode allows it.
co_present_from_runtimesRuntimes allowed for co-present transcript.
co_present_target_channelsSame-guild target channels/threads allowed for co-present replies.
remote_tool_policy_json.allow_memory_writeDefaults false; only this can allow remote jobs to use memory-write bot tools.

Legacy fields such as accept_from, delegate_to, and delegate_skills are compatibility inputs only. New setup writes canonical runtime fields.

Delivery and Transcript Modes #

ModeResponsibility
Same-channel collaborationSame Discord guild+channel/thread. The bots can share the conversation transcript when both policy and Discord permissions allow it.
Receiver-owned workDifferent or unknown channel. The executor bot works in its own Discord channel/thread and returns the result to the requester.
TransparentResult visibility is less mediated, but policy still controls delivery.
Co-presentExecutor and delegator can share a Discord channel/thread transcript when both policy and permissions allow it.

trusted=true does not imply transparent or co-present readiness. For direct same-thread replies, the opposite bot's inbound policy, co-present allowlist, target channel policy, and Discord send permissions must all pass.

Security Boundaries #

A2A does not bypass existing bot boundaries:

Operator Surfaces #

SurfaceUse
/doctorCheck A2A enabled state, auth mode, runtime mode, peer status, and readiness without exposing secrets.
/a2a peersList visible peer runtimes, skills, trust, staleness, and delivery readiness.
/a2a allowAllow a peer runtime to delegate into this channel; inbound consent is the default.
/a2a askSend a normal task to a peer runtime.
/a2a revokeStop allowing a peer runtime to delegate into this channel.
/a2a statusInspect durable local task state and events.
bot_a2a_* toolsAgent-facing MCP surface for policy, delegation, status, and peer inspection.

Do not inspect or edit raw data/a2a/*.sqlite files for normal operation.