Available specs
!find docs/use-cases -mindepth 1 -maxdepth 1 -type d -name 'UC-*' 2>/dev/null | sort
Empty above → none yet, run /use-case-design first. (find, not an ls glob: under zsh an unmatched glob
aborts the command before any fallback runs.)
Target
$ARGUMENTS
Messaging Architect
Designs how a domain event leaves the process and how another one gets consumed: topic,
key, serialization, delivery semantics, retry and DLQ, and the two adapters that carry it.
What domain-modeling already named as an event with a consumer, this skill gives a broker
transport to.
Entry rule: without 10-dominio.md naming an event for external delivery, there's nothing
to transport. This skill reads docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md and
10-dominio.md and treats them as a contract. Without the domain partial, it stops and tells
you to run /domain-modeling. If the domain partial's Events block is "none," or names only
a sibling use case in the same process, it also stops: an in-process call has no broker to
design — designing Kafka config for it would be scope no one asked for.
Exit rule: it doesn't write code. It emits 25-mensageria.md. The Java classes come from
the executor agent, which reads the partial and the templates/ exemplars.
Rule rule: the rules don't live here. Idempotent consumer, acks=all, topic naming,
retry/DLQ, and the rest are @.claude/rules/messaging.md. This skill applies them and cites
them; it doesn't reproduce them.
How it's invoked
Two paths, and both matter: /messaging-architect by hand, or chained by /new-feature once
that orchestrator's pipeline reaches this point. That's why it does not carry
disable-model-invocation — a skill the model can't see is a skill the orchestrator can't
call.
The guard against out-of-order or unnecessary firing isn't the frontmatter: it's the entry
rule above. Recorded in @.claude/decisions/0007-pipeline-skills-invocation.md.
Why this is a skill and not a subagent
Form 1, motivated by axis 2 (chained by /new-feature, also invocable by hand) and axis 9
(domain-modeling already owns the event; this skill only owns its transport). The closest
rejected form was a fourth option in the same decision — a dedicated "domain events" skill —
which failed invariant 2: domain-modeling already asks whether the aggregate emits an event
and already writes the Events block of 10-dominio.md. A subagent was never viable either: it
fails the § 5 counter-test in claude-code-architect-designer's decision matrix on all three
points — the interview over topic/partitioning/DLQ is the heart of the task, the reference
content fits in templates/, and the partial it produces is short.
Boundary with neighboring skills
The division is by moment and artifact, not technology:
| Piece | When it acts | What it produces |
|---|---|---|
use-case-design |
Before the domain exists | 00-caso-de-uso.md — boundary and canonical names |
domain-modeling |
After the mother spec | 10-dominio.md — aggregate, invariants, ports, and the event itself |
persistence-architect |
After the domain partial | 20-persistencia.md + migration |
| this skill | After the domain partial, only when an event needs external delivery | 25-mensageria.md |
rest-api-architect |
In parallel with the two above | 30-rest.md — transport, no schema |
test-architect |
After all of them | 40-testes.md |
domain-modeling decides whether an event exists and who consumes it. This skill
never redefines the event or its payload's business meaning — it only decides how it travels.
If the consuming use case is a sibling in the same process, there is no broker to design and
this skill doesn't run at all (see Entry rule).
Out of scope, on purpose: in-process dispatch. Spring's own ApplicationEventPublisher/
@EventListener is a same-JVM transport for the same kind of domain event this skill handles
for Kafka. No use case in this repo has needed it yet, so it isn't built — anti-pattern 9
(anticipation) in claude-code-architect-designer's decision matrix. If one does, it's one
more transport option inside this skill (another templates/ pair and a section in
@.claude/rules/messaging.md), not a new skill: the boundary above already put event
definition in domain-modeling and event transport here, and in-process dispatch is
still transport.
Procedure
Read the specs.
00-caso-de-uso.mdand10-dominio.mdfrom the folder in$ARGUMENTS. Without the second, stop. Extract: the event's name, payload fields, the consuming use case, and whether that consumer is external (another service, another deployable) — that's what makes this skill apply at all.Survey what already exists. A topic or consumer group already wired gets reused, not duplicated.
grep -rln "@KafkaListener\|KafkaTemplate" --include='*.java' src/ 2>/dev/null grep -rn "group-id\|bootstrap-servers" src/main/resources/ 2>/dev/null grep -rl "processed_events\|ProcessedEventStore" --include='*.java' src/ 2>/dev/nullInterview — only what the specs don't fix.
AskUserQuestion, at most 4 questions per call. Don't re-ask what00-caso-de-uso.mdor10-dominio.mdalready answered.Axis Decides Partition key candidate (which field must stay ordered) Whether the aggregate id is enough, or a composite key is needed Consumer group id, new or existing Reuse vs. a fresh subscription with its own offset auto-offset-resettolerance (losing vs. reprocessing on redeploy)earliestorlatestOrdering requirement across different aggregates Whether one topic is enough or the event needs to fan out differently Existing processed_events-style dedupe table in this projectReuse vs. ask persistence-architectto model oneDesign the producer adapter. Implements the outbound port
domain-modelingalready declared — never a new interface. Payload is the minimum the consumer needs, mapped explicitly from the domain event; the event itself never serializes directly. Shape:templates/KafkaProducerAdapter.java.example.Design the consumer adapter. Translates the inbound payload into a call on the target use case's inbound port. Dedupe on the event's own identity before calling it; manual acknowledgment, offset commits only after the use case returns. Shape:
templates/KafkaConsumerAdapter.java.example.5a. Dedupe table, when step 2 found none yet. Not per-consumer: one table (
processed_eventsor equivalent), shared by every listener in the project, modeled once. If missing, name it in the partial and flag thatpersistence-architectneeds to model it — this skill doesn't design tables,20-persistencia.mddoes.Fix retry and DLQ. Backoff attempts and the DLQ topic name, per
@.claude/rules/messaging.md§ Retry and DLQ. A business rejection (typed domain exception from the consumed use case) skips retry and goes straight to the DLQ.Fix the configuration. Bootstrap servers, producer
acks/idempotence, consumer group and offset reset, ack mode — fromtemplates/application-kafka.yml.example. Mandatory values are the rule; what this skill decides is the per-use-case sizing (group id, offset reset tolerance) from step 3.Write the partial.
docs/use-cases/UC-NNN-<slug>/25-mensageria.md, fromtemplates/messaging-spec.md.example. Five blocks, all mandatory.Check Kafka has a container.
grep -A2 "^services:" docker-compose.ymlfor akafkaservice. Missing → invokedocker-architectwith this UC's folder, so the dev-time broker matches the topic just designed. Don't editdocker-compose.ymlhere — that skill is its single owner.Report and stop. Path of the file written, whether a dedupe table was requested from
persistence-architect, whetherdocker-architectran, and what's missing for the folder to be complete (30-rest.md,40-testes.md). Don't invoke anyone else.
What the partial contains
Five blocks. An empty block is written as "none" — deleting it hides a question nobody asked.
| Block | Fixes | Form exemplar |
|---|---|---|
| Topic and delivery | Topic name, partition key, serialization, delivery semantics | @.claude/rules/messaging.md § Topics and serialization |
| Producer adapter | The port from 10-dominio.md, the adapter, the payload shape |
KafkaProducerAdapter.java.example |
| Consumer adapter and idempotency | The consuming use case, the listener, the dedupe key and table | KafkaConsumerAdapter.java.example |
| Retry and DLQ | Backoff, DLQ topic, which failures skip retry | @.claude/rules/messaging.md § Retry and DLQ |
| Configuration | Group id, offset reset, ack mode, with the decided value and why | application-kafka.yml.example |
The exemplars in templates/ are reference for form, not files to copy. It's the
executor agent that reads them when generating code.
Contract
Reads docs/use-cases/UC-NNN-<slug>/00-caso-de-uso.md and 10-dominio.md (mandatory —
stops without the second, or without its Events block naming external delivery),
@.claude/rules/messaging.md, @.claude/rules/architecture-ddd.md (Adapters section),
@.claude/rules/naming.md, @.claude/rules/error-handling.md, @.claude/rules/lombok.md,
and the active blueprint's packages.map.
Writes docs/use-cases/UC-NNN-<slug>/25-mensageria.md. Nothing else.
Does not write Java code. The publisher, listener, and payload classes come from the executor agent.
Does not edit docker-compose.yml. When step 9 finds no kafka service, it invokes
docker-architect instead of writing the service block itself — single owner, see that
skill's Contract.
Does not model the dedupe table. When step 5a finds none, it names the need in the
partial for persistence-architect to pick up — this skill doesn't design schema.
Does not decide the use case boundary (00-caso-de-uso.md), whether an event exists or
its payload's business meaning (10-dominio.md, domain-modeling's call), the transport for
synchronous HTTP (30-rest.md), or the tests (40-testes.md). Doesn't touch
.claude/rules/**.
Does not cover in-process Spring events (ApplicationEventPublisher/@EventListener) —
see § Boundary with neighboring skills. No symptom for it yet in this repo.
Does not collide with domain-modeling: that one declares the event and the outbound
port, this one says how the port is served over Kafka. The event's payload meaning belongs to
the other; if it needs to change, report the divergence instead of rewriting it.