Agent Architecture Lessons from Unix V4

Table of Contents

1. The Unix Kernel as Agent Blueprint

The 1973 Unix V4 kernel's organization into ken/ and dmr/ directories isn't just historical artifact–it's a masterclass in separation of concerns that directly maps to modern agent architectures.

2. The Original Split

/usr/sys/
├── ken/     Ken Thompson - Core Intelligence
│   ├── main.c      Initialization/orchestration
│   ├── slp.c       Sleep/wakeup (blocking/async)
│   ├── trap.c      Interrupt handling
│   ├── sys1-4.c    System calls (the API)
│   ├── sig.c       Signal handling
│   ├── pipe.c      Inter-process communication
│   └── ...
│
└── dmr/     Dennis Ritchie - I/O & Peripherals
    ├── bio.c       Block I/O abstraction
    ├── tty.c       Terminal handling
    ├── rk.c        Disk driver
    ├── tm.c        Tape driver
    └── ...

3. Mapping to Agent Architecture

Unix V4 Agent Equivalent
main.c Agent orchestrator/main loop
slp.c async task management
trap.c Tool call dispatcher
sys1-4.c Capability/tool definitions
sig.c Event/notification handling
pipe.c Agent-to-agent communication
bio.c I/O abstraction layer
tty.c User interface/terminal
rk.c, tm.c Specific tool implementations

4. Key Patterns

4.1. 1. Core vs. Periphery

Ken's code handles what the system does (process, memory, files). Dennis's code handles how it interfaces with the world.

For agents:

  • Core: Reasoning, planning, memory, tool selection
  • Periphery: Specific tools (web fetch, file edit, shell exec)

4.2. 2. Clean Interface Boundaries

/* bio.c - Dennis's abstraction */
bread(dev, blkno)    /* Read block from any device */
bwrite(bp)           /* Write block to any device */

/* Ken's code just calls bread/bwrite */
/* Never touches device-specific details */

For agents: Tools should expose clean interfaces. The reasoning core shouldn't know if "read file" uses local fs, S3, or network fetch.

4.3. 3. The Trap Table Pattern

/* sysent.c - System call dispatch */
int (*sysent[])()
{
    &nullsys,     /* 0 = indir */
    &rexit,       /* 1 = exit */
    &fork,        /* 2 = fork */
    &read,        /* 3 = read */
    &write,       /* 4 = write */
    ...
};

This is exactly how agent tool dispatch works:

tools = {
    "read_file": read_file_impl,
    "write_file": write_file_impl,
    "web_search": web_search_impl,
    "bash": bash_impl,
}

def dispatch(tool_name, params):
    return tools[tool_name](**params)

4.4. 4. Sleep/Wakeup (async patterns)

/* slp.c - Thompson's async primitives */
sleep(chan, pri)     /* Block until event */
wakeup(chan)         /* Signal event occurred */

Maps to modern agent patterns:

  • Background tasks
  • Waiting for user input
  • Polling for tool completion
  • multi-agent coordination

4.5. 5. Signals (Interrupts)

/* sig.c - Interrupt handling */
signal(sig, handler)   /* Register handler */
kill(pid, sig)         /* Send signal */

Agent equivalents:

  • User interruption (Ctrl-C → Escape)
  • Timeout handling
  • Graceful shutdown
  • Priority escalation

5. Suggested Agent Structure

Based on Unix V4 patterns:

agent/
├── core/            "Ken's code" - The thinking part
│   ├── main.py          Orchestration loop
│   ├── memory.py        Context/state management
│   ├── planner.py       Task decomposition
│   ├── dispatch.py      Tool selection & routing
│   └── signals.py       Interrupt/event handling
│
├── tools/           "Dennis's code" - The doing part
│   ├── base.py          Abstract tool interface
│   ├── filesystem.py    File operations
│   ├── shell.py         Command execution
│   ├── web.py           HTTP/search
│   └── ...
│
└── interface/       User-facing (like tty.c)
    ├── cli.py           CLI interface
    ├── api.py           HTTP API
    └── mcp.py           MCP server

6. The 41 System Call Lesson

Unix V4 had exactly 41 system calls. Not 400. Not 4000.

This minimal surface area is why Unix became portable and why it's still recognizable 50 years later.

For agents: A small, well-defined tool set beats a sprawling one. Claude Code has ~15 core tools. That's intentional.

Unix V4 Syscalls ~41
Claude Code Tools ~15
Ratio ~3:1

Both achieve remarkable capability through composition rather than explosion of primitives.

7. The Pipe Insight

Thompson's pipe implementation is 95 lines of C. It enables infinite composition of simple tools.

cat file | grep pattern | sort | uniq -c

