{"id":42126360,"url":"https://github.com/hyperifyio/goagent","last_synced_at":"2026-01-26T15:09:18.588Z","repository":{"id":309789112,"uuid":"1036969368","full_name":"hyperifyio/goagent","owner":"hyperifyio","description":"Autonomous AI agent","archived":false,"fork":false,"pushed_at":"2025-08-20T20:47:27.000Z","size":56754,"stargazers_count":0,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-08-20T21:26:15.961Z","etag":null,"topics":["coding-agent","coding-assistant","llm","openai"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/hyperifyio.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-08-12T21:27:38.000Z","updated_at":"2025-08-20T20:47:33.000Z","dependencies_parsed_at":"2025-08-13T21:23:05.961Z","dependency_job_id":"6983110e-d0cb-4b74-81ad-aca577cb4093","html_url":"https://github.com/hyperifyio/goagent","commit_stats":null,"previous_names":["hyperifyio/goagent"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/hyperifyio/goagent","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperifyio%2Fgoagent","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperifyio%2Fgoagent/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperifyio%2Fgoagent/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperifyio%2Fgoagent/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/hyperifyio","download_url":"https://codeload.github.com/hyperifyio/goagent/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/hyperifyio%2Fgoagent/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28781308,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T13:55:28.044Z","status":"ssl_error","status_checked_at":"2026-01-26T13:55:26.068Z","response_time":59,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["coding-agent","coding-assistant","llm","openai"],"created_at":"2026-01-26T15:09:17.733Z","updated_at":"2026-01-26T15:09:18.580Z","avatar_url":"https://github.com/hyperifyio.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# goagent — Minimal, safe, non‑interactive agent CLI\n\n[![CI (lint+test+build)](https://github.com/hyperifyio/goagent/actions/workflows/ci.yml/badge.svg)](https://github.com/hyperifyio/goagent/actions/workflows/ci.yml)\n[![Go Version](https://img.shields.io/github/go-mod/go-version/hyperifyio/goagent)](https://github.com/hyperifyio/goagent/blob/main/go.mod)\n[![Go Reference](https://pkg.go.dev/badge/github.com/hyperifyio/goagent.svg)](https://pkg.go.dev/github.com/hyperifyio/goagent)\n[![Release](https://img.shields.io/github/v/release/hyperifyio/goagent?sort=semver)](https://github.com/hyperifyio/goagent/releases)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)\n\ngoagent is a compact, vendor‑agnostic command‑line tool for running non‑interactive, tool‑using agents against any OpenAI‑compatible Chat Completions API. It executes a small, auditable allowlist of local tools (argv only; no shell), streams JSON in/out, and prints a concise final answer.\n\n- Why use it: deterministic, portable, and safe by default. Works with hosted providers and with local endpoints like `http://localhost:1234/v1`.\n- Who it’s for: engineers who want a minimal agent runner with clear guarantees and zero vendor lock‑in.\n- What makes it different: strict \"argv‑only\" tool execution, explicit allowlists, and a pragmatic default LLM policy for predictable behavior across providers.\n\n## Table of contents\n- [At a glance](#at-a-glance)\n- [Features](#features)\n- [Installation](#installation)\n- [Quick start](#quick-start)\n- [Usage](#usage)\n  - [Common flags](#common-flags)\n  - [Image generation flags](#image-generation-flags)\n  - [Why you usually don’t need to change knobs](#why-you-usually-dont-need-to-change-knobs)\n  - [Capabilities](#capabilities)\n- [Configuration](#configuration)\n  - [Inheritance and precedence](#inheritance-and-precedence)\n- [Examples](#examples)\n  - [Zero-config with GPT-5](#zero-config-with-gpt-5)\n  - [Tool calls transcript](#tool-calls-transcript)\n  - [Worked example: tool calls and transcript](#worked-example-tool-calls-and-transcript)\n  - [View refined messages (pre-stage and final)](#view-refined-messages-pre-stage-and-final)\n  - [Exec tool](#exec-tool)\n  - [Filesystem tools](#filesystem-tools)\n  - [Image generation tool (img_create)](#image-generation-tool-img_create)\n  - [fs_search](#fs_search)\n- [Security](#security)\n- [Troubleshooting](#troubleshooting)\n- [Tests](#tests)\n - [Documentation](#documentation)\n- [Diagrams](#diagrams)\n- [Contributing](#contributing)\n- [Development tooling](#tooling)\n- [Support](#support)\n- [Roadmap](#roadmap)\n- [Project status](#project-status)\n- [License and credits](#license-and-credits)\n- [Changelog](#changelog)\n- [More examples](#more-examples)\n- [CI quality gates](docs/operations/ci-quality-gates.md)\n - [State persistence (-state-dir)](#state-persistence--state-dir)\n\n\n## At a glance\n- Minimal, portable, vendor‑agnostic: works with any OpenAI‑compatible endpoint\n- Deterministic and auditable: argv‑only tool execution, JSON stdin/stdout, strict timeouts\n- Safe by default: explicit allowlist of tools; no shell evaluation\n- Batteries included: a small toolbelt for filesystem, process, network, and image tasks\n\n## Features\n- OpenAI‑compatible `POST /v1/chat/completions` via `net/http` (no SDK)\n- **Explicit tools allowlist**: `tools.json` with JSON Schema parameters (see [Tools manifest reference](docs/reference/tools-manifest.md))\n- **Deterministic execution**: argv‑only tools, JSON stdin/stdout, per‑call timeouts\n- **Predictable error surface**: tool errors mapped as structured JSON\n- **Observability \u0026 hygiene**: audit logging with redaction; transcript size controls\n\n## Installation\n\n### Requirements\n- Go 1.24+ on Linux, macOS, or Windows\n- Network access to an OpenAI‑compatible API\n- For development and examples: `ripgrep` (rg), `jq`, and `golangci-lint`\n\n### Install options\n1) Download a binary: see [Releases](https://github.com/hyperifyio/goagent/releases)\n\n2) With Go (adds `agentcli` to your `GOBIN`):\n```bash\ngo install github.com/hyperifyio/goagent/cmd/agentcli@latest\n```\n\n3) Build from source:\n```bash\ngit clone https://github.com/hyperifyio/goagent\ncd goagent\nmake bootstrap tidy build build-tools\n```\n\nVerify installation:\n```bash\n./bin/agentcli --version\n```\n\nDeveloper prerequisites (examples):\n```bash\n# ripgrep\n# - Ubuntu/Debian\nsudo apt-get update \u0026\u0026 sudo apt-get install -y ripgrep\n# - macOS (Homebrew)\nbrew install ripgrep\n# - Windows (Chocolatey)\nchoco install ripgrep -y\n\n# golangci-lint (pinned; installs into ./bin via Makefile)\nmake install-golangci\n./bin/golangci-lint version\n\n# jq (used by examples and runbooks)\n# - Ubuntu/Debian\nsudo apt-get install -y jq\n# - macOS (Homebrew)\nbrew install jq\n# - Windows (Chocolatey)\nchoco install jq -y\n\n# make (Windows, for running Makefile targets used in docs)\n# - Windows (Chocolatey)\nchoco install make -y\n```\n\n## Configuration\nConfiguration precedence is: **flags \u003e environment \u003e built‑in defaults**.\n\nEnvironment variables:\n- `OAI_BASE_URL` — API base (default `https://api.openai.com/v1`). Helper scripts will also read `LLM_BASE_URL` if present.\n- `OAI_MODEL` — model ID (default `oss-gpt-20b`). Helper scripts will also read `LLM_MODEL` if present.\n- `OAI_API_KEY` — API key when required. The CLI also accepts `OPENAI_API_KEY` for compatibility.\n- `OAI_HTTP_TIMEOUT` — HTTP timeout for chat requests (e.g., `90s`). Mirrors `-http-timeout`.\n  `OAI_PREP_HTTP_TIMEOUT` — HTTP timeout for pre-stage; overrides inheritance from `-http-timeout`.\n\n### Inheritance and precedence\n\nThe CLI resolves values independently for chat (main), pre-stage, and image flows, with inheritance when explicit values are not provided.\n\nEndpoints and API keys:\n\n| Setting | Resolution order |\n|---|---|\n| Chat base URL | `-base-url` → `OAI_BASE_URL` → default `https://api.openai.com/v1` |\n| Pre-stage base URL | `-prep-base-url` → `OAI_PREP_BASE_URL` → inherit Chat base URL |\n| Image base URL | `-image-base-url` → `OAI_IMAGE_BASE_URL` → inherit Chat base URL |\n| Chat API key | `-api-key` → `OAI_API_KEY` → `OPENAI_API_KEY` |\n| Pre-stage API key | `-prep-api-key` → `OAI_PREP_API_KEY` → inherit Chat API key |\n| Image API key | `-image-api-key` → `OAI_IMAGE_API_KEY` → inherit Chat API key |\n\nModels and sampling:\n\n| Setting | Resolution order |\n|---|---|\n| Chat model | `-model` → `OAI_MODEL` → default `oss-gpt-20b` |\n| Pre-stage model | `-prep-model` → `OAI_PREP_MODEL` → inherit Chat model |\n| Image model | `-image-model` → `OAI_IMAGE_MODEL` → default `gpt-image-1` |\n| Chat temperature vs top-p | One‑knob rule: if `-top-p` is set, omit `temperature`; otherwise send `-temp` (default 1.0) when supported |\n| Pre-stage temperature vs top-p | One‑knob rule applies independently with `-prep-temp`/`-prep-top-p` |\n\nHTTP controls:\n\n| Setting | Resolution order |\n|---|---|\n| Chat HTTP timeout | `-http-timeout` → `OAI_HTTP_TIMEOUT` → fallback to `-timeout` if set |\n| Pre-stage HTTP timeout | `-prep-http-timeout` → `OAI_PREP_HTTP_TIMEOUT` → inherit Chat HTTP timeout |\n| Image HTTP timeout | `-image-http-timeout` → `OAI_IMAGE_HTTP_TIMEOUT` → inherit Chat HTTP timeout |\n| Chat HTTP retries | `-http-retries` → `OAI_HTTP_RETRIES` → default (e.g., 2) |\n| Pre-stage HTTP retries | `-prep-http-retries` → `OAI_PREP_HTTP_RETRIES` → inherit Chat HTTP retries |\n| Image HTTP retries | `-image-http-retries` → `OAI_IMAGE_HTTP_RETRIES` → inherit Chat HTTP retries |\n| Chat HTTP retry backoff | `-http-retry-backoff` → `OAI_HTTP_RETRY_BACKOFF` → default |\n| Pre-stage HTTP retry backoff | `-prep-http-retry-backoff` → `OAI_PREP_HTTP_RETRY_BACKOFF` → inherit Chat backoff |\n| Image HTTP retry backoff | `-image-http-retry-backoff` → `OAI_IMAGE_HTTP_RETRY_BACKOFF` → inherit Chat backoff |\n\n## Quick start\nInstall the CLI and point it to a reachable OpenAI‑compatible API (local or hosted):\n```bash\nexport OAI_BASE_URL=http://localhost:1234/v1\nexport OAI_MODEL=oss-gpt-20b\nmake build build-tools # skip if installed via go install / release binary\n```\n\nCreate a minimal `tools.json` next to the binary (Unix/macOS):\n```json\n{\n  \"tools\": [\n    {\n      \"name\": \"get_time\",\n      \"description\": \"Return current time for an IANA timezone (default UTC). Accepts 'timezone' (canonical) and alias 'tz'.\",\n      \"schema\": {\n        \"type\": \"object\",\n        \"properties\": {\n          \"timezone\": {\"type\": \"string\", \"description\": \"e.g. Europe/Helsinki\"},\n          \"tz\": {\"type\": \"string\", \"description\": \"Alias for timezone (deprecated)\"}\n        },\n        \"required\": [\"timezone\"],\n        \"additionalProperties\": false\n      },\n      \"command\": [\"./tools/bin/get_time\"],\n      \"timeoutSec\": 5\n    }\n  ]\n}\n```\n\nOn Windows, use a `.exe` suffix for tool binaries:\n```json\n{\n  \"tools\": [\n    {\n      \"name\": \"get_time\",\n      \"schema\": {\"type\":\"object\",\"properties\":{\"timezone\":{\"type\":\"string\"}},\"required\":[\"timezone\"],\"additionalProperties\":false},\n      \"command\": [\"./tools/bin/get_time.exe\"],\n      \"timeoutSec\": 5\n    }\n  ]\n}\n```\n\nRun the agent:\n```bash\n./bin/agentcli \\\n  -prompt \"What's the local time in Helsinki? If tools are available, call get_time.\" \\\n  -tools ./tools.json \\\n  -debug\n```\n\nExpected behavior: the model may call `get_time`; the CLI executes `./tools/bin/get_time` (or `get_time.exe` on Windows) with JSON on stdin, appends the result as a `tool` message, calls the API again, then prints a concise final answer.\n\nTip: run `./bin/agentcli -h` for the complete help output.\n\n## Usage\nFlags are order‑insensitive. You can place `-prompt` and other flags in any order; precedence remains flag \u003e environment \u003e default.\n### Common flags\n```text\n-prompt string         User prompt (required)\n-tools string          Path to tools.json (optional)\n-system string         System prompt (default: helpful and precise)\n-base-url string       OpenAI‑compatible base URL (env OAI_BASE_URL; scripts accept LLM_BASE_URL fallback)\n-api-key string        API key if required (env OAI_API_KEY; falls back to OPENAI_API_KEY)\n-model string          Model ID (env OAI_MODEL; scripts accept LLM_MODEL fallback)\n-max-steps int         Maximum reasoning/tool steps (default 8)\n                       A hard ceiling of 15 is enforced; exceeding the cap\n                       terminates with: \"needs human review\".\n-http-timeout duration HTTP timeout for chat completions (env OAI_HTTP_TIMEOUT; default falls back to -timeout)\n-prep-http-timeout duration HTTP timeout for pre-stage (env OAI_PREP_HTTP_TIMEOUT; default falls back to -http-timeout)\n-prep-model string      Pre-stage model ID (env OAI_PREP_MODEL; inherits -model if unset)\n-prep-base-url string   Pre-stage base URL (env OAI_PREP_BASE_URL; inherits -base-url if unset)\n-prep-api-key string    Pre-stage API key (env OAI_PREP_API_KEY; falls back to OAI_API_KEY/OPENAI_API_KEY; inherits -api-key if unset)\n-prep-http-retries int  Pre-stage HTTP retries (env OAI_PREP_HTTP_RETRIES; inherits -http-retries if unset)\n-prep-http-retry-backoff duration Pre-stage HTTP retry backoff (env OAI_PREP_HTTP_RETRY_BACKOFF; inherits -http-retry-backoff if unset)\n-prep-dry-run           Run pre-stage only, print refined Harmony messages to stdout, and exit 0\n-print-messages         Pretty-print the final merged message array to stderr before the main call\n-http-retries int      Number of retries for transient HTTP failures (timeouts, 429, 5xx). Uses jittered exponential backoff. (default 2)\n-http-retry-backoff duration Base backoff between HTTP retry attempts (exponential with jitter). (default 300ms)\n-tool-timeout duration Per-tool timeout (default falls back to -timeout)\n-timeout duration      [DEPRECATED] Global timeout; prefer -http-timeout and -tool-timeout\n-temp float            Sampling temperature (default 1.0)\n-top-p float           Nucleus sampling probability mass (conflicts with -temp; omits temperature when set)\n-prep-top-p float      Nucleus sampling probability mass for pre-stage (conflicts with -temp; omits temperature when set)\n-prep-profile string   Pre-stage prompt profile (deterministic|general|creative|reasoning); sets temperature when supported (conflicts with -prep-top-p)\n-prep-enabled          Enable pre-stage (default true). When false, skip pre-stage and proceed directly to main call.\n-debug                 Dump request/response JSON to stderr\n-verbose               Also print non-final assistant channels (critic/confidence) to stderr\n-channel-route name=stdout|stderr|omit\n                       Override default channel routing (final→stdout, critic/confidence→stderr); repeatable\n-quiet                 Suppress non-final output; print only final text to stdout\n-capabilities          Print enabled tools and exit\n-print-config          Print resolved config and exit\n-dry-run               Print intended state actions (restore/refine/save) and exit without writing state\n--version | -version   Print version and exit\n```\nRun `./bin/agentcli -h` to see the built‑in help.\n\n### Image generation flags\n\nThe following flags control the Images API behavior used by the assistant when generating images. Precedence is always: flags \u003e environment \u003e inheritance \u003e default.\n\n| Flag | Environment | Default / Inheritance | Description |\n|---|---|---|---|\n| `-image-base-url string` | `OAI_IMAGE_BASE_URL` | Inherits `-base-url` | Image API base URL |\n| `-image-api-key string` | `OAI_IMAGE_API_KEY` | Inherits `-api-key`; falls back to `OPENAI_API_KEY` | API key for Images API |\n| `-image-model string` | `OAI_IMAGE_MODEL` | `gpt-image-1` | Images model ID |\n| `-image-http-timeout duration` | `OAI_IMAGE_HTTP_TIMEOUT` | Inherits `-http-timeout` | HTTP timeout for image requests |\n| `-image-http-retries int` | `OAI_IMAGE_HTTP_RETRIES` | Inherits `-http-retries` | Retry attempts for transient image HTTP errors |\n| `-image-http-retry-backoff duration` | `OAI_IMAGE_HTTP_RETRY_BACKOFF` | Inherits `-http-retry-backoff` | Base backoff for image HTTP retries |\n| `-image-n int` | `OAI_IMAGE_N` | `1` | Number of images to generate |\n| `-image-size string` | `OAI_IMAGE_SIZE` | `1024x1024` | Size WxH |\n| `-image-quality string` | `OAI_IMAGE_QUALITY` | `standard` | `standard` or `hd` |\n| `-image-style string` | `OAI_IMAGE_STYLE` | `natural` | `natural` or `vivid` |\n| `-image-response-format string` | `OAI_IMAGE_RESPONSE_FORMAT` | `url` | `url` or `b64_json` |\n| `-image-transparent-background` | `OAI_IMAGE_TRANSPARENT_BACKGROUND` | `false` | Request transparent background when supported |\n\n### Why you usually don’t need to change knobs\n- The default `-temp 1.0` is standardized for broad provider/model parity and GPT‑5 compatibility.\n- The one‑knob rule applies: if you set `-top-p`, the agent omits `temperature`; otherwise it sends `temperature` (default 1.0) and leaves `top_p` unset.\n- The one‑knob rule applies for both stages: if you set `-top-p` (or `-prep-top-p`), the agent omits `temperature` for that stage; otherwise it sends `temperature` (default 1.0) when supported. Pre‑stage profiles are available via `-prep-profile`, e.g. `deterministic` sets temperature to 0.1 when supported.\n- See the policy for details and rationale: [ADR‑0004: Default LLM policy](docs/adr/0004-default-llm-policy.md).\n\n### Capabilities\nList enabled tools from a manifest without running the agent. The output includes a prominent header warning, and certain tools like `img_create` are annotated with an extra warning because they make outbound network calls and can save files:\n```bash\n./bin/agentcli -tools ./tools.json -capabilities\n```\n\n## Examples\n### Zero-config with GPT-5\nRun against a GPT‑5 compatible endpoint without tuning sampling knobs. The CLI sends `temperature: 1.0` by default for models that support it.\n```bash\n./bin/agentcli -prompt \"Say ok\" -model gpt-5 -base-url \"$OAI_BASE_URL\" -api-key \"$OAI_API_KEY\" -max-steps 1 -debug\n# stderr will include a request dump containing \"\\\"temperature\\\": 1\"\n```\n\n### Tool calls transcript\nMinimal JSON transcript showing correct tool‑call sequencing:\n```json\n[\n  {\"role\":\"user\",\"content\":\"What's the local time in Helsinki?\"},\n  {\n    \"role\":\"assistant\",\n    \"content\":null,\n    \"tool_calls\":[\n      {\n        \"id\":\"call_get_time_1\",\n        \"type\":\"function\",\n        \"function\":{\n          \"name\":\"get_time\",\n          \"arguments\":\"{\\\"timezone\\\":\\\"Europe/Helsinki\\\"}\"\n        }\n      }\n    ]\n  },\n  {\n    \"role\":\"tool\",\n    \"tool_call_id\":\"call_get_time_1\",\n    \"name\":\"get_time\",\n    \"content\":\"{\\\"timezone\\\":\\\"Europe/Helsinki\\\",\\\"iso\\\":\\\"2025-08-17T12:34:56Z\\\",\\\"unix\\\":1755424496}\"\n  },\n  {\"role\":\"assistant\",\"content\":\"It's 15:34 in Helsinki.\"}\n]\n```\nNotes:\n- For parallel tool calls (multiple entries in `tool_calls`), append one `role:\"tool\"` message per `id` before calling the API again. Order of tool messages is not significant as long as each `tool_call_id` is present exactly once.\n- Transcript hygiene: when running without `-debug`, the CLI replaces any single tool message content larger than 8 KiB with `{\"truncated\":true,\"reason\":\"large-tool-output\"}` before sending to the API. Use `-debug` to inspect full payloads during troubleshooting.\n\n### Worked example: tool calls and transcript\nSee `examples/tool_calls.md` for a self-contained, test-driven worked example that:\n- Exercises default temperature 1.0\n- Demonstrates a two-tool-call interaction with matching `tool_call_id`\n- Captures a transcript via `-debug` showing request/response JSON dumps\n\nRun the example test:\n```bash\ngo test ./examples -run TestWorkedExample_ToolCalls_TemperatureOne_Sequencing -v\n```\n\n### Split backends for chat and image\n\nUse one provider for chat and another for image generation by overriding the image backend only:\n\n```bash\nexport OAI_BASE_URL=https://api.example-chat.local/v1\nexport OAI_API_KEY=chat-key\n\n./bin/agentcli \\\n  -prompt \"Create a simple logo\" \\\n  -tools ./tools.json \\\n  -image-base-url https://api.openai.com/v1 \\\n  -image-api-key \"$OPENAI_API_KEY\" \\\n  -image-model gpt-image-1 \\\n  -image-size 1024x1024\n```\n\n### View refined messages (pre-stage and final)\nSee also ADR‑0005 for the pre‑stage flow and channel routing details: `docs/adr/0005-harmony-pre-processing-and-channel-aware-output.md`.\nInspect message arrays deterministically without running the full loop:\n\n```bash\n# Pre-stage only: print refined Harmony messages and exit\n./bin/agentcli -prompt \"Say ok\" -prep-dry-run | jq .\n\n# Before the main call: pretty-print merged messages to stderr, then proceed\n./bin/agentcli -prompt \"Say ok\" -print-messages 2\u003e \u003e(jq .)\n```\n\n### Exec tool\nBuild the exec tool and run a simple command (Unix):\n```bash\nmake build-tools\necho '{\"cmd\":\"/bin/echo\",\"args\":[\"hello\"]}' | ./tools/bin/exec\n# =\u003e {\"exitCode\":0,\"stdout\":\"hello\\n\",\"stderr\":\"\",\"durationMs\":\u003cn\u003e}\n```\nTimeout example:\n```bash\necho '{\"cmd\":\"/bin/sleep\",\"args\":[\"2\"],\"timeoutSec\":1}' | ./tools/bin/exec\n# =\u003e non-zero exit, stderr contains \"timeout\"\n```\n\n### Filesystem tools\nThe following examples assume `make build-tools` has produced binaries into `tools/bin/*`.\n\n#### fs_read_file\n```bash\nmake build-tools\nprintf 'hello world' \u003e tmp_readme_demo.txt\necho '{\"path\":\"tmp_readme_demo.txt\"}' | ./tools/bin/fs_read_file | jq .\nrm -f tmp_readme_demo.txt\n```\n\n#### fs_append_file\n```bash\nmake build-tools\necho -n 'hello ' | base64 \u003e b64a.txt\necho -n 'world'  | base64 \u003e b64b.txt\necho '{\"path\":\"tmp_append_demo.txt\",\"contentBase64\":\"'\"$(cat b64a.txt)\"'\"}' | ./tools/bin/fs_append_file | jq .\necho '{\"path\":\"tmp_append_demo.txt\",\"contentBase64\":\"'\"$(cat b64b.txt)\"'\"}' | ./tools/bin/fs_append_file | jq .\ncat tmp_append_demo.txt; rm -f tmp_append_demo.txt b64a.txt b64b.txt\n```\n\n#### fs_write_file\n```bash\nmake build-tools\necho -n 'hello world' | base64 \u003e b64.txt\necho '{\"path\":\"tmp_write_demo.txt\",\"contentBase64\":\"'\"$(cat b64.txt)\"'\"}' | ./tools/bin/fs_write_file | jq .\ncat tmp_write_demo.txt; rm -f tmp_write_demo.txt b64.txt\n```\n\n#### fs_mkdirp\n```bash\nmake build-tools\necho '{\"path\":\"tmp_mkdirp_demo/a/b/c\",\"modeOctal\":\"0755\"}' | ./tools/bin/fs_mkdirp | jq .\nls -ld tmp_mkdirp_demo/a/b/c\necho '{\"path\":\"tmp_mkdirp_demo/a/b/c\",\"modeOctal\":\"0755\"}' | ./tools/bin/fs_mkdirp | jq .\nrm -rf tmp_mkdirp_demo\n```\n\n#### fs_rm\n```bash\nmake build-tools\nprintf 'temp' \u003e tmp_rm_demo.txt\necho '{\"path\":\"tmp_rm_demo.txt\"}' | ./tools/bin/fs_rm | jq .\nmkdir -p tmp_rm_dir/a/b \u0026\u0026 touch tmp_rm_dir/a/b/file.txt\necho '{\"path\":\"tmp_rm_dir\",\"recursive\":true}' | ./tools/bin/fs_rm | jq .\nrm -rf tmp_rm_dir\n```\n\n#### fs_move\n```bash\nmake build-tools\nprintf 'payload' \u003e tmp_move_src.txt\necho '{\"from\":\"tmp_move_src.txt\",\"to\":\"tmp_move_dst.txt\"}' | ./tools/bin/fs_move | jq .\nprintf 'old' \u003e tmp_move_dst.txt; printf 'new' \u003e tmp_move_src.txt\necho '{\"from\":\"tmp_move_src.txt\",\"to\":\"tmp_move_dst.txt\",\"overwrite\":true}' | ./tools/bin/fs_move | jq .\nrm -f tmp_move_src.txt tmp_move_dst.txt\n```\n\n#### fs_listdir\n```bash\nmake build-tools\nmkdir -p tmp_listdir_demo/a b \u0026\u0026 touch tmp_listdir_demo/.hidden tmp_listdir_demo/a/afile tmp_listdir_demo/bfile\necho '{\"path\":\"tmp_listdir_demo\"}' | ./tools/bin/fs_listdir | jq '.entries | map(.path)'\njq -n '{path:\"tmp_listdir_demo\",recursive:true,globs:[\"**/*\"],includeHidden:false}' | ./tools/bin/fs_listdir | jq '.entries | map(select(.type==\"file\") | .path)'\nrm -rf tmp_listdir_demo\n```\n\n#### fs_apply_patch\n```bash\nmake build-tools\ncat \u003e /tmp/demo.diff \u003c\u003c'EOF'\n--- /dev/null\n+++ b/tmp_patch_demo.txt\n@@ -0,0 +1,2 @@\n+hello\n+world\nEOF\njq -n --arg d \"$(cat /tmp/demo.diff)\" '{unifiedDiff:$d}' | ./tools/bin/fs_apply_patch | jq .\nprintf 'hello\nworld\n' | diff -u - tmp_patch_demo.txt \u0026\u0026 echo OK\n```\n\n#### fs_edit_range\n```bash\nmake build-tools\nprintf 'abcdef' \u003e tmp_edit_demo.txt\necho -n 'XY' | base64 \u003e b64.txt\njq -n --arg b \"$(cat b64.txt)\" '{path:\"tmp_edit_demo.txt\",startByte:2,endByte:4,replacementBase64:$b}' | ./tools/bin/fs_edit_range | jq .\ncat tmp_edit_demo.txt # =\u003e abXYef\nrm -f tmp_edit_demo.txt b64.txt\n```\n\n#### fs_stat\n```bash\nmake build-tools\nprintf 'hello world' \u003e tmp_stat_demo.txt\necho '{\"path\":\"tmp_stat_demo.txt\",\"hash\":\"sha256\"}' | ./tools/bin/fs_stat | jq .\nrm -f tmp_stat_demo.txt\n```\n\n### Image generation tool (img_create)\n\nGenerate images via an OpenAI‑compatible Images API and save files into your repository (default) or return base64 on demand.\n\nQuickstart (Unix/macOS/Windows via `make build-tools`):\n\n```bash\nmake build-tools\n```\n\nMinimal `tools.json` entry (copy/paste next to your binary):\n\n```json\n{\n  \"tools\": [\n    {\n      \"name\": \"img_create\",\n      \"description\": \"Generate image(s) with OpenAI Images API and save to repo or return base64\",\n      \"schema\": {\n        \"type\": \"object\",\n        \"required\": [\"prompt\"],\n        \"properties\": {\n          \"prompt\": {\"type\": \"string\"},\n          \"n\": {\"type\": \"integer\", \"minimum\": 1, \"maximum\": 4, \"default\": 1},\n          \"size\": {\"type\": \"string\", \"pattern\": \"^\\\\d{3,4}x\\\\d{3,4}$\", \"default\": \"1024x1024\"},\n          \"model\": {\"type\": \"string\", \"default\": \"gpt-image-1\"},\n          \"return_b64\": {\"type\": \"boolean\", \"default\": false},\n          \"save\": {\n            \"type\": \"object\",\n            \"required\": [\"dir\"],\n            \"properties\": {\n              \"dir\": {\"type\": \"string\"},\n              \"basename\": {\"type\": \"string\", \"default\": \"img\"},\n              \"ext\": {\"type\": \"string\", \"enum\": [\"png\"], \"default\": \"png\"}\n            },\n            \"additionalProperties\": false\n          }\n        },\n        \"additionalProperties\": false\n      },\n      \"command\": [\"./tools/bin/img_create\"],\n      \"timeoutSec\": 120,\n      \"envPassthrough\": [\"OAI_API_KEY\", \"OAI_BASE_URL\", \"OAI_IMAGE_BASE_URL\", \"OAI_HTTP_TIMEOUT\"]\n    }\n  ]\n}\n```\n\nRun the agent with a prompt that instructs the assistant to call `img_create` and save under `assets/`:\n\n```bash\nexport OAI_BASE_URL=${OAI_BASE_URL:-https://api.openai.com/v1}\nexport OAI_API_KEY=your-key\n\n./bin/agentcli \\\n  -tools ./tools.json \\\n  -prompt \"Generate a tiny illustrative image using img_create and save it under assets/ with basename banner\" \\\n  -debug\n\n# Expect: one or more PNGs under assets/ (e.g., assets/banner_001.png) and a concise final message on stdout\n```\n\nNotes:\n- By default, the tool writes image files and does not include base64 in transcripts, avoiding large payloads.\n- To return base64 instead, pass `{ \"return_b64\": true }` to the tool; base64 is elided in stdout unless `IMG_CREATE_DEBUG_B64=1` or `DEBUG_B64=1` is set.\n- Windows: the built binary is `./tools/bin/img_create.exe` and `tools.json` should reference the `.exe`.\n- See Troubleshooting for network/API issues and timeouts: `docs/runbooks/troubleshooting.md`.\n\n#### fs_search\n```bash\nmake build-tools\nmkdir -p tmp_search_demo \u0026\u0026 printf 'alpha\\nbeta\\ngamma\\n' \u003e tmp_search_demo/sample.txt\njq -n '{query:\"^ga\",globs:[\"**/*.txt\"],regex:true}' | ./tools/bin/fs_search | jq '.matches'\nrm -rf tmp_search_demo\n```\n\n## Security\n- Tools are an explicit allowlist from `tools.json`\n- No shell interpretation; commands executed via argv only\n- JSON contract on stdin/stdout; strict timeouts per call\n- Treat model output as untrusted input; never pass it to a shell\n\nSee the full threat model in `docs/security/threat-model.md`.\n\n### Unrestricted tools warning\n- Enabling `exec` grants arbitrary command execution and may allow full network access. Treat this as remote code execution.\n- Run the CLI and tools in a sandboxed environment (container/jail/VM) with least privilege.\n- Keep `tools.json` minimal and audited. Do not pass secrets via tool arguments; prefer environment variables or CI secret stores.\n- Audit log redaction: set `GOAGENT_REDACT` to mask sensitive values in audit entries. `OAI_API_KEY`/`OPENAI_API_KEY` are always masked if present.\n\n## State persistence (-state-dir)\n\nPersist and restore execution state to make repeated runs deterministic and faster.\n\n- Enable by passing `-state-dir \u003cdir\u003e` (or `AGENTCLI_STATE_DIR`). The directory must be private (`0700`).\n- On first run, the CLI saves a snapshot `state-\u003cRFC3339UTC\u003e-\u003c8charSHA\u003e.json` and a pointer file `latest.json`.\n- On subsequent runs with the same scope, the CLI restores prompts/settings and skips pre-stage unless `-state-refine` is provided.\n- Partition contexts with `-state-scope` (or `AGENTCLI_STATE_SCOPE`); when unset, a default scope is derived from model, base URL, and toolset.\n- Inspect actions without touching disk using `-dry-run`.\n\nExamples:\n\n```bash\n# First run saves a snapshot\n./bin/agentcli -prompt \"Say ok\" -tools ./tools.json -state-dir \"$PWD/.agent-state\"\n\n# Restore and skip pre-stage\n./bin/agentcli -prompt \"Say ok\" -tools ./tools.json -state-dir \"$PWD/.agent-state\"\n\n# Refine existing state with inline text\n./bin/agentcli -prompt \"Say ok\" -state-dir \"$PWD/.agent-state\" -state-refine -state-refine-text \"Tighten tone\"\n\n# Use a custom scope to keep contexts separate\n./bin/agentcli -prompt \"Say ok\" -state-dir \"$PWD/.agent-state\" -state-scope docs-demo\n```\n\nSee ADR‑0012 for rationale and details: `docs/adr/0012-state-dir-persistence.md`.\n\n## Troubleshooting\nCommon issues and deterministic fixes are documented with copy‑paste commands in `docs/runbooks/troubleshooting.md`.\n\n## Documentation\nStart with the [Documentation index](docs/README.md) for design docs, ADRs, and references:\n- [Tools manifest reference](docs/reference/tools-manifest.md)\n- [Research tools reference](docs/reference/research-tools.md)\n- [CLI reference](docs/reference/cli-reference.md)\n- [Interface: code.sandbox.js.run](docs/interfaces/code.sandbox.js.run.md)\n- [Architecture: Module boundaries](docs/architecture/module-boundaries.md)\n- [Security: Threat model](docs/security/threat-model.md)\n- [ADR‑0005: Harmony pre‑processing and channel‑aware output](docs/adr/0005-harmony-pre-processing-and-channel-aware-output.md)\n- [Pre-stage Harmony output contract](docs/harmony-prestage.md)\n- [ADR‑0006: Image generation tool (img_create)](docs/adr/0006-image-generation-tool-img_create.md)\n- [ADR‑0010: Adopt SearXNG \u0026 network research toolbelt (CLI-only)](docs/adr/0010-research-tools-searxng.md)\n\n## Diagrams\n- Agent loop: `docs/diagrams/agentcli-seq.md`\n- Toolbelt interactions: `docs/diagrams/toolbelt-seq.md`\n- Pre‑stage flow: `docs/diagrams/harmony-prep-seq.md`\n\n## Tests\nRun the full test suite (offline):\n```bash\ngo test ./...\n```\nLint, vet, and formatting checks:\n```bash\nmake lint\nmake fmt   # apply gofmt -s -w to the repo\n```\n\nGuarded logs cleanup:\n```bash\n# Only removes ./logs when ./logs/STATE trimmed equals DOWN\nmake clean-logs\n\n# End-to-end verification of the guard logic (creates temp dirs)\nmake test-clean-logs\n```\n\nReproducible builds: the `Makefile` uses `-trimpath` and stripped `-ldflags` with VCS stamping disabled so two clean builds produce identical binaries. Verify locally by running two consecutive `make clean build build-tools` and comparing `sha256sum` outputs.\n\n## Contributing\nContributions are welcome! See `CONTRIBUTING.md` for workflow, coding standards, and how to run quality gates locally. Please also read `CODE_OF_CONDUCT.md`.\n\nUseful local helpers during development:\n- `make check-tools-paths` — enforce canonical `tools/cmd/NAME` sources and `tools/bin/NAME` invocations (requires `rg`)\n- `make verify-manifest-paths` — ensure relative `tools.json` commands use `./tools/bin/NAME` (absolute allowed in tests)\n- `make build-tool NAME=\u003cname\u003e` — build a single tool binary into `tools/bin/NAME`\n- `make check-go-version` — fail fast if your local Go major.minor differs from `go.mod`\n\nIf your local toolchain does not match, you will see:\n```text\nGo toolchain mismatch: system X.Y != go.mod X.Y\n```\nRemediation: install the matching Go version shown by `go.mod` (e.g., from the official downloads) or switch via your version manager, then rerun `make check-go-version`.\n\n## Tooling\n\nThis repository pins the toolchain for deterministic results:\n- CI uses the Go version declared in `go.mod` across all OS jobs.\n- Linting is performed with a pinned `golangci-lint` version managed by the `Makefile`.\n\nSee ADR‑0003 for the full policy and rationale: [docs/adr/0003-toolchain-and-lint-policy.md](docs/adr/0003-toolchain-and-lint-policy.md).\n\n## Support\n- Open an issue on the tracker: [Issues](https://github.com/hyperifyio/goagent/issues)\n  - For security concerns, avoid posting secrets in logs. If a private report is needed, open an issue with minimal detail and a maintainer will reach out.\n - Follow updates from the author on LinkedIn: [Jaakko Heusala](https://www.linkedin.com/in/jheusala/)\n\n## Roadmap\nPlanned improvements and open ideas are tracked in `FUTURE_CHECKLIST.md`. Larger architectural decisions are recorded under `docs/adr/` (see ADR‑0001 and ADR‑0002). Contributions to the roadmap are welcome via issues and PRs.\n\n## Project status\nExperimental, but actively maintained. Interfaces may change before a stable 1.0.\n\n## License and credits\nMIT license. See `LICENSE`.\n\nMaintainers and authors:\n- Hyperify.io maintainers\n- Primary author: Jaakko Heusala\n\nAcknowledgements: inspired by OpenAI‑compatible agent patterns; built for portability and safety.\n\n## Changelog\nSee `CHANGELOG.md` for notable changes and release notes.\n\n## More examples\nSee `examples/unrestricted.md` for copy‑paste prompts demonstrating `exec` + file tools to write, build, and run code in a sandboxed environment.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperifyio%2Fgoagent","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fhyperifyio%2Fgoagent","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fhyperifyio%2Fgoagent/lists"}