Redouble AI Nucleo documentation
Part 2 · Writing an agent

Built-in tools

ai.redouble.nucleo.tools.builtin · nucleo-core

Some tools every agent ends up needing: the current time, because a model's sense of "today" is whatever its training left it; date arithmetic, because models fumble exact calculation; fetching a web page; asking a model one quick question without building an agent for it. This package ships them, ready to put on any agent's palette. Nothing here is offered to an agent automatically: a thinker names what it wants in declareDefaultTools(), the way Your first agent names OrderStatusTool, or a host pushes providers through ToolHub.

Catalog

Tool nameClassResourcesTimeoutWhat it does
get_current_timeCurrentTimeToolnone1sThe current instant in ISO (system zone, offset included), epoch millis, human-readable and timezone forms; every field names the same calendar date
calculate_datesDateCalculatorToolnone5sSigned day/month difference between two dates, optional within-N-months judgment
calculate_durationDurationCalculatorToolnone5sHours and minutes between two date-times, judged against a minimum
calculate_unit_rateUnitRateCalculatorToolnone5sRate times quantity with an optional cap, disclosed in the formula
quick_llm_questionQuickLLMQuestionToolone LLM call6minA focused question about supplied text, at the grade the asker names in the input
summarize_textSummarizationToolLLM calls6minSummary at a SummarySize, hierarchical chunking for text of any length
analyze_violationAnalyzeViolationToolone LLM call6minJudges whether a guardrail violation was the thinker's mistake or a hijack attempt
web_fetchWebFetchToolHTTP client30sFetches a page and reduces it to readable text as a WebPageArtifact

LLMSummarizer is in this package but is not a tool: it is the Summarizer implementation behind @LLMSummarizable fields. A static summary from the annotation wins without any model call; content marked llmSafe = false or past the 200K-char ceiling truncates instead of reaching a model; otherwise it summarizes through summarize_text as a job and falls back to truncation when that job fails. Every result carries the [SUMMARY: N chars] prefix with the ORIGINAL length.


The calculators

LLMs are unreliable at exact arithmetic. They can reason about which numbers to multiply, but will fumble the actual multiplication. The calculators provide deterministic, verifiable math the LLM calls instead of attempting it in-context, and each returns a human-readable formula or explanation so the model (and a human reviewing the output) can verify the math. They hold no resources, are read-only, and an unparseable date or a negative rate surfaces as InvalidInputException - correctable, so the model fixes the format and retries.

calculate_dates - DateCalculatorTool

Compares two dates and calculates the difference in days and months. Differences are signed from dateB to dateA: positive means dateA is after dateB. The within-months judgment requires dateA AFTER dateB and within N months.

FieldTypeRequiredDescription
Input
dateAString (YYYY-MM-DD)YesFirst date
dateBString (YYYY-MM-DD)YesSecond date
withinMonthsIntegerNoCheck if dateA is within N months after dateB
Output
daysDifferenceLong Signed days from dateB to dateA
monthsDifferenceLong Signed months from dateB to dateA
isWithinMonthsBoolean Only set if withinMonths was provided
explanationString Natural language explanation

Example:

Input:  dateA=2025-10-15, dateB=2025-01-01, withinMonths=12
Output: daysDifference=287, monthsDifference=9, isWithinMonths=true
        explanation="dateA (2025-10-15) is 9 months and 14 days after dateB (2025-01-01). This IS within 12 months."

calculate_duration - DurationCalculatorTool

Calculates hours and minutes between two date-times, with the hours rounded down, and judges the duration against a minimum (default 20 hours when none is given).

FieldTypeRequiredDescription
Input
startDateTimeString (YYYY-MM-DD HH:mm)YesStart timestamp
endDateTimeString (YYYY-MM-DD HH:mm)YesEnd timestamp
minimumHoursIntegerNoMinimum hours threshold (default 20)
Output
totalHoursInteger Hours between start and end (rounded down)
totalMinutesInteger Total minutes between start and end
meetsMinimumBoolean Whether totalHours >= minimumHours
minimumRequiredInteger The threshold that was checked
explanationString Natural language explanation

Example:

