Skip to content
hydra-dynamixPublic

About

agent and noise context summarizer tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

10 Commits

Folders and files

Repository files navigation

agentctl

agentctl is a small Rust library and CLI for giving LLM coding agents compact, bounded reports from noisy commands and long-running agent tasks.

The common use case is a tool loop around Claude Code, Codex, or another agent: run the expensive command normally, keep the full raw log on disk, and return a short structured summary that is cheap for the model to read.

Install

cargo install --git https://github.com/hydra-dynamix/agentctl agentctl

Generate a starter config:

agentctl config generate
agentctl config check

agentctl reads ./agentctl.toml first, then falls back to ~/.agentctl/config.toml. Raw command logs and detached job records live under ~/.agentctl/jobs/. On Windows, USERPROFILE is used when HOME is unset.

Inspect the current platform, home-directory resolution, LDGR harness selection, configured agents, and commands actually available on PATH:

agentctl discover
agentctl discover --json

For an ldgr-loop or ldgr-summary profile, agentctl negotiates ldgr.launcher-compatibility.v1 before reading worker output or starting the agent. Agentctl 0.1.x requires LDGR Core >=0.1.13, <0.2.0. An older or mixed Core is rejected with a durable agentctl.compatibility/core-incompatible recovery record and exact upgrade/rollback commands. Install both executables from the same LDGR Core release bundle.

Agentctl works with HOME on Unix, macOS, WSL, and Windows environments that provide it. Native Windows also falls back to USERPROFILE, then HOMEDRIVE + HOMEPATH; AGENTCTL_HOME is an explicit final configuration override for unusual service or test environments. A synthesized home is passed to child harnesses as HOME.

ldgr install records its ordered harness selection in ~/.ldgr/config.toml. Agentctl also reads the older ~/.ldgr/config.json during migration. Resolution order is:

  1. an explicit agentctl run <agent> profile;
  2. a runnable generic ldgr-loop profile;
  3. the first runnable profile among LDGR's default and selected harnesses;
  4. for an agent omitted on the command line, another runnable configured profile.

An unavailable explicit non-LDGR profile never silently changes providers. When the generic LDGR profile is stale, Agentctl can use the next selected harness and prints the decision to stderr. Pass --no-fallback to disable that behavior. Failures list the detected platform, config source, selected harnesses, runnable alternatives, and repair commands.

Run a predefined PR review

From a CI checkout of a pull request, launch the built-in audit policy against the open PR whose head exactly matches HEAD:

agentctl task pr-review \
  --agent codex \
  --requirements /absolute/path/to/requirements.md \
  --detach \
  --json

The requirements file is optional, must be an absolute path to a readable UTF-8 regular file, and is embedded in the review prompt. The confidence threshold defaults to 90 and can be changed with --confidence-threshold <0-100>.

The task resolves the repository and associated PR through git and gh, then pins the audit to the exact HEAD SHA. Blocking findings produce a request-changes review. A passing review below the confidence threshold produces a non-approving comment. At or above the threshold, the reviewer may approve and enable GitHub auto-merge using a merge commit, but only after rechecking that the PR head is unchanged. Required checks and branch protections are never bypassed. The merge-commit strategy preserves the PR's development commits and records a distinct landing event. The reviewer also checks that the PR description records all material changes so the repository's change history remains useful.

The runner needs GitHub permissions to read pull requests, submit reviews, and enable auto-merge. Ambient gh authentication works, or configure an organization-installed GitHub App once in the runner's home config:

[github_app]
app_id_command = ["op-vault", "get", "AGENTCTL_GH_APP_ID", "--account", "production"]
private_key_command = ["op-vault", "get", "AGENTCTL_GH_PRIVATE_KEY", "--account", "production"]

