Foundation
Every Flightdeck agent is a scalable, reliable, and durable AI Agent, enabled by Apache Kafka and Kafka Streams.
Durable Execution
Every message that flows in and out of the pipeline is replicated, replayable, and fault tolerant. This is achieved through the immutable append-log property of Apache Kafka and its distributed brokers, which replicate the log across the cluster so it stays available even when individual brokers fail.
The processors and applications that read from these append logs (the topics) commit offsets back to the Kafka brokers, so each service tracks how far it has read and, if it fails, resumes from exactly that point rather than reprocessing from the start or skipping ahead.
Some processing, such as joining two streams or keeping a running count of messages, requires the processor to be stateful. That state is managed by Kafka Streams and stored in RocksDB (memory and disk), while the source of truth lives in a compacted changelog topic in Kafka, so it is durable too.
Debuggability
Because every step crosses a topic, the full history of a turn is durable and
inspectable after the fact. Nothing important happens inside a hidden call stack:
each message-input, think-request-response, tool-use, and
tool-use-result is a record you can read, replay, or audit.
- Replay a turn. Since topics retain their records, you can re-feed a session's events to reproduce a bug instead of trying to catch it live.
- Tap every topic. The
monitoringconsumer tails all topics for a live view, andtool-use-latencycarries per-tool timing for spotting slow tools. - Inspect the topology. The processing service logs
topology.describe()at startup, so the exact wiring of stages and topics is visible. - Fail loudly, not silently. Malformed tool items are routed to
tool-use-dlqrather than crashing the turn, and a tool that never answers is turned into an explicit timeout error rather than hanging.
Scalability
Because every conversation is keyed by session_id, sessions are independent and
partition-parallel: add partitions and stream instances and throughput
scales horizontally, with each session's state pinned to a single partition
(state locality, no cross-partition coordination).
The two heaviest, slowest stages — calling the LLM and fetching information via
tool execution — are outside the Streams topology, so they scale on their own
as ordinary Kafka consumer groups. Doubling LLM throughput is just running more
ThinkConsumer instances; it doesn't touch the topology.
Generally, increasing the number of partitions and increasing the number of consumers increases parallelism. However, increasing the partitions can cause reordering of keys, so it is critical to think about scaling within a single partition.
For the Think consumers, even within the same partition, different session_id
keys should run in parallel — so that each session can call the LLM — and
something like a Parallel Consumer (with key ordering preserved) should be used.
For the tool consumers, work can run freely in parallel, so either a Parallel
Consumer without key ordering or a KIP-932 Queue for Kafka can be used to scale
out within the partition, at a cost of losing ordering, which is accepted for
tool calling.
Performance
Throughput comes from processing the pipeline as a continuous stream rather than a request that blocks until it finishes. Records flow through the topology without waiting, and because each stage hands off through a topic, many sessions are in flight at once across partitions and stream threads instead of one blocking call at a time.
There are three distinct performance profiles, one per kind of service:
- Processors. The Kafka Streams processors that move messages between topics. These handle tens of thousands of messages per second, so the messaging workflow itself is never the bottleneck.
- Think consumer. Where the LLM is called. An LLM's response time varies widely and each call is slow, which caps the throughput of this service. That throughput is recovered by running the calls in parallel with a Parallel Consumer rather than one at a time.
- Tool consumer. Where tools are executed. Tools are relatively fast, since they are typically an API or database call (target P99 under 1 second). As with the think consumer, high throughput is achieved by executing in parallel.
Extensibility
The same topic seams that make stages reusable also make the pipeline open to extension without forking it. A topic is a public integration point: anything can subscribe to a topic to observe, or produce to one to participate, and the existing stages neither know nor care.
- Tap any stage for a new capability. The
monitoringconsumer tails every topic for observability; thetool-use-latencytopic feeds metrics; memoir is itself "just" two extra stages subscribed tothink-request-responseandsession-end. New cross-cutting features — auditing, analytics, guardrails, per-user settings — attach the same way: subscribe to the relevant topic, emit to a new one. None of the core stages change. - Swap an implementation behind a seam. Because
tool-use→tool-use-resultis a topic boundary, what fulfills a tool is pluggable: a local SDK consumer, a remote agent over HTTP callback, a human-in-the-loop queue — all satisfy the same contract. Likewise Think is swappable between Claude and Gemini behindenriched-message-input→think-request-responsewith zero topology change. - Add a new entry point. Anything that can produce a
MessageInputtomessage-inputbecomes a trigger — the chat-api's REST/WebSocket front door, a scheduler, a webhook, or the loop's ownrole: "tool"re-entry. New ingress sources require no pipeline changes. - Inject a new stage between two existing ones. Because stages communicate only through topics, you can interpose a processor (e.g. a moderation or redaction step) by reading the upstream topic and producing to the downstream one — the neighbors are unaffected.
In short, extension is additive: new behavior is a new subscriber or a new topic, not a modification to the existing flow. This is the same property multi-agent communication exploits — it is an extension of this pipeline, not a rewrite of it.
High Level Workflow
All topics are prefixed with the agent's name ({AGENT_NAME}-…), which is what
lets many agents share one Kafka cluster without colliding. Below the prefix is
dropped for readability.