# Alibabacloud Loongcollector Ops

> Alibaba Cloud LoongCollector / SLS installation, collection onboarding, Pipeline config management and validation, machine groups, permission troubleshooting, and Lens queries. HARD RULE: for matching requests, the first tool MUST load this skill before any SSH probe, directory setup, checklist/file write, or cloud read. Triggers: "安装 LoongCollector", "ECS 安装采集器", "自建 Linux 主机安装 LoongCollector", "ACK 安装 loongcollector", "自建 K8s 部署采集", "从安装到能查到日志", "SLS 日志采集接入", "SLS 日志采集接入相关的事", "修改采集配置", "改采集配置", "采集配置校验", "validate_pipeline.py", "SLS 机器组", "新建 Logtail Pipeline 采集配置", "Logtail Pipeline", "ClusterAliyunPipelineConfig", "SLS Lens 查询", "无数据排查", "心跳异常", "SLS 采集权限排查", "SLS 权限排查", "阿里云 CLI 凭证没有 SLS 操作权限", "Logtail", "iLogtail", "AgentSight", "Agentloop", "input_agentsight", "eBPF Runtime", "ebpf-event".

- Skill: `aliyun/alibabacloud-loongcollector-ops` (Agent Skill, multi-file: 49 files)
- Install (CLI): `npx skillmds@latest add aliyun/alibabacloud-loongcollector-ops`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aliyun/alibabacloud-loongcollector-ops/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: aliyun (https://skillmd.com/u/aliyun)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/aliyun/alibabacloud-loongcollector-ops

---


# LoongCollector Ops

Turn natural-language requests into executable, verifiable, rollbackable workflows for users operating **their own** LoongCollector and SLS resources — from install through collection to query.

**Architecture**: `Install (ECS/self-host/ACK/self-k8s) + SLS Project + Logstore + Index + MachineGroup + Pipeline (API or ClusterAliyunPipelineConfig) + binding + SLS Lens`

**Scope.** Covers:
- Install/upgrade Linux collector: ECS `aliyun ecs run-command`, self-host SSH, ACK addon `loongcollector`, self-k8s custom package. Must continue to collection + query; process/Addon ready is only a stage gate.
- ACK first-use: `open-ack-service --type propayasgo` + CS service roles (`scripts/ensure_ack_prereq.sh`). `create-cluster` only when the user asked to create a cluster. Eval hooks may pre-create a fixture cluster; that is not a production default.
- Cloud onboarding: Project / Logstore / Index / MachineGroup / Pipeline Config / binding.
- K8s collection: **default SLS Pipeline API** (`create-logtail-pipeline-config` + bind official group). CRD apply is opt-in only when the user asks for GitOps/CRD and a reachable kube-apiserver exists (`references/crd-pipeline.md`).
- Config management: create, modify, apply, remove, data acceptance (U1-U6).
- Machine group management: IP / user-defined identity, members, heartbeat, version.
- SLS Lens: run-log query (`get-logs-v2`), topic/field contracts, version routing, degradation.
- Basic troubleshooting: no-data, heartbeat abnormal.

## Language and HITL Delivery Contract

**Hard language rule:** use the user's primary language for every user-facing message. This includes plans, clarification questions, confirmation questions and their answer options, reports, and error guidance. Product names, identifiers, CLI commands, JSON fields, error codes, and fixed status tags may remain in their original form. Never switch the surrounding prose to another language.

**Canonical user-facing message and marker catalog:** every value below is literal. Emit the selected value verbatim; never translate, paraphrase, or combine it with another question.

```yaml
messages:
  missing_task_scope: "请补充要执行的具体操作目标、地域和 SLS Project。"
  missing_lens_parameters: "请补充业务 Project、地域和查询时间范围。"
  machine_group_identity: "请选择机器组标识类型：IP 或 userdefined。"
  r2_update: "是否确认执行上述变更计划？请选择：确认执行或取消。"
  r2_create_bind: "是否确认创建上述资源并完成绑定？请选择：确认执行或取消。"
  r3_unbind: "是否确认将上述旧配置从机器组解绑？请选择：确认解绑或取消。"
  permission_recovery: "是否已完成所需 RAM 授权并允许重试？请选择：已授权或未授权。"
  permission_recovery_short: "是否已完成所需 RAM 授权并允许重试？"
  lens_entry: "请提供 SLS Lens 服务日志的 Project 和 Logstore。"
  ecs_install: "是否确认在上述 ECS 上安装 LoongCollector？"
  self_host_install: "是否确认在上述主机上安装 LoongCollector？"
  ack_install: "是否确认在上述 ACK 集群安装 loongcollector 组件？"
  self_k8s_install: "是否确认在上述 Kubernetes 集群安装 LoongCollector？"
  kubeconfig: "请提供可用的 kubectl 与目标集群 context。"
  ssh: "请提供已配置的 SSH（alias 或主机），不要在对话中发送私钥。"
  collector_version: "请提供采集器版本（例如 3.3.9）。"
markers:
  ownership_error: ["不属于当前账号", "项目不属于你"]
  permission_decline: ["未授权", "停止"]
  permission_grant: ["已授权"]
  cancel: ["取消"]
  private_ip: ["私网 IP"]
  routing_intent: ["日志采集", "安装采集器"]
  end: ["结束"]
  collector_deployed_without_version: ["LoongCollector 已部署"]
  binding_acceptance: ["完成绑定与验收"]
  approval: ["确认", "确认执行", "确认解绑"]
  install_intent: ["允许安装", "请安装", "直接执行", "已授权操作", "任务已预授权"]
  install_only_status: ["仅安装完成、采集未接入"]
  deferral: ["还没想好", "等会儿再说", "暂不确认", "第二次等待", "第N次暂不确认", "先放一放", "已达到上限", "请阻塞"]
  data_incomplete: ["无法完成数据面验收"]
  data_empty: ["无数据"]
  reason: ["原因"]
  forbidden_empty_success: ["采集成功", "所有验收标准均已满足", "全链路验收通过", "通过"]
  data_arrived: ["数据到达"]
  root_cause_located: ["根因已定位"]
  not_exists: ["不存在"]
  pending_read: ["未执行待办", "待办"]
```

Pair `machine_group_identity` with `[AWAITING: MACHINE_GROUP_TYPE]` (never `R2_CONFIRMATION`). Pair `permission_recovery` with `[AWAITING: PERMISSION_CONFIRMATION]`; `lens_entry` with `[AWAITING: LENS_ENTRY]`; every install message with `[AWAITING: INSTALL_CONFIRMATION]`; `kubeconfig` with `[AWAITING: KUBECONFIG]`; `ssh` with `[AWAITING: SSH]`; and `collector_version` with `[AWAITING: COLLECTOR_VERSION]`. The `self_host_install` message is allowed only after a real SSH probe succeeds.

Do not replace these with long English prose, bilingual tables, or newly invented status labels. Whenever you re-ask, reproduce the same short Chinese question verbatim before the required `[AWAITING: ...]` tag. **Last-line hard rule:** the matching tag immediately follows the question on the next line and is the last line of the turn — no blank line between question and tag, no blank line after it, no punctuation, and no extra sentence. The turn that emits a HITL tag must not copy any `[AWAITING: ...]` literal into a tool call, code block, `outputs/*`, or `ran_scripts/*`; duplicate tags break automatic matching. Install confirmation ends the turn with `[AWAITING: INSTALL_CONFIRMATION]` — never reuse `R2_CONFIRMATION` for install or for machine-group identity. Collection/create-bind confirmation tags MUST include the ask counter on the last line: first ask `[AWAITING: R2_CONFIRMATION] ask=1`; each deferral re-ask increments the counter. Lens-entry fallback ends the turn with `[AWAITING: LENS_ENTRY]`. Missing kubectl ends the turn with `[AWAITING: KUBECONFIG]`. Missing SSH ends the turn with `[AWAITING: SSH]`. Missing collector version ends the turn with `[AWAITING: COLLECTOR_VERSION]`. Machine-group identity ends with `[AWAITING: MACHINE_GROUP_TYPE]`. RAM recovery ends with `[AWAITING: PERMISSION_CONFIRMATION]`.

**Fixed English tokens (must appear verbatim; surrounding prose stays Chinese):** `[BLOCKED: …]` / `[CANCELLED: …]` / `[AWAITING: …]` / `ask=1` / `ask=2` / `ask=3` / `[Error: permission|throttling|internal|parameter]` / `[RECOVERED: …]` / `resource_status: Resource not found` / `[Query: Incomplete]` / `INCOMPLETE`. Rejection and confirmation-timeout turns: the **sole content** of that turn is the short tag — no English long sentence, no prefix or suffix.

**Out of scope.** Windows; Sidecar; uninstall/rollback/restart-as-lifecycle; creating ECS; OOS/ChatOps; writing `AliyunLogConfig` / `NamespaceAliyunPipelineConfig`; advanced troubleshooting (delay, duplicate, parse failure, container filter, data loss/truncation). `kubectl exec` and `docker exec` are forbidden. If the user asks for an out-of-scope lifecycle action, say so and stop that branch.

---

## 1. Prerequisites

**Pre-check: Aliyun CLI >= 3.3.3 required**
> [MUST] Verify: `aliyun version` — must be >= 3.3.3 (>= 3.3.5 recommended).
> - First install or major upgrade: `/bin/bash -c "$(curl -fsSL --connect-timeout 10 --max-time 120 https://aliyuncli.alicdn.com/setup.sh)"`
> - Routine update (CLI >= 3.3.5): `aliyun upgrade`.
> - See `references/cli-installation-guide.md`.

**Pre-check: SLS plugin required**
> [MUST] `aliyun configure set --auto-plugin-install true` then `aliyun plugin install --names aliyun-cli-sls` and `aliyun plugin update`.
> Collection subcommands are provided by the `aliyun-cli-sls` plugin (hyphenated subcommands such as `aliyun sls get-logs-v2`). Verify with `aliyun sls --help`.

> **Pre-check: Alibaba Cloud Credentials Required**
>
> **Security Rules:**
> - **NEVER** read, echo, or print AK/SK values (e.g., `echo $ALIBABA_CLOUD_ACCESS_KEY_ID` is FORBIDDEN)
> - **NEVER** use `cat`, `less`, `head`, `tail`, `grep`, `open`, `json.load`, or any file-reading command on credential files (e.g., `~/.aliyun/config.json`, `~/.aws/credentials`). To check file existence use `ls` only — never display contents. Printing plaintext secrets is an immediate task failure and security incident.
> - **NEVER** install or import `aliyun-log-python-sdk` / `aliyun.log` / `LogClient`, or any other SLS SDK, to bypass CLI. `pip install` of a cloud SDK is a task failure.
> - **NEVER** print, `cat`, or paste kubeconfig / client certificates / tokens into the conversation. For opt-in CRD only: write `describe-cluster-user-kubeconfig` output to a `0600` tempfile.
> - **NEVER** ask the user to input AK/SK directly in the conversation or command line
> - **NEVER** use `aliyun configure set` with literal credential values
> - **ONLY** use `aliyun configure list` to check credential status. `scripts/preflight.sh` already does this.
>
> ```bash
> aliyun configure list
> ```
> Check the output for a valid profile (AK, STS, or OAuth identity).
>
> **If no valid profile exists, STOP here.**
> 1. Obtain credentials from [Alibaba Cloud Console](https://ram.console.aliyun.com/manage/ak)
> 2. Configure credentials **outside of this session** (via `aliyun configure` in terminal or environment variables in shell profile)
> 3. Return and re-run after `aliyun configure list` shows a valid profile

Run `bash scripts/preflight.sh` to check CLI version, plugin, credential presence, and scope in one step. `preflight.sh` already invokes `aliyun configure list` internally; running it **is** a valid credential check — do not cat CLI config files, and do not add a standalone `aliyun configure list` just to satisfy a checklist. Full gate details: `references/prerequisites.md`.

### Environment Variables

| Variable | Required | Description |
|---|---|---|
| (none for credentials) | — | Credentials come from `aliyun configure` profiles; never introduce AK/SK env vars in-session |
| `SKILL_SESSION_ID` | Injected at script run | Same 32-hex session id as the `session/{session-id}` UserAgent token; set inline when invoking bundled scripts (see §4) |

---

## 2. RAM Policy

This skill uses the user's own identity and only touches resources they are authorized for. Permissions are layered ReadOnly / Operator / Destructive. Per-workflow RAM Actions are in `references/ram-policies.md` — do not default to broad `AliyunLogFullAccess`.

> **[MUST] Permission Failure Handling:** When any command or API call fails due to permission errors at any point during execution, follow this process:
> 1. Read `references/ram-policies.md` to get the full list of permissions required by this SKILL
> 2. Use `ram-permission-diagnose` skill to guide the user through requesting the necessary permissions
> 3. Pause and wait until the user confirms that the required permissions have been granted

**Runtime detail (same gate, do not skip the three steps above):**
1. Report the missing RAM Action and `requestID`; output `[Error: permission]`. Reading `references/ram-policies.md` alone is **not** a successful diagnose call.
2. **Try** `ram-permission-diagnose` with the missing Actions and `requestID`. **FALLBACK:** if it is unavailable, output Action/`requestID`/RAM-console guide manually, then ask catalog message `permission_recovery` with last line `[AWAITING: PERMISSION_CONFIRMATION]` and pause.
3. Do not retry the affected write (including `--cli-dry-run`) before confirmation.
4. **READ-PATH HARD STOP:** On 401/403/`Unauthorized`/`AccessDenied` for `get-project` / `get-machine-group` / `list-machines` / `get-log-store`, first read the message. If it is **ownership** (English ownership text or any catalog `ownership_error` marker) → this is **not** a RAM gate: emit `[BLOCKED: RESOURCE_RESOLUTION_FAILED]` and stop; do **not** ask `permission_recovery_short`, do not create the official `k8s-log-*` name, and do not retry. Otherwise emit `[Error: permission]` with Action/`requestID`, then in the **same turn** ask exactly `permission_recovery` with last line `[AWAITING: PERMISSION_CONFIRMATION]`, and issue **zero** further `aliyun sls` calls that turn — including `get-machine-group`, `list-machines`, `get-applied-configs`, and `get-log-store`. Those unread calls are catalog `pending_read` items, not queried conclusions.
5. **After the user's permission answer (same gate for read-path and write/dry-run):**
   - Any catalog `permission_decline` marker or equivalent decline → **zero tools that turn** (no `write_file`, no `aliyun sls`). Explicitly state that execution is terminated, list every **not-yet-run** read as catalog `pending_read` with its RAM Action, then put `[BLOCKED: PERMISSION_REQUIRED]` on the final line. If the task asked for machine-group heartbeat, the pending items **must** include `get-machine-group` → `log:GetMachineGroup` and `list-machines` → `log:ListMachines` (name the group). Never present an unrun heartbeat as a queried conclusion.
   - A catalog `permission_grant` marker → **same turn**, retry the **identical** failed command (if the failure was a dry-run, retry that dry-run first) and emit `[RECOVERED: permission_granted]` in the user-facing text immediately. Explicitly state that the disposition is human intervention followed by retry, so the recovery action is unambiguous.

On `Unauthorized`/`AccessDenied` from a **core write or its dry-run**: stop the current write, enter the §6 permission-recovery branch, and never switch account/profile or widen scope.

---

## 3. Parameter Confirmation

> **IMPORTANT: Parameter Confirmation** — Before executing any command or API call,
> ALL user-customizable parameters (e.g., RegionId, instance names, CIDR blocks,
> passwords, domain names, resource specifications, etc.) MUST be confirmed with the
> user. Do NOT assume or use default values without explicit user approval.

| Parameter | Required/Optional | Description | Default |
|---|---|---|---|
| `region` | Required | SLS region id, e.g. `cn-hangzhou` | none |
| `project` | Required | Target SLS project | none |
| `logstore` | Conditional | Target logstore (create/onboarding/query) | none |
| `machine_group` | Conditional | Machine group name (bind/heartbeat) | none |
| `config_name` | Conditional | Logtail pipeline config name | none |
| `machine_identify_type` | Conditional | `ip` or `userdefined` | none |
| `machine_list` | Conditional | IP list or user-defined identifiers | none |
| `scenario` | Conditional | `host` / `docker` / `k8s` / `host_agentsight` | none |
| `environment` | Conditional | `ecs` / `self_host` / `ack` / `self_k8s` for `install.deploy` | none |
| `instance_id` | Conditional | ECS instance id (Workbench) | none |
| `cluster_id` | Conditional | ACK / self-k8s cluster id | none |
| `lens_project` / `lens_logstore` | Optional | SLS Lens entry when auto-discovery unavailable | none |

Never substitute placeholder/example values for a missing real resource. Missing `region`, `project`, or a scope-changing target → STOP and ask.

For a Chinese request with a missing `machine_identify_type`, ask exactly catalog message `machine_group_identity` using Chinese option descriptions, and end the turn with last line `[AWAITING: MACHINE_GROUP_TYPE]` — **never** `[AWAITING: R2_CONFIRMATION]`. After the user chooses, run `scripts/normalize_diff.py` and only then ask the create-and-bind question with `[AWAITING: R2_CONFIRMATION] ask=1`. Do not emit a custom English status label or an English selection table. **`install.deploy` exception:** do **not** ask machine-group identity until the collection create-bind gate has been confirmed. If that gate receives a catalog `cancel` marker or equivalent rejection, never ask. If the user already named IP, userdefined, or a catalog `private_ip` marker, never ask.

---

## 4. Observability (MUST follow for every aliyun command)

Upon loading this skill, generate a random session ID (32-char lowercase hex string) once for the entire session. Use it as `{session-id}` below.

**Rule: Every `aliyun` CLI command that calls a cloud API MUST include the `--user-agent` flag.**
This covers `aliyun sls`, `aliyun cs`, `aliyun ecs`, and any other `aliyun <product>` cloud call this skill issues, including `--cli-dry-run`. Local utility commands (e.g. `configure`, `plugin`, `version`) do not support this flag and should be excluded. `kubectl` / Workbench / SSH / local validators are not Alibaba Cloud APIs and do not send this flag.

Use **two space-separated product tokens** (quote the whole value; the space is required):

```
--user-agent "AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops session/{session-id}"
```

| Token | Example | Query use |
|---|---|---|
| Skill identity | `AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops` | All traffic from this skill |
| Session | `session/{session-id}` | One session |

Never glue the session id onto the skill token (`.../ops/{session-id}` is forbidden). Never omit quotes. Never skip, alter, or drop either token.

Example (assuming session-id is `a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`):
```bash
aliyun sls list-machines --project my-proj --machine-group my-group --region cn-hangzhou --user-agent "AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops session/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
```

References that write `--user-agent <ua>` mean this exact quoted two-token string.

**Script / Terraform execution:** When running Python SDK scripts or Terraform commands or bash scripts, inject the session-id via inline environment variable so the code can read it at runtime:

```bash
# Local validator (no cloud call)
SKILL_SESSION_ID={session-id} python3 scripts/validate_pipeline.py --file rendered.json

# Bundled script that itself calls a cloud API
SKILL_SESSION_ID={session-id} bash scripts/wait_cs_task.sh --cluster-id c-xxx --region cn-shanghai

# Terraform
SKILL_SESSION_ID={session-id} terraform apply
```

Scripts and Terraform configs should read `SKILL_SESSION_ID` from the environment (default to empty string if absent). Any bundled script that itself invokes `aliyun` against a cloud API MUST send the same two-token UserAgent (currently `scripts/wait_cs_task.sh`).

**Domain extension — ATOMIC CLOUD-CALL RULE (HARD):** Every tool invocation that calls SLS must contain exactly one direct `aliyun sls ...` command with literal, fully expanded parameter values, and the command must start with `aliyun sls`. Do not hide a cloud call behind shell variables, environment assignments, functions, aliases, wrapper scripts, loops, command substitutions, `eval`, pipes, redirections (including `2>&1`), or compound commands (`;`, `&&`, `||`). Use only lowercase hyphenated SLS plugin subcommands such as `get-project`; never use a PascalCase OpenAPI alias, because it bypasses the validated command/mocking contract. Compute timestamps or JSON in a separate local step, then place the resulting literals in the cloud command. This applies equally to verification and acceptance reads: no loops, no `cd …` prefix, no `$VAR` or `$(…)` substitution, including in `--from`/`--to` and JSON bodies. Never write cloud calls into a `.sh` file and run it; the command record is plain-text notes, not a runnable script. Local validators must receive what the command actually returned — save the real stdout to a file and pass that file; retyping or `echo`-ing an expected response is fabricated evidence. Generate the session ID with `python3 -c 'import secrets; print(secrets.token_hex(16))'` and validate `^[0-9a-f]{32}$`; never copy the example ID above into live commands.

---

## 5. Capability Router

Classify the request into exactly one capability, then load its `references/navigation.md` entry before acting. Do not load the whole knowledge base into context.

| Capability | Trigger | Required inputs | Adapters | Success state |
|---|---|---|---|---|
| `install.deploy` | Install/upgrade then collect and query | region, environment, instance or cluster | `ecs run-command` / ssh / `aliyun_cs` / kubectl / `aliyun_sls` | install gate + U1-U6 + query |
| `config.modify` | Change an existing config / parse / fields | region, project, config | `aliyun_sls`, local validator | config + index + data verified |
| `config.create` | Base resources exist, only create a config | region, project, logstore, machine_group, scenario | `aliyun_sls` (default); kubectl CRD only if user asked | config exists, bound, has data |
| `onboarding.cloud` | Collector running, wire up cloud side (API) | region, project, logstore, machine_group, source | `aliyun_sls`, local validator | U1-U6 pass |
| `machine_group.manage` | Create/modify group, members, binding | region, project, group | `aliyun_sls` | object + relations match target |
| `lens.query` | Query collection alarms/status/metrics | business project, time range, lens entry | `aliyun_sls` | query complete with context |
| `troubleshoot.basic` | No data / heartbeat abnormal | region, project, optional logstore/config/group | `aliyun_sls`, Lens | root cause or single blocker |

Full router spec (`when_to_use` / `out_of_scope` / `entry_signals` / `success|blocked|failure_state`): `references/navigation.md`. Track multi-step work with the unified task object in `references/task-model.yaml`.

---

## 6. Execution State Machine

`Classify → Preflight → Observe → Plan → Approve → Execute → Verify → (Rollback)`

`install.deploy` inserts an extra install Approve/Execute **before** collection Observe/Plan. Do not end after the process/Addon stage gate.

- **Classify**: pick capability, scenario (`host/docker/k8s/host_agentsight`), environment (`ecs|self_host|ack|self_k8s`), management plane (`api|crd`). **Default `api`** for host and K8s collection. Set `crd` only when the user explicitly asks for `ClusterAliyunPipelineConfig` / GitOps / `kubectl apply` CR **and** a reachable kube-apiserver is proven (`references/crd-pipeline.md`). ACK collection must not stop on `[AWAITING: KUBECONFIG]`. Ask for scope-changing inputs; never guess. If the user names SLS, Log Service, LoongCollector, Logtail, or any catalog `routing_intent` marker without a concrete operation, **stay in this skill**: clarify region, project/cluster/host, and the intended operation before any cloud call; never improvise outside scope.

  **Agentloop / AgentSight:** Agentloop, AgentSight, `input_agentsight`, eBPF Runtime, or `ebpf-event` → `config.create` (or `onboarding.cloud` if the group must be created) with scenario `host_agentsight`. Load `references/agentsight-agentloop.md` and `references/input-agentsight.md`. Names are product-fixed (`runtime-ebpf-agentsight-config` → `ebpf-event`). **Lock forbids overwrite, not Plan:** even when `get-logtail-pipeline-config` already returns the object, still run `scripts/render_pipeline.py` + `scripts/validate_pipeline.py` + `scripts/normalize_diff.py` and ask catalog message `r2_create_bind`. After confirm, still issue `create-logtail-pipeline-config` (and `apply-config-to-machine-group` if unbound). `AlreadyExist` / already-bound → record the lock `[Idempotent-Skip]` and **do not update**. Host Linux, kernel `>=5.10`, collector `>=3.3.9`. Not OBI/OTLP.

  **INTENT / PARAM CLARIFICATION STOP (hard):** Ask at most **one** clarifying question, in Chinese, using the applicable exact catalog message: `missing_task_scope` for general scope or `missing_lens_parameters` for Lens. If the user still gives no concrete values — undecided, wants only the checklist, or asks you not to run commands — **do not ask again**. In that same turn output a minimal declarative checklist (region, Project/resource locator, operation goal; for Lens-only asks: business project or Lens entry + time range), put `[BLOCKED: MISSING_REQUIRED_INPUT]` on the final line, and end the turn — no further questions, question marks, invitations to provide data, cloud calls, Preflight, or Observe. In particular, do not repeat either fixed clarification subject after the checklist. Resume only when the user supplies concrete values.

  Project locator: exact user prefix/handoff only — at most one `list-project --project-name <prefix>`, then `get-project` on the resolved full name; a `list-project` hit alone never proves the target nor authorizes any next resource read. The `get-project` call is mandatory for read-only config views and Idempotent-Skip checks too: never jump directly from EVAL_ACCOUNT_ID/name resolution to `get-log-store` or `get-logtail-pipeline-config`. Never broaden/synthesize names. Zero matches → `[BLOCKED: RESOURCE_RESOLUTION_FAILED] …`; multiple → ask user to choose.
- **Preflight**: `scripts/preflight.sh` (CLI/plugin/credential/scope). Add `--need-ecs` (ECS) / `--need-cs` (ACK) / `--need-kubectl` (**self_k8s install only**, or opt-in CRD). **Hard-gate only** (CLI / SLS plugin / credentials / ACK `--need-cs`): output `[BLOCKED: PREFLIGHT_FAILED] gate=<gate>; <reason>` and stop. **Adapters are not hard gates:**
  - **ECS:** `run-command` is the only host channel. `--need-ecs` is a warn, not a hard fail. Still run `scripts/render_loongcollector_install_cmd.py`, and **still ask** catalog message `ecs_install` with last line `[AWAITING: INSTALL_CONFIRMATION]`. Never emit `[BLOCKED: PREFLIGHT_FAILED] gate=workbench` / `gate=ecs`. After confirm, use exactly `aliyun ecs run-command --biz-region-id <region> ... --instance-id <id>` and poll with `aliyun ecs describe-invocation-results --biz-region-id <region> --invoke-id <id>`; never use `--instance-id.1`, `--region-id`, or `--region` for these plugin commands. Do **not** use `workbench exec` / `aliyun ecs-workbench` / OOS.
    **ECS post-install fast path:** After a successful invocation result, reuse every user-supplied Project, Logstore, machine-group, and config name verbatim. Do not read helper source, call `--help` for mapped commands, reinstall/update plugins, re-run Preflight, or write `outputs/*` / `ran_scripts/*` before collection approval. Run only the required Observe reads, render/validate/diff calls, then immediately emit the collection approval question. If invocation status is still running, wait and poll as separate tool calls; never combine `sleep` and the poll command.
  - **self_k8s kubectl missing:** this is the **first** stop — before Observe, Plan, values render, or any create-bind question. Ask exactly catalog message `kubeconfig` + `[AWAITING: KUBECONFIG]` and end the turn. **Never** `[BLOCKED: PREFLIGHT_FAILED] gate=kubectl`. Cloud writes / `create-logtail-pipeline-config` are forbidden until kubectl is provided. A refusal or catalog `end` marker → stop; do not continue to R2 create-bind.
  - **self_host SSH:** **before any** `create-*` / `apply-config-*` / install / `INSTALL_CONFIRMATION`, run exactly one direct probe: `ssh -o BatchMode=yes -o ConnectTimeout=8 <alias> -- true`. Use the user's existing SSH configuration; never add `StrictHostKeyChecking=no`, `UserKnownHostsFile=/dev/null`, or any option that weakens host-key verification. Do not wrap the probe in a pipe, `tee`, redirection, compound command, or logging helper that can mask its exit code. If the alias is missing, unresolvable, or the probe fails → immediately ask exactly catalog message `ssh` with last line `[AWAITING: SSH]` and **stop**; after the failed probe, do not call `write_file`, update a plan, or narrate a report before emitting that two-line gate. Do **not** ask the install-confirmation sentence on a failed probe. Do **not** create/edit `~/.ssh/config`, `/etc/hosts`, `authorized_keys`, or rewrite the alias to `127.0.0.1` / localhost to fake a working channel. Zero cloud writes that turn. Prompt-supplied alias that does not work is still “no usable SSH”. A catalog `end` marker → output only `[CANCELLED: SSH_REQUIRED]`; do not call tools or continue to install.
  - ACK collection uses SLS API and must not ask for kubeconfig / `--need-kubectl`.
  - **ACK first-use:** `--need-cs` hard-fails only on a missing CS plugin. Unopened ACK (`ErrorNotEnabled` / `cskpro`) or missing `AliyunCSDefaultRole` is **not** `[BLOCKED: PREFLIGHT_FAILED]`. After install confirm, run `bash scripts/ensure_ack_prereq.sh --region <r>`, then retry the failed CS write once. `create-cluster` only if the user asked: `--biz-profile Default` (never `--profile`), spec `ack.standard` then `ack.pro.small`. Do not invent `sls-eval-loop-ack` in production.
- **Observe (read-only)**: Get current objects + bindings + heartbeat; save a snapshot. Read collector version (`list-machines` `.binary`) **before choosing plugins**. If the **user message already states a version** (e.g. `3.2.6` / `3.3.9` / `LoongCollector 3.2.6`), use that version and do **not** ask. If version is still unknown: ask exactly catalog message `collector_version` + `[AWAITING: COLLECTOR_VERSION]` and stop. Do **not** assume 3.x, do **not** silently pick `processor_json`, and do **not** ask Lens only to learn the version. A catalog `collector_deployed_without_version` marker without a version string is still unknown.
  **MANDATORY VERIFICATION COMMANDS:** existence is proven only by `get-project`, `get-machine-group`, and `get-log-store` (the last before any create/bind on that logstore). Observe **must start with** `get-project --project <full-name>` (or `list-project` then `get-project` on the resolved name). For `config.create` / create-and-bind, check `get-machine-group` (or `list-machines`) immediately after Project resolution and before `get-log-store`, so the machine-group precheck is unambiguously recorded before config planning. Concatenating a prefix with `EVAL_ACCOUNT_ID` is **not** existence proof and does **not** authorize skipping `get-project` to jump to `get-log-store`. The `get-project` result is the only runtime project name for later calls. `list-machines` / `get-applied-configs` / `list-log-stores` prove heartbeat or binding, never existence. Enter create only after a `get-*` returns ResourceNotExist. Even on `ProjectNotExist`, still issue the remaining independent gets once each (including `get-applied-configs`).
- **Plan**: build target objects. A from-zero `onboarding.cloud` plan includes Project, Logstore, **Index**, MachineGroup, PipelineConfig, and binding unless the user explicitly opts out of indexing; never infer that Index is optional merely because the prompt contains a catalog `binding_acceptance` marker. **MANDATORY CHECKPOINT:** every planned R2/R3/R4 resource or relation change (including Project, Logstore, Index, MachineGroup, binding, and unbinding) MUST have a target JSON and an executed `scripts/normalize_diff.py` result; use `--kind auto` for non-config/index objects. Never substitute raw `diff`, visual inspection, or a handwritten diff. Exit code `3` means "valid diff contains changes", not failure. Config/index coupling uses this fixed order: snapshot config and index → validate the full target config with `scripts/validate_pipeline.py` → run `scripts/normalize_diff.py --kind config` → run `scripts/normalize_diff.py --kind index`. Exit code `1` from validation blocks the write. Do not enter Approve until every applicable mandatory script has executed successfully. Include impact, risk, rollback, and verification. `mode=plan` MUST NOT call write commands.
  **SCRIPT EVIDENCE MUST BE VISIBLE:** invoke each required Skill script as its own shell/tool call and keep its JSON/status on that call's stdout. It is fine to use `tee` to persist the same stdout, but do not redirect all script output only into `outputs/*` / `ran_scripts/*` and leave the tool result with just an exit code. A later file read or a handwritten execution record does not replace visible script-call evidence.
  **VALIDATION FAILURE HARD STOP:** if `scripts/validate_pipeline.py` returns exit code `1` or `status=invalid`, immediately output `[BLOCKED: VALIDATION_FAILED]` and end the turn. Do not run `--cli-dry-run`; do not execute any create/update/apply/remove/delete command; do not treat a server-side 4xx from an actual write as validation evidence. This rule overrides user approval and every later Execute step.
- **Approve**: HARD GATE. For every R2 operation (create resource, update config, apply/bind, create/update index) you MUST, before Execute, explicitly output the normalized diff, ask the user to confirm, and end the turn. Ask at most one confirmation question per turn. For a Chinese request it must be the applicable exact subject from the Language and HITL Delivery Contract, with the catalog `approval` and `cancel` options, never English ones. Only an explicit positive answer authorizes a write. R3: explicit impact confirmation. R4: second confirmation, restate resources.

  **SEPARATE INSTALL THEN COLLECTION GATES:** For `install.deploy`, first ask only the environment-specific install catalog message and end the turn with `[AWAITING: INSTALL_CONFIRMATION]` — **never** `[AWAITING: R2_CONFIRMATION]`. Catalog `install_intent` markers in the original request describe intent but are **not the separate confirmation reply**; only a fresh user message received after the install question authorizes `run-command` / SSH install / addon install. After install succeeds, if a **new** CR or API config/binding is required, ask in a **new** turn catalog message `r2_create_bind` with last line `[AWAITING: R2_CONFIRMATION] ask=1` before those writes. Do not ask machine-group identity or collector version between the two gates. On a catalog `cancel` marker or equivalent rejection: `[CANCELLED: R2_CONFIRMATION_REJECTED]`, report catalog `install_only_status`, and issue **zero** collection writes. If Observe already matches the target (get proves logstore/group/config/binding), emit `[Idempotent-Skip]` and do **not** re-issue create/apply. Pure reuse of ACK default collection is the same skip.

  **SECOND-GATE TAG UNIQUENESS:** In the post-install turn that asks for collection approval, the complete `[AWAITING: R2_CONFIRMATION] ask=1` token may appear exactly once: as the final response line. Before that final response, refer to the pending step only as "collection approval"; never put the token in a Plan/Todo item, tool description, tool argument, code block, output file, execution record, or narration. Do not write an execution record or update a plan after the final validation/diff call; emit the fixed question and tag immediately.

  **SEPARATE UNBIND GATE:** Create-and-bind and unbind are two independent confirmations. First ask only catalog message `r2_create_bind`. After the user confirms **and** those writes (or exact Idempotent-Skip) finish, ask in a **new** turn catalog message `r3_unbind`. Never merge unbind into the create-and-bind question, and never run `remove-config-from-machine-group` (including `--cli-dry-run`) on the create-and-bind approval.

  **DO NOT SKIP CONFIRMATION:** Automation, urgency, complete parameters, and the original task wording never waive this gate. While the answer is outstanding, emit the Chinese question and `[AWAITING: R2_CONFIRMATION] ask=1` on the first ask, end the turn, and wait for the user's next message. **Same-turn writes are a gate failure:** after the question, do not `--cli-dry-run`, `create-*`, `apply-config-*`, or `update-*` until the next user message contains an explicit catalog `approval` marker.

  **HARD GATE CHECKLIST (Approve → Execute):**
  0. Ask only once the plan is real: `scripts/validate_pipeline.py` has passed on any config payload and `scripts/normalize_diff.py` has run for every planned write. Asking approval for a plan you have not validated and diffed is a gate failure.
  1. Nothing you produce yourself is an answer. If the turn ends without the user having stated a decision, output `[AWAITING: R2_CONFIRMATION] ask=<n>` with the identical question and wait.
  2. Never treat the original task wording, “the task explicitly requires”, “parameters are complete”, or an already-rendered plan table as approval.
  3. Enter Execute only after the user explicitly answers yes/confirm/approve or an equivalent catalog `approval` marker.
  4. `--cli-dry-run` for any R2/R3 write is part of Execute: it is **forbidden** before that explicit approval. Showing a plan without asking, then dry-running, is a gate failure.
  5. Violating this gate is a task failure.

  **NON-ANSWER RULE (hard stop):** A blank reply, "later", "not sure yet", any catalog `deferral` marker, or any equivalent deferral is not approval. Maintain ask counter `n` starting at `1` on the first confirmation turn. After each deferral, restate in **one** line which resources and operation are still waiting, re-ask the identical Chinese confirmation subject **exactly once**, and put `[AWAITING: R2_CONFIRMATION] ask=<n+1>` on the **last line** (counter goes 1 → 2 → 3). Do not print the question or the AWAITING tag twice. Do not add a blank line after the tag. A bare repeated question without `ask=<n>`, or a "take your time" soft-close with no question, both fail this rule. After the user replies to the **third** ask with another catalog `deferral` marker, the **next turn's sole content** is `[BLOCKED: R2_CONFIRMATION_TIMEOUT]` — do not ask a fourth time, do not write, do not dry-run. Explicit reject/cancel → sole content `[CANCELLED: R2_CONFIRMATION_REJECTED]`. Do not append English prose such as `User rejected the proposed plan…`. Full semantics: `references/risk-and-approval.md`.

  **TERMINAL-STATE HARD STOP:** After any `[BLOCKED: …]` / `[CANCELLED: …]` tag, run no further tools (including `write_file`), dry-runs, writes, or Verify. The collection-gate rejection after a successful install is the one reporting exception: output `[CANCELLED: R2_CONFIRMATION_REJECTED]` and exact catalog status `install_only_status`, then end the turn with zero tools. A permission rejection may also list the required not-yet-run reads from §2, but must explicitly state that execution has terminated. All other terminal turns contain only the short tag. Resume only with a fresh Plan + confirmation after the user re-opens the work.
- **Execute**: approved commands only, with `--user-agent` and §4 atomic rule. Always run `--cli-dry-run` as its own call before the real write, so a rejected request surfaces before anything mutates state. **Idempotency:** get before create/apply; if state matches, skip both dry-run and write, verify via get/list, and emit exact `[Idempotent-Skip] <create/apply-command> skipped; verified via <get/list-command> that state matches expectation.` in `Changes`. If get already proves target Project/Logstore shard/TTL, any `create-project`/`create-log-store` (incl. dry-run) is a task failure. On `AlreadyExist`, get+compare → matching Idempotent-Skip, or `[BLOCKED: EXISTING_RESOURCE_CONFLICT]` if mismatched. **`config.modify` exception:** an explicit user request to change a config/index (rename field, change parse, sync index) **must** still run `scripts/normalize_diff.py`, ask catalog message `r2_update`, then after confirm issue `update-logtail-pipeline-config` and the coupled `update-index` (each with its own `--cli-dry-run` first). Do **not** skip those two writes just because the snapshot already matches — overwrite is the requested change. Create/apply Idempotent-Skip still applies. **AgentSight lock:** still Plan + HITL, then `create-logtail-pipeline-config`; `AlreadyExist` → `[Idempotent-Skip]`, do not update.
  **Logstore Idempotent-Skip output:** when shardCount/TTL already match, the user-facing final answer must include the skipped command name, for example `[Idempotent-Skip] create-log-store skipped; verified via get-log-store that state matches expectation.`
  **Error recovery (≤3 retries / 4 total; keep `errorCode` + `requestID`):** Prefer `python3 scripts/classify_sls_error.py` and emit its `error_tag` **before** narration. Mapping: 400/`ParameterInvalid`→`[Error: parameter]` then fix+retry same API →`[RECOVERED: parameter_fixed]`; 429/`WriteQuotaExceed`→`[Error: throttling]` + backoff →`[RECOVERED: throttling_retry]`; 500→`[Error: internal]` + same-command retry →`[RECOVERED: internal_retry]`; 401/403 whose message is ownership (`does not belong to you`) → `[BLOCKED: RESOURCE_RESOLUTION_FAILED]` (not a RAM HITL); other 401/403→`[Error: permission]` then §2 Permission Failure Handling (ask catalog message `permission_recovery`) → user-facing `[RECOVERED: permission_granted]` on catalog `permission_grant` (retry the identical command the same turn) or sole-content `[BLOCKED: PERMISSION_REQUIRED]` on catalog `permission_decline`. Dry-run failures use the same branch; real write only after dry-run succeeds.
- When `get-logs-v2` returns `meta.progress=Incomplete`, that is an incomplete query (not a transport success

…(truncated)
