In my last post, we set up pi, a terminal-native AI agent, and pointed it at a locally hosted open-weight model to investigate a network configuration.
Today, we’re going to give that same agent a substantially harder task: read an existing network utility and produce a working implementation in another programming language.
Code is another form of language, and modern models have been trained on many programming languages and software projects. An agent can use that knowledge alongside tools such as compilers, test suites, and source-code search. It can inspect an implementation, write new code, run it, observe failures, and revise its approach. To see what that looks like, we’re going to ask the agent to rewrite the common network utility ping in Go, the target language for the rewrite.
The setup
First, let’s find the original source code for ping. For this exercise, I’m going to use the Linux iputils repository:
$ mkdir ~/Code/ping-rewrite
$ cd ~/Code/ping-rewrite
$ git clone https://github.com/iputils/iputils
Because target language for the rewrite is Go, I also need the Go toolchain on my workstation:
$ brew install go
The agent at work
Now I’ll launch pi inside the ping-rewrite directory, and give it the following prompt:
Prompt: This directory contains ./iputils, which is a repository of Linux-based networking utilities. I’d like you to review the source code for ping and rewrite it in Go in this directory. Please base the rewrite as much as possible on the original code rather than your internal knowledge of ping. Please also include a Makefile for ease of building and testing. Ask any questions you need for clarification.
The agent, in fact, replied with a few questions:



