Reference
This reference is organized by exported API surface. Every exported symbol is listed explicitly below for predictable coverage and navigation.
NimbleAgents.NimbleAgentsNimbleAgents.bash_toolNimbleAgents.delete_file_toolNimbleAgents.edit_file_toolNimbleAgents.eval_julia_toolNimbleAgents.fetch_webpage_toolNimbleAgents.find_files_toolNimbleAgents.github_trending_toolNimbleAgents.glob_toolNimbleAgents.grep_toolNimbleAgents.http_get_toolNimbleAgents.http_post_toolNimbleAgents.list_dir_toolNimbleAgents.read_file_toolNimbleAgents.recall_memory_toolNimbleAgents.save_artifact_toolNimbleAgents.save_memory_toolNimbleAgents.write_file_toolNimbleAgents.AbstractMemoryServiceNimbleAgents.AbstractSessionStoreNimbleAgents.AbstractToolNimbleAgents.AgentNimbleAgents.AgentHooksNimbleAgents.ApprovalTimeoutNimbleAgents.ArtifactNimbleAgents.BlockNimbleAgents.CLIArgNimbleAgents.CLIToolNimbleAgents.ContextConfigNimbleAgents.EvalCaseNimbleAgents.EvalReportNimbleAgents.EvalResultNimbleAgents.ExternalAgentToolNimbleAgents.GeminiOpenAISchemaNimbleAgents.GuardrailNimbleAgents.HandoffNimbleAgents.HandoffFilterNimbleAgents.HumanInterruptNimbleAgents.InMemoryMemoryServiceNimbleAgents.InMemorySessionStoreNimbleAgents.JSONSessionStoreNimbleAgents.MCPClientNimbleAgents.MCPHTTPClientNimbleAgents.MCPServerNimbleAgents.MemoryEntryNimbleAgents.ModifyNimbleAgents.NimbleToolNimbleAgents.PassNimbleAgents.RateLimiterNimbleAgents.RetryConfigNimbleAgents.SQLiteMemoryServiceNimbleAgents.SQLiteSessionStoreNimbleAgents.SessionNimbleAgents.SkillNimbleAgents.ToolNimbleAgents.ToolEventNimbleAgents.ToolMessageNimbleAgents.TraceNimbleAgents.TurnEventNimbleAgents.add_memory!NimbleAgents.agent_as_toolNimbleAgents.build_tool_mapNimbleAgents.chat!NimbleAgents.claude_code_toolNimbleAgents.cleanup!NimbleAgents.close!NimbleAgents.codex_toolNimbleAgents.compact!NimbleAgents.connect!NimbleAgents.cost_budgetNimbleAgents.delete_memory!NimbleAgents.discover_skillsNimbleAgents.dispatch_toolNimbleAgents.exact_matchNimbleAgents.fan_outNimbleAgents.fuzzy_matchNimbleAgents.get_model_pricingNimbleAgents.handoff_toolNimbleAgents.latency_budgetNimbleAgents.listNimbleAgents.list_memoriesNimbleAgents.list_toolsNimbleAgents.loadNimbleAgents.load_evalNimbleAgents.load_traceNimbleAgents.loop_pipeline!NimbleAgents.print_evalNimbleAgents.print_traceNimbleAgents.register_artifact!NimbleAgents.remove_model_pricing!NimbleAgents.remove_rate_limit!NimbleAgents.reset!NimbleAgents.resume!NimbleAgents.run!NimbleAgents.run_evalNimbleAgents.run_pipeline!NimbleAgents.save!NimbleAgents.save_evalNimbleAgents.save_traceNimbleAgents.search_memoryNimbleAgents.serveNimbleAgents.set_model_pricing!NimbleAgents.set_rate_limit!NimbleAgents.spawn_subagentsNimbleAgents.store_artifacts_dirNimbleAgents.tool_coverageNimbleAgents.tool_trajectoryNimbleAgents.tools_schemaNimbleAgents.@tool
Module
NimbleAgents.NimbleAgents Module
NimbleAgentsA lightweight Julia framework for building tool-using LLM agents.
sourceCore Types and Runtime
NimbleAgents.@tool Macro
@tool [return_direct=true] function f(args...) ... endDefine a Julia function and automatically register it as a NimbleTool (with name, description, and JSON parameter schema inferred from the function signature and its docstring).
A variable <funcname>_tool is created in the calling scope holding the resulting NimbleTool object.
When return_direct=true, the agent loop short-circuits immediately after this tool executes — its return value becomes the agent's final output without any further LLM call. Useful for lookup tools, cache hits, or any tool whose result is already the definitive answer.
Example — standard tool
@tool function add(x::Int, y::Int)
"Add two integers together."
x + y
endExample — return_direct tool
@tool return_direct=true function lookup_faq(question::String)
"Look up a frequently asked question. Returns a definitive answer."
faq_db[question]
end
# When the agent calls lookup_faq, its result is returned immediately —
# no follow-up LLM call is made.NimbleAgents.AbstractTool Type
AbstractToolAbstract supertype for all tool definitions that can be exposed to the model.
sourceNimbleAgents.Tool Type
NimbleTool(; name, parameters, description, callable, return_direct, strict)A tool with all the fields of Tool plus return_direct::Bool.
When return_direct = true, the agent loop short-circuits immediately after this tool executes — its return value becomes the agent's final output without any further LLM call.
Use @tool (or @tool return_direct=true) to create tools; you rarely need to construct NimbleTool directly.
NimbleAgents.NimbleTool Type
NimbleTool(; name, parameters, description, callable, return_direct, strict)A tool with all the fields of Tool plus return_direct::Bool.
When return_direct = true, the agent loop short-circuits immediately after this tool executes — its return value becomes the agent's final output without any further LLM call.
Use @tool (or @tool return_direct=true) to create tools; you rarely need to construct NimbleTool directly.
NimbleAgents.ToolMessage Type
ToolMessage(; content=nothing, raw="", tool_call_id="", name="", args=nothing)Represents a tool result message in conversation history.
sourceNimbleAgents.build_tool_map Function
build_tool_map(tools) -> Dict{String, Tool}Convert a vector of Tool objects into a name-keyed dict for fast dispatch.
NimbleAgents.tools_schema Function
tools_schema(tools) -> Vector{Dict}Render a vector of Tools into the JSON-serialisable list that OpenAI-compatible APIs expect under the tools key.
NimbleAgents.dispatch_tool Function
dispatch_tool(tool_map, name, args) -> AnyLook up name in tool_map and call the corresponding tool with args (a Dict{Symbol, Any}). Returns the tool's return value, or rethrows on error.
Argument ordering uses the required list from the tool's JSON schema (which reflects the original parameter order), so this is robust to Julia mangling method argument names in test environments.
dispatch_tool(tool_map, msg::ToolMessage) -> AnyConvenience overload that accepts a ToolMessage directly (as returned by NimbleAgents when parsing an LLM response with tool calls).
NimbleAgents.Agent Type
Agent(; name, instructions, tools, model, max_iterations, output_type, hooks)A configured AI agent with a system prompt, a set of tools, and a model.
Fields
name::String: Human-readable name for the agent.instructions::Union{String, Function}: The system prompt — what the agent does and how it behaves. Can be a staticStringor a callable(session, agent) -> Stringfor dynamic prompts (e.g. per-user context, RAG injection, time-aware instructions).tools::Vector{Tool}: Tools the agent can call.model::String: Model identifier (default:"gpt-5.4-mini").max_iterations::Int: Maximum number of LLM calls before the loop stops (default:10).output_type::Union{Type, Nothing}: When set, the final response is parsed into this Julia struct instead of returned as a plainString.api_kwargs::NamedTuple: Extra keyword arguments passed through to every internal LLM call (aitools,aigenerate,aiextract). Use this for model-specific features like OpenAI or Gemini reasoning config (default:NamedTuple()).hooks::AgentHooks: Optional lifecycle callbacks (default: all no-ops).sub_agents::Vector{Agent}: Child agents the LLM can hand off to. Ahandoff_toolis generated automatically for each one — no manual wiring needed.retry::RetryConfig: Exponential-backoff retry policy for LLM API calls (default: 3 retries, 0.5s–60s window). Setretry=RetryConfig(max_retries=0)to disable.context::ContextConfig: Context-window management policy. Whensession.historyexceedscontext.compact_threshold × context.context_windowtokens, older messages are summarised and replaced, keeping the most recentcontext.keep_lastmessages verbatim.skills::Vector{Skill}: Explicitly attached skills. Metadata is injected into the system prompt; full instructions are loaded on demand via the built-inread_skilltool.skill_dirs::Vector{String}: Directories to scan for skill subdirectories at run time. Discovered skills are merged with any explicitly listed inskills.max_tool_output::Int: Global character limit for tool result strings inserted into the conversation (default:0= unlimited). When a tool result exceeds this limit, it is trimmed with head+tail preservation and an informative gap marker. Per-tool limits (NimbleTool.max_output) override this when set.cache::Union{Nothing, Symbol}: Reserved for provider-specific prompt caching strategies. Cached tokens are tracked inTurnEvent.cache_read_tokensandcache_write_tokenswhen the provider returns them.
Example — plain text output
@tool function add(x::Int, y::Int)
"Add two integers."
x + y
end
agent = Agent(
name = "MathBot",
instructions = "You are a helpful assistant that can do arithmetic.",
tools = [add_tool],
)
result = run!(agent, "What is 3 + 4?") # StringExample — with session memory
session = Session(app_name="MyApp", user_id="alice")
run!(agent, "What is 8 + 14?"; session=session)
run!(agent, "Now multiply that by 3"; session=session) # remembers 22NimbleAgents.AgentHooks Type
AgentHooks(; before_llm_call, after_llm_call, should_interrupt, on_tool_call, on_tool_result, on_complete)Optional lifecycle callbacks for an Agent. All fields default to nothing (no-op). Provide a function to observe or log that event.
Callbacks
| Field | Signature | Fired |
|---|---|---|
before_llm_call | (agent, iteration, messages) -> messages | Before each LLM request — can modify the messages vector |
after_llm_call | (agent, iteration, response) | After each LLM response — before any tool executes |
should_interrupt | (tool_name, args) -> Bool | Before each tool executes — return true to pause and require human approval |
on_tool_call | (agent, tool_name, args) | Before each tool is executed (after approval) |
on_tool_result | (agent, tool_name, result) | After each tool returns |
on_complete | (agent, result) | When run! is about to return |
should_interrupt is the recommended way to gate dangerous tools. When it returns true for any pending tool call, the framework collects all flagged calls, throws a HumanInterrupt, and no tools execute. Call resume!(session, response) then re-run run! to continue.
Example — gate dangerous tools
hooks = AgentHooks(
should_interrupt = (name, args) -> name in ["send_email", "delete_file"]
)
agent = Agent(name="Bot", instructions="...", hooks=hooks)
try
run!(agent, "Send an email and delete the log"; session=session)
catch e
e isa HumanInterrupt || rethrow(e)
println(e.message) # "About to call: send_email, delete_file"
resume!(session, readline()) # inject human response
run!(agent, "Send an email and delete the log"; session=session)
endExample — observability only
hooks = AgentHooks(
on_tool_call = (ag, name, args) -> println("calling $name with $args"),
on_tool_result = (ag, name, result) -> println("$name returned $result"),
on_complete = (ag, result) -> println("done: $result"),
)NimbleAgents.RetryConfig Type
RetryConfig(; max_retries, initial_delay, max_delay, multiplier, jitter,
retry_on_status, max_parse_retries)Exponential-backoff retry policy for LLM API calls made inside run!.
Fields
max_retries::Int: Maximum number of retry attempts after the first failure (default:3).initial_delay::Float64: Seconds to wait before the first retry (default:0.5). Consensus across Anthropic SDK and LangGraph; fast enough for transient errors.max_delay::Float64: Maximum seconds to wait between retries (default:60.0). Chosen to match the standard 1-minute rate-limit reset window used by both OpenAI and Anthropic — capping here means later retries will wait long enough to clear a sustained 429 burst without hanging indefinitely.multiplier::Float64: Exponential growth factor (default:2.0). Universal across all reference SDKs (OpenAI, Anthropic, ADK, LangGraph).jitter::Bool: Whentrue, multiplies each delay by a random factor in[0.75, 1.0](Anthropic-style multiplicative jitter). Prevents thundering-herd when many parallel agents retry simultaneously (default:true).retry_on_status::Vector{Int}: HTTP status codes that warrant a retry.408request timeout,429rate limit — always transient500/502/503/504server-side errors — usually transient529Anthropic-specific overload status
4xx errors outside this list (401, 400, 403, 404) are permanent failures and are never retried regardless of this setting.
max_parse_retries::Int: Maximum number of re-prompts whenoutput_typeparsing fails (default:2). On each failure the parse error is fed back to the LLM as a user message so it can correct its response. Set to0to disable.
Example
agent = Agent(
name = "Bot",
instructions = "...",
retry = RetryConfig(max_retries=5, max_delay=120.0),
)NimbleAgents.ContextConfig Type
ContextConfig(; context_window, compact_threshold, keep_last, summary_model)Controls automatic context-window management for long-running sessions.
When session.history grows large enough to risk hitting the model's context limit, NimbleAgents compacts it using a hybrid strategy:
The most recent
keep_lastmessages are always kept verbatim — they carry the immediate context the model needs for the current turn.Everything older is summarised into a single compressed message by the LLM, replacing the raw history.
This mirrors the approach used by Claude Code and Codex: compact at ~80% of the context window, not at 100%, so there is always headroom for the current turn's input and the model's output.
Fields
context_window::Int: Total token capacity of the model (default:400_000). Set to match Claude Opus 4.5/4.6 (400k context, 128k max output).compact_threshold::Float64: Fraction ofcontext_windowat which compaction is triggered (default:0.80). At 80% × 400k = 320k tokens, there is still 80k of headroom — enough for a large current-turn input plus a full output. Claude Code and Codex both use ~80% as their trigger.keep_last::Int: Number of recent messages to preserve verbatim after compaction (default:20). These are never summarised so the agent retains immediate conversational context.summary_model::Union{String,Nothing}: Model used for the summarisation call. Defaults tonothing, which means the agent's own model is used.
Example
# Long-running session with aggressive compaction
session_agent = Agent(
name = "LongBot",
instructions = "...",
context = ContextConfig(keep_last=10),
)NimbleAgents.run! Function
run!(agent, input; session, verbose, on_token, approval_channel, approval_timeout) -> Union{String, Any}Run the agent loop on input.
If
agent.output_typeisnothing→ returns aString.If
agent.output_typeis set → returns an instance of that type.
Arguments
session::Union{Session, Nothing}: Pass aSessionto retain conversation history, key-value state, and an event log across calls.verbose::Bool: Print iteration info (defaulttrue).on_token::Union{Function, Nothing}: When set, the final LLM response is streamed token-by-token.on_token(token::String)is called for each chunk as it arrives. Tool-call rounds are always blocking (streaming + tool calls are not supported by the underlying API). Passon_token = token -> print(token)to stream directly to the terminal.approval_channel::Union{Channel{String}, Nothing}: When set alongsideshould_interrupt, the agent pauses mid-loop waiting for a response on this channel instead of throwingHumanInterrupt. Use this for non-blocking approval flows (web servers, notebooks, GUIs) where the human's response arrives asynchronously from another thread or HTTP handler. Put"approve"to proceed, any other string to redirect, or close the channel to abort. The agent holds all its state while waiting — no re-run needed.approval_timeout::Float64: Seconds to wait for a response onapproval_channelbefore throwing anApprovalTimeouterror (default:300.0).
Example — CLI (blocking, throw-based)
run!(agent, "Write a short poem"; on_token = token -> print(token))Example — async approval (non-blocking, channel-based)
ch = Channel{String}(1)
task = Threads.@spawn run!(agent, input; session=session, approval_channel=ch)
# agent is paused waiting for approval — this thread is free
put!(ch, "approve") # unblocks the agent from anywhere
result = fetch(task)NimbleAgents.HumanInterrupt Type
HumanInterrupt(tool_calls; message)Thrown from after_llm_call to pause the agent loop before any tool executes.
The LLM has produced a plan (tool_calls) but no side-effects have occurred yet. The caller catches this, presents the pending actions to a human, and either:
Approves → calls
resume!(session, "Approved")and re-runsrun!Rejects → calls
resume!(session, "Rejected — do X instead")and re-runsrun!Aborts → discards the session entirely
Fields
tool_calls: The pending tool calls the LLM intended to execute.message::String: Optional context message (default:"Human approval required").
Example
hooks = AgentHooks(
after_llm_call = (agent, iter, msg) -> begin
dangerous = ["delete_file", "send_email", "write_db"]
pending = filter(t -> t.name in dangerous, something(msg.tool_calls, []))
isempty(pending) && return
throw(HumanInterrupt(pending;
message = "About to: " * join([t.name for t in pending], ", ")))
end
)NimbleAgents.ApprovalTimeout Type
ApprovalTimeout(tool_calls, timeout)Thrown when an approval_channel is provided but no response arrives within approval_timeout seconds.
NimbleAgents.resume! Function
resume!(session, human_response)Inject a human approval/rejection into session.history so the agent can continue after a HumanInterrupt. Call run! again after this.
try
run!(agent, input; session=session)
catch e
e isa HumanInterrupt || rethrow(e)
println("Pending: ", e.message)
response = readline()
resume!(session, response)
run!(agent, input; session=session)
endGuardrails and Rate Limits
NimbleAgents.Guardrail Type
Guardrail(; name, check, on=:input)A check that runs at the boundary of the agent loop.
Fields
name::String: Human-readable label shown in verbose output.check::Function:(value::String) -> GuardrailResult. ReturnPass()to continue,Block(reason)to halt, orModify(new_value)to rewrite.on::Symbol: When to run —:input(before the agent loop) or:output(after the agent produces its final response). Default::input.
Examples
# Rule-based input guardrail
no_pii = Guardrail(
name = "no_pii",
on = :input,
check = input -> occursin(r"\d{3}-\d{2}-\d{4}", input) ?
Block("Input contains a Social Security Number.") : Pass(),
)
# Sanitising input guardrail
strip_html = Guardrail(
name = "strip_html",
on = :input,
check = input -> Modify(replace(input, r"<[^>]+>" => "")),
)
# Output guardrail
no_links = Guardrail(
name = "no_links",
on = :output,
check = output -> occursin(r"https?://", output) ?
Block("Response contained external links.") : Pass(),
)
agent = Agent(
name = "SafeBot",
guardrails = [no_pii, strip_html, no_links],
...
)NimbleAgents.Block Type
Block(reason::String)Guardrail result — halt execution and return reason as the agent's response.
NimbleAgents.Modify Type
Modify(value::String)Guardrail result — replace the current input or output with value and continue.
NimbleAgents.RateLimiter Type
RateLimiterToken-bucket rate limiter. Allows up to rate requests per second, with a burst capacity equal to rate.
NimbleAgents.set_rate_limit! Function
set_rate_limit!(model::String, requests_per_second::Real)
set_rate_limit!(::Symbol, requests_per_second::Real)Set a rate limit for a specific model (e.g. "gpt-5.4-mini") or for all models (:default). The limiter allows up to requests_per_second LLM calls per second with short bursts up to that same number.
Example
# Limit gpt-5.4-mini to 10 requests/second
set_rate_limit!("gpt-5.4-mini", 10)
# Limit all models to 20 requests/second (unless overridden per-model)
set_rate_limit!(:default, 20)NimbleAgents.remove_rate_limit! Function
remove_rate_limit!(model::String)
remove_rate_limit!(::Symbol)Remove a previously set rate limit.
sourcePricing and Cost
NimbleAgents.set_model_pricing! Function
set_model_pricing!(model, input_per_million, output_per_million)Register USD pricing for a model.
input_per_million and output_per_million are the cost per 1 million tokens.
Example
set_model_pricing!("gpt-5.4-mini", 0.40, 1.60)
set_model_pricing!("claude-sonnet-4-6", 3.00, 15.00)NimbleAgents.get_model_pricing Function
get_model_pricing(model) -> Union{NamedTuple{(:input,:output)}, Nothing}Look up pricing for a model. Returns nothing if no pricing is registered.
NimbleAgents.remove_model_pricing! Function
remove_model_pricing!(model)Remove a previously registered pricing entry.
sourceNimbleAgents.compact! Function
compact!(session, agent) -> BoolCheck whether session.history is approaching the model's context limit and, if so, compress older messages into a summary while keeping the most recent agent.context.keep_last messages verbatim.
Returns true if compaction was performed, false otherwise.
The hybrid strategy:
Recent messages (
keep_last) are always preserved — they carry the immediate context the model needs.Older messages are replaced by a single LLM-generated summary injected as a
UserMessagetagged[Conversation Summary].
Compaction is triggered when estimated token usage exceeds context_window × compact_threshold (default: 80% of 400k = 320k tokens).
Sessions and Persistence
NimbleAgents.Session Type
Session(; id, app_name, user_id)An in-memory session that persists conversation state across multiple run! calls.
Inspired by Google ADK's Session model. Holds three things:
history— the message log the LLM sees on every turnstate— a free-form key-value store for cross-turn variablesevents— an audit log of every turn (inputs, outputs, tool calls, token usage)
Fields
id::String: Unique session identifier (auto-generated UUID if not provided).app_name::String: Name of the application using this session.user_id::String: Identifier for the user (default"default").history::Vector{AbstractMessage}: Accumulated message history.state::Dict{String,Any}: Cross-turn key-value store.events::Vector{TurnEvent}: Ordered log of every completed turn.created_at::Float64:time()when the session was created.updated_at::Float64:time()when the session was last saved (used for TTL expiry).lock::ReentrantLock: Protectshistoryandeventsfor concurrentfan_out/spawn_subagentscalls.
Example
session = Session(app_name="MathApp", user_id="alice")
run!(agent, "What is 8 + 14?"; session=session)
run!(agent, "Now multiply that by 3"; session=session)
# Inspect history
length(session) # number of messages
session.state["score"] # cross-turn variable set by a tool or hook
session.events[1] # TurnEvent for the first run! callNimbleAgents.TurnEvent Type
TurnEventA record of one complete run! invocation — one "turn" in the conversation.
Fields
agent::String: Name of the agent that handled this turn.model::String: LLM model identifier used for this turn.input::String: The user message for this turn.output::Any: The final response returned byrun!.tool_calls::Vector{ToolEvent}: All tool calls made during this turn, in order.llm_calls::Int: Number of LLM requests made.input_tokens::Int: Total input tokens used across all LLM calls this turn.output_tokens::Int: Total output tokens used across all LLM calls this turn.cache_read_tokens::Int: Tokens read from prompt cache (discounted cost).cache_write_tokens::Int: Tokens written to prompt cache (premium cost on Anthropic).cost::Float64: Estimated USD cost for this turn (cache-adjusted when available).elapsed::Float64: Wall-clock time in seconds for the whole turn.timestamp::Float64:time()whenrun!was called.
NimbleAgents.ToolEvent Type
ToolEventA record of one tool call made during a run! turn.
Fields
name::String: Tool name.args::Dict{Symbol,Any}: Arguments passed to the tool.result::Any: Return value (ornothingif an error occurred).error::Union{String,Nothing}: Error message if the tool threw, otherwisenothing.timestamp::Float64:time()when the tool was called.
NimbleAgents.reset! Function
reset!(session)Clear history, state, and events from the session. Preserves id/app_name/user_id.
sourceNimbleAgents.Artifact Type
Artifact(; session_id, name, type, content_type, path, metadata)A named, typed output produced by an agent during a session.
Fields
id::String: UUID identifying this artifact.session_id::String: Session that produced this artifact.name::String: Human-readable label.type::Symbol::file,:plot,:data, or:text.content_type::String: MIME type ("image/png","text/csv", etc.).path::String: Path to the artifact file.metadata::Dict{String,Any}: Arbitrary extra info (source tool, size, etc.).created_at::Float64:time()when registered.
NimbleAgents.register_artifact! Function
register_artifact!(session, path; name, store) -> ArtifactRegister a file as an artifact in the session. If store is provided, the file is copied into the artifact store directory; otherwise the original path is recorded as-is.
Called automatically by run! for return_artifact=true tools, by eval_julia_tool for saved plots, and by save_artifact_tool.
NimbleAgents.AbstractSessionStore Type
AbstractSessionStoreInterface for session persistence backends. Implement:
save!(store, session)load(store, session_id) -> Union{Session, Nothing}delete!(store, session_id)list(store; app_name, user_id) -> Vector{String}store_artifacts_dir(store) -> String
NimbleAgents.InMemorySessionStore Type
InMemorySessionStore()An in-memory session store. Sessions are kept for the lifetime of the process and lost on restart. Suitable as the default for serve() and for testing.
Artifacts are stored in a temp directory that is also ephemeral.
store = InMemorySessionStore()
session = Session(app_name="MyApp", user_id="alice")
save!(store, session)
load(store, session.id) # → same Session objectNimbleAgents.JSONSessionStore Type
JSONSessionStore(dir)Persist sessions as JSON files in dir. Artifacts are copied into dir/../artifacts/<session_id>/.
store = JSONSessionStore(".nimble/sessions")
save!(store, session)
session = load(store, session_id)NimbleAgents.SQLiteSessionStore Type
SQLiteSessionStore(path; artifacts_dir=nothing)SQLite-backed AbstractSessionStore implementation, loaded via NimbleAgentsSQLiteExt when SQLite.jl and DBInterface.jl are available.
NimbleAgents.save! Function
save!(store, session) -> SessionPersist session into store.
Arguments
store: Session store backend.session::Session: Session to persist.
Returns
Session: The persisted session object.
NimbleAgents.load Function
load(store, session_id) -> Union{Session, Nothing}Load a persisted session by id.
Arguments
store: Session store backend.session_id::String: Session identifier to load.
Returns
Session: Loaded session when found.nothing: If no persisted session exists forsession_id.
NimbleAgents.list Function
list(store; app_name=nothing, user_id=nothing) -> Vector{String}List persisted session ids, optionally filtered by scope.
Arguments
store: Session store backend.app_name::Union{String,Nothing}: Optional application filter.user_id::Union{String,Nothing}: Optional user filter.
Returns
Vector{String}: Matching session ids.
NimbleAgents.store_artifacts_dir Function
store_artifacts_dir(store) -> StringReturn the directory where a session store persists artifact files.
Arguments
store: Session store backend.
Returns
String: Absolute or relative artifacts directory path.
NimbleAgents.cleanup! Function
cleanup!(store; max_age, before) -> IntDelete expired sessions from the store and return the number removed.
Arguments
store: Session store backend.max_age::Real: Delete sessions not updated in the lastmax_ageseconds.before::Float64: Delete sessions withupdated_at < before(Unix timestamp).
Provide exactly one of the two keyword arguments.
Returns
Int: Number of sessions deleted.
Memory
NimbleAgents.AbstractMemoryService Type
AbstractMemoryServiceAbstract type for memory backends. Implementations must define:
add_memory!(service, content; user_id, app_name, metadata, session_id) -> MemoryEntrysearch_memory(service, query; user_id, app_name, top_k) -> Vector{MemoryEntry}delete_memory!(service, id)list_memories(service; user_id, app_name) -> Vector{MemoryEntry}close!(service)
NimbleAgents.InMemoryMemoryService Type
InMemoryMemoryService()In-memory memory backend using keyword search. Good for testing and short-lived applications. All data is lost when the process exits.
sourceNimbleAgents.SQLiteMemoryService Type
SQLiteMemoryService(path)SQLite-backed AbstractMemoryService implementation, loaded via NimbleAgentsSQLiteExt when SQLite.jl and DBInterface.jl are available.
NimbleAgents.MemoryEntry Type
MemoryEntry(; content, user_id, app_name, metadata, source_session_id)A single stored fact in the memory system.
Fields
id::String: Unique identifier (auto-generated UUID).content::String: The fact text.user_id::String: User who owns this memory.app_name::String: Application scope.metadata::Dict{String,Any}: Arbitrary key-value metadata.source_session_id::Union{String,Nothing}: Session that created this memory.created_at::Float64:time()when the memory was created.
NimbleAgents.add_memory! Function
add_memory!(service, content; user_id="default", app_name="NimbleAgents", metadata=Dict{String,Any}(), session_id=nothing) -> MemoryEntryStore a memory entry for a user/application scope.
sourceNimbleAgents.search_memory Function
search_memory(service, query; user_id="default", app_name="NimbleAgents", top_k=5) -> Vector{MemoryEntry}Return the top matching memories for query in the given scope.
NimbleAgents.delete_memory! Function
delete_memory!(service, id)Delete a memory entry by id.
sourceNimbleAgents.list_memories Function
list_memories(service; user_id=nothing, app_name=nothing) -> Vector{MemoryEntry}List memories, optionally filtered by user and/or app.
sourceSkills
NimbleAgents.Skill Type
Skill(; name, description, path)A filesystem-based capability package. Contains a SKILL.md file with instructions that the agent can load on demand.
Use discover_skills(dirs) to auto-discover skills from directories, or construct directly with an explicit path.
Fields
name::String: Skill identifier (from YAML frontmatter).description::String: One-line description used in the agent's system prompt to help it decide when to load this skill.path::String: Absolute path to the skill directory.
NimbleAgents.discover_skills Function
discover_skills(dirs::Vector{String}) -> Vector{Skill}Scan each directory in dirs for skill subdirectories. A valid skill directory must contain a SKILL.md file with name and description frontmatter.
Returns all discovered skills. Silently skips directories with missing or malformed SKILL.md files.
Example
skills = discover_skills([".nimble/skills", joinpath(homedir(), ".nimble", "skills")])Multi-Agent and Handoffs
NimbleAgents.Handoff Type
HandoffA signal returned by an agent indicating that control should transfer to another agent. Use handoff_tool(agent) to create a Tool that an agent can call to trigger the transfer.
Fields
target::Agent: The agent to hand off to.message::String: The message to pass to the target agent (defaults to the current user input if empty).history_filter::HandoffFilter: How to transform conversation history on handoff.
The orchestrator loop in run_pipeline! detects Handoff results and re-runs with the new agent automatically.
NimbleAgents.HandoffFilter Type
HandoffFilterControls how conversation history is transformed when handing off to the next agent. Pass a HandoffFilter to handoff_tool or run_pipeline! to filter the session history before the receiving agent sees it.
Built-in filters (symbols)
:all— pass full history unchanged (default):none— clear history; receiving agent starts fresh:strip_tools— remove all tool-call and tool-result messages:last_n— keep only the last N messages (useHandoffFilter(:last_n, 5))
Custom filter (function)
Pass a function (history::Vector{AbstractMessage}) -> Vector{AbstractMessage} for full control over what the receiving agent sees.
Examples
# Strip tool messages on handoff
handoff_tool(billing; history_filter = HandoffFilter(:strip_tools))
# Keep only last 3 messages
handoff_tool(billing; history_filter = HandoffFilter(:last_n, 3))
# Custom function
handoff_tool(billing; history_filter = HandoffFilter(msgs -> filter(m -> m isa UserMessage, msgs)))NimbleAgents.handoff_tool Function
handoff_tool(target; name, description, history_filter) -> ToolCreate a Tool that, when called by an agent, signals a handoff to target. The LLM passes a message argument containing what to forward to the next agent.
Arguments
target::Agent: The agent to hand off to.name::String: Tool name (default:"handoff_to_$(target.name)").description::String: Tool description.history_filter::HandoffFilter: How to transform conversation history before the receiving agent sees it. Default:HandoffFilter()(pass full history).
Example
billing_agent = Agent(name="Billing", instructions="Handle billing questions.")
support_agent = Agent(
name = "Support",
instructions = "Triage customer requests.",
tools = [
handoff_tool(billing_agent; history_filter=HandoffFilter(:strip_tools)),
],
)NimbleAgents.agent_as_tool Function
agent_as_tool(agent; name, description, session) -> ToolWrap agent as a Tool that a parent (orchestrator) agent can call. The subagent runs a full run! loop for each call and its result is returned as a string back to the orchestrator.
The shared session is threaded through so the subagent's turns appear in the same event log.
Example
math_agent = Agent(name="Math", instructions="Do arithmetic.", tools=[add_tool])
orchestrator = Agent(
name = "Orchestrator",
instructions = "Route requests to specialist agents.",
tools = [agent_as_tool(math_agent)],
)
run!(orchestrator, "What is 3 + 4?")NimbleAgents.run_pipeline! Function
run_pipeline!(agent, input; session, verbose, max_handoffs) -> AnyLike run! but with automatic handoff support. When a tool returns a Handoff, the pipeline transparently re-runs with the target agent and the forwarded message. The loop stops when the active agent returns a plain result (not a Handoff) or max_handoffs is reached.
Arguments
agent::Agent: The starting agent.input::String: The initial user message.session: Optional sharedSessionacross all agents in the pipeline.verbose::Bool: Print handoff transitions (defaulttrue).max_handoffs::Int: Safety cap on the number of handoffs (default10).
Example
result = run_pipeline!(triage_agent, "I need help with my bill";
session=session)NimbleAgents.loop_pipeline! Function
loop_pipeline!(agents, input; stop_when, max_rounds, session, verbose) -> AnyRun agents in round-robin order, feeding each agent's output as the next agent's input, until stop_when returns true or max_rounds is reached.
Each "round" consists of one pass through all agents in order. After every individual agent run, stop_when(agent, result) is checked — if it returns true, the loop ends immediately and that result is returned.
Arguments
agents::Vector{Agent}: Agents to cycle through in order.input::String: The initial user message.stop_when: A function(agent, result) -> Boolthat signals termination. Default: alwaysfalse(loop runs untilmax_rounds).max_rounds::Int: Safety cap on the number of full rounds (default5).session: Optional sharedSessionacross all agents.verbose::Bool: Print round/agent transitions (defaulttrue).
Example
coder = Agent(name="Coder", instructions="Write code based on the task.")
reviewer = Agent(name="Reviewer", instructions="Review code. Say APPROVED if good.")
result = loop_pipeline!(
[coder, reviewer],
"Write a fibonacci function";
max_rounds = 5,
stop_when = (agent, result) -> occursin("APPROVED", string(result)),
session = Session(),
)NimbleAgents.fan_out Function
fan_out(agent, inputs; reducer, parallel, session, verbose) -> AnyRun agent against each element of inputs, then combine the results.
parallel = false(default): runs each input serially in order.parallel = true: spawns each run on the Julia thread pool (Threads.@spawn); result order matchesinputsorder regardless.reducer: an optional two-argument function(accumulator, result) -> accumulatorapplied viareduce. Defaults tonothing, which returnsVector{Any}.Each run shares the same
sessionif provided; concurrent writes are protected bysession.lock.
Example — serial, default reducer
summaries = fan_out(summarizer, ["chunk 1", "chunk 2", "chunk 3"])
# => Vector{Any} of three responsesExample — parallel with a string-join reducer
report = fan_out(research_agent, topics;
parallel = true,
reducer = (acc, x) -> acc * "\n\n" * x)NimbleAgents.spawn_subagents Function
spawn_subagents(pairs; parallel, session, verbose) -> Vector{Any}Run a list of (agent, input) pairs and return their results in the same order.
parallel = false(default): executes each pair serially.parallel = true: spawns each pair concurrently on the Julia thread pool; result order is preserved.Each run shares the same
sessionif provided; concurrent writes are protected bysession.lock.
Example — serial
results = spawn_subagents([
(researcher_agent, "Find facts about X"),
(analyst_agent, "Analyse the market for X"),
(writer_agent, "Draft an intro for X"),
])
draft = run!(editor_agent, join(results, "\n\n"))Example — parallel
results = spawn_subagents([
(researcher_agent, "Topic A"),
(researcher_agent, "Topic B"),
]; parallel = true, session = session)Tracing and Evaluation
NimbleAgents.Trace Type
Trace(session)
Trace(turns::Vector{TurnEvent})A lightweight view over a session's event log.
Aggregates token usage, cost, elapsed time, and tool call statistics across all turns. No new data is collected — everything comes from TurnEvent / ToolEvent already recorded by run!.
Fields
turns::Vector{TurnEvent}: All turns in order.total_input_tokens::Int: Sum of input tokens across all turns.total_output_tokens::Int: Sum of output tokens across all turns.total_cache_read_tokens::Int: Sum of tokens read from prompt cache.total_cache_write_tokens::Int: Sum of tokens written to prompt cache.total_tokens::Int:total_input_tokens + total_output_tokens.total_cost::Float64: Estimated total USD cost across all turns (cache-adjusted).total_llm_calls::Int: Total number of LLM requests made.total_tool_calls::Int: Total number of tool calls made.duration::Float64: Wall-clock seconds from first turn start to last turn end.agents::Vector{String}: Unique agent names that handled turns (in order of first appearance).
Example
session = Session(app_name="MyApp", user_id="alice")
run!(agent, "What is 2 + 2?"; session=session)
trace = Trace(session)
println("Cost: $", round(trace.total_cost; digits=4))
print_trace(trace)
save_trace(trace, "trace.json")NimbleAgents.print_trace Function
print_trace(trace; io=stdout)Print a human-readable summary of a Trace to io.
Arguments
trace::Trace: Trace to render.io::IO: Output stream (default:stdout).
━━━ Trace ━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Turns : 2
Agents : MathBot
Duration : 3.42s
LLM calls : 3
Tool calls : 2
Tokens : 312 in / 88 out / 400 total
Turn 1 — MathBot [1.8s | 2 llm | 1 tool | 210 tok]
input : What is 2 + 2?
output : The answer is 4.
tools : add(x=2, y=2) → 4
Turn 2 — MathBot [1.6s | 1 llm | 1 tool | 190 tok]
input : Now multiply by 3.
output : The result is 12.
tools : multiply(x=4, y=3) → 12
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━NimbleAgents.save_trace Function
save_trace(trace, path)Serialise a Trace to a JSON file at path.
The JSON structure mirrors the Trace fields — suitable for offline analysis, feeding into an evaluation script, or archiving agent runs.
Arguments
trace::Trace: Trace data to persist.path::String: Destination file path.
Returns
String: The samepaththat was written.
NimbleAgents.load_trace Function
load_trace(path) -> Dict{String, Any}Load a previously saved trace from a JSON file. Returns the raw parsed Dict — useful for offline analysis or evaluation scripts.
Arguments
path::String: Path to a JSON file produced bysave_trace.
Returns
Dict{String,Any}: Parsed trace payload.
NimbleAgents.EvalCase Type
EvalCase(; input, expected, expected_tools, reference, tags)A single evaluation test case.
Fields
input::String: User message to send to the agent.expected::Union{String, Nothing}: Expected answer text (for text-matching metrics).expected_tools::Union{Vector{String}, Nothing}: Expected tool call names in order.reference::Union{Dict{String,Any}, Nothing}: Arbitrary ground-truth metadata.tags::Vector{String}: Tags for filtering/grouping results.
NimbleAgents.EvalResult Type
EvalResultResult for a single eval case.
Fields
case::EvalCase: The original test case.output::Any: Actual agent response.trace::Union{Trace, Nothing}: Full trace fromrun!.scores::Dict{String, Float64}: Metric name => score (0.0–1.0).passed::Bool: Whether all scores met the pass threshold.error::Union{String, Nothing}: Error message ifrun!threw.elapsed::Float64: Wall-clock time for this case.
NimbleAgents.EvalReport Type
EvalReport(results)Aggregate report over all eval results. Computes pass rate, mean scores, total cost, and total duration from the individual results.
Fields
results::Vector{EvalResult}: All individual results.pass_rate::Float64: Fraction of cases that passed.mean_scores::Dict{String, Float64}: Mean score per metric across all cases.total_cost::Float64: Sum of trace costs.total_duration::Float64: Sum of elapsed times.timestamp::Float64: When the report was created.
NimbleAgents.exact_match Function
exact_match(case, output, trace) -> Float64Returns 1.0 if string(output) equals case.expected exactly, else 0.0. Returns 1.0 if case.expected is nothing (no expectation).
NimbleAgents.fuzzy_match Function
fuzzy_match(case, output, trace) -> Float64Returns 1.0 if case.expected is a case-insensitive substring of output. Otherwise returns a normalised similarity score based on Levenshtein distance. Returns 1.0 if case.expected is nothing.
NimbleAgents.tool_trajectory Function
tool_trajectory(case, output, trace) -> Float64Returns 1.0 if the tool names called (in order) match case.expected_tools exactly. Returns 1.0 if case.expected_tools is nothing.
NimbleAgents.tool_coverage Function
tool_coverage(case, output, trace) -> Float64Returns the fraction of case.expected_tools that were actually called (order-insensitive). Returns 1.0 if case.expected_tools is nothing.
NimbleAgents.cost_budget Function
cost_budget(max_cost) -> NamedMetricFactory: returns a metric that scores 1.0 if trace.total_cost <= max_cost, else 0.0.
NimbleAgents.latency_budget Function
latency_budget(max_seconds) -> NamedMetricFactory: returns a metric that scores 1.0 if trace.duration <= max_seconds, else 0.0.
NimbleAgents.run_eval Function
run_eval(agent, cases; metrics, verbose, pass_threshold) -> EvalReportRun an agent against a vector of EvalCases, score each with the given metrics, and return an EvalReport.
Arguments
agent::Agent: The agent to evaluate.cases::Vector{EvalCase}: Test cases.metrics: Vector of metric functions(EvalCase, output, Trace) -> Float64. Default:[exact_match, tool_trajectory].verbose::Bool: Whether to passverbose=truetorun!. Default:false.pass_threshold::Float64: Minimum score for each metric to count as passed. Default:1.0.
NimbleAgents.print_eval Function
print_eval(report; io=stdout)Print a human-readable summary of an EvalReport.
NimbleAgents.save_eval Function
save_eval(report, path)Serialise an EvalReport to a JSON file.
NimbleAgents.load_eval Function
load_eval(path) -> Dict{String, Any}Load a previously saved eval report from a JSON file.
sourceMCP and Server
NimbleAgents.MCPServer Type
MCPServer(; command, args, env, timeout, cache_tools)
MCPServer(; url, headers, timeout, cache_tools)Describes an MCP server that NimbleAgents can connect to.
Two transports are supported:
stdio — spawns the server as a local subprocess. Provide
command(and optionallyargs/env).HTTP — connects to a remote MCP endpoint via HTTP POST. Provide
url(and optionallyheadersfor authentication).
Fields (stdio)
command::String: Executable to run (e.g."uvx","npx","python").args::Vector{String}: Arguments passed to the command.env::Dict{String,String}: Extra environment variables for the subprocess.
Fields (HTTP)
url::String: HTTP endpoint URL (e.g."https://docs.langchain.com/mcp").headers::Dict{String,String}: Request headers, e.g. for auth tokens.
Fields (shared)
timeout::Float64: Seconds to wait for each JSON-RPC response (default:60.0).cache_tools::Bool: Cache tool list after first discovery (default:true).
Examples
# stdio
server = MCPServer(
command = "uvx",
args = ["--from", "mcpdoc", "mcpdoc",
"--urls", "LangGraph:https://langchain-ai.github.io/langgraph/llms.txt",
"--transport", "stdio"],
)
# HTTP — no auth
server = MCPServer(url="https://docs.langchain.com/mcp")
# HTTP — with Bearer token
server = MCPServer(
url = "https://huggingface.co/mcp",
headers = Dict("Authorization" => "Bearer hf_xxx"),
)NimbleAgents.MCPClient Type
MCPClient(server)A live connection to one MCP server via stdio. Created via connect!(MCPClient(server)). Not constructed directly by users — attach MCPServer objects to an Agent and the framework manages client lifetime.
NimbleAgents.MCPHTTPClient Type
MCPHTTPClient(server)A live connection to one MCP server via HTTP POST. Created automatically when an MCPServer is constructed with a url field.
NimbleAgents.connect! Function
connect!(client::MCPHTTPClient) -> MCPHTTPClientPerform the JSON-RPC initialize handshake with the remote HTTP MCP server. Returns the client for chaining.
sourceconnect!(client::MCPClient) -> MCPClientSpawn the MCP server subprocess and perform the JSON-RPC initialize handshake. Returns the client (mutated in place) for chaining.
sourceNimbleAgents.list_tools Function
list_tools(client::MCPClient) -> Vector{NimbleTool}Fetch the tool list from the MCP server and return them as NimbleTool objects ready to be passed to an Agent. Results are cached if server.cache_tools.
NimbleAgents.close! Function
close!(client::MCPHTTPClient)No-op for HTTP clients — there is no persistent connection to close. Clears the tool cache.
sourceclose!(client::MCPClient)Terminate the MCP server subprocess and clean up I/O handles.
sourceNimbleAgents.serve Function
serve(agents; port=8080, host="127.0.0.1", store=InMemorySessionStore())Start the NimbleAgents web UI server.
Registers agents by name and serves a local browser UI at http://$host:$port. Press Ctrl+C to stop.
Arguments
agents::Vector{<:Agent}: Agents to register in the web UI, keyed byagent.name.port::Int: TCP port to listen on (default:8080).host::String: Host/interface to bind (default:"127.0.0.1").store::AbstractSessionStore: Session persistence backend used by the server.
Returns
Nothing: Runs the HTTP server loop until interrupted (Ctrl+C).
Pass a store to persist sessions across server restarts:
serve([agent]; store=JSONSessionStore(".nimble/sessions"))Multiple threads required
The server spawns agent runs in background threads. Start Julia with at least 2 threads:
julia --project=. -t 4 examples/web/web_ui.jlExample
using NimbleAgents
agent = Agent(
name = "MyBot",
instructions = "You are a helpful assistant.",
model = "gpt-5.4-mini",
)
serve([agent]; port=8080)
# With persistence:
serve([agent]; port=8080, store=JSONSessionStore(".nimble/sessions"))NimbleAgents.chat! Function
chat!(agent::Agent; session, model, verbose)Start an interactive multi-turn conversation with agent in the Julia REPL.
Tokens stream to stdout as they arrive. Conversation history accumulates in the session across turns, so the agent maintains context throughout the conversation.
Slash Commands
/exitor/quit— end the conversation/reset— clear conversation history and start fresh/trace— show token usage, cost, and tool call summary/help— show available commands
Arguments
agent::Agent: The agent to chat with.session::Session: Session for conversation history (default: auto-created).verbose::Bool: Show tool call details (default:false).
Example
agent = Agent(name="Bot", instructions="You are helpful.", tools=[search_tool])
chat!(agent)chat!(; model)Start a conversation with the built-in NimbleAgents documentation bot. It has access to the framework's source code, docs, and examples via filesystem tools.
Requires an LLM API key — set OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY in your environment or .env file.
Arguments
model::String: LLM model to use (default:"gpt-4.1-nano").
Example
using NimbleAgents
chat!()
# You> How do I add guardrails to an agent?
# NimbleAgents> ...External and CLI Tools
NimbleAgents.CLITool Type
CLITool(; name, description, command, args, timeout, working_dir, return_direct)An AbstractTool that runs a shell command as a subprocess.
Arguments from the LLM are substituted into {arg_name} placeholders in the command vector. Each argument is passed as a separate process argument — no shell string is constructed, making injection structurally impossible.
Fields
name::String: Tool name shown to the LLM.description::String: What the tool does and when to use it.command::Vector{String}: Command and arguments with{placeholder}slots.args::Vector{Pair{String,CLIArg}}: Ordered argument definitions.timeout::Float64: Seconds before the subprocess is killed (default:30.0).working_dir::Union{String,Nothing}: Working directory for the subprocess.return_direct::Bool: Short-circuit the agent loop after this tool (default:false).
Example
git_log = CLITool(
name = "git_log",
description = "Show recent git commits.",
command = ["git", "log", "--oneline", "-{n}"],
args = ["n" => CLIArg(Int, "Number of commits to show.")],
)
agent = Agent(
name = "DevBot",
tools = [git_log],
)Example — multiple args
grep_tool = CLITool(
name = "grep",
description = "Search for a pattern in files. Returns matching lines.",
command = ["grep", "-rn", "{pattern}", "{path}"],
args = [
"pattern" => CLIArg(String, "Regex pattern to search for."),
"path" => CLIArg(String, "File or directory to search in."),
],
)NimbleAgents.CLIArg Type
CLIArg(type, description; required=true)Describes one argument of a CLITool.
Fields
type::Type: Julia type for the argument (String,Int,Float64,Bool).description::String: Shown to the LLM in the tool schema.required::Bool: Whether the argument must be provided (default:true).
NimbleAgents.ExternalAgentTool Type
ExternalAgentTool(; name, description, command, args, timeout, working_dir,
on_output, parse_result)An AbstractTool for running external CLI agents (Claude Code, Codex, etc.) as subprocesses with real-time progress streaming.
Unlike CLITool (which buffers all output), ExternalAgentTool reads stdout line-by-line and fires on_output(line) for each line as it arrives. This enables progress visibility for long-running agents.
Fields
name::String: Tool name shown to the LLM.description::String: What the tool does and when to use it.command::Vector{String}: Command with{placeholder}slots (same asCLITool).args::Vector{Pair{String,CLIArg}}: Ordered argument definitions.timeout::Float64: Seconds before the subprocess is killed (default:300.0).working_dir::Union{String,Nothing}: Working directory for the subprocess.on_output::Union{Function,Nothing}: Called with each stdout line as it arrives. Use for progress logging (default:nothing— silent).parse_result::Union{Function,Nothing}: Post-process the collected output lines into a final result string. ReceivesVector{String}of all stdout lines. Default:nothing(returns raw output joined by newlines).return_direct::Bool: Short-circuit the agent loop after this tool (default:false).
Example
coder = ExternalAgentTool(
name = "coder",
description = "Run Claude Code to implement a task.",
command = ["claude", "-p", "{task}", "--output-format", "stream-json",
"--verbose", "--model", "sonnet"],
args = ["task" => CLIArg(String, "The coding task.")],
timeout = 300.0,
on_output = line -> println("[coder] ", line),
)NimbleAgents.claude_code_tool Function
claude_code_tool(; name, description, model, working_dir, timeout,
max_budget, permission_mode, allowed_tools, on_output,
system_prompt, session_id, resume, extra_flags)Create an ExternalAgentTool that delegates tasks to Claude Code via claude -p.
Uses --output-format stream-json --verbose for structured streaming output. The on_output callback receives each line of stream-json as it arrives, enabling real-time progress monitoring.
Authentication
Claude Code must be authenticated before use. Either:
Run
claude loginonce in your terminal (stores credentials in~/.claude/), orSet
ANTHROPIC_API_KEYin your.envor environment (inherited by subprocess).
Login cannot be done programmatically — it requires an interactive browser OAuth flow.
Arguments
name::String: Tool name (default:"claude_code").description::String: Description shown to the LLM.model::String: Claude model to use (default:"sonnet").working_dir::Union{String,Nothing}: Working directory for Claude Code.timeout::Float64: Seconds before kill (default:300.0).max_budget::Union{Float64,Nothing}: Max USD budget per invocation.permission_mode::String: Permission mode (default:"bypassPermissions").allowed_tools::Union{Vector{String},Nothing}: Restrict available tools.on_output::Union{Function,Nothing}: Called per stream-json line.system_prompt::Union{String,Nothing}: Custom system prompt for Claude Code.session_id::Union{String,Nothing}: Session ID to continue a previous conversation. When set, Claude Code resumes the specified session, retaining full conversation context.resume::Bool: Iftrue, resume the most recent session (default:false). Mutually exclusive withsession_id— if both are set,session_idtakes precedence.extra_flags::Vector{String}: Additional CLI flags.
Example
coder = claude_code_tool(
working_dir = "/path/to/repo",
model = "sonnet",
max_budget = 2.00,
on_output = line -> begin
try
event = JSON3.read(line)
if event.type == "assistant"
println("[claude] ", get(event.message.content[1], :text, ""))
end
catch; end
end,
)
agent = Agent(
name = "PM",
instructions = "Delegate implementation tasks to claude_code.",
tools = [coder],
)
# Resume a previous Claude Code session by ID
coder_resume = claude_code_tool(session_id = "abc-123", working_dir = "/path/to/repo")
# Resume the most recent session
coder_latest = claude_code_tool(resume = true, working_dir = "/path/to/repo")NimbleAgents.codex_tool Function
codex_tool(; name, description, model, working_dir, timeout, on_output, extra_flags)Create an ExternalAgentTool that delegates tasks to OpenAI Codex CLI via codex -q.
Example
coder = codex_tool(working_dir="/path/to/repo")Built-In Tools
NimbleAgents.read_file_tool Constant
read_file_toolBuilt-in NimbleTool that reads file contents and returns them as a string.
NimbleAgents.write_file_tool Constant
write_file_toolBuilt-in NimbleTool that writes text to a file, creating parent directories when needed.
NimbleAgents.edit_file_tool Constant
edit_file_toolBuilt-in NimbleTool that replaces one exact string match in a file.
NimbleAgents.list_dir_tool Constant
list_dir_toolBuilt-in NimbleTool that lists directory entries with basic type/size hints.
NimbleAgents.glob_tool Constant
glob_toolBuilt-in NimbleTool that finds files matching glob patterns, including **.
NimbleAgents.delete_file_tool Constant
delete_file_toolBuilt-in NimbleTool that permanently deletes a file path.
NimbleAgents.grep_tool Constant
grep_toolBuilt-in NimbleTool that searches files/directories with a regular expression.
NimbleAgents.find_files_tool Constant
find_files_toolBuilt-in NimbleTool that recursively finds file names matching a pattern.
NimbleAgents.bash_tool Constant
bash_toolBuilt-in NimbleTool that executes shell commands and returns combined output.
NimbleAgents.http_get_tool Constant
http_get_toolBuilt-in NimbleTool that performs HTTP GET requests and returns response text.
NimbleAgents.http_post_tool Constant
http_post_toolBuilt-in NimbleTool that sends JSON POST requests and returns response text.
NimbleAgents.fetch_webpage_tool Constant
fetch_webpage_toolBuilt-in NimbleTool that fetches a webpage and returns cleaned readable text.
NimbleAgents.github_trending_tool Constant
github_trending_toolBuilt-in NimbleTool that queries GitHub Search API for trending repositories.
NimbleAgents.eval_julia_tool Constant
eval_julia_toolBuilt-in tool that evaluates Julia code in a persistent sandbox.
The sandbox persists for the lifetime of the session — variables, imports, and function definitions all carry over between calls. The project environment is active, so any package in Project.toml can be loaded with using.
Pair with a Session to get persistent state across turns:
session = Session(app_name="DataSession", user_id="alice")
agent = Agent(
name = "DataBot",
tools = [eval_julia_tool],
)
run!(agent, "Load DataFrames and create a DataFrame with columns a and b"; session=session)
run!(agent, "Now compute the mean of column a"; session=session)NimbleAgents.save_artifact_tool Constant
save_artifact_toolBuilt-in NimbleTool that registers an existing file as a session artifact.
NimbleAgents.save_memory_tool Constant
save_memory_toolBuilt-in NimbleTool that saves user facts/preferences into long-term memory.
NimbleAgents.recall_memory_tool Constant
recall_memory_toolBuilt-in NimbleTool that retrieves relevant entries from long-term memory.
Provider Schema Marker
NimbleAgents.GeminiOpenAISchema Type
GeminiOpenAISchema <: AbstractOpenAISchemaMarker schema used for Gemini models routed through Google's OpenAI-compatible chat completions endpoint.
source