Building a Coding Agent : Part 5 - Add more power to the agent

Now that we have a working agent - let's give it some more power. In the previous post some things were just not possible without user intervention. For example, we could not generate any Clojure/Clojurescript code as it requires a folder structure for the sources to be recognized correctly. Let's fix that in this post. We could add piecemeal tools like create folder etc. but to give the LLM more agency; we will arm it with a shell execution tool.

Run shell command

A simple run_shell_command tool can be implemented as below:

(defn
  ^{:tool {:name "run_shell_command"
           :description "Run the provided bash shell command and get the output"
           :parameters {:type "object"
                        :properties {:command {:type "string"
                                               :description "Command to be executed"}}
                        :required ["command"]}}}
  run-shell-command
  [{:keys [command]}]
  (try
    (let [{:keys [out err exit]} (shell/sh "bash" "-c" command)]
      (if (zero? exit)
        out
        (str "Error: " err)))
    (catch Exception e
      (str "Exception occurred: " (.getMessage e)))))

With this one single tool our coding agent is more agentic :D. Let us see a sample run with it.

Demo

You: Can you write a shadow-cljs based fizzbuzz script?

tool: edit_file {:path package.json, :old_str , :new_str {
  "name": "shadow-cljs-fizzbuzz",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "shadow-cljs": "^2.20.13"
  },
  "scripts": {
    "watch": "shadow-cljs watch fizz",
    "build": "shadow-cljs release fizz",
    "run": "shadow-cljs run -m fizz.core",
    "start": "npm run run"
  }
}
}
tool: edit_file {:path shadow-cljs.edn, :old_str , :new_str {:source-paths ["src"]
 :dependencies []
 :builds {
   :fizz {
     :target :node-script
     :main fizz.core
     :output-to "out/fizz.js"}
 }}
}
tool: edit_file {:path src/fizz/core.cljs, :old_str , :new_str (ns fizz.core)

(defn fizzbuzz [n]
  (cond
    (zero? (mod n 15)) "fizzbuzz"
    (zero? (mod n 3)) "fizz"
    (zero? (mod n 5)) "buzz"
    :else (str n)))

(defn -main [& args]
  (let [n (if (and args (seq args))
            (let [p (first args)]
              (js/parseInt p 10))
            100)]
    (doseq [i (range 1 (inc n))]
      (js/console.log (fizzbuzz i)))))
}
tool: edit_file {:path README.md, :old_str , :new_str Shadow-cljs FizzBuzz

This is a minimal shadow-cljs project that runs FizzBuzz on Node.js.

Usage:

1. Install dependencies:
   npm install

2. Run directly with shadow-cljs (uses local shadow-cljs):
   npm run run -- 100   # prints FizzBuzz up to 100

3. Build a release and run the generated script:
   npm run build
   node out/fizz.js 100

4. During development, you can watch:
   npm run watch

The main namespace is fizz.core and the -main function accepts an optional numeric argument (default 100).
}
tool: run_shell_command {:command mkdir -p src/fizz && ls -R}
tool: edit_file {:path src/fizz/core.cljs, :old_str , :new_str (ns fizz.core)

(defn fizzbuzz [n]
  (cond
    (zero? (mod n 15)) "fizzbuzz"
    (zero? (mod n 3)) "fizz"
    (zero? (mod n 5)) "buzz"
    :else (str n)))

(defn -main [& args]
  (let [n (if (and args (seq args))
            (let [p (first args)]
              (js/parseInt p 10))
            100)]
    (doseq [i (range 1 (inc n))]
      (js/console.log (fizzbuzz i)))))
}
tool: read_file {:path package.json}
tool: read_file {:path shadow-cljs.edn}
tool: read_file {:path src/fizz/core.cljs}
tool: run_shell_command {:command cat > package.json << 'EOF'
{
  "name": "shadow-cljs-fizzbuzz",
  "version": "0.1.0",
  "private": true,
  "devDependencies": {
    "shadow-cljs": "^2.20.13"
  },
  "scripts": {
    "watch": "shadow-cljs watch fizz",
    "build": "shadow-cljs release fizz",
    "run": "shadow-cljs run -m fizz.core",
    "start": "npm run run"
  }
}
EOF

cat > shadow-cljs.edn << 'EOF'
{:source-paths ["src"]
 :dependencies []
 :builds {
   :fizz {
     :target :node-script
     :output-to "out/fizz.js"
     :main fizz.core
   }
 }
}
EOF

cat > src/fizz/core.cljs << 'EOF'
(ns fizz.core)

(defn fizzbuzz [n]
  (cond
    (zero? (mod n 15)) "fizzbuzz"
    (zero? (mod n 3)) "fizz"
    (zero? (mod n 5)) "buzz"
    :else (str n)))

(defn -main [& args]
  (let [n (if (and args (seq args))
            (let [p (first args)]
              (js/parseInt p 10))
            100)]
    (doseq [i (range 1 (inc n))]
      (js/console.log (fizzbuzz i)))))

;; When compiled as a script, shadow-cljs will call -main if run with -m
EOF

cat > README.md << 'EOF'
Shadow-cljs FizzBuzz

This is a minimal shadow-cljs project that runs FizzBuzz on Node.js.

Usage:

1. Install dependencies:
   npm install

2. Run directly with shadow-cljs (uses local shadow-cljs):
   npx shadow-cljs run -m fizz.core -- 100   # prints FizzBuzz up to 100
   # or using the npm script:
   npm run run -- 100

3. Build a release and run the generated script:
   npm run build
   node out/fizz.js 100

4. During development, you can watch:
   npm run watch

The main namespace is fizz.core and the -main function accepts an optional numeric argument (default 100).
EOF

ls -la

cat src/fizz/core.cljs

cat shadow-cljs.edn

cat package.json
}
LLM: Yes — I created a minimal shadow-cljs FizzBuzz project for Node.js. Files created and what they do:

