Skill Composition and Reuse¶
Why do we need composition?¶
If every skill is a focused capability, a real user goal will often require several skills to work together.
For example:
"Review this broken PR, fix the test and update the documentation."
This contains several responsibilities:
PR Review Skill
Test Failure Analysis Skill
Implementation Skill
Documentation Update Skill
The question is:
How do we combine them without creating a new mega-skill?
That is the skill-composition problem.
Atomic vs composite capability¶
An atomic skill has one well-bounded task-level responsibility.
For example:
Analyze Test Failure
A composite capability can use several skills:
Resolve Failing Pull Request
├── review_pull_request
├── analyze_test_failure
├── propose_code_change
└── verify_tests
The composite capability does not necessarily need to be another “skill”. It can be:
- a workflow,
- an orchestrator service,
- an agentic loop,
- a planner/executor system.
This is an important boundary.
When should one skill call another?¶
A direct skill-to-skill dependency can be valid when the dependency is:
- stable,
- one-directional,
- exposed through a narrow contract,
- a natural part of the parent capability.
For example:
Release Notes Skill
↓
Change Summary Skill
If the release-notes skill always works from a structured change summary, this can be a reasonable dependency.
But if the call graph is dynamic:
Skill A may call B or C
B may call D
D may call A
we are dealing with runtime orchestration rather than simple skill dependency.
Prefer orchestration over hidden nesting¶
Weak:
User
↓
Skill A
↓ hidden call
Skill B
↓ hidden call
Skill C
The caller cannot see:
- what ran,
- where it failed,
- token/tool cost,
- required permissions,
- how retry should work.
Better:
Orchestrator
├── Skill A
├── Skill B
└── Skill C
where the execution path is explicit.
Composition should be observable and controllable rather than hidden inside prompt behavior.
Sequential composition¶
The simplest pattern:
Skill A output
↓
validation / mapping
↓
Skill B input
↓
Skill B output
Example:
PR Review Skill
↓
structured findings
↓
Fix Planning Skill
Typed contracts are especially valuable here.
Weak chain:
A: "There may be something wrong around retries..."
↓
B re-interprets free text
Better:
{
"finding_type": "NON_IDEMPOTENT_RETRY",
"severity": "HIGH",
"location": "RetryService.java:87",
"evidence": "charge() is retried without an idempotency key"
}
Parallel composition / fan-out¶
A task may be analyzed from independent perspectives:
PR diff
├── Security Review Skill
├── Architecture Review Skill
└── Test Coverage Review Skill
↓
Aggregator
This works when subtasks are genuinely independent.
Benefits:
- parallelizable,
- independently evaluable,
- specialized skills.
Costs:
- higher cost,
- conflict reconciliation,
- duplicate findings must be merged.
Aggregator pattern¶
An aggregator handles:
multiple structured results
↓
merge / deduplicate / prioritize
↓
final result
It does not necessarily need to be an LLM.
Severity ordering and deduplication by location+rule may be deterministic. Semantic conflicts may require a model or human review.
Shared primitive vs shared skill¶
Not every reusable element is a skill.
For example:
read_repository_file
is a tool/capability primitive.
summarize_code_change
may be a skill.
normalize_severity_enum
is probably a deterministic helper function.
Ask:
Does this require semantic reasoning, or is it a normal software primitive?
Do not turn every reusable function into an agent skill.
Dependency inversion in skill composition¶
A skill does not need to depend directly on a concrete skill implementation.
More coupled:
ReleaseNotesSkill requires ChangeSummarySkillV3
Better:
ReleaseNotesSkill requires capability:
change_summary_provider
The runtime can map:
change_summary_provider
↓
ChangeSummarySkill v3
or in tests:
change_summary_provider
↓
Fixture implementation
This is ordinary dependency inversion.
State ownership in composition¶
With several skills, ask:
Who owns the shared state?
Usually prefer:
Orchestrator / Runtime owns workflow state
Skill receives explicit input
Skill returns explicit output
over:
Skill A silently writes shared memory
Skill B silently reads it
Explicit state flow is easier to debug.
WorkflowState
↓ ↑
Skill A result A
↓
WorkflowState updated
↓
Skill B
Error propagation¶
If Skill A fails, Skill B should not automatically run.
For example:
Fetch Current Deployment Skill
↓ TOOL_UNAVAILABLE
Risk Analysis Skill
Risk analysis may be invalid without current deployment data.
Workflow policy can decide:
TOOL_UNAVAILABLE → stop
PARTIAL_RESULT → continue with warning
INSUFFICIENT_CONTEXT → request more input
Do not let an implicit nested chain decide this by accident.
Failure isolation¶
In parallel composition:
Security Review → SUCCESS
Architecture Review → SUCCESS
Test Review → TOOL_TIMEOUT
A valid overall outcome may be:
PARTIAL_RESULT
with a report such as:
Test coverage could not be evaluated because CI data was unavailable.
This is better than discarding all results because one optional branch failed.
Workflow vs Agent Loop¶
If the order is known in advance:
A → B → C → D
a workflow is natural.
If the next capability depends on the previous observation:
observe
↓
decide next skill
↓
execute
↓
observe
we are moving toward an agentic loop.
Workflow example:
extract invoice data
→ validate
→ store
Agentic example:
investigate production incident
→ choose which signal to query next based on observations
Do not use an agent loop where a simple workflow is enough.
Example: PR resolution pipeline¶
Pull Request
↓
PR Review Skill
↓
findings[]
↓
Fix Planning Skill
↓
change_plan[]
↓
Implementation Skill
↓
patch
↓
Test Runner tool
↓
Test Failure Analysis Skill if needed
↓
Verification
There are several boundaries here:
- review is a semantic task,
- fix planning is a semantic task,
- implementation is a semantic task,
- test running is a deterministic/tool task,
- failure analysis is a semantic task.
Do not compress all of them into:
CodingSkill.doEverything()
Composition depth¶
Deep nesting can become problematic:
A → B → C → D → E → F
because it increases:
- latency,
- cost,
- failure probability,
- observability requirements,
- context drift.
Use explicit execution budgets and regularly review whether the chain is necessary.
Circular dependencies¶
Avoid uncontrolled cycles such as:
Architecture Review Skill
↓
Refactoring Skill
↓
Architecture Review Skill
without an explicit loop controller.
Circular capability graphs can lead to unbounded execution.
If repetition is required, model it as an agentic loop or workflow retry policy with an explicit stop condition.
Skill-library modularity¶
A good skill library resembles a good modular codebase:
clear responsibilities
explicit interfaces
small dependency graph
few shared primitives
no hidden globals
The goal is not the maximum number of skills.
The goal is useful modularity.
Anti-pattern: mega-skill¶
SoftwareEngineeringSkill
├── requirements
├── architecture
├── implementation
├── review
├── test
├── deploy
└── incident response
This is:
- hard to route,
- permission-heavy,
- hard to evaluate,
- difficult to secure,
- prone to giant prompts.
Anti-pattern: skill-as-function-call fetish¶
Not every 20-line deterministic helper should become a skill.
If the task is:
calculate retry delay
and the formula is known, write code.
Anti-pattern: hidden shared memory¶
Skill A writes "current_plan" to memory
Skill B assumes it exists
This creates temporal coupling.
Prefer explicit input/output contracts.
Anti-pattern: workflow logic inside a skill prompt¶
Weak:
"First call skill A, then B, unless C, then retry D..."
inside a giant system prompt.
Put deterministic control flow in orchestration code where possible.
Takeaways¶
- Skill composition is about reusing focused capabilities, not building new mega-skills.
- Sequential and parallel composition are both useful patterns.
- Execution orchestration should be explicit and observable.
- Typed contracts reduce semantic drift between skills.
- Shared primitives are not necessarily skills; ordinary code and tools still matter.
- Prefer capability dependencies over concrete implementation dependencies.
- Shared state is typically owned by the runtime/workflow, not hidden skill memory.
- Failure policy should belong to the orchestrator rather than implicit nesting.
- Use workflows for known steps; use agentic loops for dynamic next-action decisions.
- Replace circular dependencies with explicit loops and stop conditions.