AWS Builder Center

From Rust to React/Ink: The Architecture Behind Kiro CLI's Classic vs TUI Mode

A deep dive into why Kiro CLI migrated from a Rust PTY-streaming terminal to a React/Ink TUI — the trade-offs in rendering, input handling, and what it means for developers choosing between modes.

AWS Golden Jacket | Kiro Ambassador 🟣 | Founder & Global Lead — Golden Jackets

Why Kiro Ditched Rust for React in the Terminal (And What Broke)

Level: 300-400
Tags: Developer Tools, Terminal UI, Architecture, React, Rust, CLI Design
Author: Ricardo Gulias

I've been using Kiro CLI daily since the early builds. When they shipped the TUI mode — the React/Ink rewrite — I noticed immediately. Not because it looked different (it does), but because my muscle memory broke. Vi mode gone. Shell output buffered instead of streaming. My cargo build no longer scrolled in real-time.
I was frustrated at first. Then I dug into why they did it, and honestly? The reasoning is solid — even if the trade-offs hurt.
This is my breakdown of what changed architecturally, why it had to change, and what it means if you're building agentic CLI tools yourself.

What the old Rust CLI did right

The original was dead simple. Rust binary, readline loop, PTY passthrough. You type, it sends to the LLM, tokens stream back. When it runs a command, you see the output live — exactly like running it yourself.
1
2
3
stdin → readline → parser → agent
LLM response → stdout (streamed, token by token)
Tool exec → PTY → stdout (real-time)
I loved it. Zero latency. Interactive commands just worked — sudo, ssh, whatever needed a TTY was fine. Vi mode on the input buffer felt native because it was native (Rust readline handles that natively).
For months, this was perfect. Then Kiro started doing more complex stuff.

Where it fell apart (for me, specifically)

Three things killed the linear scrollback model in my workflow:
1. I kept missing approval prompts.
Kiro asks "Allow? y/n" before running destructive commands. In the streaming model, that prompt appears inline — buried between 200 lines of analysis output. I'd walk away to grab coffee, come back, and spend 30 seconds scrolling to find where it was waiting for me.
Annoying once. Infuriating daily.
2. Subagents made the output unreadable.
When I started using pipelines (research agent + implementation agent running in parallel), their outputs interleaved in the scrollback. I couldn't tell which agent said what. It was like reading two conversations shuffled together.
3. Large pastes destroyed the input area.
Pasting a 50-line stack trace into the prompt flooded the screen. No way to collapse it, no preview, just... 50 lines of raw text pushing everything up.

Why React/Ink (and not something else)

I asked myself this too. React? In a terminal? Feels wrong.
But here's the thing — the problem they needed to solve was regions on screen. A status bar that stays put. An approval snackbar that appears over the conversation. An overlay panel for the crew monitor. None of that works in a linear buffer.
They evaluated Ratatui (Rust, but forces full-screen redraws), Bubbletea (Go, great but means rewriting everything), and Blessed (dead project). Ink won because:
  • Declarative rendering — you describe what the screen looks like, not how to repaint it
  • Components can appear/disappear without cursor math
  • Hooks handle async agent state naturally
  • npm ecosystem for syntax highlighting, markdown, diffs
The snackbar is literally just:
1
{pendingApproval && <ApprovalSnackbar tool={pendingApproval.tool} />}
Try doing that in a scrollback buffer. You can't. I tried mentally — you'd need to rewrite the visible screen constantly.

The buffering thing (this is the controversial part)

Here's what actually happens now when Kiro runs a shell command:
Old way: spawn process with PTY → output streams live → you watch your build scroll.
New way: spawn process (no PTY) → buffer stdout in memory → render it as a React component when done.
Long builds show ...+47 lines (ctrl+o to toggle) instead of scrolling output. First time I saw that, I hated it.
But the reason is architectural, not lazy:
React redraws the entire visible terminal on every state change. If cargo build outputs 300 lines per second, that's 300 React re-renders per second. Terminal flickers, CPU spikes, frames drop. I've seen it happen in early builds — it's ugly.
Plus, if you're streaming output AND the approval snackbar is showing, they fight for screen space. Output pushes the snackbar off-screen, or the snackbar covers output. Pick one.
And stdin capture: Ink grabs all keyboard input for its own event system (Ctrl+G for crew monitor, Esc for overlays). If stdin also goes to the child process, does Ctrl+C kill React or kill the build? There's no clean answer.
So they accepted the trade-off. Buffered output = structured UI works. For long builds where I need streaming, --classic still exists.
Do I use --classic sometimes? Yeah, for deploys. But for my normal Kiro workflow — code generation, reviews, multi-file changes — the TUI is better. The approval snackbar alone saves me minutes per day.

Why vi mode died (and why it's not coming back)

This one hurt personally. I use vi keybindings everywhere.
But the new input isn't a string. It's an array of segments:
1
[text "fix the bug in "] [file chip: src/main.rs] [text " using "] [paste chip: 47 lines, collapsed]
Vi motions assume a flat buffer where cursor position = character index. When your "buffer" contains collapsed paste chips and file reference chips, w (word forward) doesn't mean anything coherent. What's a "word" inside a collapsed 47-line paste?
It's not laziness. It's architectural incompatibility. The segment model enables paste chips and file references. Vi mode requires flat text. You can't have both.
I adjusted. Took a week. Still miss ci" sometimes.

The crew monitor (this is where it pays off)

Ctrl+G opens an overlay showing what each subagent is doing:
1
2
3
šŸ”„ research-agent Reading files...
āœ… lint-agent Complete (2.1s)
šŸ”„ test-agent Running tests...
This is impossible in a linear scrollback without taking over the full screen ncurses-style. In React, it's just a component that renders on top. When I close it, the conversation is still there underneath — nothing destroyed.
For my pipeline workflows (I use subagents a lot for Golden Jackets automation and client assessments), this changed everything. I can actually see what's happening instead of waiting blind.

What I'd tell someone building an agentic CLI

Five things from watching this migration up close:
Pick streaming OR structured UI early. You won't get both cleanly. If your tool runs long processes, stream. If your tool has approval workflows and parallel agents, structure.
Your input model sets your ceiling. Flat string = vi mode, simple, fast. Segments = paste chips, file refs, async search. I've seen devs rage about vi mode loss without understanding this constraint.
Keep a fallback. Kiro's --classic flag saved the migration. People who need streaming aren't stuck — they just use a flag. Don't burn the old path.
React/Ink works in production. I was skeptical. After months of daily use, it's solid. Startup is ~500ms vs ~50ms for native Rust — noticeable but not painful. The component model pays for itself in complexity reduction.
PTY and modern TUI frameworks don't mix. Any framework that captures stdin (Ink, Bubbletea, Ratatui full-screen) can't simultaneously wire stdin to a child. This isn't a bug. It's physics.

Bottom line

The Rust CLI was a better terminal. The React/Ink TUI is a better interface for an AI agent that lives in a terminal. Different problems.
I lost vi mode and real-time streaming. I gained a crew monitor, approval snackbar, paste chips, message queuing, and overlays. For my daily work — TAM assessments, community automation, code reviews — the trade-off is clearly positive.
For deploys and long builds, I type --classic and get the old behavior. Best of both worlds, one flag away.
The right architecture depends on what you actually do all day. Now you know what's behind both choices.
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article