{"id":48671538,"url":"https://github.com/papercomputeco/agentd","last_synced_at":"2026-04-10T12:31:09.215Z","repository":{"id":341052248,"uuid":"1155506209","full_name":"papercomputeco/agentd","owner":"papercomputeco","description":"The StereOS agent management daemon.","archived":false,"fork":false,"pushed_at":"2026-02-27T21:16:30.000Z","size":3134,"stargazers_count":31,"open_issues_count":1,"forks_count":2,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-02-27T22:23:28.842Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/papercomputeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","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,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-02-11T15:37:10.000Z","updated_at":"2026-02-27T21:02:52.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/papercomputeco/agentd","commit_stats":null,"previous_names":["papercomputeco/agentd"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/papercomputeco/agentd","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fagentd","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fagentd/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fagentd/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fagentd/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/papercomputeco","download_url":"https://codeload.github.com/papercomputeco/agentd/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/papercomputeco%2Fagentd/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31642626,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-10T07:40:12.752Z","status":"ssl_error","status_checked_at":"2026-04-10T07:40:11.664Z","response_time":98,"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":[],"created_at":"2026-04-10T12:31:08.365Z","updated_at":"2026-04-10T12:31:09.163Z","avatar_url":"https://github.com/papercomputeco.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# `agentd` 🏃\n\nAI agent management daemon for [stereOS](https://github.com/papercomputeco/stereos).\nStarts, manages, and stops AI agents.\n\nEach agent runs in its own `tmux` session, allowing operators to\n`tmux attach` and observe the agent in real time. `agentd` handles restart\npolicies, timeouts, graceful shutdown, and serves a read-only API for status\npolling.\n\n`agentd` operates on a **reconciliation loop**. Every few seconds it re-reads the\n`jcard.toml` configuration and secrets directory from disk. If either has changed,\nthe running agent is stopped and relaunched with the new\nconfiguration.\n\n## Harnesses\n\nA harness maps a `jcard.toml` harness name to a binary and argument format.\nThe `Harness` interface:\n\n```go\ntype Harness interface {\n    Name() string\n    BuildCommand(prompt string) (bin string, args []string)\n}\n```\n\nBuilt-in harnesses:\n\n| Name | Binary | Prompt flag | Interactive (no prompt) |\n|------|--------|-------------|------------------------|\n| `claude-code` | `claude` | `-p \u003cprompt\u003e` | `claude` |\n| `opencode` | `opencode` | `--prompt \u003cprompt\u003e` | `opencode` |\n| `gemini-cli` | `gemini` | `\u003cprompt\u003e` (positional) | `gemini` |\n| `custom` | `agent` (configurable) | `\u003cprompt\u003e` (positional) | `agent` |\n\nAdding a new harness: implement the interface, register it in `harness.go`'s\n`registry` map, and add the name to config validation.\n\n## Manager\n\nThe manager handles agent lifecycle within a tmux session.\n\n**Start:** If `timeout` is set, wraps context with `context.WithTimeout`.\nCreates a tmux session, launches the harness command via `send-keys`, spawns\na monitoring goroutine.\n\n**Monitor loop:** Polls `tmux.IsSessionRunning()` every 2 seconds. On agent\nexit, evaluates `shouldRestart()`:\n\n| Restart policy | Behavior |\n|----------------|----------|\n| `no` | Never restart |\n| `on-failure` | Restart unless `max_restarts` reached |\n| `always` | Restart unless `max_restarts` reached |\n\nRestart backoff: 3 seconds between attempts.\n\n**Graceful stop:** Sends `C-c` (SIGINT) to the tmux session. Waits up to the\ngrace period (default 30s) for the session to exit. If the grace period\nexpires, forcibly destroys the session.\n\n## tmux sessions\n\n`agentd` uses a dedicated tmux server socket (`/run/agentd/tmux.sock`) isolated\nfrom user sessions. All tmux commands are wrapped in `sudo -u agent --` because\ntmux enforces UID-based ownership checks on socket connections.\n\nSession creation:\n1. Create a detached session with a default shell (`tmux new-session -d`)\n2. Set environment variables via `-e` flags\n3. Set working directory via `-c`\n4. Use `send-keys` to type the command + press Enter\n\nThis approach keeps the underlying shell alive after the agent exits, allowing\ninspection. Socket permissions are set to `0770` with `admin` group ownership\nso admin users can attach.\n\n```bash\n# As admin user inside the VM:\nsudo tmux -S /run/agentd/tmux.sock attach -t claude-code\n```\n\n## Configuration\n\n`agentd` reads only the `[agent]` section from `jcard.toml`, ignoring everything\nelse.\n\n```toml\n[agent]\nharness = \"claude-code\"\nprompt = \"review the code and fix failing tests\"\n# prompt_file = \"./prompts/review.md\"    # takes precedence over prompt\nworkdir = \"/home/agent/workspace\"\nrestart = \"on-failure\"\nmax_restarts = 5\ntimeout = \"2h\"\ngrace_period = \"30s\"\nsession = \"my-session\"\n\n[agent.env]\nMY_VAR = \"my_value\"\n```\n\n| Field | Type | Default | Description |\n|-------|------|---------|-------------|\n| `harness` | string | (required) | `claude-code`, `opencode`, `gemini-cli`, `custom` |\n| `prompt` | string | `\"\"` | Prompt given to the agent (empty = interactive) |\n| `prompt_file` | string | | Path to a prompt file (takes precedence over `prompt`) |\n| `workdir` | string | `/home/agent/workspace` | Agent working directory |\n| `restart` | string | `\"no\"` | `no`, `on-failure`, `always` |\n| `max_restarts` | int | `0` (unlimited) | Max restart attempts (0 = no limit) |\n| `timeout` | string | | Agent timeout as Go duration (e.g. `\"2h\"`) |\n| `grace_period` | string | `\"30s\"` | SIGTERM grace period before force kill |\n| `session` | string | harness name | tmux session name |\n| `env` | map | `{}` | Environment variables for the agent process |\n\n## Secrets\n\nSecrets are files on disk in a directory (default `/run/stereos/secrets/`),\nwritten by `stereosd` to an `admin` accessible tmpfs.\nEach file represents one secret: **filename = env var\nname**, **content = value**.\n\n- Hidden files (`.` prefix) and directories are skipped\n- Trailing newlines are trimmed\n- If the directory does not exist, an empty map is returned\n- Secrets are merged into the agent environment; `[agent.env]` values override\n  secrets with the same name\n\n## API\n\nHTTP over Unix domain socket (`/run/stereos/agentd.sock`, mode `0660`,\ngroup `admin`). This API is read-only.\n\n| Method | Path | Response |\n|--------|------|----------|\n| `GET` | `/v1/health` | `{\"state\":\"running\",\"uptime_seconds\":123}` |\n| `GET` | `/v1/agents` | `[{\"name\":\"claude-code\",\"running\":true,\"session\":\"claude-code\",\"restarts\":0}]` |\n| `GET` | `/v1/agents/{name}` | Single agent status (404 if not found, case-insensitive match) |\n\n\n## NixOS module\n\nThe flake exports `nixosModules.default`:\n\n| Option | Type | Default | Description |\n|--------|------|---------|-------------|\n| `services.agentd.enable` | bool | `false` | Enable the `agentd` daemon |\n| `services.agentd.package` | package | flake default | The `agentd` package |\n| `services.agentd.extraArgs` | list of str | `[]` | Additional CLI arguments |\n\nThe systemd unit includes `tmux` and `sudo` in its `PATH` (required for\nsession management as the agent user). `DynamicUser=true` in the base module;\noverridden to `false` by the stereOS NixOS module since `agentd` needs root to\nmanage tmux sessions for the agent user.\n\n### Local development\n\n`agentd` is designed to run inside stereOS managed by `systemd`. For local\ntesting, override the paths:\n\n```bash\n./build/agentd \\\n    -config /tmp/agentd-test/jcard.toml \\\n    -secret-dir /tmp/agentd-test/secrets \\\n    -tmux-socket /tmp/agentd-test/tmux.sock \\\n    -api-socket /tmp/agentd-test/agentd.sock \\\n    -debug\n```\n\nDebug mode logs the full command, working directory, environment key names,\nand captures tmux pane output on exit.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapercomputeco%2Fagentd","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpapercomputeco%2Fagentd","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpapercomputeco%2Fagentd/lists"}