A proof-of-concept CLI agent built in Go that uses the CodeAct (code-as-action) paradigm: instead of calling predefined tools, the agent instructs a language model to emit executable bash scripts that interact with the environment directly.
Default use case: scanning codebases for hardcoded credentials, exposed API keys, and common security misconfigurations.
Traditional agents use function calling — the LLM picks from a fixed set of predefined tools. This agent does the opposite: the LLM writes arbitrary bash code, and a local engine runs it. This allows the agent to compose multi-step operations, use conditionals, and adapt its approach based on execution feedback — without any tool being predefined in advance.
User task
│
▼
LLM (Gemini) ──► ```bash ... ``` ◄── generates executable script
│
▼
os/exec engine ◄── runs on host, captures stdout/stderr
│
exit code == 0? ──yes──► return result
│
no
│
▼
feedback to LLM ◄── retries up to N times
codeact-agent/
├── cmd/agent/main.go # Entry point — assembles and runs the agent
├── internal/
│ ├── llm/client.go # Gemini API client; Provider and Session interfaces
│ ├── parser/extractor.go # Extracts bash code blocks from LLM responses
│ ├── engine/executor.go # Executes scripts via os/exec; captures stdout/stderr/exit code
│ ├── prompt/builder.go # Builds system directive and per-turn prompts
│ └── loop/orchestrator.go # Feedback loop: LLM → parse → execute → retry
├── .env.example
└── testdir/ # Sample directory with intentional security issues for demo
- Go 1.21+
- A Gemini API key
git clone https://github.com/andesvel/codeact-agent
cd codeact-agent
cp .env.example .env
# Add your credentials to .env.env.example
GENAI_API_KEY=your_key_here
GEMINI_MODEL=gemini-2.5-flashCreate a test directory:
./makeTestDir.shgo build -o agent ./cmd/agent
./agent "your instruction here"Example — security scan:
./agent "Scan ./testdir for hardcoded credentials, API keys, and exposed secrets"Example output:
--- Iniciando escaneo de seguridad ---
--- Searching for AWS and cloud provider keys ---
./testdir/config.yaml:1:aws_secret: AKIAIOSFODNN7EXAMPLE
--- Searching for common password patterns ---
./testdir/database.py:3:password = "admin123"
--- Scan complete ---
Interfaces over concrete types. Every dependency (Provider, Session, Runner) is injected via interface. The orchestrator never imports the Gemini SDK directly — this makes the feedback loop testable with mocks without hitting the real API.
Multi-turn conversation. The feedback loop uses Gemini's chat session API rather than stateless single-turn calls. The LLM retains full context of what it previously generated and what failed, enabling genuine self-correction across retries.
Parser as a hard boundary. LLM responses are never passed raw to the executor. A regex parser extracts the bash block first, enforcing a clean separation between "what the model said" and "what the system runs."
grepexits with code 1 when no matches are found, which the agent may treat as failure. A production implementation would distinguish between execution errors and empty results.- Scripts run directly on the host with no sandboxing. Intended for local development use only.
- Non-determinism: the LLM generates a different script on each run for the same task. Results may vary between executions.