Input:  startDateTime=2025-10-20 14:30, endDateTime=2025-10-21 10:00, minimumHours=20
Output: totalHours=19, totalMinutes=1170, meetsMinimum=false, minimumRequired=20
        explanation="Start: 2025-10-20 14:30 -> End: 2025-10-21 10:00 = 19 hours and 30 minutes. DOES NOT MEET minimum of 20 hours."

calculate_unit_rate - UnitRateCalculatorTool

Multiplies a unit rate by a quantity with an optional quantity cap. When the cap wins, the formula discloses it, so the model cannot misreport an uncapped total.

FieldTypeRequiredDescription
Input
unitRateLongYesRate per unit, non-negative
quantityIntegerYesNumber of units, non-negative
maxQuantityIntegerNoCap on quantity (null = no cap)
Output
totalLong unitRate * appliedQuantity
appliedQuantityInteger Quantity after cap
formulaString Math formula for verification

Example (with cap):

Input:  unitRate=10000, quantity=7, maxQuantity=5
Output: total=50000, appliedQuantity=5, formula="10000 * 5 = 50000 (capped from 7 to 5)"

The LLM utilities

quick_llm_question - QuickLLMQuestionTool

One focused question about supplied text, answered in one model exchange with a typed QuickLLMQuestionOutput (answer, confidence, reasoning). The grade travels IN THE INPUT, per question: this tool is how anyone - a thinker's model, a doer, a servlet - asks one question without paying for a thinker, and no fixed grade could serve every asker. A code caller that builds the input by hand and leaves the grade unset is refused at requirements time, because only the asker knows how much model its question deserves.

summarize_text - SummarizationTool

Summarizes text to a target SummarySize (BRIEF / SHORT / PARAGRAPHS). Text that fits the model's context is summarized directly; larger text is chunked on paragraph, then sentence, then word boundaries, each chunk summarized at SHORT, and the combined summaries re-summarized at the requested size. The size reaches the prompt as a prose phrase with no digits - a precise count in the prompt is what triggers small-model scratchpad spirals on length verification - and the numeric bound rides max_tokens as a per-size backstop (768 / 1536 / 3072) that exists to stop runaway output, not to shape it. Runs at Grade.SMALL as a UTILITY job.

analyze_violation - AnalyzeViolationTool

Security monitoring: given the user's original input and a guardrail violation message, a Grade.SMALL model judges whether the violation looks like the thinker's own reasoning error or a manipulation attempt, with confidence and reasoning (ViolationAnalysisOutput). Results feed security monitoring and incident response.

Both one-call tools are built on AbstractModelDependentTool: the conversation is wired at requirements time, the exchange runs under the shared ResponseCorrection budget, and a malformed answer goes back to the model as a correction turn before it ever surfaces as a failure.


The web fetcher

web_fetch - WebFetchTool

Fetches a URL over the framework HTTP client, parses the HTML with Jsoup, strips scripts, styles and navigation, and returns the readable text as a WebPageArtifact (title, description, content, source). Content past maxLength (default 50,000 chars) is cut and the cut is stated in the text, so a truncated page is never mistaken for a short one. Upstream statuses map to the framework error types: 400/422 InvalidInputException, 401/403 UnauthorizedException, 404 ResourceNotFoundException, anything else ExternalServiceException.

The tool carries its own address policy. It declares ai.redouble.nucleo.tools.guardrails.UrlGuardrail as an INPUT content guardrail, so every dispatched call is judged before the fetch runs: public http and https addresses are admitted, and loopback, private and link-local addresses, the cloud metadata endpoints among them, are refused. Redirects are followed by the tool itself, at most 5 of them, and each target passes the same policy before it is fetched, so a page the policy admits cannot lead the fetch to an address it refuses; the artifact's url is the address fetched in the end. A deployment with another policy, a domain allow-list for instance, subclasses the tool and overrides addressPolicy().


Usage

Include tools in a thinker's tool list:

@Override
protected List<Class<? extends Tool>> declareDefaultTools() {
    return List.of(
        CurrentTimeTool.class,
        DateCalculatorTool.class,
        WebFetchTool.class
    );
}

The calculators and the clock need no guardrails (no scope, no resources, no side effects). web_fetch declares UrlGuardrail itself. Every tool's input/output POJO pair lives in this package beside it.

Example: Hello, model - quick_llm_question from a plain main: start the runtime, ask, print the typed answer.

^