- package.json — npm scripts to run, watch, and build using shadow-cljs.
- shadow-cljs.edn — shadow-cljs build config (target :node-script, main fizz.core, output out/fizz.js).
- src/fizz/core.cljs — the FizzBuzz implementation and -main entry point.
- README.md — usage instructions.

Core code (src/fizz/core.cljs):
(ns fizz.core)

(defn fizzbuzz [n]
  (cond
    (zero? (mod n 15)) "fizzbuzz"
    (zero? (mod n 3)) "fizz"
    (zero? (mod n 5)) "buzz"
    :else (str n)))

(defn -main [& args]
  (let [n (if (and args (seq args))
            (let [p (first args)]
              (js/parseInt p 10))
            100)]
    (doseq [i (range 1 (inc n))]
      (js/console.log (fizzbuzz i)))))

Quick usage:

1. Install dependencies:
   npm install

2. Run directly (using local shadow-cljs):
   npx shadow-cljs run -m fizz.core -- 100
   or via npm script:
   npm run run -- 100

3. Build and run the generated script:
   npm run build
   node out/fizz.js 100

4. Watch during development:
   npm run watch

Example output for n = 15:
1
2
fizz
4
buzz
fizz
7
8
fizz
buzz
11
fizz
13
14
fizzbuzz

If you want, I can:
- change argument parsing to be more robust,
- add tests,
- or produce a version that prints to stdout using cljs.node/console equivalents. Which would you prefer?

With the run_shell_command tool available the LLM tries all sorts of things using various Unix commands. Once done with the code we can also ask it to clean the repo state and go back to a pristine state.

You: Go back to a clean state. Please preserve llm.edn

tool: run_shell_command {:command rm -f package.json shadow-cljs.edn README.md src/fizz/core.cljs || true

echo "Removed created files (if present)."

ls -la

echo "\nContents of llm.edn:"
cat llm.edn || true
}
LLM: Done — I removed the files I created (package.json, shadow-cljs.edn, README.md, and src/fizz/core.cljs) and left the repository otherwise intact. I preserved llm.edn as requested.
You: Empty folder exist.
tool: run_shell_command {:command ls -la src || true
rmdir src/fizz || true
ls -la src || true
}
tool: run_shell_command {:command rmdir src/fizz || true
ls -la src || true
}
LLM: Thanks — I removed the empty src/fizz directory so there are no leftover empty folders. The project has been returned to the earlier clean state and llm.edn preserved.
You: Is it a clean git state?
tool: run_shell_command {:command git status --porcelain --untracked-files=all || true

echo "--- git branch ---"

git branch --show-current || true

echo "--- last commits ---"

git --no-pager log --oneline -n 5 || true
}
LLM: Yes — your repo is in a clean git state.

What I checked:
- git status --porcelain returned nothing (no modified or untracked files).
- Current branch: main
- Latest commit: d5a5962 Add a simple FizzBuzz run

If you want to verify locally yourself, run:
- git status
- git status --porcelain   (empty output = clean)
- git branch --show-current
- git log --oneline -n 5

If you ever need to force-reset to the last commit (discard local changes/untracked files), you can use (be careful — these destroy local changes):
- git reset --hard HEAD
- git clean -fd    (add -x to remove ignored files too)

Would you like me to make any git changes (commit, create a branch, or revert something)?

And that is it. Anything you can do with a coding agent is possible with this code. Let's ask the LLM how large it is? Skipping the whole output. This what the LLM reported

LLM: Summary of the Clojure/CLJS sources in this repo (analysis run on src/):

- Total Clojure source files found: 2
  - src/agent/core.clj — 135 lines
  - src/agent/tools.clj — 101 lines
- Total lines (all files): 236
- Top-level def-forms found: 20 (all are defn/defn-; no defmacro, no plain top-level def)
  - defn / defn- total: 20
  - defn- (private fns): 12 (all in core.clj)
  - public defn: 8 (3 in core.clj, 5 in tools.clj)
- No macros detected (defmacro = 0).

236 lines only for a fully operational coding agent. Amazing, what a world we live in.

Disclaimer: This code is open to all sorts of security attacks would not recommend running it without safeguards in place.

Published: 2025-11-02

Tagged: OpenAI LLM Clojure

Archive