Skip to content

fangorn-network/fangorn-agent

Repository files navigation

Fangorn Agent

Fangorn agent of agentic tool usage and development.

Note: For the PL Network Hackathon, Ollama has been pushed aside due to the complex nature of the MCP tool usage and high amount of potential tokens generated by queries

About

The Fangorn Agent is an experiment in personal agent development. Currently, the top models for agent development are ChatGPT and Claude. However, these models are extremely expensive to run and do not really fit into the "personal" portion of a personal agent. Therefore, the Fangorn Agent is first and foremost meant to be able to run on consumer grade hardware.

We are also aiming to create a personal agent that can only do what you allow it to do. This is simple via the use of tools versus the use of skills (although skill support is provided by LangChain as well). From the perspective of the agent, it has access to many different black boxes that allow it to accomplish tasks without revealing how the tasks are completed or if sensitive information was involved.

To learn more about the initial development and the context in which it was built, please refer to our HackMD article discussing our Arbitrum Open house project.

Stack

  • LangChain
  • Ollama
  • Typescript
  • Docker

Computer Spec information:

This agent is intended to be run on consumer grade hardware, but even so the current qwen3.5:9b model is somewhat heavy (~8GB of VRAM if using an NVIDIA GPU). These are the specs of a computer that can run the agent:

  • Form factor: Full Tower
  • OS: Ubuntu 24.04.3 LTS
    • Kernel Version: Linux 6.17.0-14-generic
  • GPU: NVIDIA GeForce RTX™ 2080 Ti
  • CPU: AMD Ryzen™ 9 9900X × 24
  • RAM: 32 GB DDR5
  • Storage: Samsung 970 EVO NVMe SSD 500 GB

Also note that the qwen3.5:4b model also reliably executes the available tool flows and only uses ~6GB of VRAM.

Pre-reqs for Ollama:

  1. Have Docker installed
  2. Install the Ollama docker image
    • If you have an NVIDIA GPU you would like to use, note that there are specific instructions you must follow to allow for it to be used in a Docker environment.

Pre-reqs for Claude (Always use Claude for Fangorn Explorer/Fangorn MCP interactions):

  1. Obtain an API key and credits from console.anthropic.com
  2. Include API info in .env

Pre-reqs

  1. Make run_agent.sh and run_web.sh executable chmod +x run_agent.sh and chmod +x run_web.sh.
  2. (Ollama only) Run the ollama container then download the qwen3.5:9b (or qwen3.5:4b) model docker exec -it ollama ollama pull qwen3.5:9b.
  3. Ensure you have pnpm installed
  4. Run pnpm i at the root of the agent project
  5. Run pnpm i at the root of the web-app directory
  6. Run cp env.example .env and fill in the information
  7. (OPTIONAL) Ensure you have OAuth2.0 tokens created for the gmail address you wish to use and USE_GMAIL=true

To run

  1. Run ./run_web.sh at the root of the project if you wish to interact via a web browser (uses default localhost:3000 for UI)
  2. Run ./run_agent.sh at the root of the project if you wish to interact via the command line (Not recommended for rich interactions)

Agent Components

Fangorn Explorer

"Making discovery more human by making it agentic"

Fangorn Explorer is a browser based chat that combines The Graph's Subgraphs, reactive UI components, and the Fangorn Agent to enable data discovery in a brand new way. Users can dynamically discover content through the use of natural language, explore data cards related to their interests, and obtain useful summaries and recommendations from the agent. Thanks to the use of schemas, the LLM can easily infer the contents of the subgraphs and what type of data can be retrieved from them simply by looking at the contents of the schemas. Here is an example of how the LLM's summary:

Summary:

29 total schemas across 2 unique owners
The vast majority are music schemas with fields: artist, audio, title
A few unique schemas stand out:
fangorn.test.music.v0 & fangorn.test.place.v0 — use fields: address, description, name
test.fangorn.music.v0 — includes an additional genre field
pl-genesis.fangorn.music — a named music schema without a version timestamp
Would you like to explore any of these schemas in more detail?