For every review, agentctl retrieves the credentials, signs a short-lived JWT, and mints an installation token scoped to the current repository. The token is provided to gh and the review agent as GH_TOKEN; it is not written to the prompt, logs, supervisor specification, or job record. GitHub App credential commands are accepted only from the machine-owned home config, never from an untrusted repository agentctl.toml. If [github_app] is absent, existing ambient gh authentication behavior is unchanged.

Repository and PR content are treated as untrusted review material by the predefined policy.

Wrap a noisy command

Use exec when an agent wants to run a command but should receive only the important lines:

agentctl exec --json --label cargo-test -- cargo test

The exit code is the child process exit code, so existing tool-loop error handling still works. A failing cargo test returns a compact JSON summary:

{
  "command": "cargo test",
  "exit_code": 101,
  "duration_ms": 1842,
  "preview_lines": [
    "failures:",
    "test result: FAILED. 12 passed; 1 failed"
  ],
  "raw_log_path": "~/.agentctl/jobs/1780000000-1234-cargo-test/output.log"
}

The full stdout and stderr remain in raw_log_path for later inspection. Use --raw when you deliberately want the untouched command output streamed back.

Output is streamed to capture files while only the configured head/tail window is retained in memory; the complete raw log is assembled when the command finishes. On Unix, agent commands run in an isolated process group. Interrupting agentctl forwards the signal to the complete command tree, waits for it, and terminates descendants left behind by a command that exits early. On Linux, agentctl also adopts and reaps orphaned descendants so repeated tool loops do not accumulate zombie or live background agent processes. Cleanup is scoped to each capture, preserving unrelated child processes and concurrent captures in library hosts. Each Linux capture has a dedicated subreaper supervisor, so descendants remain owned even after changing session, clearing their environment, or exiting before cleanup. The embedding application is never made a subreaper. This is lifecycle management, not a security sandbox. On Windows, commands start suspended and join a Job Object before running, so remaining descendants are terminated when the command finishes or its host exits.

Run a detached agent task

Configure agents in TOML:

[summary]
max_output_bytes = 16384
tail_bytes = 4096
max_preview_lines = 12

[agents.codex]
command = ["codex", "exec", "--sandbox", "danger-full-access"]
prompt_stdin = true

[agents.pi]
command = ["pi", "-p"]
prompt_stdin = false

[agents.claude-code]
command = ["claude", "-p"]
prompt_stdin = false

# Alias for Claude Code.
[agents.claude]
command = ["claude", "-p"]
prompt_stdin = false

# Provider-specific wrappers are external commands. Point these profiles at
# your chosen REST/websocket bridge scripts or binaries.
[agents.openai-rest]
command = ["openai-rest-agent"]
prompt_stdin = true

[agents.openai-websocket]
command = ["openai-websocket-agent"]
prompt_stdin = true

[agents.ollama]
command = ["ollama", "run", "llama3"]
prompt_stdin = true

# Native OpenRouter Chat Completions provider. The command returns only the key
# on stdout. agentctl never writes the key to its config or job logs.
[providers.openrouter]
kind = "openrouter"
base_url = "https://openrouter.ai/api/v1"
api_key_command = ["op", "read", "op://vault/OPENROUTER_API_KEY/password"]
model = "openrouter/auto"

# Use the native provider through the normal run command.
[agents.openrouter-api]
provider = "openrouter"
prompt_stdin = true

# Run Pi with its OpenRouter provider instead.
[agents.openrouter]
command = ["pi", "--provider", "openrouter", "--model", "openrouter/auto", "--print", "--no-session"]
prompt_stdin = false

[agents.openclaw]
command = ["openclaw", "run"]
prompt_stdin = false

[agents.opencode]
command = ["opencode", "run"]
prompt_stdin = false

Use either OpenRouter profile:

agentctl run openrouter-api --prompt "Summarize the current design."
agentctl run openrouter --prompt "Inspect the repository and fix the failing test."

api_key_env = "OPENROUTER_API_KEY" is also supported. Set exactly one of api_key_env and api_key_command for each provider.

Start a long task without keeping the caller attached:

