pub(crate) const SKILL_MD: &str = "---\nname: scx-sched-policy\ndescription: Modify a sched_ext Rust+BPF scheduler crate (under scheds/rust/<name>) in place. Use when AI agents need to customize, tune, validate, or iterate on a scx scheduler, including BPF policy callbacks, Rust control-plane setup, topology and DSQ maps, stats, and CLI options.\n---\n\n# scx_ext Scheduler Policy\n\nModify the target scheduler crate (the `scheds/rust/<name>` crate named by the\nspec\'s `[scheduler].package`) directly to implement and tune sched_ext\nscheduling policy. Edit the existing crate in place rather than copying it to a\nnew scheduler; treat the current policy as a scaffold to evolve, not as\nproduction policy to preserve unchanged. You are told which scheduler you operate\non; read that crate\'s files before editing.\n\n## Start Here\n\nRead these files in the target crate before editing:\n\n- `README.md`: scheduler purpose, user-facing options, and current policy summary.\n- `Cargo.toml`: package name, dependencies, and workspace-facing metadata.\n- `src/main.rs`: Rust control plane, CLI options, topology setup, BPF rodata/map\n initialization, struct_ops attach, stats server, and shutdown handling.\n- `src/bpf/main.bpf.c`: core sched_ext policy and BPF maps.\n- `src/bpf/intf.h`: C structs/constants shared with Rust through generated bindings.\n- `src/stats.rs`: stats schema, formatting, JSON export, and monitor loop.\n- `build.rs`: BPF skeleton and interface generation through `scx_cargo::BpfBuilder`.\n- `https://www.kernel.org/doc/Documentation/scheduler/sched-ext.rst`: upstream\n sched_ext semantics, especially Dispatch Queues, Scheduling Cycle, task\n custody / `dequeue`, Task Lifecycle, status events, module parameters, and ABI\n instability.\n\n(Some schedulers add extra modules or BPF files - e.g. a `tuner.rs`,\n`load_balance.rs`, or supplementary `*.bpf.c` - so list the crate\'s `src/` and\n`src/bpf/` directories and read whatever the policy actually uses.)\n\n## Plan Phase\n\nBefore editing, turn the user\'s spec into a concrete plan:\n\n- Restate the scheduling goal in one or two sentences (what should be optimized\n or guaranteed, and for which kind of workload).\n- Read the scheduler\'s BPF callbacks (`select_cpu`, `enqueue`, `dispatch`,\n `running/stopping`, etc.) and identify the helper functions they delegate to, as\n these define the scheduler\'s policy seams. Map the goal to the specific seams\n it touches (queue ordering, wakeup placement, time-slice / charging, dispatch\n pull domains). Prefer preserving callback lifecycle semantics, but do not\n limit yourself to a numeric tweak when the objective calls for a larger\n scheduling change. A coherent redesign may touch multiple helpers, maps, and\n Rust-populated knobs if they implement one policy idea.\n- If the spec defines \"good\" as a measurable number, capture that as a\n validation spec up front (see \"Iterate To A Metric\") so the same definition\n drives every iteration.\n\n## Customize The Scheduler In Place\n\nWork directly in the target crate; do not copy it to a new crate or rename the\npackage, the binary, or its `struct_ops` symbols. The crate already builds and\nattaches, so keep the scaffolding intact and change only the policy:\n\n1. Keep the package name, the binary name, the scheduler-name constant in\n `src/main.rs`, the `SCX_OPS_DEFINE(<ops>, ...)` `struct_ops` symbol, the\n callback function names, the `scx_ops_open!`/`scx_ops_load!`/`scx_ops_attach!`\n macro uses, and the `.name = \"...\"` ops name unchanged - whatever they are\n for this scheduler - so existing tooling, sysfs paths, stats, and the\n validation harness keep working. Discover these identifiers by reading\n `src/main.rs` and `src/bpf/main.bpf.c`; do not assume names from another\n scheduler.\n2. Leave `build.rs` and its relative paths to `rust/scx_cargo` unchanged; the\n crate stays in its existing directory.\n3. Make policy changes in `src/bpf/main.bpf.c` (and the matching Rust knobs),\n keeping each change focused on one coherent policy idea and the crate\n buildable. That idea may span multiple seams, such as queue ordering plus\n dispatch pull domains, or idle-CPU selection plus topology state.\n\n## sched_ext Semantics\n\nOnly `ops.name` is mandatory in `struct sched_ext_ops`; other callbacks are\noptional, but preserve the callbacks needed by the scheduler\'s state machine.\nUse the current lifecycle model from\n`https://www.kernel.org/doc/Documentation/scheduler/sched-ext.rst` when\ndeciding which callback owns each piece of policy state:\n\n1. `init_task` runs when a task is created and can allocate task-local state.\n2. `enable` admits the task into BPF scheduling and should initialize\n scheduler-visible task state such as `dsq_vtime`.\n3. While the task remains in `SCHED_EXT`, wakeups for migratable tasks may call\n `select_cpu` first. `ops.select_cpu()` is not invoked for migration-disabled\n tasks (`is_migration_disabled(p)`) or per-CPU tasks\n (`p->nr_cpus_allowed == 1`); those paths reach `enqueue` without CPU\n selection. Treat the selected CPU as an optimization hint and idle-wakeup\n mechanism, not as a binding placement decision; the core ignores unusable CPU\n selections and may pick a fallback.\n4. `runnable` runs when the task becomes ready to run (task wakeup).\n5. While runnable, a task that is not in a DSQ can be passed to `enqueue`, which\n can insert it into a DSQ or keep it queued using BPF data structures.\n6. Property changes can occur while a task is queued or running. If a queued\n task is in BPF scheduler custody, the core calls `dequeue`, then `quiescent`,\n then the relevant property callback, then `runnable` again.\n7. When a CPU needs work, it consumes its local DSQ first, then the global DSQ,\n then calls `dispatch` so the scheduler can move or insert work into the local\n DSQ.\n8. If a task leaves BPF custody through dispatch to a terminal DSQ, `dequeue` is\n called. If it was direct-dispatched to a local DSQ from `enqueue`, `dispatch`\n and `dequeue` are skipped.\n9. `running` runs when the task starts executing on its assigned CPU.\n10. While the task remains runnable and has slice left, `tick` may run every\n `1/HZ` seconds. If the slice reaches zero, `dispatch` may refill\n `task->scx.slice`.\n11. `stopping` runs when the task stops using the CPU because its slice expires,\n it waits, it is preempted by a higher-priority scheduling class, or another\n transition removes it from the CPU.\n12. `quiescent` runs when the task releases its CPU and is no longer runnable.\n13. `disable` removes the task from BPF scheduling, and `exit_task` runs when\n the task is destroyed.\n\nIdentify which callbacks the target scheduler implements by reading\n`src/bpf/main.bpf.c` and its `SCX_OPS_DEFINE(...)` block. Add callbacks such as\n`tick`, `exit_task`, or additional property callbacks only when the scheduler\ngains state that must be updated at those lifecycle points. If the scheduler\ncreates user DSQs, tasks inserted there enter BPF custody and need `dequeue`\naccounting when they leave those DSQs.\n\nTask custody rules:\n\n- A task enters BPF scheduler custody when it is dispatched to a user-created\n DSQ or stored in BPF scheduler data structures. This normally happens from\n `enqueue`.\n\n- Dispatching to a user DSQ from `select_cpu` has the same custody effect as\n dispatching from `enqueue`, but storing tasks in BPF-internal structures from\n `select_cpu` is discouraged and does not suppress `enqueue`.\n\n- Dispatching to terminal DSQs, meaning `SCX_DSQ_LOCAL`,\n `SCX_DSQ_LOCAL_ON | cpu`, or `SCX_DSQ_GLOBAL`, does not put the task in BPF\n custody and does not trigger `dequeue`.\n\n- When a task leaves BPF custody, `dequeue` is called exactly once. Regular\n dispatch to a terminal DSQ has no special flag; core-sched execution uses\n `SCX_DEQ_CORE_SCHED_EXEC`; scheduling property changes use\n `SCX_DEQ_SCHED_CHANGE`.\n\n- `enqueue` can be called multiple times without an intervening `dequeue`, for\n example around `scx_bpf_dsq_reenq()`, while the task remains in custody.\n\n- After a task has left BPF custody, later property changes do not trigger\n `dequeue` for that queued instance.\n\nDSQ rules to preserve:\n\n- A CPU always executes from its local DSQ. Non-local work must be moved or\n inserted into the target local DSQ before it runs.\n\n- Do not rely on `select_cpu` for pinned work: migration-disabled tasks and\n per-CPU tasks skip `ops.select_cpu()`, so their placement and direct-dispatch\n handling must be done in `enqueue`.\n\n- A common, safe pattern is to keep `select_cpu` as a pure wakeup placement\n hint: it returns a CPU and does not necessarily insert the task into a DSQ, so\n all tasks stay visible to `enqueue()` before any direct placement\n optimization. Check whether the target scheduler follows this pattern before\n relying on it.\n\n- `select_cpu` may directly insert with `scx_bpf_dsq_insert()` or\n `scx_bpf_dsq_insert_vtime()` only when the scheduler explicitly chooses that\n fast path. If the target is `SCX_DSQ_LOCAL`, insertion skips `enqueue` and\n resolves to the local DSQ of the CPU returned by `select_cpu`. Dispatching to a\n user-created DSQ from `select_cpu` has custody semantics; storing tasks in\n BPF-internal structures from `select_cpu` does not suppress `enqueue` and is\n discouraged. When using this fast path, document why tasks that skip\n `select_cpu()` cannot be starved.\n\n- `enqueue` may insert into `SCX_DSQ_GLOBAL`, `SCX_DSQ_LOCAL`,\n `SCX_DSQ_LOCAL_ON | cpu`, or a custom DSQ ID below `2^63`; alternatively, it\n may retain the task in BPF-managed queues for later dispatch. Tasks inserted\n to `SCX_DSQ_LOCAL` or `SCX_DSQ_LOCAL_ON | cpu` are processed before tasks\n inserted into `SCX_DSQ_GLOBAL`, which are processed before tasks in\n BPF-managed DSQs. Tasks in `SCX_DSQ_GLOBAL` can be consumed by any CPU. When\n creating custom user DSQs, keep IDs small and above the CPU-ID range: use IDs\n greater than `MAX_CPUS`, not high-bit or very large values, because built-in\n DSQs use reserved high-bit encodings.\n\n- A scheduler\'s DSQ topology is a policy choice: read the BPF file to learn it.\n For example, a scheduler may create one user DSQ per possible CPU (using the\n CPU ID as the DSQ ID) and have `dispatch()` scan those per-CPU DSQs, stealing\n an eligible task from another CPU\'s DSQ when the task\'s affinity allows it.\n Others use a single shared DSQ, per-LLC DSQs, or per-domain DSQs - match the\n policy you are editing.\n\n- DSQs work in a push/pop fahion: `enqueue()` is the push side for tasks that\n should be selected later, it inserts tasks into the chosen DSQ with the queue\n ordering key; `dispatch` is the pop side: it consumes tasks from a DSQ with\n `scx_bpf_dsq_move_to_local()`. The core calls `dispatch` when a CPU becomes\n available for more work, for example after the current task blocks, yields,\n exits, or exhausts its assigned time slice. Because dispatch runs at this\n CPU-available boundary, it is also the natural place to perform load balancing\n by pulling from local, sibling, LLC, node, domain, or shared DSQs according to\n the policy. If an experiment switches from per-CPU DSQs to per-LLC, per-node,\n per-domain, or shared DSQs, update both `enqueue` and `dispatch` in the same\n edit so DSQ IDs, ordering semantics, dispatch pull domains, and affinity\n checks match.\n\n- Inserting a task into a terminal local DSQ with `SCX_DSQ_LOCAL` or\n `SCX_DSQ_LOCAL_ON | cpu` makes the core implicitly wake the resolved target\n CPU. Do not also call `scx_bpf_kick_cpu()` for the same placement; that is a\n redundant kick. Use explicit kicks when the task is left in a user-created DSQ\n or BPF-owned queue that the target CPU might not otherwise observe, or when\n the policy intentionally wants a preemption/idle kick that terminal local-DSQ\n insertion does not cover. For idle-only nudges, `SCX_KICK_IDLE` is normally the\n right flag.\n\n- `dispatch` should populate the dispatching CPU\'s local DSQ: use\n `scx_bpf_dsq_move_to_local(dsq_id, 0)` to move the first task from `dsq_id`\n to the CPU\'s local DSQ.\n\n- In `dispatch`, `scx_bpf_dsq_insert()` schedules insertions rather than\n performing them immediately, with up to `ops.dispatch_max_batch` pending\n tasks. `scx_bpf_dsq_move_to_local()` flushes pending insertions before\n attempting the move.\n\n- Use `scx_bpf_dsq_insert()` for FIFO insertion. Use\n `scx_bpf_dsq_insert_vtime()` for priority-queue ordering. Built-in DSQs such\n as `SCX_DSQ_LOCAL` and `SCX_DSQ_GLOBAL` do not support vtime insertion.\n\n- `scx_bpf_dsq_insert_vtime(p, dsq_id, slice, vtime, enq_flags)` internally\n updates `p->scx.dsq_vtime` to the `vtime` argument and `p->scx.slice` to the\n `slice` argument. If policy also needs an accumulated vruntime separate from\n the queue key, keep it in task-local state instead of assuming\n `p->scx.dsq_vtime` still contains the old value.\n\n- `scx_bpf_dsq_insert(p, dsq_id, slice, enq_flags)` internally updates\n `p->scx.slice` to the `slice` argument.\n\n- If all tasks are immediately inserted into built-in DSQs from `select_cpu` or\n `enqueue`, `dispatch` can often remain simple because the core drains\n local/global DSQs automatically.\n\nsched_ext can be unloaded, or aborted automatically on internal errors or\nrunnable-task stalls; tasks then revert to the fair class.\n\n## Implement Policy\n\nMake policy changes primarily in `src/bpf/main.bpf.c`:\n\n- `select_cpu`: implement wakeup placement hints and, when there is a clear\n reason, terminal-DSQ direct-dispatch fast paths such as dispatching immediately\n to an idle CPU. Preserve affinity checks with `p->cpus_ptr`. Remember that\n this callback is skipped for migration-disabled tasks\n (`is_migration_disabled(p)`) and per-CPU tasks (`p->nr_cpus_allowed == 1`), so\n put required handling for those tasks in `enqueue`. Do not implement global\n queueing or global selection policy here.\n- `enqueue`: admit every runnable task that reaches BPF. Reuse the scheduler\'s\n own helpers - a direct-dispatch fast path as an idle-CPU optimization, and its\n DSQ-selection / queue-ordering helpers for tasks that should be selected from\n `dispatch()`. When `enqueue()` directly inserts into a terminal local DSQ,\n rely on the core\'s implicit target-CPU wakeup instead of calling\n `scx_bpf_kick_cpu()` for the same task. For user DSQs, keep this insertion\n path paired with the matching consumption path in `dispatch()`; changing only\n one side of the push/pop pair can leave runnable tasks stranded in queues that\n dispatch no longer reads.\n- `dispatch`: implement the primary selection decision. A CPU should pull from\n the scheduler\'s chosen source DSQ with `scx_bpf_dsq_move_to_local(dsq_id, 0)`\n or explicitly refill the previous task through the scheduler\'s time-slice\n helpers. Treat this as a load-balancing point: the dispatching CPU is asking\n for runnable work, so this is where the scheduler can choose whether to keep\n locality or pull eligible work from another queue/domain.\n- `dequeue`: add this when the scheduler keeps per-task state while tasks are in\n user DSQs or BPF-owned queues and must clean up or account for custody exit,\n property changes, or core-sched execution.\n- `running` and `stopping`: update per-task timestamps, vruntime, accounting,\n and policy state.\n- `runnable`, `quiescent`, `enable`, `disable`, `init_task`, and `exit_task`:\n initialize, pause, or tear down task-local state at lifecycle boundaries when\n the policy needs it.\n- `init`: create DSQs and initialize BPF-side topology or policy metadata.\n- `exit`: record `UserExitInfo` with `UEI_RECORD`.\n\nReuse the scheduler\'s existing structures as anchors instead of inventing new\nones. Before editing, grep `src/bpf/main.bpf.c` (and `intf.h`) for the crate\'s\nown equivalents of:\n\n- per-task state storage (a task-local storage map) and per-CPU state storage\n (per-CPU context, e.g. SMT sibling masks).\n- Rust-populated topology lookups (e.g., a CPU-to-LLC map).\n- the helpers each callback delegates to for DSQ selection / dispatch pull\n domains, the DSQ-ID scheme, idle-CPU placement, and queue ordering (FIFO vs\n weighted virtual-time, including any CFS-style sleep-lag preservation and\n wakeup rebasing).\n- the time-slice / runtime-charging helpers.\n- `const volatile` rodata knobs (e.g. a slice length, `smt_enabled`,\n `numa_enabled`) and the matching `rodata_data` assignments in Rust.\n- the existing BPF counters exported through Rust stats - follow their pattern\n when adding a new counter.\n\n## Wire Rust And BPF Together\n\nWhen adding a BPF tunable, declare it as `const volatile` in\n`src/bpf/main.bpf.c` and set it in `src/main.rs` through\n`skel.maps.rodata_data.as_mut().unwrap()` before `scx_ops_load!`.\n\n`const volatile` globals in the BPF program are almost always initialized from\nRust (through that same rodata path) before the skeleton is loaded - the BPF\nside only reads them. The initializer in `src/bpf/main.bpf.c` is often just a\nfallback or generated-field default; once Rust writes `rodata_data.<field>`,\nthat Rust value is what the running scheduler sees.\n\nDo not treat a BPF-only `const volatile` initializer edit as a scheduler\noptimization. It may compile and produce a diff, but if Rust assigns the field\nbefore load, the runtime policy is unchanged. Before editing any existing\n`const volatile`, grep `src/main.rs` for both the symbol name and\n`rodata_data`, then update the Rust-side source of truth in the same edit:\n\n- Rename or retype a `const volatile` in `src/bpf/main.bpf.c` -> update the\n corresponding `rodata_data` field assignment in `src/main.rs` (the field name\n and type are generated from the BPF declaration, so a mismatch is a build\n error, and a stale value silently defeats the change).\n- Change the value of an existing tunable -> update the Rust default, CLI option,\n computed value, or `rodata_data` assignment that feeds it. Changing only the\n BPF initializer is valid only after verifying Rust never assigns that field.\n- Change the meaning, units, or default of a tunable (e.g. ns vs us, a new\n scaling) -> update the Rust value that feeds it and any CLI option / validation\n that computes it, so BPF and Rust agree on the same units.\n- Add a new `const volatile` -> it defaults to 0 in BPF unless Rust sets it;\n always wire the Rust assignment, or the policy runs with a zeroed knob.\n\nWhen adding or changing BPF maps:\n\n- Define the map in `src/bpf/main.bpf.c`.\n- Populate host-derived data from `Scheduler::init()` after the skeleton is\n opened or loaded and before attach.\n- Keep topology-derived logic in Rust when it is easier or safer than computing\n it in BPF.\n\nWhen sharing structs or syscall test-run inputs between Rust and BPF:\n\n- Add the C definition to `src/bpf/intf.h`.\n- Use the generated Rust binding from `src/bpf_intf.rs`.\n- Follow the crate\'s existing pattern for BPF syscall programs invoked with\n `ProgramInput` (e.g. an `enable_sibling_cpu`-style setup program), if it has one.\n\nWhen adding CLI options:\n\n- Extend `Opts` in `src/main.rs`.\n- Validate and normalize user input in Rust.\n- Pass only compact, verifier-friendly values into BPF rodata or maps.\n- Update `README.md` examples and option descriptions.\n\nWhen changing scheduler flags:\n\n- Set flags in `Scheduler::init()` with `scx_utils::compat` constants where\n possible.\n- Keep `SCX_OPS_ENQ_EXITING`, `SCX_OPS_ENQ_MIGRATION_DISABLED`, and related skip\n flags aligned with how much special-case handling the BPF policy implements.\n- Use `SCX_OPS_SWITCH_PARTIAL` only when the scheduler should manage explicit\n `SCHED_EXT` tasks instead of all normal-class tasks.\n\nWhen adding stats:\n\n- Add BPF counters or state in `src/bpf/main.bpf.c`.\n- Read them in `Scheduler::get_metrics()`.\n- Extend `Metrics` in `src/stats.rs`, including `#[stat(desc = \"...\")]`,\n `format()`, and `delta()`.\n- Check `--help-stats`, `--stats <interval>`, and `--monitor <interval>`\n behavior.\n\n## BPF Safety Rules\n\nKeep BPF verifier constraints central while designing:\n\n- Check all map and task-storage lookups for null before dereferencing.\n- Treat every `bpf_map_lookup_elem()` and task-storage lookup result as\n independently nullable. The verifier does not infer that a key read from one\n map exists in another map, even if Rust initialization populated both maps.\n Never write `lookup_foo(key)->field`. Bind the result to a pointer, check it\n for null, then dereference it only on the checked path.\n- Bound loops with verifier-visible limits, such as `bpf_for(...)` over known\n counts.\n- Release cpumasks obtained from `sched_ext` helpers with the matching put helper.\n- Use RCU sections when reading kptr cpumasks, following the crate\'s existing\n RCU cpumask examples (e.g., an SMT-sibling lookup).\n- Prefer small integer IDs and precomputed arrays/maps over dynamic BPF-side\n discovery.\n- Keep hot-path helper calls minimal in `select_cpu`, `enqueue`, and `dispatch`.\n- Return explicit errors from `init` when DSQ or metadata setup fails, and call\n `scx_bpf_error()` with useful context.\n- Treat `sched_ext` callback, constant, and `scx_bpf_*` helper APIs as\n kernel-version-specific. Prefer the repo\'s existing compat wrappers and\n current local examples over stale external snippets.\n\n## Validate\n\nRun focused validation after each meaningful change:\n\n```bash\ncargo fmt -p <package_name>\ncargo build -p <package_name>\n```\n\nOn a `sched_ext`-capable kernel, smoke-test manually:\n\n```bash\nsudo ./target/debug/<binary_name> --help\nsudo ./target/debug/<binary_name> --stats 1\nsudo ./target/debug/<binary_name> --monitor 1\ncat /sys/kernel/sched_ext/state\ncat /sys/kernel/sched_ext/root/ops\ncat /sys/kernel/sched_ext/<sched_name>/events\n```\n\n(`<sched_name>` is the scheduler\'s `.name` ops value - read it from the\n`SCX_OPS_DEFINE(...)` block or `cat /sys/kernel/sched_ext/root/ops`.)\n\nUse `--debug` for BPF `bpf_printk()` output and `--verbose` for libbpf details\nwhen diagnosing attach, verifier, topology, or dispatch behavior.\n\nUse `sched_ext` event counters as policy diagnostics:\n\n- `SCX_EV_SELECT_CPU_FALLBACK`: `select_cpu` returned an unusable CPU.\n- `SCX_EV_DISPATCH_LOCAL_DSQ_OFFLINE`: a local-DSQ dispatch was redirected\n because the target CPU went offline.\n- `SCX_EV_ENQ_SKIP_EXITING` and `SCX_EV_ENQ_SKIP_MIGRATION_DISABLED`: exiting or\n migration-disabled task handling is relying on core bypass behavior.\n- `SCX_EV_REENQ_LOCAL_REPEAT`: recurring counts suggest incorrect\n `SCX_ENQ_REENQ` handling.\n- `SCX_EV_REFILL_SLICE_DFL`: the core refilled a task slice with\n `SCX_SLICE_DFL`, often indicating the policy did not refill it.\n- `SCX_EV_INSERT_NOT_OWNED`: the scheduler attempted to insert a task it no\n longer owns.\n\nFor system-level checks, inspect `/sys/kernel/sched_ext/enable_seq`, or grep\n`ext.enabled` in `/proc/<pid>/sched`.\n\nBypass-mode module parameters under `/sys/module/sched_ext/parameters/` are\ndebugging knobs; avoid baking them into the scheduler.\n\n## Iterate To A Metric\n\nWhen the user defines \"good\" as a measurable quantity, close the loop: edit the\npolicy, build, run the candidate under a real workload, read back one metric,\nand repeat. The validation harness is built into the agent (it runs in\nprocess; there is no separate script):\n\n- The harness builds the candidate (`cargo build`), refuses to run if a\n scheduler is already attached, launches it on the host as root, drives a\n workload, verifies `/sys/kernel/sched_ext/state` is still `enabled`, extracts\n one metric, always tears the scheduler down (verifying the state returns to\n `disabled`), optionally records the configured `perf` events while the\n workload runs, and produces a single JSON verdict. It needs root: run as root,\n with passwordless sudo, or set\n `SCX_SUDO_PASSWORD_FILE` to a file containing the sudo password.\n- `tools/scx_forge_agent/spec.toml` documents the spec: `[scheduler]` (package -\n which `scheds/rust/<name>` crate to optimize - plus profile), `[system]`\n (`sudo_passwd_file` for host sudo integration), `[ai]` (`base_url`, `model`,\n and optional `coding_base_url` / `coding_model`; each base-URL is an\n OpenAI-compatible URL or one of the agent-CLI keywords\n claude/codex/opencode/cursor-agent,\n and the planner/coder can use different backends. API keys come from\n `$SCX_FORGE_API_KEY` / `$SCX_FORGE_CODING_API_KEY`),\n `[tracing]` (`enable_tracing`, `trace_events` passed to perf record with `-e`,\n plus `max_trace_size` passed to `perf record --max-size`, e.g. `256M`),\n `[workload]` (`command`, `duration` in seconds, repeated measurement\n count `runs`), and `[goal]` (a plain-language `prompt`, `direction`, and\n `accept_threshold_stddev`; the reported metric name is always `score`).\n\nThe workload command is the only metric source. It must run the workload and\nprint exactly one numeric metric value; any extraction or aggregation belongs in\nthe command itself.\n\nAgent loop (driven by the controller, not by you):\n\n1. Read the specified `spec.toml` from the user\'s definition of \"good\". Confirm\n the workload and metric actually reflect the goal before optimizing.\n2. The planner/reasoner role chooses one targeted scheduling-policy experiment,\n using read-only target-crate and scheduler-reference tools.\n3. The coding role applies that experiment as one focused policy edit, often in\n `src/bpf/main.bpf.c` plus matching Rust-side rodata/CLI plumbing when needed.\n4. `cargo build -p <package>` as a cheap gate; fix verifier/compile errors first.\n5. The harness builds, attaches, checks that the scheduler stayed enabled during\n measurement, and measures the candidate, yielding a JSON verdict. If the\n scheduler exits `sched_ext` during measurement (`stage = \"runtime\"`), the\n controller gives the coding role a bounded runtime-fix loop with the verdict\n JSON and scheduler log tail before reverting the round. The same runtime-fix\n path is used if the workload cannot emit a parseable metric\n (`stage = \"metric\"`), because that often means the candidate disrupted the\n workload. Read `metric.value` (vs the previous best value), normalized\n `improvement`, the `scheduler_log_tail` (the scheduler stats deltas), and\n `sched_trace` if present (observed perf event/sample counts, global\n retained-window sample rates, and a compact CPU-rate summary from the\n configured perf events, not raw perf script data) to understand *why* the\n number moved. Tracepoint events are sampled per occurrence; hardware PMU events\n are sampled according to perf\'s configured/default sampling period, so they are\n not raw hardware event totals. Improvement is always measured against the\n starting (round 0) scheduler, never the default kernel scheduler.\n6. Keep a short running log of (change -> metric value) so the search is\n auditable. The controller can append a completed run to a state file with\n `--save <path>` and load that memory into future prompts with\n `--resume <path>` as factual context.\n7. Repeat from step 2 until the user\'s target is met, or the iteration budget is\n exhausted. Stop and report if `stage` is `build`, `attach`, or `metric` (the\n candidate did not build, attach, or produce a measurable result).\n\nChange one thing per iteration. Scheduler metrics are noisy: use the spec\'s\n`runs`/`stddev` to tell a real improvement from variance, and do not chase a gain\nsmaller than the stddev.\n\n## Common Verifier Errors\n\nWhen `cargo build` fails inside the BPF program, the fix is usually one of:\n\n- \"math between ... pointer and register\" / unbounded access: add an explicit\n `if (idx >= BOUND) return;` before indexing, so the bound is verifier-visible.\n- \"Unreleased reference\" on a cpumask: every `scx_bpf_get_*_cpumask()` needs the\n matching put helper on all paths, inside the same RCU section.\n- \"invalid mem access \'map_value_or_null\'\" / \"invalid mem access \'scalar\'\" /\n null deref: a map or task-storage lookup result was dereferenced before a\n null check.\n- \"back-edge\" / \"infinite loop detected\": replace open-coded loops with\n `bpf_for(i, 0, n)` over a known-bound `n`.\n- \"dereference of modified ctx ptr\" or helper-arg type errors: confirm the\n `scx_bpf_*` signature against the current repo headers, not external snippets;\n these APIs are kernel-version-specific (see \"BPF Safety Rules\").\n\n## Implementation Discipline\n\n- Keep the scheduler buildable at each step; when adding advanced policy, make\n the first version verifier-friendly and measurable before adding extra polish.\n- Preserve existing shutdown, `UserExitInfo`, restart, and stats-server plumbing\n unless the policy has a concrete reason to diverge.\n- Keep policy-specific names, docs, CLI flags, and stats internally consistent.\n- Avoid broad Rust or BPF refactors while implementing scheduling behavior.\n- Update docs and validation commands in the final response so the next agent or\n maintainer can reproduce the result.\n";Expand description
The resource markdown files are embedded at compile time so prompt edits stay close to the domain text instead of the Rust control flow.