The UI is completely dynamic and combines elements that are familiar from more traditional UI experiences. There is a form of display for every primitive element that may come from querying strucutured data. The explorer also includes the ability for the user to easily enquire about specific manifests, schemas,and files. Reply funcionality is also implemented that allows for conversations to stay related to data of interest. Finally, not only does the browser enable discovery of content, but it also allows a human initiated then out of the loop purchasing experience. The user simply finds the file they wish to purchase and asks the LLM to purchase it for them. Everything else is handled completely on the user's behalf thanks to the fangornFetch tool.

The explorer offers a next generation decentralized web experience that offers a streamlined and responsive web 2.0 feel.

index.ts

This is the entry point for chat loop. Here, the agent is created and the user interaction loop begins.

FangornAgent.ts

This is the agent and where the agent loop runs, via invokeAgent(), and the Toolbay is initialized.

toolbay.ts

This is where tools are created, tool invocation occurs, and hotswapping tools happens.

types.ts

This contains two interfaces and a factory function:

  1. Toolboxes: This interface enables tools to be grouped under a broader category. A toolbox exposes two functions:
    • getTools(): this returns all of the tools that are in the toolbox.
    • getToolboxAsTool(): this returns the toolbox as a callable tool itself. This primes the model to re-plan when a tool hotswap is going to occur. It also enables us to perform the hotswap itself.
  2. AsyncFactory<T>: This interface ensures that Toolboxes implement a static init function. This allows for tools with asynchronous dependencies to be created (see x402fToolbox.ts)
  3. initializeToolbox(): This function is used by the toolbay to create the toolboxes and their tools.

Toolboxes

Why a Toolbox

When building the intial agent for the Arbitrum hackathon, it was noted that when a smaller LLM receives too many tools, it seems to get "confused" and "forgets" how to call tools. One solution we are exploring is "compressing" the tool context by introducing Toolboxes.

What is a Toolbox

Simply put, a Toolbox allows for lazy loading of tools by giving a brief summary of what the tools do.

How does it work

The Toolbox and ToolBay work in conjunction with each other to enable "hotswapping" tools. You can see that we do not use the typical createAgent function offered by LangChain, but instead use the model directly. This is because once an agent is created via createAgent, there is no way to hotswap tools outside of re-creating the agent. However, the models do expose a function called bindTools which allows for the available tools to be updated for a model directly. We, therefore, expose the agent loop and check if the agent intends to use tools.

If tool usage is intended, we intercept the tool call and check if it is calling a Toolbox. If it is, we update the currentTools field in the ToolBay with the tools within the specified toolbox, and then allow the Toolbox "tool" to be called directly by the agent. The Toolbox tool simply returns a response to the agent to let it know that it has new tools available and that it should re-plan with these new tools. When the agent event loop goes back to the top, we check if the toolbay is marked as "dirty" (new tools are available), if it is we re-bind the tools to the model.

fangornToolbox

This toolbox offers the necessary tools to complete the x402f protocol's flow.

Here is the "tool" that is returned by the getToolboxAsTool function:

  public getToolboxAsTool(): DynamicStructuredTool {
    const fangornAgentToolboxTool = tool(
      async () => {
        console.log("console.log - agent called fangornAgentToolboxTool tool");

        return JSON.stringify({
          status: 200,
          statusText: "OK",
          result:
            "x402Fangorn tools are now available. You now have access to: fangorn_fetch. Re-plan and use them to complete the task.",
        });
      },
      {
        name: this.name,
        description:
          "Activates the Fangorn toolbox, which provides tools for purchasing and decrypting files. Call this whenever the user wants to buy or decrypt a resource. Once called, you will gain access to the fangorn_fetch tool.",
        schema: z.object({}),
      },
    );
    return fangornAgentToolboxTool;
  }

You can see that it doesn't actually do anything other than let the agent know that it has new tools. By the time the agent has received this response, the magic really already occurred because the tools have already been updated, the toolbay has been marked as dirty, and agent has been prompted to start the agent loop from the beginning with the dirty toolbay (and thus new tools).

Tools

  1. fangornFetch(owner, schemaName, tag): This tool allows the agent to use the Fangorn x402 middleware in order to fulfill the x402f requirements.

mcpToolbox

This toolbox integrates MCP functionality into the toolbox pattern. What tools are available depends on which MCP server you are connected to.

agent0Toolbox (currently non-functional)

This toolbox provides the ability to discover agents registered on-chain.

Tools

  1. searchAgents(agentName): This tool is used by the agent to find other agents by their human readable name. It currently assumes that the human readable name is unique. The human is responsible for specifying the target agent's human readable name in their query.
  2. getAgentCard(a2aEndpoint): This allows the agent to retrieve the target agent's agent card as advertised in the a2a field. It assumes that the a2a field provides the base URL and that the agent card is located at /.well-known/agent-card.json

GmailToolbox

This toolbox is currently just a wrapper around one tool, but will later implement more email functionality.

Tools

  1. sendEmail(recipient, subject, message): This tool allows for an email to be sent to a specific recipient with a subject and message. It uses OAuth2.0 for agent authorization.

searchAgents.ts

Agent lookup on Arbitrum Sepolia via the agent-0 sdk npm run search

run_agent.sh

This runs the LLM within the Docker container, builds and starts the agent, then prompts for user input once everything is ready.

Note: If you have an issue with port 11434 being taken, verify that Ollama (not the container) isn't starting on system startup and taking that port.

Useful commands when using Ollama with Docker

  • Running the container (with GPU support): docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
  • Running the container (CPU only): docker run -d -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama
  • Download model (container already running): docker exec -it ollama ollama pull your-desired-model
  • Models already downloaded to the container: curl http://localhost:11434/api/tags
  • Remove model: docker exec -it ollama ollama rm your-desired-model
  • Currently running models and their memory usage: curl http://localhost:11434/api/ps
  • Stopping the container: docker stop ollama
  • Force stopping the container: docker kill ollama
  • Removing the container: docker rm ollama

Debugging port being taken (Linux)

  • Find what's using the port sudo lsof -i :11434
  • If it is ollama, you can check if it's running as a system service systemctl status ollama
  • If that's it you can stop it with: sudo systemctl stop ollama
  • You can also stop it from being auto-started on boot if you'd like: sudo systemctl disable ollama

If you'd like to see what models are offered by Ollama that support tool calling, you can check here

Known issues

  • Anthropic limiting: There are times (even if you have a sufficient balance) that Anthropic will deny calls to Claude. Switching between claude-opus-4-6 and claude-sonnet-4-6 seems to remedy this issue.
  • /chat endpoint unavailable: If an error occurs during agent startup when running run_web.sh, the NextJs server is not always killed. In this case, when you run ./run_web.sh again, it will start another NextJs server which will occupy the port typically used by the agent. Use ps aux to list all process, and kill any orphaned nextjs instances.

TODOs:

  1. When an agent calls a toolbox, it gets back ALL of the tools in the toolbox. We should investigate a way to minimize the amount of tools returned by a toolbox. One idea may be that the agent requests a specific tool from the toolbox instead of getting them all.
  2. The LLM now runs within a container, but the run_agent.sh script should allow for more options (CPU only, GPU, what model, etc.)
  3. Right now all of our toolboxes are included. We should consider using clack to allow for a user to select what toolboxes they would like to include before starting the agent.
  4. Implement dynamic filtering for all Blocks in the Fangorn Explorer.
  5. Ensure clean handling of failures in run_web.sh

About

An agent for exploring and pushing the boundaries of agentic applications

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors