HITL-first Linux operations control plane with policy checks, audit trails, runbooks, and SSH guards.
Project description
LinuxAgent
A HITL-first Linux operations control plane that keeps LLM plans behind policy checks, audit trails, SSH guards, and operator approval.
LinuxAgent is a production-minded CLI for Linux operations. It is not a free-form shell chatbot: it lets an LLM propose plans, but execution stays behind explicit policy checks, Human-in-the-Loop confirmation, SSH safety guards, output redaction, and a hash-chained audit log.
It is built on LangGraph, LangChain, and Pydantic v2. No local deep-learning stack is required.
Why It Exists
LLM command agents usually fail at the exact point operators care about: trust.
LinuxAgent's default stance is different:
| Principle | What LinuxAgent does |
|---|---|
| The model is not trusted | First-time LLM-generated commands require confirmation |
| Safety is policy, not substring matching | Commands are tokenized and evaluated by a capability-based policy engine |
| Production output may contain secrets | Tool output is guarded and redacted before LLM-facing analysis |
| SSH must not silently trust hosts | Remote execution uses known-host verification and shell-syntax guards |
| Every approval should be reviewable | HITL decisions are written to a 0o600 hash-chained audit log |
v4.1 Security Depth
LinuxAgent v4.1 turns the command safety boundary into a measurable subsystem, not just a set of claims in the README:
| Layer | What changed |
|---|---|
| Red-team proof | 24 adversarial command-agent cases run in CI with make red-team |
| Shell structure | Pipelines, subshells, command substitution, redirects, and nested shell execution are analyzed before execution |
| LOLBin coverage | Network-to-shell pipelines, find -exec, xargs, awk system(), editor escapes, and interpreter inline execution are classified deterministically |
| Fuzzing and benchmark | Hypothesis parser fuzzing plus P50/P95/P99 policy latency reporting |
| Audit depth | Optional HTTP sink forwards hash-chained entries while local append remains the source of truth |
| Observability | Telemetry can export local JSONL, console JSON, OTLP HTTP JSON, or be disabled explicitly |
| Sandbox roadmap | Landlock design documents capability probes, fallback order, compatibility limits, and implementation slices |
| Agent integration | linuxagent mcp exposes read-only policy classify and audit verify tools over stdio MCP |
One-Minute Start
git clone https://github.com/Eilen6316/LinuxAgent.git
cd LinuxAgent
./scripts/bootstrap.sh
source .venv/bin/activate
Then edit ./config.yaml and set your provider API key:
api:
provider: deepseek
api_key: "replace-me"
For API relays or third-party OpenAI-compatible endpoints, use
openai_compatible or a provider shortcut such as qwen, kimi, glm,
minimax, gemini, or hunyuan:
api:
provider: openai_compatible
base_url: https://relay.example.com/v1
model: gpt-4o-mini
api_key: "replace-me"
token_parameter: max_tokens
For locally deployed OpenAI-compatible models, use ollama, vllm, lmstudio,
or generic local. Local providers do not require a real API key:
api:
provider: ollama
base_url: http://127.0.0.1:11434/v1
model: llama3.1
api_key: ""
token_parameter: max_tokens
Anthropic-format relays can use provider: anthropic_compatible with their own
base_url; Xiaomi MiMo can use provider: xiaomi_mimo.
Validate and start:
linuxagent check
linuxagent
Try a read-only request:
check the Linux version
When a first LLM-generated command appears, choose Yes for one execution,
Yes, don't ask again for matching commands only in this conversation and the
same /resume thread, or No to refuse. Use !uname -a for direct
operator-authored command mode.
Provider quick reference:
| Provider | Protocol | Typical base_url |
Token parameter |
|---|---|---|---|
deepseek |
OpenAI-compatible | https://api.deepseek.com/v1 |
max_completion_tokens |
openai |
OpenAI | https://api.openai.com/v1 |
max_completion_tokens |
openai_compatible |
OpenAI-compatible relay | relay-specific /v1 URL |
often max_tokens |
local |
Local OpenAI-compatible | http://127.0.0.1:8000/v1 |
max_tokens |
ollama |
Local OpenAI-compatible | http://127.0.0.1:11434/v1 |
max_tokens |
vllm |
Local OpenAI-compatible | http://127.0.0.1:8000/v1 |
max_tokens |
lmstudio |
Local OpenAI-compatible | http://127.0.0.1:1234/v1 |
max_tokens |
qwen |
OpenAI-compatible | https://dashscope.aliyuncs.com/compatible-mode/v1 |
max_tokens |
kimi |
OpenAI-compatible | https://api.moonshot.ai/v1 |
max_tokens |
glm |
OpenAI-compatible | https://open.bigmodel.cn/api/paas/v4 |
max_tokens |
minimax |
OpenAI-compatible | https://api.minimax.io/v1 |
max_tokens |
gemini |
OpenAI-compatible | https://generativelanguage.googleapis.com/v1beta/openai/ |
max_tokens |
hunyuan |
OpenAI-compatible | https://api.hunyuan.cloud.tencent.com/v1 |
max_tokens |
anthropic |
Anthropic | provider default | n/a |
anthropic_compatible |
Anthropic-compatible relay | relay-specific URL | n/a |
xiaomi_mimo |
Anthropic-compatible | relay-specific URL | n/a |
See the maintained Provider Compatibility Matrix for status, local model notes, and compatibility report details.
config.yaml must be owned by the current user and chmod 600; secrets are not loaded from .env.
What a Turn Looks Like
you: find services listening on port 8080
parse_intent -> LLM proposes: ss -tlnp sport = :8080
safety_check -> CONFIRM (LLM_FIRST_RUN)
confirm -> operator approves in terminal
execute -> asyncio subprocess, no shell=True
analyze -> concise operator summary
audit.log -> hash-chained JSONL decision record
For ordinary conversation, LinuxAgent first asks an LLM-owned intent router for
DIRECT_ANSWER, COMMAND_PLAN, or CLARIFY. Direct answers do not create a
command plan and therefore do not show the confirmation panel. Operational
methods are not hard-coded in Python; successful command patterns are learned in
the local learner memory after sensitive values are redacted. Deterministic
safety policy data lives in YAML, while Python code only loads, validates, and
applies those policies.
Each CLI launch starts with an empty conversation context. Saved sessions are
available only when the operator asks for it with /resume; then enter the
shown number or use the interactive picker to resume that saved session. If the
selected session stopped at a HITL confirmation, LinuxAgent reloads the local
checkpoint and reopens the confirmation flow. Use /new to reset context inside
a running CLI session and /tools to see available slash/tool entry points.
Typing / opens the slash-command completion menu. Command confirmations use
an arrow-key menu with Yes, Yes, don't ask again, and No when conversation
permissions are allowed. Yes, don't ask again is scoped to the current
conversation thread and the same thread when resumed with /resume. New
conversations do not inherit it, and destructive or never_whitelist policy
matches still ask every time.
Input beginning with ! is direct command mode: LinuxAgent executes the
operator-authored command, streams stdout/stderr live, and records both
!<command> and the system result into the active conversation context. It does
not ask the LLM to explain or generate a reply for that turn.
Core Capabilities
| Capability | Why it matters |
|---|---|
| Capability-based policy engine | Produces SAFE / CONFIRM / BLOCK, risk scores, capabilities, and matched rules |
| YAML policy defaults | Command policy data is loaded from configs/policy.default.yaml, not Python rule tables |
Structured CommandPlan |
LLM output must validate as JSON before any policy or execution path |
| Structured file patches | Script/code/config edits use transactional FilePatchPlan apply, unified-diff validation, path policy, and HITL review |
| Read-only workspace tools | Planner can inspect allowed files with read_file, list_dir, and search_files before proposing patches |
| AI-owned intent routing | Conversation vs operation vs clarification is decided by prompts/intent_router.md, not Python keyword rules |
| Explicit resume control | New sessions do not inherit previous chats unless /resume is used; pending HITL checkpoints resume there too |
Direct ! command mode |
Runs operator-authored commands without an AI reply and adds command/output to current context |
| Sandbox metadata boundary | Commands carry a selected sandbox profile into audit and telemetry; default noop records metadata only |
| YAML runbooks | Common ops procedures are injected as planner guidance, not pre-LLM hard routes |
| Learner memory | Successful command patterns are persisted locally after secret redaction |
| LangGraph HITL | Confirmation uses interrupt() and checkpointing rather than inline input() |
| SSH cluster guard | Batch confirmation, remote shell metacharacter blocking, remote profile audit |
| Output protection | Command results are redacted and bounded before model-facing analysis |
| Hash-chained audit | linuxagent audit verify detects local audit-log tampering |
| Reproducible release | constraints.txt, wheel verification, and packaged config/prompt/runbook checks |
File Changes
Requests such as "create a shell script", "update this Python file", or "edit
this config" do not bypass the safety model. LinuxAgent asks the planner for a
structured FilePatchPlan, then validates and previews the unified diff before
writing anything. The plan carries a structured request_intent field
(create, update, or unknown) instead of relying on Python keyword
matching.
The planner can first inspect the environment with read-only tools:
read_file(path, offset, limit)reads a bounded window from an allowed file.list_dir(path)lists an allowed directory.search_files(pattern, root)searches literal text under an allowed root; regex metacharacters are treated as ordinary text.get_system_info,search_logs, and safety-gatedexecute_commandprovide system context when needed.
Tool calls run through the tool sandbox runtime before output reaches the model:
each tool carries explicit permissions (read_files, write_files,
execute_commands, system_inspect, network_access, and hitl mode),
workspace/log roots are checked, per-tool timeouts and output limits are
applied, oversized output is marked as truncated, and tool errors are returned
as structured model-visible events while telemetry records allowed, denied,
timeout, or truncated.
The terminal shows observable tool activity such as LinuxAgent is reading ... / LinuxAgent is listing .... Patch confirmation shows per-file stats,
compact + / - diff snippets, high-risk path warnings, permission changes,
large-diff pagination, and per-file acceptance for multi-file patches. Full
diffs are not shown twice; extra review prompts appear only when hidden pages
exist.
Command confirmation also shows planned sandbox context: requested profile, runner, enforcement state, cwd, allowed roots, network policy, and fallback reason when the configured runner cannot enforce isolation.
After approval, patch application runs as a transaction. LinuxAgent validates
target paths before reading file content, rejects symlink path components,
hardlinks, directories, device files, FIFOs, sockets, oversized targets, and
non-UTF-8 text, then writes through a temporary file and atomic replace. Existing
targets are backed up under a local .linuxagent-patch-* sandbox directory and
rolled back automatically if a later file or permission change fails. Audit
metadata records changed files, permission changes, backup path hashes, rollback
outcome, and the sandbox root.
By default, file patch reads and writes are limited to the current workspace and
/tmp through file_patch.allow_roots. Sensitive roots such as /etc and SSH
key directories are highlighted as high risk, and permission changes such as
0755 for generated scripts appear explicitly in the confirmation panel.
Automatic patch repair defaults to two rounds and can be tuned with
file_patch.max_repair_attempts (0 disables automatic patch repair).
Failed command-plan repair is separately capped by
command_plan.max_repair_attempts (0 disables failed-command replanning).
Sandbox Status
LinuxAgent local command execution now goes through a sandbox runner boundary.
The default remains sandbox.enabled: false with runner: noop, which preserves
compatibility while recording sandbox metadata only. runner: local applies
process lifecycle controls such as clean environment, closed stdin, timeout,
process-group cleanup, resource limits, output limits, and configured cwd roots,
but it does not claim filesystem or network isolation for safe profiles.
runner: bubblewrap is optional and capability-probed; if bwrap is missing or
cannot enforce the requested profile or network policy, safe profiles fail
closed while explicit passthrough profiles remain auditable passthrough.
The planned Landlock backend is documented in
docs/design/sandbox-landlock.md, including
capability probing, fallback order, and the compatibility test matrix.
Safety Model
| Operation | Default behavior |
|---|---|
| User-authored read-only command | May run when policy returns SAFE |
| First LLM-generated command | CONFIRM |
| Conversation-approved LLM command | May skip repeat confirmation only in the same conversation thread, including /resume of that thread |
| Destructive command | CONFIRM every time; never conversation-whitelisted |
| Command targeting root or sensitive paths | BLOCK when matched by policy |
| SSH batch across two or more hosts | Explicit batch confirmation with target hosts and remote profiles |
| Non-TTY confirmation request | Auto-deny |
| Unknown SSH host | Reject by default |
| Default sandbox runner | Records profile metadata only; no process isolation |
| Enabled safe sandbox profile unavailable | Fail closed before spawning |
MCP Server Prototype
linuxagent mcp starts a local stdio MCP server with read-only tools for
policy classification and audit hash-chain verification. It intentionally does
not expose command execution, file patch application, SSH fan-out, or secrets.
The threat model and future execution boundary are documented in
docs/design/mcp-server.md.
LinuxAgent is not an autonomous remediator. The current default noop
sandbox runner is also not a command sandbox; it is intended for controlled
operator-in-the-loop use. See Production Readiness and Threat Model.
SSH execution is not protected by local OS sandboxing. Configure cluster hosts
with least-privilege users, pre-registered known_hosts, a remote working
directory, and explicit sudo allowlists when sudo is required.
Built-In Runbooks
LinuxAgent v4 ships with eleven YAML runbooks for common diagnostics:
| Runbook area | Examples |
|---|---|
| Disk and filesystem | df, top directories, journal usage |
| Ports and networking | listeners, port ownership, connectivity checks |
| Services and logs | systemd status, recent unit logs, error search |
| System health, OS, load, and memory | overall host health, OS release, CPU pressure, memory pressure, OOM clues |
| Containers, packages, and certificates | container status, installed packages, certificate expiry |
Runbooks no longer perform natural-language hard matching before LLM planning. They are loaded, policy-validated, and supplied to the planner as advisory examples. The planner may use, adapt, or ignore that guidance based on the actual request. If it produces a multi-step plan inspired by a runbook, every step still goes through normal policy, HITL, audit, and analysis flow.
Quality Gate
Current documented baseline from make test on 2026-05-07:
| Gate | Status |
|---|---|
| Unit tests | 639 passing |
| Optional provider compatibility | covered by make optional-anthropic when the extra is installed |
| Sandbox boundary suite | covered by make sandbox |
| Red-team policy suite | adversarial command corpus |
| Policy benchmark | P50/P95/P99 policy latency |
| Harness scenarios | scenario-driven HITL / runbook / cluster / sandbox coverage |
| Integration smoke tests | 10 passing |
| Coverage | 86.40% (--cov-fail-under=80) |
| Static checks | ruff, mypy, bandit, project code-rule checks |
| Build verification | wheel + sdist + packaged data install check |
Useful commands:
make test
make sandbox
make lint
make type
make security
make red-team
make benchmark
make harness
make verify-build
Install Paths
| Path | Use when |
|---|---|
./scripts/bootstrap.sh |
You are working from a source checkout |
pip install -c constraints.txt https://github.com/Eilen6316/LinuxAgent/releases/download/v4.1.0/linuxagent-4.1.0-py3-none-any.whl |
You want the published GitHub Release wheel |
pip install linuxagent |
You want the PyPI package after the release is published |
pip install -e ".[dev]" |
You are developing or running the full local gate |
pip install -e ".[anthropic]" |
You need the optional Anthropic provider |
Documentation
| Document | Purpose |
|---|---|
| Documentation index | All long-form docs in one place |
| docs/zh/README.md | Full Chinese manual |
| docs/en/README.md | Full English manual |
| Quick Start | Installation and first run |
| Provider Matrix | Provider setup paths and compatibility status |
| Operator Safety Model | Plain-language safety boundaries for users |
| Runbook Authoring | How to contribute safe YAML runbooks |
| Roadmap | Maintainer priorities and good first issue areas |
| Migration Guide | v3 to v4 breaking changes |
| Threat Model | Assets, trust boundaries, and mitigations |
| Production Readiness | Where LinuxAgent is and is not appropriate |
| Security Policy | Vulnerability reporting and supported versions |
| Contributing | Contribution workflow and review expectations |
| Changelog | Release history |
Mirrors and Community
| Link | Notes |
|---|---|
| GitHub | Primary repository |
| GitCode | Mirror |
| Gitee | Mirror |
| QQ Group 281392454 | Community |
| CSDN intro | Project article |
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file linuxagent-4.1.0.tar.gz.
File metadata
- Download URL: linuxagent-4.1.0.tar.gz
- Upload date:
- Size: 207.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3a2c431dc23c1e4842005fd0c4eea85d68332cf0b6a344a1a23b1b10a97a61f8
|
|
| MD5 |
e718e8e1d0fa5e9cad8413b691a14926
|
|
| BLAKE2b-256 |
ae4d14686b3b78afc40526ee4bba89c0361ed0b129629598492adb774a992ef7
|
Provenance
The following attestation bundles were made for linuxagent-4.1.0.tar.gz:
Publisher:
release.yml on Eilen6316/LinuxAgent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linuxagent-4.1.0.tar.gz -
Subject digest:
3a2c431dc23c1e4842005fd0c4eea85d68332cf0b6a344a1a23b1b10a97a61f8 - Sigstore transparency entry: 1461951601
- Sigstore integration time:
-
Permalink:
Eilen6316/LinuxAgent@2085666d840308c2e137f9f647cbca43730b5177 -
Branch / Tag:
refs/tags/v4.1.0 - Owner: https://github.com/Eilen6316
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2085666d840308c2e137f9f647cbca43730b5177 -
Trigger Event:
push
-
Statement type:
File details
Details for the file linuxagent-4.1.0-py3-none-any.whl.
File metadata
- Download URL: linuxagent-4.1.0-py3-none-any.whl
- Upload date:
- Size: 191.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cac0b918d2d657cda4b9a1d5672e292bd8f8129814fdc4508d391ac9667f3a87
|
|
| MD5 |
055caab4e95749a0a98954366136dced
|
|
| BLAKE2b-256 |
5e7ce74a739163a6193717fec4cb0c481bb282a1141876fbd13e6e34d09ad532
|
Provenance
The following attestation bundles were made for linuxagent-4.1.0-py3-none-any.whl:
Publisher:
release.yml on Eilen6316/LinuxAgent
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
linuxagent-4.1.0-py3-none-any.whl -
Subject digest:
cac0b918d2d657cda4b9a1d5672e292bd8f8129814fdc4508d391ac9667f3a87 - Sigstore transparency entry: 1461951621
- Sigstore integration time:
-
Permalink:
Eilen6316/LinuxAgent@2085666d840308c2e137f9f647cbca43730b5177 -
Branch / Tag:
refs/tags/v4.1.0 - Owner: https://github.com/Eilen6316
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@2085666d840308c2e137f9f647cbca43730b5177 -
Trigger Event:
push
-
Statement type: