This is the official implementation of the paper FederatedSkill: Federated Learning for Agentic Skill Evolution (arXiv:2606.03143).
Modern LLM agents accumulate skills (executable SKILL.md procedures + supporting scripts) as they solve tasks. When several agents work on the same family of tasks but use different backbone models or CLI scaffolds, naively sharing skills hurts more than it helps — a SKILL.md that works on Claude Code may be unparseable to qwen-code, and an OCR umbrella refined by GLM-5 may not transfer to Kimi K2.5. FederatedSkill is a federated learning setup where each client keeps its own private skill library, and a per-client cloud-side merger combines peer trial trajectories into personalized library updates. We release (1) a 20-family task benchmark with verifier scripts; (2) a federation runner with four reproducible settings (SE baseline, homogeneous federation, heterogeneous-model federation, heterogeneous-model-and-CLI federation); and (3) the cloud-merge agent procedure that decides per round whether to absorb, repair, refactor, or drop a peer's skill from the target client's point of view.
One command does everything (clones SkillFlow upstream, sets up the venv, downloads the benchmark, pre-pulls the container image):
bash setup.shAfter it finishes, activate the venv and you're done:
source .venv/bin/activateWhat setup.sh actually does, in order:
- Installs
uv(Astral's Python package manager) if missing. - Clones SkillFlow upstream into
external/SkillFlow/. The upstream repo ships the benchmark scaffold we depend on (libs/skill_evolution/,libs/harbor_noinstall_agents/,libs/terminus_env/,libs/terminus_agent/, anditerative_shared_skills_runner.py). - Runs
uv syncinside SkillFlow to createexternal/SkillFlow/.venvwith harbor + every dep SkillFlow declares. - Writes a
.pthfile into that venv pointing at the SkillFlow source root, solibs.skill_evolution.patcher,libs.harbor_noinstall_agents.agents, etc. are importable from anywhere — not just from inside the SkillFlow directory. - Installs federate_skill (this repo's
skillfl/skillflow_adapter/) as an editable package into the same venv. Ourpyproject.tomlpins harbor to the exact commit the experiments ran against, which uv reconciles against the version SkillFlow pulled. - Symlinks
.venv → external/SkillFlow/.venvat this repo's root sosource .venv/bin/activateworks the way it does in any other Python project. - Copies
.env.example → .envif missing. - Downloads
test_tasks/from HuggingFace (zhang-ziao/SkillFlow-Task, ~1.6 GB). - Pre-pulls the prebuilt container image used by the per-trial agents and the cloud-skill merger.
Each of those steps can be skipped via env var (see setup.sh --help-equivalent comments at the top of the script): SKIP_IMAGE_PULL=1, SKIP_TASKS_DOWNLOAD=1, etc. Re-running setup.sh is idempotent — it pulls SkillFlow upstream, refreshes the venv, and leaves your .env alone.
If you'd rather install by hand: replicate setup.sh step-by-step. The package layout is:
skillfl/skillflow_adapter/— the federation runner from this paper.external/SkillFlow/— SkillFlow upstream (created bysetup.sh; not committed).test_tasks/— the benchmark dataset (downloaded bysetup.sh; not committed).harbor— installed viauv sync(git pin in ourpyproject.toml). Provides the trial orchestrator (Job,JobConfig,TrialHookEvent) that drives each per-task container.
Optional analysis extras (matplotlib) for the plotting helpers:
uv pip install -e ".[analysis]"cp .env.example .env
Fill in only the providers you plan to use. Supported providers and their env vars:
| Provider | Env vars |
|---|---|
| Dashscope (qwen3.6-plus / glm-5) | DASHSCOPE_KEY |
| Moonshot (kimi-k2.5) | MOONSHOT_KEY |
| Anthropic (claude-code merger) | ANTHROPIC_API_KEY |
.env is sourced by the launcher; the configs reference its variables as $DASHSCOPE_KEY, $MOONSHOT_KEY, etc.
The cloud_skill_merge merger and every worker trial run inside a podman/docker container. Two layers are involved:
- Base image (
skillflow/harbor-cli-base:ubuntu24.04, also aliased asskillevlove/harbor-cli-openhands:ubuntu24.04for compatibility with the HuggingFace dataset's unmodified Dockerfiles). Contains Ubuntu 24.04 + Node.js + claude-code / qwen-code / kimi-cli / codex / gemini CLIs. Built once via SkillFlow'sdocker/harbor-cli-base/build.sh. - Per-task image (
harbor-prebuilt:task-<hash>). Eachtest_tasks/<family>/<task>/environment/Dockerfilebuilds on top of the base image to add task-specific dependencies and copy task input files.
The base image is not on a public registry — setup.sh builds it locally from external/SkillFlow/docker/harbor-cli-base/ on first run (15–30 min, network-heavy). Per-task images build on top in seconds; harbor uses force_build: True (see skillfl/skillflow_adapter/harbor_bridge.py) so the first trial against each task builds its image and subsequent trials reuse the cache.
To rebuild the base image manually:
cd external/SkillFlow/docker/harbor-cli-base
bash build.sh skillflow/harbor-cli-base:ubuntu24.04
podman tag skillflow/harbor-cli-base:ubuntu24.04 skillevlove/harbor-cli-openhands:ubuntu24.04On systems where rootless podman cannot delegate the cpu controller, container start can fail with conmon failed: exit status 1 (the OCI runtime tries to write cpu.max in a cgroup that doesn't have the cpu controller). Two ways to work around it:
- (Preferred) Ask the host admin to add
Delegate=cpu cpuset io memory pidsto/etc/systemd/system/user@.service.d/delegate.confand restartuser@$(id -u).service. - (No-root workaround) Edit the harbor
docker-compose-base.yamlshipped with the SDK to drop thedeploy.resources.limitsblock:This disables in-container CPU/memory limits but lets the trial start.# Before services: main: volumes: [...] deploy: resources: limits: cpus: ${CPUS} memory: ${MEMORY} # After: drop the entire deploy: block
pip show harbortells you the install path; the YAML is under<install>/harbor/environments/docker/.
setup.sh downloads test_tasks/ from HuggingFace (zhang-ziao/SkillFlow-Task) — all 20 families used in the paper (~1.6 GB on disk). To re-fetch by hand:
.venv/bin/hf download zhang-ziao/SkillFlow-Task --repo-type dataset --local-dir test_tasksEach family directory has 8–9 sub-task directories with task.toml, instruction.md, environment/ (the bundle harbor mounts into the container), solution/ (reference), and tests/ (verifier).
Each setting has a one-line wrapper under scripts/ that reads .env, activates the venv, and dispatches to the right entry point (python -m skillfl.skillflow_adapter.cli for settings 2–4, external/SkillFlow/iterative_shared_skills_runner.py for setting 1). The four configs in configs/ reproduce the paper's four headline settings:
| # | Config | Setting |
|---|---|---|
| 1 | 1_se_qwen.local.yaml |
Self-Evolve baseline. Single qwen3.6-plus worker, no federation. |
| 2 | 2_fed_3glm_cc.local.yaml |
Federation, homogeneous: three GLM-5 workers, all on claude-code. |
| 3 | 3_fed_hetero_cc.local.yaml |
Federation, heterogeneous backbone: qwen / glm / kimi on a shared claude-code CLI. |
| 4 | 4_fed_hetero_mixed_cli.local.yaml |
Federation, heterogeneous backbone + CLI: qwen-code / claude-code / kimi-cli triple. |
The main arguments:
--config Path to YAML config.
--only-family Restrict the run to a single family by name.
--max-parallel-groups N Run N families in parallel (subprocess per family).
Default 10; use 1 for in-process / smoke runs.
Example — setting 3 (heterogeneous CC-CLI), one family for a quick smoke test, via the shell wrapper:
FAMILY=Production-Capacity-Planning bash scripts/run_3_fed_hetero_cc.shExample — same thing via the Python entry point directly (after activating the venv):
source .venv/bin/activate
python -m skillfl.skillflow_adapter.cli \
--config configs/3_fed_hetero_cc.local.yaml \
--only-family Production-Capacity-Planning \
--max-parallel-groups 1Example — setting 1 (SE baseline) on the same family:
FAMILY=Production-Capacity-Planning bash scripts/run_1_se.sh
# or, equivalently:
source .venv/bin/activate
python external/SkillFlow/iterative_shared_skills_runner.py \
--config configs/1_se_qwen.local.yaml \
--max-parallel-groups 1Outputs land under the path configured by runs_root in the YAML (default: jobs/<job_name>/<family>/). Each family directory has:
runs_root/<job_name>/<family>/
├── shard_manifest.json // partition of tasks → worker shards
├── family_summary.json // final rewards + elapsed
├── shared_skills/ // (shared-merger mode) merged library
├── worker_skills/u{i}/ // (unshared mode) per-worker library
├── cloud_skill_merge_sandboxes/u{i}/ // per-round merger workspaces
│ └── library/ // target worker's library post-merge
└── round_<NNN>/
├── round_summary.json
├── worker_<i>/
│ ├── <trial_name>/ // harbor's per-trial output
│ │ ├── agent/ // agent CLI logs + sessions
│ │ ├── verifier/ // sub-test results (ctrf.json)
│ │ └── result.json
│ └── patch.json // per-worker distilled patch
└── merged_for_u{i}/merged_patch.json
family_summary.json carries the headline numbers:
{
"family_name": "Production-Capacity-Planning",
"n_workers": 3,
"n_rounds": 9,
"elapsed_sec": 27184.4,
"reward_stats": {
"n": 27,
"mean": 0.667,
"max": 1.0,
"min": 0.0,
"n_passed": 18
},
"merger_cost_total_usd": 1.42
}For per-round trace, round_<NNN>/round_summary.json shows each worker's (task, reward, exception?) plus the merger's per-target cost.
Each of the four configs sweeps one family per launch. To loop over all 20 families:
for fam in $(ls test_tasks/); do
python -m skillfl.skillflow_adapter.cli \
--config configs/3_fed_hetero_cc.local.yaml \
--only-family "$fam" \
--max-parallel-groups 1
donePer-pipeline tunable env vars: SKILLFL_AGENT_429_RETRIES (default 20), SKILLFL_AGENT_429_BASE_SLEEP (30), SKILLFL_AGENT_429_MAX_SLEEP (600) — control how aggressively the runner retries claude-code 429-exhaustion failures before bailing the family.
federate_skill/ # this repo (~500 KB)
├── README.md
├── setup.sh # one-click installer (clones SkillFlow, downloads tasks)
├── pyproject.toml # our pip dependencies + skillfl/ packages
├── .env.example # API-key template
├── LICENSE # Apache 2.0
├── configs/ # 4 launch configs (one per paper setting)
│ ├── 1_se_qwen.local.yaml
│ ├── 2_fed_3glm_cc.local.yaml
│ ├── 3_fed_hetero_cc.local.yaml
│ └── 4_fed_hetero_mixed_cli.local.yaml
├── scripts/ # one-line shell wrappers (read .env, activate venv)
│ ├── _common.sh
│ ├── run_1_se.sh
│ ├── run_2_fed_3glm_cc.sh
│ ├── run_3_fed_hetero_cc.sh
│ └── run_4_fed_hetero_mixed_cli.sh
└── skillfl/ # the federation adapter (this paper's code)
├── harbor_runner.py # UserSpec dataclass
└── skillflow_adapter/
├── cli.py # federation entry (settings 2-4)
├── runner.py # FedRunner + per-round loop
├── config.py # FedConfig dataclass
├── harbor_bridge.py # harbor trial launcher
├── patcher_bridge.py # per-worker patcher routing
├── llm_client.py # LiteLLM-based call wrapper
├── merge.py # CloudSkillMerge + RewardWeightedFileMerge
├── partitioning.py # task → worker assignment
├── sync_schedule.py # when to flush pending patches
├── worker_trial.py # per-trial result dataclass
├── _single_trial.py # subprocess entry per worker / per task
├── merge_skill/SKILL.md # cloud merger procedure (read by the agent)
└── task_update_skill/SKILL.md # task_memory.md updater procedure
# Created by setup.sh, not committed:
├── external/SkillFlow/ # upstream SkillFlow checkout (libs/ + iter runner)
├── test_tasks/ # 20 benchmark families (downloaded from HuggingFace)
└── .venv → external/SkillFlow/.venv # symlinked venv (harbor + SkillFlow + federate_skill)
If you use this code or benchmark, please cite:
@misc{yang2026federatedskillfederatedlearningagentic,
title={FederatedSkill: Federated Learning for Agentic Skill Evolution},
author={Jingbo Yang and Guanyu Yao and Yang Zhang and Ramana Rao Kompella and Gaowen Liu and Shiyu Chang},
year={2026},
eprint={2606.03143},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/2606.03143},
}Apache 2.0. See LICENSE.