Once I answered the questions, the agent began the work.
It took around an hour to produce a version that compiled, and could complete, a basic test. What’s interesting is that the agent would try a solution, find an issue, and then determine a new way of solving the problem. It changed its implementation strategy several times before producing a usable result.
I had the agent provide a summary of its own work. This is useful as a session record, but it is important to remember that it is the agent’s assessment of what it accomplished, not an independent code review:
Session Summary
Based on the 48-minute session log (234 messages, 8 validation errors):
### Methods Tried
1. Multi-file modular approach (failed first)
- Initially attempted to write 8 separate files under ping/ (config.go, checksum.go, engine.go, options.go, ping.go, state.go, etc.)
- Multiple write tool calls failed with validation errors
2. Bash heredoc approach (successful)
- Switched to writing the entire implementation as a single main.go using cat > main.go << 'GOEOF' bash heredocs
- Split into 5 parts to stay within tool limits
- This was the breakthrough that allowed progress
3. Incremental debugging approach
- go build → found errors → targeted sed fixes → rebuild
- 7 rounds of compile error fixes
### Major Difficulties
┌────────────────────────────────────────────┬──────────────────┐
│ Issue │ Resolution Time │
├────────────────────────────────────────────┼──────────────────┤
│ Validation errors on ask_user_question │ ~5 min │
│ (3 attempts before working) │ │
├────────────────────────────────────────────┼──────────────────┤
│ Write tool failures (8 validation errors │ ~10 min │
│ across the session) │ │
├────────────────────────────────────────────┼──────────────────┤
│ Go compilation errors: UDPAddr struct │ ~12 min across 7 │
│ mismatch, unused variables, missing │ rounds of fixes │
│ fields, incorrect net.ReverseLookupAddr │ │
├────────────────────────────────────────────┼──────────────────┤
│ Logic bug: New() overwriting parsed │ ~3 min │
│ options (DataLen, Ident) │ │
├────────────────────────────────────────────┼──────────────────┤
│ Missing mu field causing runtime panic │ ~2 min │
├────────────────────────────────────────────┼──────────────────┤
│ IPv6 address format for icmp.ListenPacket │ ~2 min │
└────────────────────────────────────────────┴──────────────────┘
### Approximate Timeline (~48 minutes)
- 0-10 min: Source code exploration (10 read calls)
- 10-25 min: Clarifying questions (6 attempts at ask_user_question, 3 failed with validation)
- 25-35 min: Initial implementation attempts (10 write calls, mostly failed)
- 35-50 min: Bash heredoc approach (90 bash calls for writing/debugging)
- 50-55 min: Final testing and verification
### Token Usage Estimate
With 234 messages and the complexity of the session, this was a substantial session:
- ~150-200K tokens for source code reads (ping.c, ping_common.c, ping_output.c, etc.)
- ~50-100K tokens for thinking/reasoning (most messages had thinking blocks)
- ~50-100K tokens for tool call outputs
- Estimated total: 250-400K tokens for the full session
### Outcome
Despite significant friction with the tool validation system, the final result is a fully working ping implementation in 1000 lines of Go that passes go build, go vet, and functional testing with correct output format matching the original.
When the agent mentions “tools,” it means the functions exposed by the harness for operations, such as reading and writing files, running shell commands, or asking the user a question. Each tool expects input in a particular structure. Some of the model’s tool calls did not match that structure, so the harness rejected them. The model then adapted its approach within the session; it did not permanently learn a new capability.
There are two especially interesting details in this summary. First, the agent did not smoothly produce a clean implementation on its first attempt. Tool errors pushed it away from an eight-file design and toward a single file containing approximately 1,000 lines. Second, it used the compiler as a feedback mechanism, fixing errors incrementally until the program was built.
That is useful agentic behaviour, but “the compiler accepted it” is not the same as “the design is good.” The monolithic file and repeated mechanical fixes are technical debt that a human reviewer would still need to assess.
A first functional test
Now let’s test it:
$ ./ping -4 -c 5 google.ca
PING google.ca (142.251.45.131) 56(84) bytes of data.
56 bytes from pnseab-aa-in-f3.1e100.net. (142.251.45.131): icmp_seq=1 ttl=64 time=40.3 ms
56 bytes from pnseab-aa-in-f3.1e100.net. (142.251.45.131): icmp_seq=2 ttl=64 time=26.1 ms
56 bytes from pnseab-aa-in-f3.1e100.net. (142.251.45.131): icmp_seq=3 ttl=64 time=25.9 ms
56 bytes from pnseab-aa-in-f3.1e100.net. (142.251.45.131): icmp_seq=4 ttl=64 time=29.5 ms
56 bytes from pnseab-aa-in-f3.1e100.net. (142.251.45.131): icmp_seq=5 ttl=64 time=31.8 ms
--- google.ca ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 6002ms
rtt min/avg/max/mdev = 25.876/30.714/40.269/11.752 ms
The prototype sent five ICMP echo requests and received five replies. It resolved the hostname, displayed round-trip times, and produced output resembling the familiar Linux utility. That is a meaningful result: in less than an hour, the agent moved from reading an unfamiliar C codebase to producing a Go program that could perform the basic operation.
But this test does not prove that the implementation is a complete or compatible replacement for iputils ping.
Even this small output deserves closer inspection. Values, such as the total elapsed time and the calculated mdev, should be compared with the original utility using controlled input. Output that looks familiar can still contain incorrect calculations or subtly different behaviour.
What would “working” actually mean?
ping looks simple from the outside, but it has accumulated decades of behaviour around protocols, operating systems, privileges, error handling, and command-line compatibility. Before using this rewrite for real work, I would want a repeatable test suite covering at least:
- IPv4 and IPv6 destinations
- Hostnames, literal addresses, and failed DNS lookups
- Reachable hosts, unreachable hosts, timeouts, and packet loss
- Exit codes that scripts may depend on
- Packet sizes, counts, intervals, deadlines, and other supported options
- Duplicate, delayed, malformed, and out-of-order replies
- Signal handling and summary output when the process is interrupted
- Behaviour as an unprivileged user and on systems with different ICMP permissions
- Side-by-side results from the original and rewritten utilities
Supporting these tests would require a robust testing environment with deterministic successes and failures of hostname lookups, routing, and latency. There is a security dimension as well. Depending on the operating system and implementation, a ping utility may need different operating system capabilities. Passing go build and go vet is a useful baseline, not a security assessment.
Source code also has a licence
This was an exercise. I am not planning to distribute the resulting code or present it as an alternative to the original ping utility. The purpose was to explore what an agent could accomplish when given an existing codebase and a difficult implementation task.
If I were planning to distribute the rewrite, licensing would become part of the release process. The prompt explicitly asked the agent to base its implementation on the existing iputils source, whose ping code is distributed under the BSD 3-Clause licence. I would need to review how closely the generated code follows the original, preserve any required copyright and licence notices, document its dependencies, and clearly state the licence under which the rewrite is being distributed.
Changing the programming language — or having an AI perform the rewrite – does not remove those responsibilities. They simply are not necessary to take this private experiment through to its intended conclusion.
A shift in development
Does it work perfectly? I don’t know. I did not perform an exhaustive code review or run the compatibility and security tests described above. There are almost certainly missing behaviours and bugs.
So what’s the point, then? What good is it to have an agent generate a bunch of code if it’s not perfect? Those are fair questions, and they’re being asked all over the industry right now.
Software automation has evolved enormously over the last two decades. We can place software in Continuous Integration and Continuous Deployment (CI/CD) pipelines that build it, analyze it, and run it through a series of tests. A well-documented specification and test suite can catch regressions and expose missing behaviour long before software reaches production.
Agentic coding makes those engineering controls more important, not less. If generating an implementation becomes faster, more of the work shifts toward defining the intended behaviour, constructing representative tests, reviewing architecture and security, and deciding what evidence is sufficient to release it.
The agent demonstrated that it could explore source code, respond to compiler feedback, and produce a functional prototype with remarkable speed. It did not demonstrate that it could replace the maintainers, testing history, or engineering judgement behind a mature network utility. That distinction is the point. The most useful role for an agent today is not to declare the work complete. It is to help engineers reach the point where rigorous review and validation can begin much sooner.
In the next post, we’ll look at how to use agentic AI to explore data.
Missed the previous blogs in this series?
Pt. 1: A primer on agentic AI for network administrators
Pt. 2: Inference Services
Pt. 3: Vendor lock-in, open models, and architectural choice
Pt. 4: Using an AI Agent for Network and Systems Operations

