Compare commits
2 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c6bd3d0d74 | |||
| 0d75d73c23 |
7 changed files with 234 additions and 33 deletions
19
.dockerignore
Normal file
19
.dockerignore
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
.git
|
||||||
|
.gitignore
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
!.env.example
|
||||||
|
.venv
|
||||||
|
venv
|
||||||
|
__pycache__
|
||||||
|
*.pyc
|
||||||
|
*.pyo
|
||||||
|
.pytest_cache
|
||||||
|
.mypy_cache
|
||||||
|
.ruff_cache
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
README.md
|
||||||
|
Dockerfile
|
||||||
|
docker-compose.yml
|
||||||
|
run.sh
|
||||||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
* text=auto eol=lf
|
||||||
|
*.sh text eol=lf
|
||||||
|
Dockerfile text eol=lf
|
||||||
|
*.yml text eol=lf
|
||||||
|
*.yaml text eol=lf
|
||||||
|
*.py text eol=lf
|
||||||
|
*.md text eol=lf
|
||||||
|
*.txt text eol=lf
|
||||||
|
.env.example text eol=lf
|
||||||
58
CHANGELOG.md
Normal file
58
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Changelog
|
||||||
|
|
||||||
|
All notable changes to this project are documented here.
|
||||||
|
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
|
||||||
|
this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.2.0] — 2026-05-20
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Docker support.** `Dockerfile` (python:3.12-slim, non-root user, Streamlit
|
||||||
|
healthcheck) and `docker-compose.yml` for one-command Linux deploys.
|
||||||
|
Configurable `HOST_PORT`, optional CA-bundle volume for self-signed GUIs.
|
||||||
|
- **Bash launcher.** `run.sh` for native (non-Docker) Linux runs. Creates a
|
||||||
|
`.venv` on first use and launches either the Streamlit UI (default) or the
|
||||||
|
REPL. Honours `HOST` and `PORT` env vars.
|
||||||
|
- **Cross-platform line endings.** `.gitattributes` pins LF on shell scripts,
|
||||||
|
YAML, Python and Markdown so checkouts on Linux from a Windows host don't
|
||||||
|
break the launcher with `^M` characters.
|
||||||
|
- **`.dockerignore`** to keep the image lean.
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
- **README rewritten** with Docker + Linux quick starts in front of the
|
||||||
|
Windows / PowerShell path.
|
||||||
|
- `.env.example` now flags v3 as the current API and documents the GUI port
|
||||||
|
conventions (443 for CNSA / containerised, 46443 for native installs) plus
|
||||||
|
the Swagger UI location.
|
||||||
|
|
||||||
|
## [0.1.0] — 2026-05-20
|
||||||
|
|
||||||
|
Initial release.
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- **Agentic chat over the GPFS REST API v3.** Anthropic Claude with native
|
||||||
|
tool-use; a single `gpfs_request` tool whose verb enum is narrowed by env
|
||||||
|
config so disallowed methods can't even be requested.
|
||||||
|
- **Two front-ends.** Rich-styled terminal REPL (`python -m gpfs_agent`) and a
|
||||||
|
pure-Python Streamlit web UI (`gpfs_agent/web.py`). Shared
|
||||||
|
Agent/Dispatcher/Client stack.
|
||||||
|
- **Env-driven guardrails.**
|
||||||
|
- Read-only by default; `GPFS_ALLOW_WRITES` enables `POST/PUT`;
|
||||||
|
`GPFS_ALLOW_DESTRUCTIVE` enables `DELETE`.
|
||||||
|
- `GPFS_CONFIRM_MUTATIONS` gates each mutating call at the human.
|
||||||
|
- Optional `GPFS_PATH_ALLOW` / `GPFS_PATH_DENY` regex on the request path.
|
||||||
|
- Free-text `GPFS_INSTRUCTIONS` / `GPFS_INSTRUCTIONS_FILE` appended to the
|
||||||
|
system prompt for site-specific rules.
|
||||||
|
- **Curated `/scalemgmt/v3` endpoint reference** baked into the system prompt
|
||||||
|
(`gpfs_agent/endpoints.py`), compiled from IBM Storage Scale 5.2.3 / 6.0.0
|
||||||
|
docs. Covers cluster, nodes, NSDs, filesystems, filesets, snapshots,
|
||||||
|
quotas, policies, jobs and perfmon — plus async-job conventions and the
|
||||||
|
`:link` / `:unlink` / `:batchDelete` custom-verb syntax.
|
||||||
|
- **TLS hardening knobs.** `GPFS_VERIFY_TLS` and `GPFS_CA_BUNDLE` for
|
||||||
|
self-signed lab gear.
|
||||||
|
|
||||||
|
[Unreleased]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.2.0...HEAD
|
||||||
|
[0.2.0]: https://git.discworld.casa/laurence/gpfsagent/compare/v0.1.0...v0.2.0
|
||||||
|
[0.1.0]: https://git.discworld.casa/laurence/gpfsagent/releases/tag/v0.1.0
|
||||||
31
Dockerfile
Normal file
31
Dockerfile
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
FROM python:3.12-slim AS base
|
||||||
|
|
||||||
|
ENV PYTHONDONTWRITEBYTECODE=1 \
|
||||||
|
PYTHONUNBUFFERED=1 \
|
||||||
|
PIP_DISABLE_PIP_VERSION_CHECK=1 \
|
||||||
|
PIP_NO_CACHE_DIR=1 \
|
||||||
|
STREAMLIT_SERVER_ADDRESS=0.0.0.0 \
|
||||||
|
STREAMLIT_SERVER_PORT=8501 \
|
||||||
|
STREAMLIT_SERVER_HEADLESS=true \
|
||||||
|
STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y --no-install-recommends curl ca-certificates \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
COPY requirements.txt ./
|
||||||
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
|
COPY gpfs_agent ./gpfs_agent
|
||||||
|
|
||||||
|
RUN useradd --create-home --uid 10001 app && chown -R app:app /app
|
||||||
|
USER app
|
||||||
|
|
||||||
|
EXPOSE 8501
|
||||||
|
|
||||||
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
|
||||||
|
CMD curl -fsS http://localhost:8501/_stcore/health || exit 1
|
||||||
|
|
||||||
|
CMD ["streamlit", "run", "gpfs_agent/web.py"]
|
||||||
98
README.md
98
README.md
|
|
@ -1,39 +1,70 @@
|
||||||
# gpfs-agent
|
# gpfs-agent
|
||||||
|
|
||||||
Agentic chat front-end for the IBM Spectrum Scale (GPFS) REST API, driven by
|
Agentic chat front-end for the IBM Storage Scale (GPFS) REST API v3, driven by
|
||||||
Claude with tool-use. Read-only by default; writes and destructive operations
|
Claude with tool-use. Read-only by default; writes and destructive operations
|
||||||
are gated behind explicit env flags. Ships with both a terminal REPL and a
|
are gated behind explicit env flags. Ships with both a terminal REPL and a
|
||||||
pure-Python web UI (Streamlit).
|
pure-Python web UI (Streamlit). Runs natively or in Docker.
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
```powershell
|
### 1. Configure
|
||||||
cd D:\Projects\gpfs-agent
|
|
||||||
python -m venv .venv
|
|
||||||
.\.venv\Scripts\Activate.ps1
|
|
||||||
pip install -r requirements.txt
|
|
||||||
|
|
||||||
Copy-Item .env.example .env
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
# edit .env: ANTHROPIC_API_KEY, GPFS_API_BASE, GPFS_USERNAME, GPFS_PASSWORD
|
# edit .env: ANTHROPIC_API_KEY, GPFS_API_BASE, GPFS_USERNAME, GPFS_PASSWORD
|
||||||
```
|
```
|
||||||
|
|
||||||
### Web UI
|
### 2a. Docker (recommended for Linux)
|
||||||
|
|
||||||
```powershell
|
```bash
|
||||||
streamlit run gpfs_agent\web.py
|
docker compose up -d --build
|
||||||
|
# open http://<host>:8501
|
||||||
```
|
```
|
||||||
|
|
||||||
Opens http://localhost:8501 with a chat panel, a sidebar showing the mode and
|
Stop / view / update:
|
||||||
system prompt, a tool-call log, and (when writes are enabled in `.env`) a
|
|
||||||
session-scoped "Auto-approve mutations" toggle.
|
|
||||||
|
|
||||||
### Terminal REPL
|
```bash
|
||||||
|
docker compose logs -f
|
||||||
```powershell
|
docker compose pull && docker compose up -d
|
||||||
python -m gpfs_agent
|
docker compose down
|
||||||
```
|
```
|
||||||
|
|
||||||
Commands inside the REPL: `/reset`, `/system`, `/quit`.
|
Override the host port with `HOST_PORT=9000 docker compose up -d`. If your GPFS
|
||||||
|
GUI uses a private CA, drop the cert into `./certs/ca.pem` and uncomment the
|
||||||
|
volume + `GPFS_CA_BUNDLE` lines in `docker-compose.yml`.
|
||||||
|
|
||||||
|
### 2b. Native Linux (no Docker)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./run.sh # web UI on 0.0.0.0:8501
|
||||||
|
./run.sh cli # terminal REPL
|
||||||
|
HOST=127.0.0.1 PORT=8080 ./run.sh # bind override
|
||||||
|
```
|
||||||
|
|
||||||
|
`run.sh` creates a `.venv` on first run and installs dependencies.
|
||||||
|
|
||||||
|
### 2c. Windows (PowerShell)
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
python -m venv .venv
|
||||||
|
.\.venv\Scripts\Activate.ps1
|
||||||
|
pip install -r requirements.txt
|
||||||
|
streamlit run gpfs_agent\web.py # web UI
|
||||||
|
python -m gpfs_agent # REPL
|
||||||
|
```
|
||||||
|
|
||||||
|
## Web UI
|
||||||
|
|
||||||
|
The Streamlit app lives at `/` on the configured port. It shows:
|
||||||
|
|
||||||
|
- A chat panel (uses Claude with tool-use against the GPFS REST API)
|
||||||
|
- A sidebar with model, API base, mode, the live system prompt, and a
|
||||||
|
tool-call log (last 25 calls with status, reason and payload)
|
||||||
|
- A "Reset conversation" button
|
||||||
|
- When `GPFS_ALLOW_WRITES=true`: a session-scoped *"Auto-approve mutations"*
|
||||||
|
checkbox. Mutating calls are denied unless this is on.
|
||||||
|
|
||||||
|
REPL commands: `/reset`, `/system`, `/quit`.
|
||||||
|
|
||||||
## How the guardrails work
|
## How the guardrails work
|
||||||
|
|
||||||
|
|
@ -54,15 +85,9 @@ Guardrails are enforced in three layers:
|
||||||
`cfg.allowed_methods`, so it cannot ask for `DELETE` when disabled.
|
`cfg.allowed_methods`, so it cannot ask for `DELETE` when disabled.
|
||||||
2. **Dispatcher** — `tools.py` re-checks method and allow/deny regex and
|
2. **Dispatcher** — `tools.py` re-checks method and allow/deny regex and
|
||||||
triggers the confirm hook before any mutating HTTP call.
|
triggers the confirm hook before any mutating HTTP call.
|
||||||
3. **System prompt** — `agent.py` injects natural-language guardrails so the
|
3. **System prompt** — `agent.py` injects natural-language guardrails plus a
|
||||||
model behaves sensibly even when a guardrail is loose.
|
curated v3 endpoint reference (see `endpoints.py`) so the agent has
|
||||||
|
accurate context without having to guess.
|
||||||
### Mutation confirmation
|
|
||||||
|
|
||||||
- **REPL**: prints the planned call and waits for `y` at the terminal.
|
|
||||||
- **Web UI**: mutations are denied unless the sidebar checkbox
|
|
||||||
*"Auto-approve mutations this session"* is on. The checkbox only appears
|
|
||||||
when `GPFS_ALLOW_WRITES=true`.
|
|
||||||
|
|
||||||
## Project layout
|
## Project layout
|
||||||
|
|
||||||
|
|
@ -73,17 +98,24 @@ gpfs_agent/
|
||||||
├── agent.py # Anthropic tool-use loop
|
├── agent.py # Anthropic tool-use loop
|
||||||
├── cli.py # Rich REPL
|
├── cli.py # Rich REPL
|
||||||
├── config.py # .env loading & validation
|
├── config.py # .env loading & validation
|
||||||
|
├── endpoints.py # curated /scalemgmt/v3 reference (in-context)
|
||||||
├── gpfs_client.py # httpx wrapper around the GPFS REST API
|
├── gpfs_client.py # httpx wrapper around the GPFS REST API
|
||||||
├── tools.py # Tool schemas + guarded dispatcher
|
├── tools.py # Tool schemas + guarded dispatcher
|
||||||
└── web.py # Streamlit chat app
|
└── web.py # Streamlit chat app
|
||||||
|
Dockerfile # python:3.12-slim + streamlit
|
||||||
|
docker-compose.yml # one-command deploy
|
||||||
|
run.sh # bash helper for native Linux
|
||||||
```
|
```
|
||||||
|
|
||||||
## Notes on the GPFS REST API
|
## Notes on the GPFS REST API
|
||||||
|
|
||||||
- `GPFS_API_BASE` should include the version prefix, e.g.
|
- `GPFS_API_BASE` should include the version prefix, e.g.
|
||||||
`https://gui:443/scalemgmt/v3`. Shipping Spectrum Scale builds currently
|
`https://gui:46443/scalemgmt/v3`. v3 is the current native REST API on
|
||||||
serve `/v2`; set whichever your cluster exposes.
|
Storage Scale 5.2.x / 6.0.x; the typical port is 443 (CNSA / container
|
||||||
- Auth is HTTP Basic against the Spectrum Scale GUI user. Use an account
|
deploys) or 46443 (native install).
|
||||||
scoped to the operations you intend to allow.
|
- Auth is HTTP Basic against a Storage Scale GUI user. Use an account scoped
|
||||||
|
to the operations you intend to allow.
|
||||||
- For self-signed lab gear, set `GPFS_VERIFY_TLS=false` or point
|
- For self-signed lab gear, set `GPFS_VERIFY_TLS=false` or point
|
||||||
`GPFS_CA_BUNDLE` at a PEM file.
|
`GPFS_CA_BUNDLE` at a PEM file (mount as a volume when running in Docker).
|
||||||
|
- The interactive Swagger UI lives at `https://<gui>/openapi/` — useful for
|
||||||
|
discovering endpoints your specific release ships.
|
||||||
|
|
|
||||||
15
docker-compose.yml
Normal file
15
docker-compose.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
services:
|
||||||
|
gpfs-agent:
|
||||||
|
build: .
|
||||||
|
image: gpfs-agent:latest
|
||||||
|
container_name: gpfs-agent
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "${HOST_PORT:-8501}:8501"
|
||||||
|
env_file:
|
||||||
|
- .env
|
||||||
|
# Uncomment to mount a custom CA bundle and tell the app to use it.
|
||||||
|
# volumes:
|
||||||
|
# - ./certs:/certs:ro
|
||||||
|
# environment:
|
||||||
|
# GPFS_CA_BUNDLE: /certs/ca.pem
|
||||||
37
run.sh
Executable file
37
run.sh
Executable file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
if [[ ! -f .env ]]; then
|
||||||
|
echo "No .env found. Copy .env.example to .env and edit it first." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ ! -d .venv ]]; then
|
||||||
|
echo "Creating virtualenv at .venv..."
|
||||||
|
python3 -m venv .venv
|
||||||
|
.venv/bin/pip install -q --upgrade pip
|
||||||
|
.venv/bin/pip install -q -r requirements.txt
|
||||||
|
fi
|
||||||
|
|
||||||
|
MODE="${1:-web}"
|
||||||
|
HOST="${HOST:-0.0.0.0}"
|
||||||
|
PORT="${PORT:-8501}"
|
||||||
|
|
||||||
|
case "$MODE" in
|
||||||
|
web)
|
||||||
|
exec .venv/bin/streamlit run gpfs_agent/web.py \
|
||||||
|
--server.address "$HOST" \
|
||||||
|
--server.port "$PORT" \
|
||||||
|
--server.headless true \
|
||||||
|
--browser.gatherUsageStats false
|
||||||
|
;;
|
||||||
|
cli|repl)
|
||||||
|
exec .venv/bin/python -m gpfs_agent
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 [web|cli]" >&2
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
esac
|
||||||
Loading…
Add table
Add a link
Reference in a new issue