Skip to content

fromDir

JavaScript · package toolnexus · SPEC §7E · js/src/agents/home.ts

function fromDir(dir: string, opts?: FromDirOptions): Agent
interface FromDirOptions {
does?: string // routing description a delegating model sees; default derived from dir
name?: string // agent name; default the directory's basename
model?: string // model id; default "inherit" (the runtime's llm.model)
tools?: Tool[] // extra tools beyond the memory builtin
memory?: boolean // set false to omit the memory tool (a read-only persona)
}

The directory is the agent. fromDir calls composeSoul on dir to build the system prompt, wires a memoryTool(dir) over the same directory (unless memory: false), and hands both to agent(name, spec). What comes back is a plain agents.Agent.run(), .asTool(), and agents.startAgent all apply exactly as they do to any other agent. Nothing new rides on top: this is composition, not a new runtime concept.

Reach for fromDir any time a persona’s identity lives in files you want to edit outside code — a product-owned SOUL.md, an ops team’s AGENTS.md, notes an agent accumulates for itself in MEMORY.md. The directory becomes the unit of deployment: check it into a repo, mount it into a container, point fromDir at it, and the agent is fully specified without touching TypeScript.

1. The smallest useful call — one soul file, run once

Section titled “1. The smallest useful call — one soul file, run once”
import assert from "node:assert"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { agents } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))
fs.writeFileSync(path.join(dir, "SOUL.md"), "You are Ava, a calm and precise ops assistant.")
const ava = agents.fromDir(dir)
assert.equal(ava.name, path.basename(dir)) // default name = directory basename
assert.equal(ava.spec.uses?.tools?.length, 1) // the memory tool, wired by default
const canned: typeof fetch = async () =>
new Response(JSON.stringify({
choices: [{ message: { content: "Hi, I'm Ava." } }],
usage: { prompt_tokens: 5, completion_tokens: 4, total_tokens: 9 },
}), { status: 200, headers: { "content-type": "application/json" } })
const result = await ava.run("Who are you?", { fetch: canned })
assert.equal(result.status, "done")
assert.equal(result.text, "Hi, I'm Ava.")
console.log("ok:", ava.name, "->", result.text)

2. A read-only persona — memory: false plus your own extra tools

Section titled “2. A read-only persona — memory: false plus your own extra tools”
import assert from "node:assert"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { agents, defineTool } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))
fs.writeFileSync(path.join(dir, "SOUL.md"), "You are Reporter, a read-only status agent.")
const lookup = defineTool({
name: "system_status",
description: "Read the current system status",
inputSchema: { type: "object", properties: {} },
run: () => "all systems nominal",
})
const reporter = agents.fromDir(dir, { name: "reporter", memory: false, tools: [lookup] })
// No memory tool wired: `memory: false` means read-only — only `lookup` is on the toolkit view.
assert.equal(reporter.spec.uses?.tools?.length, 1)
assert.equal(reporter.spec.uses?.tools?.[0].name, "system_status")
console.log("ok:", reporter.name, "tools:", reporter.spec.uses?.tools?.map((t) => t.name))

3. Custom does/model, bridged into a classic run via .asTool()

Section titled “3. Custom does/model, bridged into a classic run via .asTool()”

A fromDir agent is an ordinary Agent.asTool() drops it straight into a classic createClient run’s extraTools, exactly as it does for agent()-declared agents (see agents.Agent example 3).

import assert from "node:assert"
import fs from "node:fs"
import os from "node:os"
import path from "node:path"
import { agents, createClient, createToolkit } from "toolnexus"
const dir = fs.mkdtempSync(path.join(os.tmpdir(), "persona-"))
fs.writeFileSync(path.join(dir, "SOUL.md"), "You are Ava, an ops assistant.")
const ava = agents.fromDir(dir, { does: "ops status persona", name: "ava", model: "m-ava" })
assert.equal(ava.spec.does, "ops status persona")
assert.equal(ava.spec.model, "m-ava")
const avaFetch: typeof fetch = async () =>
new Response(JSON.stringify({
choices: [{ message: { content: "Ava here — all green." } }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}), { status: 200, headers: { "content-type": "application/json" } })
let calls = 0
const outerFetch: typeof fetch = async () => {
calls++
if (calls === 1) {
return new Response(JSON.stringify({
choices: [{ message: { content: null, tool_calls: [{ id: "o1", type: "function", function: { name: "ava", arguments: JSON.stringify({ prompt: "status check" }) } }] } }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}), { status: 200, headers: { "content-type": "application/json" } })
}
return new Response(JSON.stringify({
choices: [{ message: { content: "Relayed: Ava here — all green." } }],
usage: { prompt_tokens: 1, completion_tokens: 1, total_tokens: 2 },
}), { status: 200, headers: { "content-type": "application/json" } })
}
const tk = await createToolkit({ builtins: false, extraTools: [ava.asTool({ fetch: avaFetch })] })
const client = createClient({ baseUrl: "http://mock.local", style: "openai", model: "outer", apiKey: "k", fetch: outerFetch })
const res = await client.run("check on ava", { toolkit: tk })
assert.equal(res.toolCalls[0].name, "ava")
assert.match(res.text, /Relayed: Ava here/)
await tk.close()
console.log("ok:", res.text)
Option Type What it does
does string Routing description a delegating model sees. Default derived from the directory.
name string Agent name. Default the directory’s basename.
model string Model id for this agent’s client. Default "inherit".
tools Tool[] Extra tools alongside the memory builtin.
memory boolean Set false to omit the memory tool — a read-only persona. Default true.
  • composeSoul — Build a persona’s system prompt from its home directory: identity, memory, skills.
  • memoryTool — The opt-in built-in that lets a persona write durable notes to its own home.
  • agents.Agent — What fromDir returns; soul/uses.tools are just spec fields.
  • agents.AgentRuntime — What actually runs the agent underneath .run().