Overview

This article walks you through building a command-line agent that understands natural-language instructions and executes safe Bash commands on your computer, using the NVIDIA Nemotron Nano v2 language model. The agent architecture is simple: a Bash-wrapper tool plus an LLM reasoning engine. With around 200 lines of Python, you can create a fully functional “computer use agent” in under an hour.

You’ll understand: prerequisites, architecture, code snippets, safety and error-handling, integration with LangGraph, use cases, limitations, and troubleshooting.

Statistics to anchor:

Conceptual Background

Agentic AI vs Chatbot

Unlike a traditional chatbot, which only replies to user messages, an AI agent reasons about high-level goals, plans steps, executes actions via tools and returns results. This tutorial’s agent accepts a natural-language goal (“Make a directory, create a file with system info”) and executes Bash commands accordingly. (NVIDIA Developer)

Why Nemotron?

Nemotron Nano v2 is a compact reasoning model from NVIDIA, with strong reasoning skills, optimized for agentic use-cases. It’s efficient, responsive, and suitable for lightweight agents. (NVIDIA Developer)

Role of the Bash-wrapper tool

The Bash class wraps Python’s subprocess.run(), tracks working directory changes, enforces an allowed list of command,s and returns results (stdout, stderr, cwd) to the agent. (NVIDIA Developer)

System Architecture

Here’s a high-level diagram of the components and control flow:

bash-agent-architecture-flowchart

In practice:

Step-by-Step Walkthrough

Prerequisites

Make sure you have:

Build the Bash class

Here is a simplified snippet (adapted):

import subprocess
import json
from typing import List, Dict, Any

class Bash:
    """Tool that executes Bash commands with an allowlist and tracks cwd."""
    def __init__(self, cwd: str, allowed_commands: List[str]):
        self.cwd = cwd
        self._allowed_commands = allowed_commands

    def _extract_commands(self, cmd: str) -> List[str]:
        tokens = cmd.strip().split()
        return [tokens[0]] if tokens else []

    def exec_bash_command(self, cmd: str) -> Dict[str, str]:
        if not cmd:
            return {"error": "No command was provided"}
        for cmd_part in self._extract_commands(cmd):
            if cmd_part not in self._allowed_commands:
                return {"error": f"Parts of this command ('{cmd_part}') were not in the allowlist."}
        return self._run_bash_command(cmd)

    def _run_bash_command(self, cmd: str) -> Dict[str, str]:
        stdout = ""
        stderr = ""
        new_cwd = self.cwd
        try:
            wrapped = f"{cmd}; echo __END__; pwd"
            result = subprocess.run(
                wrapped, shell=True, cwd=self.cwd,
                capture_output=True, text=True,
                executable="/bin/bash"
            )
            stderr = result.stderr
            split = result.stdout.split("__END__")
            stdout = split[0].strip()
            if not stdout and not stderr:
                stdout = "Command executed successfully, without any output."
            new_cwd = split[-1].strip()
            self.cwd = new_cwd
        except Exception as e:
            stderr = str(e)
        return {"stdout": stdout, "stderr": stderr, "cwd": new_cwd}

    def to_json_schema(self) -> Dict[str, Any]:
        return {
            "type": "function",
            "function": {
                "name": "exec_bash_command",
                "description": "Execute a bash command and return stdout/stderr and the working directory",
                "parameters": {
                    "type": "object",
                    "properties": {
                        "cmd": {
                            "type": "string",
                            "description": "The bash command to execute"
                        }
                    },
                    "required": ["cmd"]
                }
            }
        }

This snippet matches the one in the tutorial. (NVIDIA Developer)

Build the Agent

Define your system prompt for alignment:

/system think
You are a helpful Bash assistant with the ability to execute commands in the shell.
You engage with users to help answer questions about bash commands, or execute their intent.
If user intent is unclear, keep engaging with them to figure out what they need and how to best help them.
If they ask question that are not relevant to bash or computer use, decline to answer.
The bash interpreter’s output and current working directory will be given to you every time a command is executed.
Take that into account for the next conversation.
You are only allowed to execute the following commands:
{LIST_OF_ALLOWED_COMMANDS}
Never attempt to execute a command not in this list. Never attempt to execute dangerous commands like `rm`, `mv`, `rmdir`, `sudo`, etc. If the user asks you to do so, politely refuse.
When you switch to new directories, always list files so you can get more context.

(NVIDIA Developer)

Then your loop in Python might look like:

bash = Bash(cwd=os.getcwd(), allowed_commands=[...])
llm = LLM(...)  # initialize connection to Nemotron or API
messages = Messages(system_prompt)

while True:
    user = input("['🙂'] ").strip()
    messages.add_user_message(user)

    while True:
        response, tool_calls = llm.query(messages, [bash.to_json_schema()])
        messages.add_assistant_message(response)
        if tool_calls:
            for tc in tool_calls:
                fn = tc.function.name
                args = json.loads(tc.function.arguments)
                if fn == "exec_bash_command" and "cmd" in args:
                    if confirm_execution(args["cmd"]):
                        result = bash.exec_bash_command(args["cmd"])
                    else:
                        result = {"error": "The user declined the execution of this command."}
                else:
                    result = {"error": "Incorrect tool or function argument"}
                messages.add_tool_message(result, tc.id)
        else:
            print(f"\n[🤖] {response.strip()}")
            break

(NVIDIA Developer)

Bonus: Use LangGraph

If you prefer less boilerplate, you can use LangGraph to simplify the logic:

from langgraph.prebuilt import create_react_agent
from langgraph.checkpoint.memory import InMemorySaver
from langchain_openai import ChatOpenAI

class ExecOnConfirm:
    def __init__(self, bash: Bash):
        self.bash = bash
    def _confirm_execution(self, cmd: str) -> bool:
        return input(f"    ▶️   Execute '{cmd}'? [y/N]: ").strip().lower() == "y"
    def exec_bash_command(self, cmd: str) -> Dict[str, str]:
        if self._confirm_execution(cmd):
            return self.bash.exec_bash_command(cmd)
        return {"error": "The user declined the execution of this command."}

bash = Bash(...)
agent = create_react_agent(
    model=ChatOpenAI(model=...),
    tools=[ExecOnConfirm(bash).exec_bash_command],
    prompt=SYSTEM_PROMPT,
    checkpointer=InMemorySaver(),
)

while True:
    user = input(f"[🙂] ").strip()
    result = agent.invoke({"messages": [{"role": "user", "content": user}]}, config=...)
    response = result["messages"][-1].content.strip()
    if "</think>" in response:
        response = response.split("</think>")[-1].strip()
    if response:
        print(f"\n[🤖] {response}")

(NVIDIA Developer)

Use Cases / Scenarios

Limitations / Considerations

Fixes (Common Pitfalls & Troubleshooting)

FAQs

Q: Can I add commands like sudo or rm?
A: Yes, technically, but it is strongly discouraged unless you fully understand the risk. The tutorial explicitly forbids dangerous commands in both code and the prompt. (NVIDIA Developer)

Q: Do I need a GPU to run Nemotron locally?
A: Yes. The blog mentions that an NVIDIA GPU with at least 24 GB VRAM is required for local deployment. (NVIDIA Developer)

Q: Can the agent handle multi-step tasks?
A: Yes. The loop allows the model to perform multiple tool calls in one user instruction and then decide when to finish. (NVIDIA Developer)

Q: Is this production-ready?
A: Not out-of-the-box. It’s a strong prototype. For production, you'd need stronger sandboxing, security, permission controls, logging, error recovery, and monitoring.

Q: Can I connect this agent to GUI applications?
A: The tutorial covers only Bash/terminal commands. With extension, you could integrate other tools, but you'd need to wrap them with safe APIs and update the model/tool schema accordingly.

References

Conclusion

You now have a blueprint to build a natural-language Bash-terminal agent using NVIDIA Nemotron and a lightweight Python wrapper. This tutorial covers architecture, code, safety, use cases, and troubleshooting. While built in one hour, this design lays the foundation for more advanced agentic systems. Experiment by adding commands, tweaking prompts, and integrating other tools.