Agent skills have converged on a folder convention. A skill is a directory: a SKILL.md with frontmatter and prose instructions, plus an optional scripts/ subfolder for helper code. Claude's skills work this way, and so do the open-source agents built in that mould. The agent sees a catalogue of skill names and descriptions in its system prompt, loads the full body when a task matches, and reaches any scripts through the prose.
It's a reasonable design. The agent loads details only when needed, and a folder is easy to package and share. Unify has no skill folders. It has one library of executable functions and another of written guidance. Both are searchable, and an explicit link joins entries that belong together.
What the folder can't express
In the folder layout, the SKILL.md document is what the agent can find. A script is an implementation detail inside that document. When we used this structure, we repeatedly ran into two limits.
Shared prose has nowhere natural to live. A tone rule may apply to email drafting and deck building. In a folder layout, you either paste it into both skills and let the copies drift, or create a separate tone skill and hope the model loads it alongside the task skill. The format cannot directly say that one rule governs several capabilities.
Code is also harder to find because a script is discovered through its skill's description. If a task matches the script but not the surrounding prose, the agent may never see the code. Some skills work around this with a one-line document that says "run the script". In that case the document exists only to make the program retrievable, even though the program is what the agent needs.
Two libraries, linked both ways
In Unify a stored function is a real catalogue entry, with a name, signature, docstring, and the implementation itself, indexed for semantic search. Guidance is a separate entry with a title and freeform prose. The link between them is explicit and many-to-many, so guidance carries function_ids and functions carry the inverse guidance_ids, with foreign keys enforced both ways.

So the tone rule becomes one guidance entry linked to draft_email, build_deck, and post_to_linkedin. Editing it once means every linked function feels the change. A deck-layout procedure links to just the one function it governs. Nothing forces a pairing either, so guidance with an empty function_ids is fine ("prefer the staging database for experiments" attaches to nothing executable), and most functions carry no guidance at all because a good docstring already says what they do.
We tested this with three automations governed by one escalation threshold. The storage reviews created one policy entry and linked all three functions to it. When the threshold changed, those links became the update list. The change cost less than editing three skill-folder prompts, and later runs used about 25 times fewer tokens. OpenClaw still changed its three automations more cheaply because they lived in one small store that its agent could rewrite in a single turn. The links become useful when the affected family is too large or scattered to inspect all at once. The full result is in the benchmarks post.
Retrieval treats the two libraries as peers. The actor's discovery step searches functions and guidance separately, and execute_function runs a function by name with no guidance gate anywhere in the path, so code doesn't need a document wrapped around it to be found or to run.
Whole tasks become functions
After a task finishes, a separate librarian pass reviews what happened. It checks the existing stores for duplicates and decides whether anything should persist. Reusable code that ran successfully can become a function. A workflow rule that the code cannot carry can become guidance linked through function_ids. Most runs store nothing.

The storage prompt says that when the only reusable artifact is one standalone function, the librarian must store that function and not manufacture a wrapper procedure. Prose is stored only when it carries knowledge that the code cannot.
Deterministic code, focused LLM calls
A normal agent alternates between thinking and calling a tool. Every step is another pass through a frontier model. A weekly task may therefore pay for the same forty reasoning steps every week, with another chance to take a different path each time.
A distilled function here is ordinary Python: loops, branches, calls to managed primitives. For the genuinely fuzzy substeps, the sandbox injects a query_llm(...) helper (backed by unillm, so it can hit any provider), and our prompting explicitly pushes stored functions toward this shape:
async def triage_inbox(label: str) -> list[str]:
emails = fetch_unread(label) # deterministic
to_reply = []
for e in emails: # deterministic
c = await query_llm( # focused LLM call
f"Classify for triage.\nSubject: {e.subject}\nBody: {e.body}",
response_format=EmailClassification,
)
if c.needs_reply and c.confidence >= 0.8:
to_reply.append(e.id)
return to_replyThe control flow now does the same thing every time. The model is called only for the judgment step, with a small prompt and a typed response. On the next run, the agent findstriage_inbox and calls it instead of reconstructing the workflow. Two focused model calls replace forty open-ended ones.
We measured that difference with an hourly triage task. Every system classified all 96 inquiries correctly. The stored function used about 645 tokens per run, compared with roughly 21,500 for Hermes and 30,000 for OpenClaw. Their lower setup costs were used up after 62 and 47 runs respectively. In the weekly-report experiment, the stored function used no model calls on repeat runs and never selected the wrong week. The protocol and raw results are in the benchmarks post.
When the distilled code breaks
Distilled code can break when an API changes. A stored entrypoint therefore has a bounded repair loop. When a function throws, a model receives the exception and source, probes the live environment without changing it, and rewrites the function before retrying the same run.
We tested this by renaming an API field halfway through a series. Unify repaired the function for $0.18 and delivered all ten runs. A zero-token script failed silently until a person asked for a fix. OpenClaw adapted without help and delivered nine runs, but it never updated its stored instructions, so every later run paid to rediscover the change. Keeping the function inside the runtime lets the system repair the stored artifact once.
What the folder design gets right
To be fair to it, progressive disclosure is the correct instinct and we do the same thing, searching first and loading bodies on demand. Some of these agents also close the write loop by nudging the model to save a skill after a task goes well, and that instinct is right too. We don't really disagree about whether agents should learn from their own work, just about the shape of what gets learned. A folder of prose with code tucked inside is optimised for handing packs of instructions to an agent, whereas a graph of functions and guidance is optimised for an agent building its own library as it goes.
Folders do share better, since a directory zips and installs anywhere and there are real registries built on that. Our stores are database-backed and scoped instead, shared through team roots rather than downloads. I'll take that trade, but it is one.
A folder system can add an index when one rule needs to govern several skills or a script needs to be found without its wrapper. That index starts to resemble the two libraries, while the folders remain useful as packaging. Whether the distinction matters depends on how much the agent is expected to build and revise its own library.
Where to look
All open at github.com/unifyai/unify:
- The function model, with
guidance_ids:unify/function_manager/types/function.py - The guidance model, with
function_ids:unify/guidance_manager/types/guidance.py - Discovery and storage prompts, and the StorageCheck loop:
unify/actor/code_act_actor.py,unify/actor/prompt_builders.py - The
query_llmsandbox helper:unify/common/reasoning.py,unify/function_manager/execution_env.py


