How to Integrate Custom Code or Models with learn-claude-code

You can integrate custom code or models with learn-claude-code by registering new Python callables in the TOOL_HANDLERS dispatch map and updating the TOOLS schema list, or by replacing the global client variable with your preferred LLM SDK.

The shareAI-lab/learn-claude-code repository is built as a set of tiny, interchangeable Python modules centered around a single entry point: the agent_loop(messages: list) function. All capabilities—including shell commands, file I/O, and background tasks—are provided through tools registered in a central dispatch map, while the language model client is instantiated once at module import time and referenced globally. This design allows you to extend functionality or swap the underlying LLM by touching only two integration points without breaking the core execution flow.

Understanding the Modular Architecture

The framework separates concerns into three layers that remain stable regardless of your customizations:

  • agent_loop in agents/s01_agent_loop.py – The invariant core loop that processes messages and dispatches tool calls. It consumes the response object’s content and stop_reason fields generically, so it works with any LLM that returns a compatible structure.
  • TOOL_HANDLERS in agents/s02_tool_use.py – A Python dictionary mapping tool names to callables. New capabilities are added here (lines 93-101) without touching the loop logic.
  • Global client in agents/s_full.py – The LLM client instance created at import time (lines 48-58). Every agent file references this global variable, making model swaps a single-line change.

Because agent_loop never changes, adding new behavior or swapping models never breaks the existing flow.

Adding Custom Tools

To integrate your own code, implement a Python callable and register it in two places: the dispatch map and the schema list.

Step 1: Implement the Tool Logic

Create a safe, side-effect-free function in a new file or inline. The following example implements a secure arithmetic evaluator called calc:


# agents/custom_tools.py

import ast
import operator

_OPS = {
    ast.Add: operator.add,
    ast.Sub: operator.sub,
    ast.Mult: operator.mul,
    ast.Div: operator.truediv,
    ast.Pow: operator.pow,
}

def safe_eval(expr: str) -> str:
    """Evaluate a simple arithmetic expression without side-effects."""
    node = ast.parse(expr, mode="eval").body

    def _eval(node):
        if isinstance(node, ast.Num):
            return node.n
        if isinstance(node, ast.BinOp):
            left = _eval(node.left)
            right = _eval(node.right)
            op = _OPS[type(node.op)]
            return op(left, right)
        raise ValueError("Unsupported expression")

    try:
        return str(_eval(node))
    except Exception as e:
        return f"Error: {e}"

Step 2: Register in TOOL_HANDLERS

Import your function into the main agent file and add it to the dispatch map. In agents/s_full.py (or any sXX_*.py you run), update the TOOL_HANDLERS dictionary:

from agents.custom_tools import safe_eval

# Extend the existing dispatch map

TOOL_HANDLERS.update({
    "calc": lambda **kw: safe_eval(kw["expr"]),
})

Step 3: Update the TOOLS Schema

Add a JSON schema entry so the LLM knows how to call your tool. In the same file, append to the TOOLS list:

TOOLS.append({
    "name": "calc",
    "description": "Evaluate a simple arithmetic expression safely.",
    "input_schema": {
        "type": "object",
        "properties": {
            "expr": {"type": "string", "description": "e.g. '3 * (7 + 2)'"}
        },
        "required": ["expr"]
    },
})

Now you can invoke the agent with /calc 3 * (7 + 2) and the agent_loop will automatically dispatch to your safe_eval function.

Swapping the Language Model

To integrate your own models—whether from OpenAI, Ollama, or a self-hosted endpoint—replace the Anthropic client construction with your own SDK.

Using OpenAI Models

Install the SDK (pip install openai) and modify agents/s_full.py to override the global client and adjust the call signature:

from openai import OpenAI
import os

client = OpenAI(api_key=os.getenv("OPENAI_API_KEY"))
MODEL = "gpt-4o-mini"

def _create_message(**kwargs):
    return client.chat.completions.create(**kwargs)

# Inside agent_loop, replace the Anthropic call with:

response = _create_message(
    model=MODEL,
    messages=messages,
    tools=TOOLS,
    max_tokens=8000,
)

The rest of the codebase works unchanged because it only accesses response.content and response.stop_reason, which OpenAI’s structure also provides.

Using Self-Hosted Ollama Models

For local inference via Ollama, install the client (pip install ollama) and adapt the request format:

from ollama import Client as OllamaClient
import os
import json

client = OllamaClient(host=os.getenv("OLLAMA_HOST", "http://localhost:11434"))
MODEL = "llama3.2"

def _ollama_chat(messages, tools):
    # Flatten messages and embed tool specs via system prompt

    system = "\n".join([json.dumps(t) for t in tools])
    prompt = "\n".join([m["content"] for m in messages if m["role"] == "user"])
    return client.generate(
        model=MODEL,
        prompt=prompt,
        system=system,
        stream=False
    )

# Replace the Anthropic call inside agent_loop:

response = _ollama_chat(messages=messages, tools=TOOLS)

The exact JSON format depends on your Ollama version, but the principle remains: you only replace the request/response layer while the core agent_loop continues to dispatch tools based on the returned content.

Loading Domain-Specific Knowledge

Beyond code and models, you can inject domain knowledge through the skill system. The agents/s05_skill_loading.py file demonstrates how SkillLoader fetches markdown assets from the skills/*/SKILL.md directory tree. Place your own SKILL.md files in skills/your_domain/ and the agent can load them on-demand via the load_skill tool, effectively integrating your documentation without code changes.

Summary

  • Tool integration requires updating TOOL_HANDLERS and TOOLS in agents/s_full.py (or any session file) with your Python callable and JSON schema.
  • Model integration requires replacing the global client variable and the request logic in agents/s_full.py, keeping the agent_loop unchanged.
  • Knowledge integration utilizes the skills/ directory and SkillLoader shown in agents/s05_skill_loading.py to inject domain-specific documentation.
  • The core agent_loop in agents/s01_agent_loop.py remains invariant, ensuring your extensions never break the execution flow.

Frequently Asked Questions

Can I add multiple custom tools to learn-claude-code?

Yes. Simply add multiple entries to TOOL_HANDLERS and append multiple schemas to the TOOLS list in agents/s_full.py. Each tool is independent, and the agent_loop dispatches based on the tool name returned by the LLM.

Do I need to modify the core agent loop to integrate new models?

No. The agent_loop function in agents/s01_agent_loop.py is designed to remain unchanged. You only replace the global client variable and the specific API call that generates the response object, as long as the replacement provides content and stop_reason fields.

What format does the TOOLS schema require?

The TOOLS list expects JSON Schema objects with name, description, and input_schema keys, following the Anthropic tool use specification. The input_schema must define the tool’s arguments as an object type with properties and required fields so the LLM can generate valid calls.

Can I use local models like Llama or Mistral with learn-claude-code?

Yes. As long as your local model is served via an HTTP API (such as Ollama, llama.cpp, or vLLM), you can replace the client initialization in agents/s_full.py with your SDK of choice and map the request/response formats accordingly. The tool dispatch system is agnostic to the specific LLM provider.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →