docs(ai): move agent rules to ai/skills and add an AI development sandbox (#1498)

The sandbox provides an isolated Docker environment for AI-assisted development
(OpenCode + Claude Code + Qwen Code) against the local AkkudoktorEOS repository.

The sandbox does not protect the repository from harmful changes by the agent.
It limits what a misled agent can reach on the machine, but the agent can still
write any change into the repository.

Primary security defence is to check every change before it is run on the host.
Same holds for opening a pull request or updating one.

Additional changes

- Reduce AGENTS.md to the project summary, baseline rules, completion checklist,
  and an index of the skills.
- Add skills for the Python environment and REST API error responses.
- Add the development guide page docs/develop/ai.md: the AI coding
  workflow, how agents get the project rules from AGENTS.md and ai/skills,
  and how to use the sandbox (start, agent logins, project commands with a
  virtual environment outside the workspace, running EOS and EOSdash,
  GitHub access, persistent data, security model).
- Add a checklist for reviewing agent changes before pushing, starting with
  .git, which stays writable in the sandbox.
- Check out all text files with LF line endings (.gitattributes), also on
  Windows with core.autocrlf, so the sandbox scripts run in the container
  and git in the container agrees with git on the host. Batch files keep CRLF.
- Publish the ports of ai/compose.yaml on request (AI_SANDBOX_PORTS=1).
  docker compose run publishes them only with --service-ports. Off by
  default, so that several sandboxes can run in parallel.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Bobby Noelte
2026-11-09 09:15:12 +01:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 628226d3d9
commit 2245976de4
21 changed files with 2041 additions and 150 deletions
+134
View File
@@ -0,0 +1,134 @@
# ai/compose.yaml
# Sandbox for the AkkudoktorEOS AI coding agent.
# The launchers (run.sh, run.ps1, run.bat) sanity-check a few of the controls below.
# The real protection of this file is the read-only mount of ai/: the agent
# cannot change the sandbox definition from inside the sandbox.
services:
ai-dev:
build:
context: .
dockerfile: Dockerfile
args:
UID: ${HOST_UID:-1000}
GID: ${HOST_GID:-1000}
image: akkudoktor-eos-ai:latest
# NOTE: no container_name — required for `docker compose run` and parallelism.
working_dir: /workspace
# ------------------------------------------------------------------
# Filesystem isolation
# ------------------------------------------------------------------
read_only: true
volumes:
# Repo, read-write (needed by the agent to edit code).
- ..:/workspace:rw
# Read-only overlays for the sandbox definition the launcher uses.
# Without them the agent could rewrite what the next launch runs.
# - ai/: compose.yaml, Dockerfile, entrypoint.sh and the launchers.
# - ai/skills/: read-only unless the launcher is started with
# AI_SANDBOX_SKILLS_RW=1 (the launcher sets AI_SANDBOX_SKILLS_MODE).
# .git stays writable, so the agent can commit. Read-only overlays of
# .git/hooks or .git/config would not protect the host anyway: git also
# reads config and hooks from the directory named in .git/commondir, and
# .git itself can be renamed. Neither can tracked files the host runs
# (Makefile, .pre-commit-config.yaml, tests, scripts) be protected this
# way: review .git and every change on the host before running git or
# anything else there (docs/develop/ai.md, review checklist).
- ../ai:/workspace/ai:ro
- ../ai/skills:/workspace/ai/skills:${AI_SANDBOX_SKILLS_MODE:-ro}
# Persistent state for CLI auth, caches and Claude memory/transcripts.
# This volume persists between sessions, so a misled session can leave
# state behind that takes effect later (e.g. agent settings and hooks in
# ~/.claude, ~/.gitconfig, the venv in ~/.cache). The entrypoint resets
# the shell startup files on every start; for everything else, remove the
# volume (docker volume rm akkudoktor-ai-home) if a session may have been
# misled. entrypoint.sh lives in the image (/usr/local/bin), not here.
# No `nocopy`: an empty volume is populated from the image's /home/dev,
# which also gives it the owner of /home/dev (the dev user). With
# `nocopy`, a fresh volume is owned by root and the container cannot start.
- type: volume
source: ai-home
target: /home/dev
tmpfs:
# /tmp is needed by many tools but must never be executable.
- /tmp:rw,nosuid,nodev,noexec,size=512m,mode=1777
# /run and /var/tmp kept in-memory, noexec.
- /run:rw,nosuid,nodev,noexec,size=64m
- /var/tmp:rw,nosuid,nodev,noexec,size=256m
# ------------------------------------------------------------------
# Privilege & capability isolation
# ------------------------------------------------------------------
privileged: false
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
# ------------------------------------------------------------------
# Network
# ------------------------------------------------------------------
networks:
- ai-net
# ------------------------------------------------------------------
# Testing ports — loopback-only on the host.
# 127.0.0.1:... prevents exposing these services to the LAN/VPN.
# Add new ports here only when you have a concrete test need.
# ------------------------------------------------------------------
ports:
- "127.0.0.1:8503:8503"
- "127.0.0.1:8504:8504"
# ------------------------------------------------------------------
# Resource limits (defense in depth against runaway agents)
# ------------------------------------------------------------------
mem_limit: 4g
memswap_limit: 4g # == mem_limit: disables swap for this cgroup
pids_limit: 512 # fork-bomb protection
cpus: 2.0
ulimits:
nofile:
soft: 4096
hard: 8192
# ------------------------------------------------------------------
# Environment
# ------------------------------------------------------------------
environment:
- TERM=xterm-256color
- CLAUDE_CONFIG_DIR=/home/dev/.claude
- HOME=/home/dev
# Keep the agent's virtual environment out of the host's .venv in every
# shell and tool call (see ai/skills/python-environment/SKILL.md).
- UV_PROJECT_ENVIRONMENT=/home/dev/.cache/eos-venv
# Disable shell history writing so a hijacked home doesn't retain commands.
- HISTFILE=/dev/null
- HISTSIZE=0
# Trust the repository: Docker Desktop (Windows, macOS) shows the bind
# mount as owned by root, and git refuses a repository owned by another
# user ("dubious ownership"). Set here, not in ~/.gitconfig, so it does
# not depend on the home volume.
- GIT_CONFIG_COUNT=1
- GIT_CONFIG_KEY_0=safe.directory
- GIT_CONFIG_VALUE_0=/workspace
# Optional git identity, passed through from the host shell
# (export GIT_USER_NAME / GIT_USER_EMAIL before ./ai/run.sh).
- GIT_USER_NAME
- GIT_USER_EMAIL
stdin_open: true
tty: true
networks:
ai-net:
driver: bridge
volumes:
ai-home:
name: akkudoktor-ai-home