mirror of
https://github.com/eunomia-bpf/bpf-developer-tutorial.git
synced 2026-11-02 05:35:41 +08:00
Add repository-local tutorial agent skills
This commit is contained in:
71
.agents/skills/write-bpf-production-tutorial/SKILL.md
Normal file
71
.agents/skills/write-bpf-production-tutorial/SKILL.md
Normal file
@@ -0,0 +1,71 @@
|
||||
---
|
||||
name: write-bpf-production-tutorial
|
||||
description: Create or revise bilingual bpf-developer-tutorial lessons as small, production-realistic eBPF tools with host builds, benchmark-kernel KVM tests, reproducible evidence, and prose authored only by the exact Claude model claude-opus-4-6[1m]. Use when proposing, implementing, documenting, reviewing, or preparing a PR for a new eBPF tutorial, especially when deciding between numbered lessons, features, networking, security, or xpu/gpu placement.
|
||||
---
|
||||
|
||||
# Write BPF Production Tutorials
|
||||
|
||||
Build a useful tool around a kernel capability. Do not publish a wrapper around an upstream selftest or a feature-only API tour.
|
||||
|
||||
## Enforce the author boundary
|
||||
|
||||
- Let the implementation agent design, code, test, and collect technical evidence.
|
||||
- Require Claude Code with the exact model string `claude-opus-4-6[1m]` to author or revise `README.md` and `README.zh.md`.
|
||||
- Invoke Claude only through `scripts/run-claude-writer.sh`. Do not expose a model override and do not configure a fallback model.
|
||||
- Do not write tutorial prose with Codex or another model. Send factual corrections back through the same wrapper instead of rewriting paragraphs manually.
|
||||
- Permit other models to review code or prose, but never to author replacement tutorial text.
|
||||
- Preserve every wrapper trace and manifest under `~/.local/state/bpf-tutorial-writer/runs/`. Never delete, truncate, rewrite, or clean that directory.
|
||||
|
||||
## Establish the tutorial contract
|
||||
|
||||
Before coding, write one sentence naming:
|
||||
|
||||
1. the production failure or operational need;
|
||||
2. the user-visible command and result;
|
||||
3. the kernel capability that makes it possible;
|
||||
4. the positive and negative paths that prove it works.
|
||||
|
||||
Reject proposals whose only result is “the program loaded,” “the helper returned success,” or “the upstream selftest passed.”
|
||||
|
||||
Read [references/tutorial-acceptance.md](references/tutorial-acceptance.md) before selecting scope or placement. Read [references/writing-guide.md](references/writing-guide.md) before invoking the writer.
|
||||
|
||||
## Implement and verify
|
||||
|
||||
1. Read the repository contract, nearby lesson code, Makefiles, documentation generators, and CI workflows.
|
||||
2. Keep one production scenario per PR. Start from the remote default branch for every PR.
|
||||
3. Follow existing libbpf and CO-RE patterns. Add a deterministic workload, observable result, cleanup, and a relevant failure path.
|
||||
4. Build on the host without loading BPF there.
|
||||
5. Use `$test-bpf-tutorial-kvm` for every load, attach, and runtime test. Record guest kernel provenance and real output.
|
||||
6. Add a compile CI step. Add a runtime feature probe or an explicit skip when ordinary CI lacks the required kernel.
|
||||
7. Run documentation generators and link/unit tests.
|
||||
|
||||
## Delegate the prose
|
||||
|
||||
Create a task file outside the lesson with:
|
||||
|
||||
- the exact README paths Claude may edit;
|
||||
- the production scenario and intended audience;
|
||||
- the final code paths and CLI;
|
||||
- verified build and guest commands;
|
||||
- real captured output with private paths and identities removed;
|
||||
- minimum kernel, libbpf, config, privilege, and hardware requirements;
|
||||
- primary upstream references;
|
||||
- facts or claims Claude must not add.
|
||||
|
||||
Then run:
|
||||
|
||||
```bash
|
||||
TUTORIAL_ROOT="$(git rev-parse --show-toplevel)"
|
||||
"$TUTORIAL_ROOT/.agents/skills/write-bpf-production-tutorial/scripts/run-claude-writer.sh" \
|
||||
--repo "$TUTORIAL_ROOT" \
|
||||
--task /absolute/path/to/writer-task.md
|
||||
```
|
||||
|
||||
The wrapper must finish with a verified exact-model trace. Inspect both READMEs for matching structure and technical meaning, then rerun build and documentation commands exactly as written.
|
||||
|
||||
## Prepare the PR
|
||||
|
||||
- Include only one lesson and the minimal generated index/compatibility changes.
|
||||
- Explain the production problem before the kernel feature in the PR body.
|
||||
- Report the host build, KVM positive test, negative/cleanup test, guest kernel provenance, documentation tests, and model-trace manifest.
|
||||
- Follow the repository's review, Copilot, and monitored CI gates. Do not merge unless the user asks.
|
||||
@@ -0,0 +1,4 @@
|
||||
interface:
|
||||
display_name: "BPF Production Tutorial Writer"
|
||||
short_description: "Build tested eBPF lessons with Claude Opus prose"
|
||||
default_prompt: "Use $write-bpf-production-tutorial to turn this production eBPF scenario into a tested bilingual tutorial."
|
||||
@@ -0,0 +1,38 @@
|
||||
# Tutorial acceptance and placement
|
||||
|
||||
## Acceptance gate
|
||||
|
||||
A numbered lesson must ship a small tool that a developer could plausibly run while operating or debugging a real system. Require all of the following:
|
||||
|
||||
- A concrete actor and problem, such as an SRE diagnosing queueing, a security agent quarantining a live connection, or an accelerator operator finding leaked shared buffers.
|
||||
- A user-space CLI with useful output, filters, and actionable errors.
|
||||
- A deterministic workload or fixture that recreates the scenario.
|
||||
- An assertion connecting the workload to BPF output or behavior.
|
||||
- A relevant negative path, unsupported-feature probe, or policy miss.
|
||||
- Automatic cleanup of links, qdiscs, cgroups, maps, sockets, and test processes.
|
||||
- A documented minimum kernel and required config or hardware.
|
||||
- Host compile coverage and guest runtime evidence.
|
||||
|
||||
An upstream selftest may guide correctness, but do not copy its test harness as the lesson's product story.
|
||||
|
||||
## Placement
|
||||
|
||||
- Use `src/<number>-<name>/` for an end-to-end tracing, networking, security, scheduling, or operations tool.
|
||||
- Use `src/features/<name>/` only when the reusable kernel primitive itself is the teaching goal and no honest production tool can be kept small.
|
||||
- Use `src/xpu/<name>/` when the workload is specifically about GPU, NPU, DMA-BUF, accelerator drivers, or accelerator profiling. Do not use xpu merely because the host has a GPU.
|
||||
- Keep cross-cutting deployment mechanisms such as program signing under `src/features/` unless the lesson implements a complete operational policy loader.
|
||||
|
||||
## Scenario guidance
|
||||
|
||||
- `fsession`: build function-latency/SLO diagnosis, not a cookie unit test.
|
||||
- BPF qdisc: build a two-class latency-sensitive packet scheduler with queue/drop counters, not a bare `Qdisc_ops` registration test.
|
||||
- Socket destroy: build a live connection quarantine CLI with an allowlisted control case.
|
||||
- File dynptr plus task work: build deferred executable/header inspection with a direct-read failure demonstration.
|
||||
- DMA-BUF iterator: build an accelerator/shared-buffer inventory and leak check under `src/xpu/`; avoid claiming per-process ownership without fd correlation.
|
||||
- XDP multi-buffer: build a fragmented/jumbo-packet L4 parser with a truncated-packet failure case.
|
||||
- Signed programs: build a signed policy deployment workflow with an untrusted/tampered rejection path.
|
||||
- BPF crypto: defer unless the example has a defensible operational use and clear key-lifecycle boundaries.
|
||||
|
||||
## PR boundary
|
||||
|
||||
Keep each scenario in its own branch and PR. Generate README/SUMMARY/compatibility changes on that branch only. Rebase the next branch on current upstream `main`, not on another unmerged tutorial branch.
|
||||
@@ -0,0 +1,40 @@
|
||||
# BPF tutorial writing guide
|
||||
|
||||
Write for developers who want to reproduce a small, useful eBPF tool. Use oral, direct English and natural Chinese. Prefer short, connected sentences over academic prose or marketing claims.
|
||||
|
||||
## Required narrative
|
||||
|
||||
1. Open with a production incident or operational task the reader recognizes. Explain the consequence and the signal the tool will expose or control.
|
||||
2. Show the command and verified result early. Do not begin with a generic definition of eBPF.
|
||||
3. Explain the end-to-end flow from workload to hook, maps or kfuncs, user-space processing, and final output.
|
||||
4. Present complete source when it stays readable. Otherwise use copyable 15–40 line excerpts and link to the complete lesson.
|
||||
5. Explain only the kernel mechanisms needed to understand the tool. State minimum kernel/libbpf versions, configs, privileges, limitations, and cleanup.
|
||||
6. Provide exact build and run commands. Use only output captured from a real run. Never invent benchmark numbers, events, PIDs, paths, or errors.
|
||||
7. Include the positive path and one meaningful miss, failure, or unsupported path.
|
||||
8. Close with what the operator can now learn or control, plus the repository and tutorial-site links.
|
||||
|
||||
## Style rules
|
||||
|
||||
- Use a specific title such as `# eBPF Tutorial by Example: Quarantine an Established TCP Connection`.
|
||||
- Build a progressive explanation around one running example instead of listing features.
|
||||
- Use active voice and define technical terms through the example.
|
||||
- Avoid throat clearing, repeated summaries, vague claims, paper-like language, em dashes, and stacked one-line paragraphs.
|
||||
- Do not describe a demo as production-ready. State scope and missing hardening plainly.
|
||||
- Preserve exact code, command, version, and output facts supplied in the task file.
|
||||
- Link kernel claims to primary sources such as kernel commits, documentation, or selftests.
|
||||
|
||||
## Bilingual rules
|
||||
|
||||
- Keep English and Chinese headings, examples, commands, limitations, and claims aligned.
|
||||
- Translate naturally rather than sentence by sentence.
|
||||
- Keep code and verified console output identical.
|
||||
- Do not use Chinese em dashes (`——`) or spaced English em dashes (` — `).
|
||||
|
||||
## Required ending links
|
||||
|
||||
Include direct links to:
|
||||
|
||||
- <https://github.com/eunomia-bpf/bpf-developer-tutorial>
|
||||
- <https://eunomia.dev/tutorials/>
|
||||
|
||||
Do not add frontmatter unless nearby lessons use it.
|
||||
121
.agents/skills/write-bpf-production-tutorial/scripts/run-claude-writer.sh
Executable file
121
.agents/skills/write-bpf-production-tutorial/scripts/run-claude-writer.sh
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
readonly MODEL='claude-opus-4-6[1m]'
|
||||
readonly SKILL_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
readonly VERIFY="$SKILL_DIR/scripts/verify_claude_trace.py"
|
||||
readonly GUIDE="$SKILL_DIR/references/writing-guide.md"
|
||||
|
||||
usage() {
|
||||
echo "usage: $0 --repo ABSOLUTE_REPO_PATH --task ABSOLUTE_TASK_FILE" >&2
|
||||
}
|
||||
|
||||
repo=
|
||||
task=
|
||||
while (($#)); do
|
||||
case "$1" in
|
||||
--repo)
|
||||
repo=${2:-}
|
||||
shift 2
|
||||
;;
|
||||
--task)
|
||||
task=${2:-}
|
||||
shift 2
|
||||
;;
|
||||
--model|--fallback-model)
|
||||
echo "model overrides and fallbacks are forbidden; required model: $MODEL" >&2
|
||||
exit 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
usage
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
[[ $repo = /* && -d $repo/.git ]] || { echo "--repo must be an absolute Git checkout" >&2; exit 2; }
|
||||
[[ $task = /* && -f $task ]] || { echo "--task must be an absolute readable file" >&2; exit 2; }
|
||||
command -v claude >/dev/null || { echo "Claude Code is required" >&2; exit 127; }
|
||||
command -v python3 >/dev/null || { echo "python3 is required" >&2; exit 127; }
|
||||
|
||||
state_root=${XDG_STATE_HOME:-$HOME/.local/state}/bpf-tutorial-writer/runs
|
||||
mkdir -p "$state_root"
|
||||
chmod 700 "$state_root"
|
||||
run_id="$(date -u +%Y%m%dT%H%M%SZ)-$$"
|
||||
run_dir="$state_root/$run_id"
|
||||
mkdir -m 700 "$run_dir"
|
||||
trace="$run_dir/claude-trace.jsonl"
|
||||
manifest="$run_dir/manifest.json"
|
||||
|
||||
head_before=$(git -C "$repo" rev-parse HEAD)
|
||||
status_before=$(git -C "$repo" status --porcelain=v1)
|
||||
task_sha=$(sha256sum "$task" | awk '{print $1}')
|
||||
|
||||
prompt=$(printf '%s\n\n%s\n\n%s\n' \
|
||||
'You are the sole prose author for an eBPF production tutorial. Read the repository code and evidence named in the task. Edit only the README.md and README.zh.md paths explicitly allowed by the task. Do not edit code, build files, generated indexes, or tests. Do not invent output or claims. Finish by stating which README files you edited.' \
|
||||
"$(<"$GUIDE")" \
|
||||
"$(<"$task")")
|
||||
|
||||
set +e
|
||||
(
|
||||
cd "$repo"
|
||||
claude -p \
|
||||
--model "$MODEL" \
|
||||
--effort high \
|
||||
--permission-mode acceptEdits \
|
||||
--allowedTools Read Edit Write \
|
||||
--output-format stream-json \
|
||||
--verbose \
|
||||
"$prompt"
|
||||
) >"$trace" 2>&1
|
||||
claude_status=$?
|
||||
set -e
|
||||
|
||||
trace_sha=$(sha256sum "$trace" | awk '{print $1}')
|
||||
head_after=$(git -C "$repo" rev-parse HEAD)
|
||||
status_after=$(git -C "$repo" status --porcelain=v1)
|
||||
|
||||
MODEL_VALUE="$MODEL" \
|
||||
REPO_VALUE="$repo" \
|
||||
TASK_VALUE="$task" \
|
||||
TASK_SHA_VALUE="$task_sha" \
|
||||
TRACE_VALUE="$trace" \
|
||||
TRACE_SHA_VALUE="$trace_sha" \
|
||||
CLAUDE_STATUS_VALUE="$claude_status" \
|
||||
HEAD_BEFORE_VALUE="$head_before" \
|
||||
HEAD_AFTER_VALUE="$head_after" \
|
||||
STATUS_BEFORE_VALUE="$status_before" \
|
||||
STATUS_AFTER_VALUE="$status_after" \
|
||||
python3 - "$manifest" <<'PY'
|
||||
import json
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
path = pathlib.Path(__import__("sys").argv[1])
|
||||
path.write_text(json.dumps({
|
||||
"model": os.environ["MODEL_VALUE"],
|
||||
"repo": os.environ["REPO_VALUE"],
|
||||
"task": os.environ["TASK_VALUE"],
|
||||
"task_sha256": os.environ["TASK_SHA_VALUE"],
|
||||
"trace": os.environ["TRACE_VALUE"],
|
||||
"trace_sha256": os.environ["TRACE_SHA_VALUE"],
|
||||
"claude_exit_status": int(os.environ["CLAUDE_STATUS_VALUE"]),
|
||||
"head_before": os.environ["HEAD_BEFORE_VALUE"],
|
||||
"head_after": os.environ["HEAD_AFTER_VALUE"],
|
||||
"status_before": os.environ["STATUS_BEFORE_VALUE"],
|
||||
"status_after": os.environ["STATUS_AFTER_VALUE"],
|
||||
}, indent=2, sort_keys=True) + "\n", encoding="utf-8")
|
||||
PY
|
||||
chmod 600 "$trace" "$manifest"
|
||||
|
||||
if ((claude_status != 0)); then
|
||||
echo "Claude exited with status $claude_status; preserved trace: $trace" >&2
|
||||
exit "$claude_status"
|
||||
fi
|
||||
|
||||
python3 "$VERIFY" --trace "$trace" --model "$MODEL" --require-write
|
||||
echo "manifest=$manifest"
|
||||
101
.agents/skills/write-bpf-production-tutorial/scripts/verify_claude_trace.py
Executable file
101
.agents/skills/write-bpf-production-tutorial/scripts/verify_claude_trace.py
Executable file
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify that a persisted Claude trace used the required authoring model."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
|
||||
def load_events(path: Path) -> list[dict[str, Any]]:
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
try:
|
||||
value = json.loads(raw)
|
||||
except json.JSONDecodeError:
|
||||
events = []
|
||||
for number, line in enumerate(raw.splitlines(), 1):
|
||||
if not line.strip():
|
||||
continue
|
||||
try:
|
||||
value = json.loads(line)
|
||||
except json.JSONDecodeError as error:
|
||||
raise ValueError(f"invalid JSON on trace line {number}: {error}") from error
|
||||
events.extend(value if isinstance(value, list) else [value])
|
||||
return [event for event in events if isinstance(event, dict)]
|
||||
|
||||
values = value if isinstance(value, list) else [value]
|
||||
return [event for event in values if isinstance(event, dict)]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--trace", required=True, type=Path)
|
||||
parser.add_argument("--model", required=True)
|
||||
parser.add_argument("--require-write", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
events = load_events(args.trace)
|
||||
init_models = {
|
||||
event.get("model")
|
||||
for event in events
|
||||
if event.get("type") == "system" and event.get("subtype") == "init"
|
||||
}
|
||||
if init_models != {args.model}:
|
||||
raise SystemExit(f"expected init model {args.model!r}, found {sorted(init_models)!r}")
|
||||
|
||||
base_model = args.model.split("[", 1)[0]
|
||||
authored_text = 0
|
||||
write_calls = 0
|
||||
assistant_models = set()
|
||||
for event in events:
|
||||
if event.get("type") != "assistant":
|
||||
continue
|
||||
message = event.get("message") or {}
|
||||
model = message.get("model")
|
||||
content = message.get("content") or []
|
||||
if any(item.get("type") == "text" and item.get("text") for item in content):
|
||||
authored_text += 1
|
||||
assistant_models.add(model)
|
||||
write_calls += sum(
|
||||
item.get("type") == "tool_use" and item.get("name") in {"Edit", "Write"}
|
||||
for item in content
|
||||
)
|
||||
|
||||
if not authored_text:
|
||||
raise SystemExit("trace contains no authored assistant text")
|
||||
if assistant_models != {base_model}:
|
||||
raise SystemExit(
|
||||
f"expected authored text from {base_model!r}, found {sorted(assistant_models)!r}"
|
||||
)
|
||||
if args.require_write and write_calls == 0:
|
||||
raise SystemExit("trace contains no Claude Edit or Write call")
|
||||
|
||||
results = [event for event in events if event.get("type") == "result"]
|
||||
if not results:
|
||||
raise SystemExit("trace contains no terminal result")
|
||||
result = results[-1]
|
||||
if result.get("is_error"):
|
||||
raise SystemExit(f"Claude run failed: {result.get('subtype', 'unknown error')}")
|
||||
usage = result.get("modelUsage") or {}
|
||||
exact_usage = usage.get(args.model) or {}
|
||||
if exact_usage.get("outputTokens", 0) <= 0:
|
||||
raise SystemExit(f"terminal usage does not prove output from {args.model}")
|
||||
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"model": args.model,
|
||||
"authored_text_events": authored_text,
|
||||
"write_calls": write_calls,
|
||||
"trace": str(args.trace),
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user