Giving Agentic Tools a Voice: Introducing GetUserInput

There’s a quiet shift happening in how we work with AI assistants.

They’re no longer just responders. Increasingly, they’re acting as agents — performing multi-step workflows, using our tools, running commands, inspecting output, and deciding what to do next.

And that raises a new problem:

Sometimes an AI shouldn’t continue — it should ask.

Recently while working inside JetBrains IDE with Junie (the JetBrains AI Agent), I noticed moments where Junie really needed clarification. Continuing would have risked the wrong change. But stopping entirely also broke the workflow.

So I built a tiny tool to bridge that gap.

The Idea

Junie already has terminal access inside the IDE.

So instead of forcing Junie to cancel, restart, or guess, I gave it a new ability:

Junie can now pause, ask me a question, hear my response, and continue.

The tool is called GetUserInput.

  • Junie runs it from the terminal.
  • A GTK window pops up with a prompt.
  • I type an answer.
  • The program prints my response to stdout and exits with code 0.
  • If I cancel, it exits with 1.

That means Junie can use it mid-round without aborting the task.

All I needed to teach Junie was:

“Junie, you have a custom terminal command named GetUserInput. You can run it anytime you need clarification.”

And suddenly the workflow feels more like a conversation.

How You Can Use This Pattern

If your agent can:
✔ run a terminal
✔ read command output
✔ branch logic based on exit codes

…then you can give it a voice — a way to ask for direction instead of guessing.

Here’s the simple convention I use:

  • exit 0 → continue with the user’s text
  • exit 1 → stop or fall back

That’s it. No magic — just clean IO semantics used well.

GetUserInput — The Source

This version uses GtkSharp on .NET 8 so it works great on Linux (and anywhere GTK runs).

GetUserInput.csproj

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net8.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="GtkSharp" Version="3.24.24.95" />
  </ItemGroup>

</Project>

Program.cs

using System;
using Gtk;

namespace GetUserInput
{
    internal static class Program
    {
        public static int Main(string[] args)
        {
            if (args.Length == 0 ||
                (args.Length == 1 && (args[0] == "-help" || args[0] == "--help" || args[0] == "-?")))
            {
                Console.WriteLine("Usage: GetUserInput [prompt]");
                Console.WriteLine();
                Console.WriteLine("Purpose: Displays a graphical window with a prompt and a text area.");
                Console.WriteLine("         The user's input is printed to standard output upon clicking 'OK'.");
                Console.WriteLine("         Returns exit code 0 on success, and 1 if cancelled or closed.");
                return 0;
            }

            string prompt = string.Join(" ", args);

            Application.Init();

            var win = new InputWindow(prompt);
            win.ShowAll();

            Application.Run();
            return 0;
        }
    }

    public class InputWindow : Window
    {
        private readonly TextView _textView;
        private readonly Button _okButton;
        private readonly Button _cancelButton;

        public InputWindow(string prompt)
            : base("Junie – User Input")
        {
            DefaultWidth = 480;
            DefaultHeight = 260;
            WindowPosition = WindowPosition.Center;
            BorderWidth = 10;

            DeleteEvent += (_, __) => Environment.Exit(1);

            var vbox = new VBox(false, 8);

            var label = new Label { Xalign = 0f, LineWrap = true, WrapMode = Pango.WrapMode.WordChar };
            label.Text = prompt;
            vbox.PackStart(label, false, false, 0);

            var scrolled = new ScrolledWindow();
            _textView = new TextView { WrapMode = WrapMode.WordChar };
            scrolled.Add(_textView);
            vbox.PackStart(scrolled, true, true, 0);

            var buttonBox = new HButtonBox { Layout = ButtonBoxStyle.End };
            _okButton = new Button("OK");
            _cancelButton = new Button("Cancel");

            _okButton.Clicked += (_, __) =>
            {
                Console.WriteLine(_textView.Buffer.Text ?? "");
                Console.Out.Flush();
                Environment.Exit(0);
            };

            _cancelButton.Clicked += (_, __) => Environment.Exit(1);

            buttonBox.Add(_cancelButton);
            buttonBox.Add(_okButton);
            vbox.PackStart(buttonBox, false, false, 0);

            Add(vbox);
            ShowAll();
        }
    }
}

Build Instructions

dotnet restore
dotnet build -c Release

Optional: Publish as a single-file binary

dotnet publish -c Release -r linux-x64 \
  -p:PublishSingleFile=true \
  -p:IncludeAllContentForSelfExtract=true

You’ll find the binary under:

bin/Release/net8.0/linux-x64/publish/

Add that directory to your PATH — or drop the binary somewhere global.

Teaching Your Agent to Use It

I simply told Junie:

“You have a terminal command named GetUserInput. Run it whenever you need clarification, and use my answer to continue the workflow.”

And optionally:

“If the command exits with code 1, stop or fall back.”

That’s it.

Why This Matters

When an AI tool pauses and asks instead of guessing…
…the workflow becomes collaborative instead of brittle.

And that’s a direction I’m very excited about.

We’re not trying to replace agency — we’re trying to share it.

If you think of a better delivery prompt — or extend the idea — I’d love to hear about it in the comments. And if this inspires your own tools and agents, even better. That’s how living systems evolve. 😊

Tape as an Execution Substrate C# Demo

Most software execution models assume something quietly but firmly:

That you know what you’re doing before you start.

In practice, that’s rarely true.

Real systems discover correctness while running — through observation, correction, and iteration. Tape exists to support that reality.

What I Mean by Tape

Tape is not a framework and not a philosophy layer.

Tape is an execution substrate — a way of structuring work so that execution can:

  • proceed in sequence
  • preserve state across steps
  • adapt without restarting
  • and continue without collapsing the process

Tape assumes that execution is not finished when it begins.

Execution Without Collapse

In many systems, iteration is simulated by restarting:

  • rerunning jobs
  • reinitializing state
  • or replaying logic from the top

Tape takes a different approach.

It allows execution itself to:

  • pause
  • reflect on outcomes
  • alter future steps
  • and resume in-place

That’s not convenience — it’s structural stability.

Why This Matters

When execution can’t tolerate correction, systems become brittle.

When execution expects correction, systems become resilient.

Tape is designed for:

  • long-running processes
  • agent-driven workflows
  • test-and-adjust loops
  • and any system where “done” is discovered, not declared

Why I’m Sharing Tape on Its Own

Tape belongs to a larger body of work — but execution substrates should be understood before the systems that depend on them.

This release is intentionally:

  • small
  • concrete
  • and self-contained

You don’t need context to see how it behaves.

About the Release

This is a snapshot, not a final product.

It’s stable enough to explore and modify, but intentionally minimal. Tape’s value is not in how much it does — but in what it allows.

Try It

You can download the Tape demo here:

👉 Download the Tape Demo Snapshot
(replace with your actual link)

Run it. Step through it. Change it.

Observe how execution behaves when continuity is preserved.

Closing Thought

Tape does not make systems smarter.

It makes them able to change their mind without breaking themselves.

That turns out to be a very powerful property.