final class SkillsMiddleware(val sources: Vector[VPath], val backend: FileBackend, val maxSkills: Int, val maxDescriptionChars: Int, val maxSectionTokens: Int, val maxScanned: Int, val maxCachedRuns: Int, val id: MiddlewareId) extends AgentMiddleware
Agent Skills for an agent (design ยง4.35): finds the skills under sources through backend - each folder <source>/<name>/ holding a SKILL.md - and lists each one's name and description in one cacheable prompt section. The model loads a skill when a task matches it, reading its SKILL.md and other files with read_file, so give the agent file tools over the same backend (or the composite it is mounted in): the paths the section shows are the backend's.
When. The skills are read on a run's first model call, from that step's snapshot, and kept for the run (a bounded cache, maxCachedRuns runs). A skill added or edited during a run shows from the next run. A run recovered or resumed reads again: a state backend's skills come back with the checkpoint, while a store's or a host's may have changed in between, and the recovered run sees them as they are then.
Order. Sources in order, then folders in path order; a later source's skill replaces an earlier one of the same name, so list the user's skills before the project's.
Bounds. At most maxSkills skills are listed, each description cut at maxDescriptionChars code points, the section at most maxSectionTokens estimated tokens; at most maxScanned folders are examined across all sources, and of each SKILL.md only its frontmatter (at most Skill.MaxFrontmatterChars characters).
Problems. A missing source, and a folder without SKILL.md, are skipped. A skill that is malformed, not listed, shadowed or unreadable is skipped and named, by path, in a durable HarnessEvents.DocumentsReported committed with the run's first model step; it never fails the run. A backend error that is not a refusal fails the step, which recover runs again.
allowed-tools is parsed onto Skill.allowedTools and not enforced. The middleware writes nothing: its writes are the backend's keys, so a state backend's files are registered with the graph even with no file tool.
Content for the system prompt of each model request: collected before every model step from the committed state, in stack order, and never stored. Each middleware bounds its own; the agent bounds them all together (maxPromptSectionTokens). Left fails the run.
Content for the system prompt of each model request: collected before every model step from the committed state, in stack order, and never stored. Each middleware bounds its own; the agent bounds them all together (maxPromptSectionTokens). Left fails the run.
The state keys this middleware's hooks may write: an update from beforeAgent, wrapModelCall or afterAgent, or one wrapToolCall adds to a Success. A hook's write to any other key fails the run.
The state keys this middleware's hooks may write: an update from beforeAgent, wrapModelCall or afterAgent, or one wrapToolCall adds to a Success. A hook's write to any other key fails the run.
Sees the run's input before anything else runs: returns it, possibly changed, with an update to this middleware's writes that the turn's first model call sees, or Left to block the run.
Sees the run's input before anything else runs: returns it, possibly changed, with an update to this middleware's writes that the turn's first model call sees, or Left to block the run.
Where this middleware must run relative to other, when both are present; None, the default, when it does not matter. A requirement is checked, never arranged: MiddlewareStack.of - so AgentBuilder.build() - refuses a stack in which this middleware does not run outside other, and a Placement.OutsideAtBoundary is checked across an agent family too. Register the two in the right order, or name other in runsBefore. Name other by its type: ids are configurable.
Where this middleware must run relative to other, when both are present; None, the default, when it does not matter. A requirement is checked, never arranged: MiddlewareStack.of - so AgentBuilder.build() - refuses a stack in which this middleware does not run outside other, and a Placement.OutsideAtBoundary is checked across an agent family too. Register the two in the right order, or name other in runsBefore. Name other by its type: ids are configurable.
Wraps one model call. A wrapper may rewrite the request (inject a note, filter tools), call next more than once (retry, fallback), transform its response, add an update to its writes (ModelResponse.addUpdate), or return Left, which fails the run. Only an AgentMiddleware.Asking wrapper suspends, by asking its question. Every call next makes is counted in the thread's usage, whichever response the wrapper returns.
Wraps one model call. A wrapper may rewrite the request (inject a note, filter tools), call next more than once (retry, fallback), transform its response, add an update to its writes (ModelResponse.addUpdate), or return Left, which fails the run. Only an AgentMiddleware.Asking wrapper suspends, by asking its question. Every call next makes is counted in the thread's usage, whichever response the wrapper returns.
Filtering ModelRequest.tools shapes what the model is offered and is not a permission control: a tool the model calls anyway still runs through wrapToolCall, where denial belongs.
Wraps one tool call, after its arguments were validated. A wrapper denies with ToolOutcome.Error("Denied: ..."), asks for approval with NeedsApproval(reason) (unless context.approved), fails the run with Fatal, and short-circuits by not calling next. It may call next more than once (retry) and transform the outcome next returns.
Wraps one tool call, after its arguments were validated. A wrapper denies with ToolOutcome.Error("Denied: ..."), asks for approval with NeedsApproval(reason) (unless context.approved), fails the run with Fatal, and short-circuits by not calling next. It may call next more than once (retry) and transform the outcome next returns.