Agent equivalent: Chaining tools, where one's output feeds another's input. The orchestrator doesn't need complex tools–it needs composable ones.

8. What Unix V4 Got Right for Agents

  1. Separation of policy and mechanism
  2. Minimal kernel, rich userspace
  3. Everything is a file (uniform interface)
  4. Small, composable tools
  5. Clean async primitives
  6. Explicit, numbered interfaces (syscalls → tools)

9. What Modern Agents Add

Things Thompson and Ritchie didn't have:

  • Natural language dispatch (no numbered syscalls needed)
  • Reasoning over tool selection (vs. deterministic code)
  • Learned patterns (vs. hardcoded logic)
  • Conversation context (persistent memory across calls)

But the structural insights remain valid.

10. Agent Debugging Case Study: FreeBSD 15 TTO Bug (2026-05-31)

On May 31, 2026, an AI agent (Claude Opus 4.6) was tasked with booting Unix V4 on a FreeBSD 15.0 system. The session that previously worked on FreeBSD 14.3 produced no visible console output. The agent's debugging process illustrates both the power and limitations of agent-driven systems investigation.

10.1. What the Agent Did Well

  1. Systematic register examination – Deposited test code to verify CPU-level SR reads, definitively proving d sr 1 works despite e 177570 showing 0. This is the kind of hypothesis-test cycle that agents excel at.
  2. Built diagnostic tools – Created rawconsole.py (raw byte capture), v4console.py (bit-7 stripping console), and console-filter.py without being asked, recognizing the need for protocol-level visibility.
  3. Minimal reproducer creation – Reduced the problem from "V4 doesn't boot" to a 7-instruction PDP-11 program that demonstrates the exact TTO CSR interaction bug.
  4. Source code analysis – Read prf.c and traced the putchar execution path instruction by instruction, identifying the CSR clear at line 80 as the trigger.

10.2. What the Agent Got Wrong

  1. Early hypothesis lock-in – Spent significant time on the d sr 1 vs 177570 discrepancy, which turned out to be a display artifact. The research subagent eventually corrected this.
  2. Too many parallel approaches – Tried expect, telnet, script(1), LANG=C, stty changes, and TTO mode changes in rapid succession rather than systematically eliminating hypotheses.
  3. Expect scripts with wrong timing – Created multiple expect automation scripts that had the same phantom-newline issue documented in the original experience report. Should have read the existing research first.
  4. Missing the forest for the trees – The fundamental issue (SimH TTO event scheduling broken on FreeBSD 15) wasn't identified until ~90 minutes in. Earlier show queue output showing no TTO events should have been investigated immediately.

10.3. The Subagent Pattern

The session used a research subagent to investigate SimH boot documentation in parallel with the main debugging. Key findings from the subagent:

  • Confirmed d sr 1 CPU-level correctness via SimH source analysis
  • Found the squoze.net canonical boot.ini uses d sr 2
  • Located blog posts confirming V4 works on SimH 3.9-3.12
  • Identified the CSW struct as a deliberate hardware trick by Thompson

This parallels Unix V4's own fork() pattern: spawn a child process to do independent work while the parent continues.

10.4. Lesson: Agents and Hardware Emulation

Agent-driven debugging of hardware emulation hits a specific failure mode: the agent can read source code and examine registers but cannot observe timing. The TTO bug is fundamentally a timing issue – SimH's event scheduler doesn't fire the TTO completion event fast enough for V4's polling loop. An agent examining static state (register values, queue contents) can detect the symptom but cannot directly observe the dynamic behavior that causes it.

The breakthrough came from the agent's minimal test program, which reduced the problem to a deterministic sequence that eliminated timing as a variable. This is the agent equivalent of printf debugging: when you can't observe the system directly, create a controlled experiment that makes the failure visible.

10.5. The putchar Death Spiral

The V4 putchar() code contains a pattern that modern agent tool implementations should avoid:

/* V4 putchar: resource cleanup before resource reuse */
KL->xsr = 0;       // (1) Disable device
KL->xbr = rc;      // (2) Use device
putchar(0);         // (3) Wait for device -- but device is disabled!
KL->xsr = s;       // (4) Re-enable device -- never reached

The equivalent agent antipattern:

# DON'T: release resource before confirming completion
tool.close()              # (1) Close connection
tool.send(data)           # (2) Try to send -- fails silently
await tool.wait_ready()   # (3) Deadlock: closed tool never becomes ready
tool.open()               # (4) Never reached

Thompson wrote this code for real PDP-11 hardware where step (2) triggers an immediate hardware response. The emulator introduces an abstraction gap where the "immediate" response requires event scheduling that depends on the host OS's I/O subsystem.

11. References