Agentic Systems
7 min readUnix Is the IDE, and It’s Back
In the agentic era, Unix-style composability and text streams are not nostalgia, they are the real interface for reliable AI-enabled development.
2026-03-12
Unix is the IDE. Especially now, in the agentic era. It wasn’t always obvious, but it was always true.
The platform keeps shifting, but the core pattern keeps holding: one thing does one thing well, output flows to input through plain text, and behavior becomes composable, debuggable, and repeatable.
The default modality is text streams
If you step back, modern development is an exchange of text streams: prompts, diffs, logs, specs, terminal output, JSON schemas, Markdown, code, and now agent outputs. Even when there are UIs, dashboards, and visual workflows on top, the real protocol underneath is still text.
Agents do not live in widgets. They live in prompts, tools, and commands. They reason over text and generate text. That is exactly the shape Unix gave us decades ago: composable tools connected with explicit streams.
Why this matches the legends’ design
The people who built these systems understood something that feels almost obvious in hindsight: interfaces should be explicit, small, and replaceable. If a system is understandable, you can automate it. If it is automatable, you can delegate it.
- Composition: small commands join into larger behavior.
- Inspectability: every step is text, so every failure is inspectable.
- Replayability: if it can be run in a shell, it can be retried, bisected, and improved.
The environment, not the editor
We used to think IDEs were windows into code. Now, in an agentic workflow, the IDE is the environment that lets you move intent into execution with minimal ambiguity. Text gives you that.
A model can be amazing at writing code and terrible at managing uncertainty. Unix helps reduce uncertainty by making each action explicit: command in, output out, parse, verify, repeat.
Harnesses already behave like CLI pipelines
I looked at modern harness implementations, and they all describe the same control loop. That’s why this still feels Unix-native:
- OpenAI tool calling: the model emits tool calls (with IDs and JSON args), returns a turn that says tooling happened, and then expects you to feed outputs back as a correlated tool call response. In the chat format, the finish state includes tool usage, and the client replays conversation turns with tool output to continue.
- Anthropic Messages API: the assistant emits a
tool_useblock, and the next message must include a matchingtool_resultblock. Their docs require this pairing and ordering to be immediate, which is exactly the kind of strict contract a CLI loop thrives on. - Gemini function calling: the model returns
functionCallparts, you execute them, then return the correspondingfunctionResponsein the next turn. The loop remains explicit, and SDKs even provide automatic function-calling when you want to close the loop.
When your harness returns structured output plus a status signal (success/error), you get CLI-like ergonomics: discoverable tool surface, predictable ordering, and machine-checkable return codes. That’s what makes modern agent systems manageable at scale.
Concrete schemas from the harness docs
After reading the primary docs side by side, this is the practical reality: each provider exposes a machine-readable contract, and each contract is easiest to reason about with strict schemas and typed return data.
OpenAI (Chat Completions): tool call object + explicit tool_call_id roundtrip.
{
"tools": [
{
"type": "function",
"function": {
"name": "get_file_list",
"description": "Return file names for a given path.",
"parameters": {
"type": "object",
"properties": {
"path": { "type": "string" }
},
"required": ["path"]
}
}
}
]
}// OpenAI tool-calling loop (Chat Completions)
const completion = await client.chat.completions.create({
model: "gpt-5",
messages,
tools,
tool_choice: "auto",
});
for (const toolCall of completion.choices[0].message.tool_calls ?? []) {
const args = JSON.parse(toolCall.function.arguments);
const output = runTool(toolCall.function.name, args);
messages.push({
role: "tool",
tool_call_id: toolCall.id,
content: JSON.stringify(output),
});
}
const final = await client.chat.completions.create({ model: "gpt-5", messages, tools });Anthropic: tool use and tool results are interleaved in message content blocks.
{
"role": "assistant",
"content": [
{
"type": "tool_use",
"id": "toolu_01",
"name": "get_file_list",
"input": { "path": "/Users/alex" }
}
]
}{
"role": "user",
"content": [
{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "src/ index.ts README.md" },
{ "type": "text", "text": "What should I run next?" }
]
}Gemini: function declarations plus paired function calls and function responses.
// Gemini function declarations and loop
const toolDeclarations = {
functionDeclarations: [
{
name: "get_file_list",
parameters: {
type: "object",
properties: {
path: { type: "string" }
},
required: ["path"],
},
},
],
};
const result = await model.generateContent({
model: "gemini-3-flash",
contents: userPrompt,
config: { tools: [toolDeclarations] },
});
const functionCall = result.candidates?.[0]?.content?.parts?.[0]?.functionCall;
const functionResponse = runTool(functionCall.name, functionCall.args);
history.push({ role: "model", parts: [{ functionCall }] });
history.push({
role: "user",
parts: [
{
functionResponse: {
name: functionCall.name,
response: { result: functionResponse },
},
},
],
});
One thing worth noting: if multiple Gemini function calls occur in a turn, they can be returned and correlated using tool-call IDs, so you can run them asynchronously. OpenAI requires the same via tool_call_id, while Anthropic requires strict message adjacency (tool_use immediately followed by tool_result) for each call.
What to build instead
If you want reliable agentic results, stop overbuilding interfaces. Build stronger pipes:
- Use deterministic commands for discovery, editing, and validation.
- Keep artifacts in text files that are easy to review and version.
- Let each step fail loudly, and let the failure itself become feedback.
Fancy IDE features are still useful. But they are no longer the architecture. They are the convenience layer. The architecture is the protocol.
Conclusion
Ken Thompson and Dennis Ritchie didn’t just invent a terminal. They invented a durable way to think about software: composable, text-first systems where every boundary is explicit. In the age of agentic development, that philosophy is the closest thing to a stable IDE we’ve ever had, not because it’s nostalgic, but because it’s correct.