Chapter 6 — Agent Skills¶
Setup Instructions¶
To ensure you have the required dependencies to run this notebook, you'll need to have our llm-agents-from-scratch framework installed on the running Jupyter kernel. To do this, you can launch this notebook with the following command while within the project's root directory:
uv run --with jupyter jupyter lab
Alternatively, if you just want to use the published version of llm-agents-from-scratch without local development, you can install it from PyPi by uncommenting the cell below.
# Uncomment the line below to install `llm-agents-from-scratch` from PyPi
# !pip install llm-agents-from-scratch
Running an Ollama service¶
To execute the code provided in this notebook, you'll need to have Ollama installed on your local machine and have its LLM hosting service running. To download Ollama, follow the instructions found on this page: https://ollama.com/download. After downloading and installing Ollama, you can start a service by opening a terminal and running the command ollama serve.
The Hailstone Skill¶
The examples in this notebook use a hailstone-sequence skill that instructs the agent to compute a full Hailstone (Collatz) sequence using a next_number tool.
Important: Run this notebook from within the examples/ directory so the skill is discovered as a project-scoped skill.
The skill is located at: examples/.agents/skills/stop-at-one/SKILL.md
Note on obfuscation: The skill is intentionally named
stop-at-oneand contains no mention of "Hailstone" or "Collatz". This prevents the LLM from relying on its parametric knowledge of the Collatz conjecture to simulate the sequence internally — forcing it to actually call thenext_numbertool at each step as the skill instructs.
Examples¶
Example 1: Skill Discovery¶
from llm_agents_from_scratch import LLMAgent
from llm_agents_from_scratch.data_structures import Task
from llm_agents_from_scratch.llms import OllamaLLM
llm = OllamaLLM(model="qwen3:14b")
agent = LLMAgent(llm=llm)
task = Task(instruction="Compute the stop-at-one sequence for 8.")
handler = LLMAgent.TaskHandler(llm_agent=agent, task=task)
print(handler.skills)
print(handler.skills["stop-at-one"].frontmatter)
print(handler.skills["stop-at-one"].scope)
{'stop-at-one': <llm_agents_from_scratch.skills.skill.Skill object at 0x79dcb4eaaa50>}
name='stop-at-one' description='Compute a sequence for a given starting number by repeatedly calling the next_number tool until the value 1 is reached.' license=None compatibility=None metadata=None allowed_tools=None
SkillScope.PROJECT
Example 2: UseSkillTool Activation Result¶
from llm_agents_from_scratch.data_structures import ToolCall
from llm_agents_from_scratch.skills.tools import UseSkillTool
tool = UseSkillTool(skills=handler.skills)
tool_call = ToolCall(
tool_name="from_scratch__use_skill",
arguments={"name": "stop-at-one"},
)
result = tool(tool_call=tool_call)
print(result.content)
<skill_content name="stop-at-one"> # Stop At One Compute a full sequence from a starting number down to 1 using the `next_number` tool. ## Arguments The user must provide a **starting number** (a positive integer greater than 1). If no starting number is given, ask the user before proceeding. ## Steps ### 1. Initialize Set the current number `x` to the starting number provided by the user. Begin tracking the sequence as a list: `[x]`. ### 2. Call the tool Call `next_number` with the current value of `x`. ``` next_number(x=<current_number>) ``` **STOP and WAIT** for the tool result before continuing. ### 3. Record the result Append the returned value to the sequence list. Set `x` to the returned value. ### 4. Check termination - If `x == 1`, proceed to Step 5. - Otherwise, go back to Step 2. **Important rules:** - NEVER fabricate or simulate tool call results. - NEVER make multiple tool calls in a single response. - ALWAYS wait for the actual tool response before deciding next steps. ### 5. Report the result Print the complete sequence from start to finish, for example: ``` 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1 ``` Also report: - **Starting number** - **Total steps taken** (number of tool calls made) - **Maximum value reached** in the sequence Skill directory: /home/nerdai/Projects/llm-agents-from-scratch/examples/.agents/skills/stop-at-one </skill_content>
Example 3: Model-driven Skill Activation¶
import logging
from llm_agents_from_scratch.logger import enable_console_logging
enable_console_logging(logging.INFO)
from llm_agents_from_scratch import LLMAgent
from llm_agents_from_scratch.data_structures import Task
from llm_agents_from_scratch.llms import OllamaLLM
from llm_agents_from_scratch.tools.simple_function import SimpleFunctionTool
def next_number(x: int) -> int:
if x % 2 == 0:
return x // 2
return 3 * x + 1
next_number_tool = SimpleFunctionTool(func=next_number)
llm = OllamaLLM(model="qwen3:14b", think=False)
agent = LLMAgent(llm=llm, tools=[next_number_tool])
task = Task(instruction="Compute the stop-at-one sequence for 8.")
result = await agent.run(task)
INFO (llm_agents_fs.LLMAgent) : 🚀 Starting task: Compute the stop-at-one sequence for 8. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Compute the stop-at-one sequence for 8. INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to call the stop-at-one skill with the starting number 8. INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the stop-at-one skill with the starting number 8. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the stop-at-one skill with the starting number 8. INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: from_scratch__use_skill INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: <skill_content name="stop-at-one"> # Stop At One Compute a full sequence from a starting number down to 1 using the `next_num...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: I need to call the next_number tool with the starting number 8. INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the next_number tool with the starting number 8. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the next_number tool with the starting number 8. INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: next_number INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: 4 INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: The next number in the sequence after 8 is 4. I will continue the process by calling the next_number tool with the current number 4. INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the next_number tool with the current number 4. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the next_number tool with the current number 4. INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: next_number INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: 2 INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: The next number in the sequence after 4 is 2. I will continue the process by calling the next_number tool with the current number 2. INFO (llm_agents_fs.TaskHandler) : 🧠 New Step: Call the next_number tool with the current number 2. INFO (llm_agents_fs.TaskHandler) : ⚙️ Processing Step: Call the next_number tool with the current number 2. INFO (llm_agents_fs.TaskHandler) : 🛠️ Executing Tool Call: next_number INFO (llm_agents_fs.TaskHandler) : ✅ Successful Tool Call: 1 INFO (llm_agents_fs.TaskHandler) : ✅ Step Result: The next number in the sequence after 2 is 1. Now that we have reached 1, the sequence is complete. Here is the full sequence from the ...[TRUNCATED] INFO (llm_agents_fs.TaskHandler) : No new step required. INFO (llm_agents_fs.LLMAgent) : 🏁 Task completed: The next number in the sequence after 2 is 1. Now that we have reached 1, the sequence is complete. Here is the full sequence from t...[TRUNCATED]
print(result)
The next number in the sequence after 2 is 1. Now that we have reached 1, the sequence is complete. Here is the full sequence from the starting number 8: **Sequence:** 8 → 4 → 2 → 1 **Starting number:** 8 **Total steps taken:** 3 **Maximum value reached:** 8