agentctl run codex \
  --prompt "Run cargo test, fix failures, and report the files changed." \
  --cwd /path/to/project \
  --detach \
  --json

# With no explicit profile, Agentctl uses the valid LDGR selection when
# available and otherwise prefers codex. Stdin is accepted as the prompt:
printf 'Run cargo test and summarize the result.' | agentctl run --json

Codex profiles now default to --sandbox danger-full-access, allowing Git staging, commits, and branch creation. To opt into the restricted workspace sandbox, use agentctl run codex --sandbox --prompt "Inspect the project". This also works with --detach and --iterations; .git remains read-only in that mode. The flag requires a profile whose command starts with codex exec (including an absolute path to Codex). Other runtimes retain their own permission settings.

Existing config files retain their explicit commands. To migrate an older Codex profile, change its workspace-write argument to danger-full-access in agentctl.toml or ~/.agentctl/config.toml. Workers still inherit any restrictions imposed by the process that launches agentctl.

The detached command returns a job record:

{
  "id": "1780000000-1234-codex",
  "agent": "codex",
  "state": "running",
  "pid": 12345,
  "started_at": 1780000000,
  "finished_at": null,
  "exit_code": null,
  "command": "codex exec --sandbox danger-full-access",
  "cwd": "/path/to/project",
  "iterations": 1,
  "log_path": "~/.agentctl/jobs/1780000000-1234-codex/output.log"
}

Poll and inspect it later:

agentctl status 1780000000-1234-codex --json
agentctl logs 1780000000-1234-codex
agentctl list --json

logs is intentionally the raw-output command. status and list stay bounded and machine-readable for agent loops. Status reads at most the final 16 KiB of a log, even when a line is very long. Raw logs are streamed without requiring valid UTF-8.

Detached supervisors use isolated process groups on Unix. On Windows they start without a console or inherited caller handles, so the caller's output pipes close immediately after the launch report.

JSON contract

Every public command that has --json uses stable field names.

Summary:

{
  "command": "string",
  "exit_code": 0,
  "duration_ms": 0,
  "preview_lines": ["string"],
  "raw_log_path": "/path/to/output.log"
}

Foreground run --json returns an array of iteration reports:

[
  {
    "iteration": 1,
    "iterations": 2,
    "summary": {}
  }
]

If an iteration fails, its report and preceding successful reports are returned, further iterations stop, and the CLI exits with the failed command's exit code.

Detached run --json and list --json use job records:

{
  "id": "string",
  "agent": "codex",
  "state": "running",
  "pid": 12345,
  "started_at": 1780000000,
  "finished_at": null,
  "exit_code": null,
  "command": "codex exec --sandbox danger-full-access",
  "cwd": "/path/to/project",
  "iterations": 1,
  "log_path": "/path/to/output.log"
}

state is one of running, succeeded, failed, or stale.

Status wraps a job record with bounded log tail lines:

{
  "record": {},
  "tail_lines": ["string"]
}

These shapes are semver-relevant.

Library

The crate also exposes the capture, summary, config, and job modules used by the CLI. The public API is intentionally small so other local tooling can build the same bounded reports without shelling out. Embedding applications can call jobs::launch_detached_with_executable with the path to an installed agentctl binary. launch_detached re-executes the current binary and is intended for callers that implement agentctl's supervisor command.

Development

Run dev fix and dev ci using the repository's Rust-only .dev/config.toml, or use Cargo directly. CLI integration tests require Python on PATH. CI is configured for Windows, Linux, and macOS. Linux-only subreaper tests are separate from the portable command, provider, and detached-job tests.

Provider support

agentctl includes a native OpenRouter Chat Completions provider. Native provider profiles use the same bounded capture, summaries, detached jobs, and iteration handling as command profiles.

Other provider-specific clients remain external commands. Configure those clients under agents, as shown for OpenAI and Ollama above.

About

agent and noise context summarizer tool

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages