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.
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.
aliyun configure list
Check the output for a valid profile (AK, STS, or OAuth identity).
If no valid profile exists, STOP here.
- Obtain credentials from Alibaba Cloud Console
- Configure credentials outside of this session (via
aliyun configure in terminal or environment variables in shell profile)
- 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:
- Read
references/ram-policies.md to get the full list of permissions required by this SKILL
- Use
ram-permission-diagnose skill to guide the user through requesting the necessary permissions
- 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):
- Report the missing RAM Action and
requestID; output [Error: permission]. Reading references/ram-policies.md alone is not a successful diagnose call.
- 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.
- Do not retry the affected write (including
--cli-dry-run) before confirmation.
- 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.
- 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):
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:
# 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):
- 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.
- 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.
- Never treat the original task wording, “the task explicitly requires”, “parameters are complete”, or an already-rendered plan table as approval.
- Enter Execute only after the user explicitly answers yes/confirm/approve or an equivalent catalog
approval marker.
--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.
- 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)
1---2name: alibabacloud-loongcollector-ops3description: 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".4---5
6# LoongCollector Ops
7
8Turn natural-language requests into executable, verifiable, rollbackable workflows for users operating **their own** LoongCollector and SLS resources — from install through collection to query.
9
10**Architecture**: `Install (ECS/self-host/ACK/self-k8s) + SLS Project + Logstore + Index + MachineGroup + Pipeline (API or ClusterAliyunPipelineConfig) + binding + SLS Lens`
11
12**Scope.** Covers:
13- 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.
14- 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.
15- Cloud onboarding: Project / Logstore / Index / MachineGroup / Pipeline Config / binding.
16- 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`).
17- Config management: create, modify, apply, remove, data acceptance (U1-U6).
18- Machine group management: IP / user-defined identity, members, heartbeat, version.
19- SLS Lens: run-log query (`get-logs-v2`), topic/field contracts, version routing, degradation.
20- Basic troubleshooting: no-data, heartbeat abnormal.
21
22## Language and HITL Delivery Contract
23
24**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.
25
26**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.
27
28```yaml
29messages:
30 missing_task_scope: "请补充要执行的具体操作目标、地域和 SLS Project。"
31 missing_lens_parameters: "请补充业务 Project、地域和查询时间范围。"
32 machine_group_identity: "请选择机器组标识类型:IP 或 userdefined。"
33 r2_update: "是否确认执行上述变更计划?请选择:确认执行或取消。"
34 r2_create_bind: "是否确认创建上述资源并完成绑定?请选择:确认执行或取消。"
35 r3_unbind: "是否确认将上述旧配置从机器组解绑?请选择:确认解绑或取消。"
36 permission_recovery: "是否已完成所需 RAM 授权并允许重试?请选择:已授权或未授权。"
37 permission_recovery_short: "是否已完成所需 RAM 授权并允许重试?"
38 lens_entry: "请提供 SLS Lens 服务日志的 Project 和 Logstore。"
39 ecs_install: "是否确认在上述 ECS 上安装 LoongCollector?"
40 self_host_install: "是否确认在上述主机上安装 LoongCollector?"
41 ack_install: "是否确认在上述 ACK 集群安装 loongcollector 组件?"
42 self_k8s_install: "是否确认在上述 Kubernetes 集群安装 LoongCollector?"
43 kubeconfig: "请提供可用的 kubectl 与目标集群 context。"
44 ssh: "请提供已配置的 SSH(alias 或主机),不要在对话中发送私钥。"
45 collector_version: "请提供采集器版本(例如 3.3.9)。"
46markers:
47 ownership_error: ["不属于当前账号", "项目不属于你"]
48 permission_decline: ["未授权", "停止"]
49 permission_grant: ["已授权"]
50 cancel: ["取消"]
51 private_ip: ["私网 IP"]
52 routing_intent: ["日志采集", "安装采集器"]
53 end: ["结束"]
54 collector_deployed_without_version: ["LoongCollector 已部署"]
55 binding_acceptance: ["完成绑定与验收"]
56 approval: ["确认", "确认执行", "确认解绑"]
57 install_intent: ["允许安装", "请安装", "直接执行", "已授权操作", "任务已预授权"]
58 install_only_status: ["仅安装完成、采集未接入"]
59 deferral: ["还没想好", "等会儿再说", "暂不确认", "第二次等待", "第N次暂不确认", "先放一放", "已达到上限", "请阻塞"]
60 data_incomplete: ["无法完成数据面验收"]
61 data_empty: ["无数据"]
62 reason: ["原因"]
63 forbidden_empty_success: ["采集成功", "所有验收标准均已满足", "全链路验收通过", "通过"]
64 data_arrived: ["数据到达"]
65 root_cause_located: ["根因已定位"]
66 not_exists: ["不存在"]
67 pending_read: ["未执行待办", "待办"]
68```
69
70Pair `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.
71
72Do 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]`.
73
74**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.
75
76**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.
77
78---
79
80## 1. Prerequisites
81
82**Pre-check: Aliyun CLI >= 3.3.3 required**
83> [MUST] Verify: `aliyun version` — must be >= 3.3.3 (>= 3.3.5 recommended).
84> - First install or major upgrade: `/bin/bash -c "$(curl -fsSL --connect-timeout 10 --max-time 120 https://aliyuncli.alicdn.com/setup.sh)"`
85> - Routine update (CLI >= 3.3.5): `aliyun upgrade`.
86> - See `references/cli-installation-guide.md`.
87
88**Pre-check: SLS plugin required**
89> [MUST] `aliyun configure set --auto-plugin-install true` then `aliyun plugin install --names aliyun-cli-sls` and `aliyun plugin update`.
90> Collection subcommands are provided by the `aliyun-cli-sls` plugin (hyphenated subcommands such as `aliyun sls get-logs-v2`). Verify with `aliyun sls --help`.
91
92> **Pre-check: Alibaba Cloud Credentials Required**
93>
94> **Security Rules:**
95> - **NEVER** read, echo, or print AK/SK values (e.g., `echo $ALIBABA_CLOUD_ACCESS_KEY_ID` is FORBIDDEN)
96> - **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.
97> - **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.
98> - **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.
99> - **NEVER** ask the user to input AK/SK directly in the conversation or command line
100> - **NEVER** use `aliyun configure set` with literal credential values
101> - **ONLY** use `aliyun configure list` to check credential status. `scripts/preflight.sh` already does this.
102>
103> ```bash
104> aliyun configure list
105> ```
106> Check the output for a valid profile (AK, STS, or OAuth identity).
107>
108> **If no valid profile exists, STOP here.**
109> 1. Obtain credentials from [Alibaba Cloud Console](https://ram.console.aliyun.com/manage/ak)
110> 2. Configure credentials **outside of this session** (via `aliyun configure` in terminal or environment variables in shell profile)
111> 3. Return and re-run after `aliyun configure list` shows a valid profile
112
113Run `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`.
114
115### Environment Variables
116
117| Variable | Required | Description |
118|---|---|---|
119| (none for credentials) | — | Credentials come from `aliyun configure` profiles; never introduce AK/SK env vars in-session |
120| `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) |
121
122---
123
124## 2. RAM Policy
125
126This 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`.
127
128> **[MUST] Permission Failure Handling:** When any command or API call fails due to permission errors at any point during execution, follow this process:
129> 1. Read `references/ram-policies.md` to get the full list of permissions required by this SKILL
130> 2. Use `ram-permission-diagnose` skill to guide the user through requesting the necessary permissions
131> 3. Pause and wait until the user confirms that the required permissions have been granted
132
133**Runtime detail (same gate, do not skip the three steps above):**
1341. Report the missing RAM Action and `requestID`; output `[Error: permission]`. Reading `references/ram-policies.md` alone is **not** a successful diagnose call.
1352. **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.
1363. Do not retry the affected write (including `--cli-dry-run`) before confirmation.
1374. **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.
1385. **After the user's permission answer (same gate for read-path and write/dry-run):**
139 - 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.
140 - 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.
141
142On `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.
143
144---
145
146## 3. Parameter Confirmation
147
148> **IMPORTANT: Parameter Confirmation** — Before executing any command or API call,
149> ALL user-customizable parameters (e.g., RegionId, instance names, CIDR blocks,
150> passwords, domain names, resource specifications, etc.) MUST be confirmed with the
151> user. Do NOT assume or use default values without explicit user approval.
152
153| Parameter | Required/Optional | Description | Default |
154|---|---|---|---|
155| `region` | Required | SLS region id, e.g. `cn-hangzhou` | none |
156| `project` | Required | Target SLS project | none |
157| `logstore` | Conditional | Target logstore (create/onboarding/query) | none |
158| `machine_group` | Conditional | Machine group name (bind/heartbeat) | none |
159| `config_name` | Conditional | Logtail pipeline config name | none |
160| `machine_identify_type` | Conditional | `ip` or `userdefined` | none |
161| `machine_list` | Conditional | IP list or user-defined identifiers | none |
162| `scenario` | Conditional | `host` / `docker` / `k8s` / `host_agentsight` | none |
163| `environment` | Conditional | `ecs` / `self_host` / `ack` / `self_k8s` for `install.deploy` | none |
164| `instance_id` | Conditional | ECS instance id (Workbench) | none |
165| `cluster_id` | Conditional | ACK / self-k8s cluster id | none |
166| `lens_project` / `lens_logstore` | Optional | SLS Lens entry when auto-discovery unavailable | none |
167
168Never substitute placeholder/example values for a missing real resource. Missing `region`, `project`, or a scope-changing target → STOP and ask.
169
170For 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.
171
172---
173
174## 4. Observability (MUST follow for every aliyun command)
175
176Upon loading this skill, generate a random session ID (32-char lowercase hex string) once for the entire session. Use it as `{session-id}` below.
177
178**Rule: Every `aliyun` CLI command that calls a cloud API MUST include the `--user-agent` flag.**
179This 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.
180
181Use **two space-separated product tokens** (quote the whole value; the space is required):
182
183```
184--user-agent "AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops session/{session-id}"
185```
186
187| Token | Example | Query use |
188|---|---|---|
189| Skill identity | `AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops` | All traffic from this skill |
190| Session | `session/{session-id}` | One session |
191
192Never glue the session id onto the skill token (`.../ops/{session-id}` is forbidden). Never omit quotes. Never skip, alter, or drop either token.
193
194Example (assuming session-id is `a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`):
195```bash
196aliyun sls list-machines --project my-proj --machine-group my-group --region cn-hangzhou --user-agent "AlibabaCloud-Agent-Skills/alibabacloud-loongcollector-ops session/a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6"
197```
198
199References that write `--user-agent <ua>` mean this exact quoted two-token string.
200
201**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:
202
203```bash
204# Local validator (no cloud call)
205SKILL_SESSION_ID={session-id} python3 scripts/validate_pipeline.py --file rendered.json
206
207# Bundled script that itself calls a cloud API
208SKILL_SESSION_ID={session-id} bash scripts/wait_cs_task.sh --cluster-id c-xxx --region cn-shanghai
209
210# Terraform
211SKILL_SESSION_ID={session-id} terraform apply
212```
213
214Scripts 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`).
215
216**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.
217
218---
219
220## 5. Capability Router
221
222Classify the request into exactly one capability, then load its `references/navigation.md` entry before acting. Do not load the whole knowledge base into context.
223
224| Capability | Trigger | Required inputs | Adapters | Success state |
225|---|---|---|---|---|
226| `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 |
227| `config.modify` | Change an existing config / parse / fields | region, project, config | `aliyun_sls`, local validator | config + index + data verified |
228| `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 |
229| `onboarding.cloud` | Collector running, wire up cloud side (API) | region, project, logstore, machine_group, source | `aliyun_sls`, local validator | U1-U6 pass |
230| `machine_group.manage` | Create/modify group, members, binding | region, project, group | `aliyun_sls` | object + relations match target |
231| `lens.query` | Query collection alarms/status/metrics | business project, time range, lens entry | `aliyun_sls` | query complete with context |
232| `troubleshoot.basic` | No data / heartbeat abnormal | region, project, optional logstore/config/group | `aliyun_sls`, Lens | root cause or single blocker |
233
234Full 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`.
235
236---
237
238## 6. Execution State Machine
239
240`Classify → Preflight → Observe → Plan → Approve → Execute → Verify → (Rollback)`
241
242`install.deploy` inserts an extra install Approve/Execute **before** collection Observe/Plan. Do not end after the process/Addon stage gate.
243
244- **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.
245
246 **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.
247
248 **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.
249
250 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.
251- **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:**
252 - **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.
253 **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.
254 - **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.
255 - **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.
256 - ACK collection uses SLS API and must not ask for kubeconfig / `--need-kubectl`.
257 - **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.
258- **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.
259 **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`).
260- **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.
261 **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.
262 **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.
263- **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.
264
265 **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.
266
267 **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.
268
269 **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.
270
271 **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.
272
273 **HARD GATE CHECKLIST (Approve → Execute):**
274 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.
275 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.
276 2. Never treat the original task wording, “the task explicitly requires”, “parameters are complete”, or an already-rendered plan table as approval.
277 3. Enter Execute only after the user explicitly answers yes/confirm/approve or an equivalent catalog `approval` marker.
278 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.
279 5. Violating this gate is a task failure.
280
281 **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`.
282
283 **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.
284- **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.
285 **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.`
286 **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.
287- When `get-logs-v2` returns `meta.progress=Incomplete`, that is an incomplete query (not a transport success
288
289…(truncated)