Documentation
¶
Overview ¶
Package tool is Glyphoxa v2's internal Tool framework: one uniform Tool interface, a dumb in-process Registry, and the generic tool-use loop that drives an LLM through tool calls (ADR-0028/0029/0030).
A Tool's backing — built-in (in-process Go, lowest latency) or a future MCP Server (out-of-process) — is hidden behind the Tool interface; consumers (the Agent loop, the orchestrator) only ever see Tools. The framework is a reusable building block with no dependency on the voice orchestrator: the Agent loop assembles its prompt, then hands messages + granted tools to Loop.Run and gets back the LLM's final text.
The read-only inline execution path of ADR-0030 is the default; the side-effecting / deferred-to-turn-commit path is deliberately not built (an atomic-with-the-utterance effect has no v1 Tool). A Tool that reports it is not read-only is rejected by the loop rather than silently inlined — UNLESS it is proposal-mediated (ADR-0052): a ProposalMediated Tool's only effect is a GM-reviewed Knowledge Proposal row, not a canon mutation, so it runs inline and a barged draft may still leave a (harmless, GM-gated) proposal. See package-level ADR references.
Index ¶
- Constants
- Variables
- func CallerID(ctx context.Context) string
- func IsFinalAnswerRound(ctx context.Context) bool
- func ProposalSalient(w ProposedWrite) string
- func ProposalTargetKey(w ProposedWrite) string
- func WithCaller(ctx context.Context, agentID string) context.Context
- type AssistantMessage
- type Decl
- type Deps
- type Dice
- type Grant
- type GrantSet
- type KGFact
- type KGNodeRef
- type KGQuery
- type KGReader
- type KGWriter
- type KnownForTarget
- type LocateEntity
- func (*LocateEntity) Description() string
- func (t *LocateEntity) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
- func (*LocateEntity) InputSchema() json.RawMessage
- func (*LocateEntity) Name() string
- func (*LocateEntity) ReadOnly() bool
- func (*LocateEntity) SupportsScope() bool
- type Loop
- type Message
- type Place
- type ProposalMediated
- type ProposedWrite
- type Provider
- type PseudoCall
- type Recap
- type Recapper
- type Registry
- type RememberKnowledge
- func (*RememberKnowledge) Description() string
- func (rk *RememberKnowledge) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
- func (*RememberKnowledge) InputSchema() json.RawMessage
- func (*RememberKnowledge) Name() string
- func (*RememberKnowledge) ProposalMediated() bool
- func (*RememberKnowledge) ReadOnly() bool
- func (*RememberKnowledge) SupportsScope() bool
- type Role
- type SpatialReader
- type SpatialScope
- type StreamingProvider
- type Tool
- type ToolCall
- type ToolResult
- type TranscriptHit
- type TranscriptSearch
- func (*TranscriptSearch) Description() string
- func (ts *TranscriptSearch) Execute(ctx context.Context, args json.RawMessage, _ any) (string, error)
- func (*TranscriptSearch) InputSchema() json.RawMessage
- func (*TranscriptSearch) Name() string
- func (*TranscriptSearch) ReadOnly() bool
- func (*TranscriptSearch) SupportsScope() bool
- type TranscriptSearcher
- type WhatsNearby
- func (*WhatsNearby) Description() string
- func (t *WhatsNearby) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
- func (*WhatsNearby) InputSchema() json.RawMessage
- func (*WhatsNearby) Name() string
- func (*WhatsNearby) ReadOnly() bool
- func (*WhatsNearby) SupportsScope() bool
Constants ¶
const ( // DefaultRecapSessions is the session count when the LLM omits "sessions". DefaultRecapSessions = 1 // MaxRecapSessions is the hard ceiling on "sessions"; a larger request is // clamped in the handler (the schema is advisory, ADR-0029). MaxRecapSessions = 3 // RecapResultBudgetRunes bounds the whole rendered recap, counted in RUNES so a // multibyte-heavy (German) recap is never over-budget nor split mid-codepoint. // Sized for HONEST end-to-end deliverability, not the raw recap length: the // Butler RELAYS this text through its own answer completion, capped at // groq.DefaultMaxTokens (1024 ≈ ~3.5k runes of German). A larger budget would // let a near-budget recap get clipped mid-prose by the relay despite the "do not // shorten" instruction. So the Tool truncates to what the relay can actually // carry; the full untruncated recap stays available via /glyphoxa recap (the // slash surface splits it across ordered followups, #271). The Description tells // the model as much. RecapResultBudgetRunes = 3500 )
Recap Tool budgets/defaults (#372, #297 decision 5). The recap prose is a whole session's condensed narrative, so its ceiling is far larger than the knowledge Tools' per-row budget — but still bounded so one recall can't swamp the next generation's context. Pinned as consts (not magic numbers) so the bounds are one edit away and testable.
const ( // MaxToolResultChars bounds a knowledge Tool's whole rendered result. A // tool-role message is prompt-injected verbatim, so this keeps one tool call // from dominating the next generation's context regardless of DB size. MaxToolResultChars = 2000 // MaxTranscriptLineRunes caps one rendered transcript line's spoken text, in // runes (rune-safe truncation, never a split codepoint). MaxTranscriptLineRunes = 300 // DefaultSearchLimit is the row cap when the LLM omits "limit". DefaultSearchLimit = 5 // MaxSearchLimit is the hard ceiling on "limit"; a larger request is clamped, // re-validated in the handler because the schema is advisory (ADR-0029). MaxSearchLimit = 10 )
Result budgets shared by the knowledge Tools (#296), mirroring the kgfacts discipline (internal/kgfacts): a hard ceiling on the whole result plus a per-item cap so no single oversized row blows the prompt budget. Pinned as consts, not magic numbers, so the bound is one edit away and testable.
const DefaultMaxRounds = 8
DefaultMaxRounds caps how many tool-call rounds Loop.Run will execute before giving up, guarding against a misbehaving Provider that emits tool_calls forever. A round is one Generate + execute cycle; the final text-only Generate does not count against it.
const DefaultNearbyRadius = 0.15
DefaultNearbyRadius is the fraction of the map's span treated as "nearby" when the model names no radius. Normalized units, so it means the same on a continent and on a tavern floor plan — which is the honest reading of "near" on a map whose scale the system does not know.
const MaxKGFactBodyRunes = 500
MaxKGFactBodyRunes caps one rendered KG fact's body length, in runes (mirrors kgfacts.MaxFactChars). The whole result is still bounded by MaxToolResultChars.
const MaxKGNameRunes = 200
MaxKGNameRunes caps a fact's Node-name length, in runes (mirrors kgfacts.MaxNameChars): without it a single pathological name could push the first block past the whole-result budget and, since the budget stop is a deterministic prefix, silently drop every fact.
const MaxNearbyResults = 8
MaxNearbyResults caps what one whats_nearby call returns. A list longer than this is not an answer an NPC can say out loud.
const MaxProposalTextRunes = 2000
MaxProposalTextRunes caps the free-text (prose) fields a remember_knowledge proposal carries — fact and body — in runes: a pathological wall of text has no business in the GM's review queue. The shorter entity-name fields (subject, target, name) are capped at MaxKGNameRunes instead. Together these two caps bound EVERY field, so one proposed_write's jsonb is bounded. Enforced in the handler, per-field, before the writer is ever called.
Variables ¶
var ErrMaxRoundsExceeded = errors.New("tool: max tool-call rounds exceeded")
ErrMaxRoundsExceeded is returned by Loop.Run when the forced final-answer round produced no prose — the degrade path's hard stop, not its first resort, reached from either trigger: the Provider kept emitting tool_calls past Loop.MaxRounds, or the no-progress short-circuit fired after [noProgressRounds] consecutive all-error rounds. The wrapping error names the actual trigger ("(N rounds)" vs "(no-progress after N all-error rounds)") so logs point at the right failure; errors.Is matches both.
Functions ¶
func CallerID ¶
CallerID returns the Agent id stamped by WithCaller, or "" if none is set (an unstamped ctx — a standalone bench turn, or a Persona with no persisted id). A handler that needs own-node scope treats "" as "no neighbourhood to scope to" and yields an empty result rather than falling back to a wider read.
func IsFinalAnswerRound ¶ added in v0.3.0
IsFinalAnswerRound reports whether ctx carries the loop's final-answer-round marker: the ONE forced tool-less generation Loop.Run/Loop.RunStream issue when the round budget is exhausted (or the no-progress short-circuit fires) with the model still emitting tool calls. It is the seam the wiring bridge (agenttool) reads to send tool_choice none while keeping the Tools DECLARED — the conversation holds prior tool_call/tool messages, and stripping the declarations risks a provider 400 on the dangling references (#420/#427).
func ProposalSalient ¶ added in v0.2.1
func ProposalSalient(w ProposedWrite) string
ProposalSalient projects a ProposedWrite onto the free text the dedup guard compares. A fact is its statement; an edge is its relation and target; a new entry is its name and body. The result is compared normalized, so exact casing and punctuation here do not matter.
func ProposalTargetKey ¶ added in v0.2.1
func ProposalTargetKey(w ProposedWrite) string
ProposalTargetKey identifies the entity a proposal is ABOUT, so the guard only compares proposals addressing the same target — a coincidental text clash between two different subjects is never suppressed. An own_node proposal is keyed by its anchor node id (subject is cosmetic there); a campaign fact/edge by its normalized subject; a new entry by its own normalized name. An empty key means "no identifiable target" and the caller skips dedup.
func WithCaller ¶
WithCaller stamps the calling Agent's id onto ctx so a scope-narrowing Tool handler can resolve the caller WITHOUT trusting the LLM's args (S2, ADR-0029). It is set ONCE per turn, in the agenttool Engine's Generate/GenerateStream path, from the Agent's own spec — the model never supplies it, so an own_node-scoped kg_query reads exactly the caller's neighbourhood and cannot be widened by clever arguments.
Types ¶
type AssistantMessage ¶
AssistantMessage is one Provider.Generate result: the model's text plus any tool_calls it wants run. The loop terminates and returns Text when ToolCalls is empty.
type Decl ¶
type Decl struct {
Name string
Description string
InputSchema json.RawMessage
}
Decl is a Tool declared to the LLM: the grant-stripped advertisement of one callable. It is produced from a Tool by GrantSet.Declarations and is what a Provider translates into its vendor tool-spec.
type Deps ¶
type Deps struct {
// Transcripts backs transcript_search. nil ⇒ the Tool is registered but its
// Execute reports it is unavailable.
Transcripts TranscriptSearcher
// KG backs kg_query. nil ⇒ the Tool is registered but its Execute reports it
// is unavailable.
KG KGReader
// KGW backs remember_knowledge (#300, ADR-0052). nil ⇒ the Tool is registered
// but its Execute reports it is unavailable in this mode (the grant-editor RPC
// and voice bench build a zero Deps).
KGW KGWriter
// Recap backs the recap Tool (#372). nil ⇒ the Tool is registered but its
// Execute reports it is unavailable in this mode (the grant-editor RPC and
// voice bench build a zero Deps).
Recap Recapper
// Spatial backs locate_entity / whats_nearby (#539, ADR-0060). nil ⇒ the Tools
// are registered but report unavailable.
Spatial SpatialReader
}
Deps carries the injected read sources the built-in knowledge Tools need (S1, #296). It exists so pkg/tool stays free of an internal/storage import: storage already imports pkg/tool (the grant editor lists the Registry), so a reverse edge would be an import cycle. Instead the retrieval paths are handed in through these narrow, storage-free interfaces, satisfied by the adapter in internal/knowledge.
Every field is optional. A nil source means the Tool is still REGISTERED (so the grant editor's catalog is identical in every mode) but its Execute returns an "unavailable in this mode" error result rather than a nil-pointer panic — the standalone voice bench and the grant-editor RPC build a zero Deps and still surface the full Tool list. The live web boot fills the fields.
type Dice ¶
type Dice struct {
// contains filtered or unexported fields
}
Dice is the one built-in Tool v1.0 ships (ADR-0028): it rolls NdM — N dice of M sides each — and reports the rolls and their sum. It is read-only (ADR-0030) so the loop runs it inline during generation; the LLM needs the result to keep talking ("you rolled a 17").
The roll source is injectable so tests pin the outcome: a tool-use loop test asserting the model's routing must not be flaky on a global RNG. Construct with NewDice for a seeded production roller or NewDiceWithRand for a test roller.
One Dice is shared across the Registry and may be rolled concurrently: ADR-0030 runs read-only Tools inline during generation and speculation, and ADR-0025 generates all addressed Agents in parallel. A *rand.Rand is not safe for concurrent use, so the roll is guarded by mu.
func NewDice ¶
func NewDice() *Dice
NewDice returns a Dice backed by a non-deterministic seeded source, for production use.
func NewDiceWithRand ¶
NewDiceWithRand returns a Dice that rolls from rng, for deterministic tests. rng must be non-nil.
func (*Dice) Execute ¶
Execute implements Tool. It rolls args.Count dice of args.Sides sides and returns a human-readable line the LLM can speak. dice carries no grant scope, so grantConfig is ignored. The argument bounds are enforced here, not trusted from the model.
func (*Dice) SupportsScope ¶
SupportsScope implements Tool: dice carries no per-grant Config (its authority cannot be narrowed per Agent), so it is a plain on/off grant with no scope editor.
type Grant ¶
type Grant struct {
// ToolName is the [Tool.Name] this grant permits.
ToolName string
// Config narrows the Tool's authority for this Agent. It is passed to
// [Tool.Execute] as grantConfig and enforced in the handler, never by the
// LLM. nil means "no narrowing" (dice).
Config any
}
Grant is an Agent's explicit permission to invoke one named Tool, with optional per-grant Config that may narrow the Tool's authority for that Agent (ADR-0029). It is modeled as a struct, not a bare name string, so the per-grant config door is open from day one — dice's Config is always nil, but a future remember_knowledge granted "only about yourself" to an NPC vs campaign-wide to the Butler carries different Config behind the same Tool.
Grants are an in-memory value in v1.0 (no agents table yet); when persistence lands they hydrate from the DB into this identical shape and the loop never knows the difference.
type GrantSet ¶
type GrantSet struct {
// contains filtered or unexported fields
}
GrantSet is an Agent's full set of Tool Grants. It enforces least-privilege (ADR-0029): the LLM is only ever shown — and the loop only ever executes — Tools the Agent is granted. Ungranted Tools are filtered out before the prompt is built and are never declared to the model.
A GrantSet resolves grants against a Registry: the grant says "may call dice with this config", the Registry holds the dice Tool. A grant naming an unregistered Tool is silently skipped — it grants access to nothing.
func NewGrantSet ¶
NewGrantSet builds a GrantSet over registry from grants. A duplicate ToolName keeps the last grant for that name. registry must be non-nil.
func (*GrantSet) Declarations ¶
Declarations returns the Decl for every granted Tool that is registered, sorted by Name. This is the grant-stripped tool list handed to the LLM — ungranted Tools never appear, so the model cannot call what it cannot see (ADR-0029). The order is stable (sorted, not map-iteration order) so the rendered prompt — and thus the ADR-0021 cassette prompt_hash — does not thrash between runs when more than one Tool is granted.
func (*GrantSet) Without ¶
Without returns a derived GrantSet identical to this one but with the named Tool's grant removed, sharing the same Registry. The receiver is not mutated — it returns a copy — so a caller can narrow grants for one turn (e.g. drop an unneeded Tool so it is never declared to the model, saving a wasted tool-call round) without affecting any other turn. Removing a name that is not granted is a no-op copy.
type KGFact ¶
KGFact is one Knowledge Graph fact the adapter surfaces to kg_query — a storage-free projection. Type is the GM-facing label ("Character", "Location", …), already mapped by the adapter so pkg/tool needs no storage enum. Body is the Node's prose.
type KGNodeRef ¶
KGNodeRef is a storage-free handle to an Agent's own linked Node (ADR-0008 NPC-Node↔Agent link): its id (the anchor a Character NPC's own_node proposals attach to) and its display Name (the subject the handler stamps over whatever the LLM supplied). Kept storage-free so pkg/tool never sees a storage type.
type KGQuery ¶
type KGQuery struct {
// contains filtered or unexported fields
}
KGQuery is the read-only kg_query built-in (#296): a lookup over the Knowledge Graph's Node read paths (ADR-0008). It is the canonical ADR-0029 scope-narrowing example — the SAME registered Tool reads a different slice per Agent purely via the grant config, enforced HERE in the handler, never by the LLM:
- own_node (the default for a Character NPC's grant): only the caller's own linked Node and its single-hop neighbourhood, gm_private already filtered. The caller is read from the turn ctx (CallerID), NOT the LLM's args — so no crafted argument can widen the NPC to another Node's neighbourhood.
- campaign (the Butler's grant): a relevance search across the whole Campaign's public Nodes.
nil grant config defaults to campaign for this read direction (S3): a read is gm_private-filtered either way, so the wider default is safe; a write Tool fails closed to own_node instead. A nil source reports unavailable at Execute.
func NewKGQuery ¶
NewKGQuery builds the Tool over src. A nil src registers the Tool but reports unavailable at Execute time.
func (*KGQuery) Execute ¶
func (kq *KGQuery) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
Execute implements Tool. It resolves the effective scope from grantConfig (never the args), reads the corresponding KG slice, and renders the facts for the prompt. own_node reads the CALLER's neighbourhood (CallerID) filtered to the query terms; campaign runs the relevance search. A nil source yields the unavailable error; no facts yields a friendly "none" line, not an error.
func (*KGQuery) InputSchema ¶
func (*KGQuery) InputSchema() json.RawMessage
InputSchema implements Tool. It shares the query/limit schema with transcript_search; the scope is NEVER an argument (it lives in the grant).
func (*KGQuery) SupportsScope ¶
SupportsScope implements Tool: kg_query's authority is narrowed per Agent via the grant config (own_node vs campaign), so the grant editor renders its scope UI (ADR-0029).
type KGReader ¶
type KGReader interface {
OwnNodeFacts(ctx context.Context, agentID string) ([]KGFact, error)
SearchFacts(ctx context.Context, query string, limit int) ([]KGFact, error)
}
KGReader is the narrow read kg_query needs, in two scopes (S1/S3, ADR-0029):
- OwnNodeFacts returns one Agent's own linked Node plus its single-hop neighbourhood, already gm_private-filtered and edge-aware (it wraps storage.AgentNodeFacts). It is the least-privilege scope an NPC's grant narrows to — the handler resolves the agentID from the caller identity, not the LLM's args.
- SearchFacts is the campaign-wide relevance search the Butler's grant uses. The adapter MUST drop gm_private rows: storage.SearchNodes is GM-facing and does NOT filter them, so an unfiltered pass would leak GM secrets into an NPC prompt (ADR-0008, the load-bearing filter).
*knowledge.Store satisfies it.
type KGWriter ¶
type KGWriter interface {
OwnNode(ctx context.Context, agentID string) (KGNodeRef, bool, error)
CreateProposal(ctx context.Context, agentID string, w ProposedWrite) error
ExistingKnowledge(ctx context.Context, agentID string, w ProposedWrite) (KnownForTarget, error)
}
KGWriter is the narrow write seam remember_knowledge needs (#300, ADR-0052). It is deliberately not part of KGReader: writing is a distinct authority. *knowledge.Adapter satisfies it; a nil KGW on Deps reports unavailable at Execute.
- OwnNode resolves the caller's own linked Node for own_node-scoped proposals. The agentID is the turn ctx caller (CallerID), never the LLM args. ok=false means the Agent has no linked wiki entry — the handler refuses rather than proposing against a wrong or absent Node.
- CreateProposal records the proposal row (status pending). It is the ONLY side effect; per ADR-0052 barge semantics the adapter writes it under a cancel-immune context so a barged turn's proposal is never rolled back.
- ExistingKnowledge reports what the KG already holds for a proposal's target (#411): the salient text of the target's pending proposals plus its established facts, so the handler can suppress an exact/normalized re-proposal and echo the target's pending proposals back to the model. It is a READ; a nil/empty result simply means nothing is known yet.
type KnownForTarget ¶ added in v0.2.1
KnownForTarget is what the Knowledge Graph already holds for one proposal's target (#411), gathered by the KGWriter adapter and judged by the handler:
- Pending is the salient text of every pending Knowledge Proposal addressing the SAME target (per ProposalTargetKey). It is both a dedup candidate set and the echo list fed back to the model so it can see what it has already proposed this session and stop repeating itself (ADR-0052 mechanism c).
- Established is the target Node's already-canon facts (its body, split into lines). A re-proposal that matches one creates no row.
Both are RAW text (unnormalized): the handler normalizes at compare time via the shared textnorm.Normalize, and echoes the raw pending wording verbatim.
type LocateEntity ¶ added in v0.5.0
type LocateEntity struct {
// contains filtered or unexported fields
}
LocateEntity answers "where is X?" from the Map layer (#539).
func NewLocateEntity ¶ added in v0.5.0
func NewLocateEntity(src SpatialReader) *LocateEntity
NewLocateEntity builds the Tool over the spatial read seam. A nil src registers the Tool (the grant editor's catalog is identical in every mode) but its Execute reports it is unavailable rather than panicking.
func (*LocateEntity) Description ¶ added in v0.5.0
func (*LocateEntity) Description() string
Description implements Tool.
func (*LocateEntity) Execute ¶ added in v0.5.0
func (t *LocateEntity) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
Execute implements Tool.
func (*LocateEntity) InputSchema ¶ added in v0.5.0
func (*LocateEntity) InputSchema() json.RawMessage
InputSchema implements Tool.
func (*LocateEntity) Name ¶ added in v0.5.0
func (*LocateEntity) Name() string
Name implements Tool.
func (*LocateEntity) ReadOnly ¶ added in v0.5.0
func (*LocateEntity) ReadOnly() bool
ReadOnly implements Tool: this Tool only reads, so it runs inline within the turn (ADR-0030 defers only side-effecting Tools to turn commit).
func (*LocateEntity) SupportsScope ¶ added in v0.5.0
func (*LocateEntity) SupportsScope() bool
SupportsScope implements Tool: the reachable Maps are narrowed per Agent via the ADR-0029 grant scope — an innkeeper knowing the town's layout is correct; the same innkeeper knowing the enemy capital's is not.
type Loop ¶
type Loop struct {
// MaxRounds caps tool-call rounds; zero means [DefaultMaxRounds].
MaxRounds int
// OnPseudoCall fires once per pseudo-XML tool call (issue #410) found in an
// assistant message's text — the malformed `<function=…>…</function>` syntax
// some models emit as plain content instead of a real tool_call. recovered is
// true when the call parsed, named a granted Tool, AND the round still had
// budget (it will run as a real round); false when it was stripped without
// executing — ungranted, unparseable, ineligible, or found on a round whose
// calls are budget-refused / dropped (the over-budget and final-answer
// rounds), where nothing can run. nil is a no-op. It is the observability seam kept OUT of this
// vendor/metric-agnostic package (ADR-0028): the wiring layer supplies a
// callback that increments a counter. ctx is the turn's context.Context (the
// same one Run/RunStream execute under), so a wiring-layer callback can also
// reach any per-turn value it stashed there — e.g. #399's "dice actually
// called" recorder must learn about a RECOVERED pseudo-dice call, which never
// surfaced as a provider-native ToolCall.
OnPseudoCall func(ctx context.Context, name string, recovered bool)
// OnToolResult fires once per executed tool call with the [ToolResult] fed
// back to the model: name is the Tool's name, content the result text, isErr
// whether it is an error result. nil is a no-op. Like OnPseudoCall it is a
// wiring seam kept OUT of this vendor-agnostic package (ADR-0028): the
// agenttool bridge uses it to record the dice Tool's ACTUAL result so the
// invented-roll guard can verify a regenerated narration against what was
// really rolled (#438).
OnToolResult func(ctx context.Context, name, content string, isErr bool)
// contains filtered or unexported fields
}
Loop is the generic tool-use loop (ADR-0028): it drives an LLM Provider through tool calls — Generate → tool_call → execute → feed the tool-role result back → Generate again — until the model returns final text. It is the reusable building block, identical for one Tool or fifty and independent of any specific Tool or of the voice orchestrator; the Agent loop (task #2) assembles the prompt and calls Loop.Run.
Least-privilege (ADR-0029) and side-effect timing (ADR-0030) are both enforced here: only Tools the Agent is granted are declared and executable, and only read-only — or ProposalMediated (ADR-0052) — Tools run inline. A non-read-only Tool that is not proposal-mediated is refused because v1.0 does not build the deferred-to-turn-commit path.
func NewLoop ¶
NewLoop builds a Loop over provider and the Agent's grants. Both must be non-nil; passing nil for either panics — they are wiring requirements, not runtime conditions.
func (*Loop) Run ¶
Run drives the conversation to completion and returns the model's final text. messages is the prompt the Agent loop assembled (system/user/...); Run appends the assistant tool_call turns and the tool-role result turns as it goes, leaving the caller's slice untouched.
On each round Run declares only the granted Tools (grant-stripping), calls Provider.Generate, and if the model emitted tool_calls, executes each and feeds the results back as one tool-role Message before the next Generate. When Generate returns no tool_calls, its Text is the answer.
ctx governs Generate and every Tool.Execute; cancelling it (barge-in) tears down an in-flight call. A Provider error aborts the loop. A tool execution error does not abort: it is fed back to the model as an error ToolResult so the model can recover — the only hard stops are ctx cancellation, a Provider error, and ErrMaxRoundsExceeded.
Degrade path (the tool-budget silence fix): a model that keeps tool-calling to the round budget — or that burns [noProgressRounds] consecutive rounds whose executed results were ALL errors — no longer fails the turn outright. The loop forces ONE extra, marked generation (see IsFinalAnswerRound) whose prose is the answer; only an EMPTY final answer returns ErrMaxRoundsExceeded.
func (*Loop) RunStream ¶
func (l *Loop) RunStream(ctx context.Context, messages []Message, onText func(delta string) error) (string, error)
RunStream is the streaming counterpart of Loop.Run (B1): it drives the same tool-use rounds, but when the provider implements StreamingProvider it forwards the assistant's prose deltas to onText as they stream, so the caller can segment and dispatch sentences before the completion finishes. It returns the model's final text, identical to Loop.Run.
onText receives prose deltas from every round in order. Because the loop cannot know in advance whether a round will end in a tool call (that is only certain at the end of the round), a round's prose is forwarded live; for the granted dice Tool the model emits the call with no prose preamble, so in practice only the final answer's prose is spoken. A round that emits a COMPLETE sentence before its tool call would have that sentence forwarded — the caller's sentence splitter only emits on a terminator, so partial preambles are never spoken; a fully-terminated preamble is the documented residual. If the provider does not implement StreamingProvider, RunStream falls back to Loop.Run and forwards the whole final text once.
ctx governs generation and tool execution exactly as Loop.Run; cancelling it (barge-in) aborts the in-flight generation and the loop.
type Message ¶
type Message struct {
Role Role
// Text is the natural-language content. Empty is valid for an assistant
// message that only emitted tool_calls, or a tool-role message that only
// carries ToolResults.
Text string
// ToolCalls are the tool_calls an assistant message emitted. Set only on
// RoleAssistant messages.
ToolCalls []ToolCall
// ToolResults are the executed tool results carried by a RoleTool message,
// one per ToolCall the loop ran. Set only on RoleTool messages.
ToolResults []ToolResult
}
Message is one role-tagged turn in the conversation the Provider sees. Most messages carry only Text. An assistant message that called tools also carries ToolCalls; the loop's tool-role reply carries ToolResults.
type Place ¶ added in v0.5.0
type Place struct {
// Name is the entry's label on that map.
Name string
// Kind is its GM-facing type label ("Location", "NPC", …).
Kind string
// MapName is the map it sits on.
MapName string
// Distance is normalized map units from the query origin; 0 for the origin.
Distance float64
}
Place is one spatial answer: a pinned entry, where it is, and how far. Storage- free, like every other pkg/tool payload.
type ProposalMediated ¶
type ProposalMediated interface {
Tool
// ProposalMediated reports that this Tool's effect is a GM-reviewed proposal,
// not a canon mutation, so the loop may run it inline despite ReadOnly=false.
ProposalMediated() bool
}
ProposalMediated is the optional capability a non-read-only Tool declares to opt out of the loop's hard-refusal of side-effecting Tools (ADR-0052). Its ONLY effect is a GM-reviewed Knowledge Proposal — nothing touches campaign canon until the GM approves — so, unlike a turn-commit effect, it is safe to run inline from a possibly-discarded draft: a barged reply still yields the proposal (the NPC heard the fact), and the GM review is the safety net for anything malformed. A Tool that is not read-only AND not proposal-mediated is still refused inline (the ADR-0030 machinery is unbuilt). remember_knowledge is the sole v1 implementor.
type ProposedWrite ¶
type ProposedWrite struct {
V int `json:"v"`
Kind string `json:"kind"`
NodeID string `json:"node_id,omitempty"`
Subject string `json:"subject,omitempty"`
// AspectKey is the LABEL a kind=fact proposal lands under (#542): approving
// appends the Aspect row (AspectKey, Fact) to the target Node rather than
// rewriting its prose. Always non-empty on a v2 fact — the handler substitutes
// [kgvocab.DefaultAspectKey] when the model names none.
AspectKey string `json:"aspect_key,omitempty"`
Fact string `json:"fact,omitempty"`
Relation string `json:"relation,omitempty"`
Target string `json:"target,omitempty"`
// Note and Disposition are an edge proposal's texture (#546): "I now distrust
// her" after a scene. Optional — an edge with neither is exactly what it was
// before, so no stored payload changes meaning.
Note string `json:"note,omitempty"`
Disposition int `json:"disposition,omitempty"`
NodeType string `json:"node_type,omitempty"`
Name string `json:"name,omitempty"`
Body string `json:"body,omitempty"`
}
ProposedWrite is the versioned, storage-free payload one remember_knowledge call proposes to the Knowledge Graph (#300, ADR-0052). It is a tagged union over Kind ("fact", "edge", "node"); the adapter marshals it to the knowledge_proposal.proposed_write jsonb verbatim, so the field set and json tags ARE the on-disk contract. V is the schema version — always kgvocab.ProposalWriteVersion, currently 2 (#542) — so a shape change is detectable and an older stored row is refused rather than misread. Fields not part of a Kind stay zero and omitempty keeps them out of the jsonb.
Per ADR-0052 a proposal is the ONLY effect of the Tool — nothing touches kg_node/kg_edge until the GM approves in PR-b's review surface. A speculative draft (ADR-0053 ensemble fan-out) that is later discarded may still leave a proposal row; that is ADR-0052-consistent (the NPC heard the fact) and the GM review is the safety net.
type Provider ¶
type Provider interface {
Generate(ctx context.Context, messages []Message, tools []Decl) (AssistantMessage, error)
}
Provider is the LLM seam the tool-use loop drives. Generate runs one generation step: given the conversation so far and the declared (granted) Tools, it returns the model's AssistantMessage — either final text (ToolCalls empty) or one or more tool_calls the loop must execute and feed back before calling Generate again.
The Provider is supplied by the Agent loop's LLM adapter; the framework ships only a scripted fake for its own tests.
type PseudoCall ¶ added in v0.2.1
type PseudoCall struct {
Name string
Args json.RawMessage
}
PseudoCall is one recovered pseudo-XML tool call: the Tool name and the parsed JSON arguments. Args is nil when the wrapper's arguments could not be parsed as JSON — or when the wrapper was unterminated — so the occurrence is stripped from spoken text but the Loop treats it as unrecoverable (logged + metered, never executed). Name is "" when even the name could not be read.
func ExtractPseudoCalls ¶ added in v0.2.1
func ExtractPseudoCalls(text string) (string, []PseudoCall)
ExtractPseudoCalls scans text for pseudo-tool-call syntax, returns the text with every occurrence removed (clean speech/transcript text), and one PseudoCall per occurrence in order. It handles three shapes:
- well-formed `<function=…>…</function>` — parsed for recoverable args;
- an UNTERMINATED `<function=…` opener with no close (truncation / the model forgetting the tag) — stripped from the opener to end of text, Args nil (unrecoverable: the args are incomplete);
- orphan `</function>` closers left behind when a JSON string arg itself contained the literal `</function>` — stripped so no garbage is spoken.
Text with none of these is returned byte-identical with a nil slice. Excision joints are whitespace-collapsed locally (so "Los! <call>" → "Los!") while untouched prose — including newlines in Butler markdown — stays byte-identical. A whole-message pseudo-call yields clean == "".
type Recap ¶ added in v0.2.1
type Recap struct {
// contains filtered or unexported fields
}
Recap is the read-only recap built-in (#372, #297 decision 5): a Butler-flavoured summary of this Campaign's most recent ended Voice Session(s). It wraps the recap service behind the storage-free Recapper seam so pkg/tool never imports the recap engine or storage. The session window is resolved INSIDE the adapter (active campaign → newest ended non-empty rows), never from the LLM args (ADR-0029), so the model can never recap another Campaign's session.
It executes inline (ReadOnly, ADR-0030): the Butler needs the recap prose in hand to relay it in the same turn. It carries no per-grant scope (SupportsScope false): the Campaign comes from the active session, not a grant.
A nil source means the Tool is registered but recap is not wired in this mode (the standalone bench, the grant-editor RPC); Execute then reports it is unavailable rather than panic.
func NewRecap ¶ added in v0.2.1
NewRecap builds the Tool over src. A nil src is allowed — the Tool registers but reports unavailable at Execute time (the zero-Deps modes).
func (*Recap) Description ¶ added in v0.2.1
Description implements Tool.
func (*Recap) Execute ¶ added in v0.2.1
Execute implements Tool. It recaps the active Campaign's most recent ended session(s) and returns the recap prose for the LLM to relay. The session window is resolved inside the adapter (from the active session), never from the args — the model cannot recap another Campaign. grantConfig is ignored (no scope). A nil source yields the unavailable error; the result is rune-truncated to RecapResultBudgetRunes so one recall can't swamp the prompt budget.
func (*Recap) InputSchema ¶ added in v0.2.1
func (*Recap) InputSchema() json.RawMessage
InputSchema implements Tool.
func (*Recap) ReadOnly ¶ added in v0.2.1
ReadOnly implements Tool: a recap reads transcripts and mutates nothing (ADR-0030), so the loop runs it inline.
func (*Recap) SupportsScope ¶ added in v0.2.1
SupportsScope implements Tool: recap is campaign-scoped for everyone (the Campaign comes from the active session, not a grant), so it carries no narrowing config.
type Recapper ¶ added in v0.2.1
Recapper is the narrow read seam the recap Tool needs (#372, #297 decision 5): it summarizes the active Campaign's `sessions` most recent ended, non-empty Voice Sessions and returns the recap prose. Session selection lives ENTIRELY in the adapter — the active Campaign comes from the live session and the rows are the newest ENDED non-empty ones — so the LLM never names a session id (ADR-0029) and can never recap another Campaign. *knowledge.RecapAdapter satisfies it; a nil Recapper on Deps reports unavailable at Execute.
type Registry ¶
type Registry struct {
// contains filtered or unexported fields
}
Registry maps a Tool name to its Tool. It is deliberately dumb (ADR-0028): a map with registration and lookup, no lifecycle ceremony. The API makes no in-process assumption — a future MCP Server registers by enumerating its Tools into this same Registry via Registry.Register, exactly as a built-in does.
A Registry is not safe for concurrent registration; build it once at startup (sequential Registry.Register calls) and treat it as read-only thereafter. Lookup (Registry.Get, [Registry.Declarations]) is then safe to share.
func BuiltinRegistry ¶
BuiltinRegistry returns a Registry holding every built-in Tool (ADR-0028): `dice`, plus the two read-only knowledge Tools `transcript_search` and `kg_query` (#296). It is the single source of truth for "which Tools exist" — the live voice loop (wirenpc), the benchmark rig, and the grant editor RPC (#117) all build from it, so the Tools an operator can toggle on the Campaign screen are exactly the Tools a Voice Session can actually run. When a new built-in lands it is registered here once and appears everywhere.
deps injects the knowledge Tools' read sources (S1). A nil source leaves the Tool REGISTERED — the grant editor's catalog is identical in every mode — but its Execute reports it is unavailable, so a mode with no live retrieval (the standalone bench, the grant-editor RPC) still lists the full Tool set without panicking. dice is registered with a non-deterministic production roller; tests that need a pinned roll construct their own Registry with NewDiceWithRand.
func (*Registry) MustRegister ¶
MustRegister is Registry.Register that panics on error. Convenient for wiring a known-good built-in set at startup.
func (*Registry) Register ¶
Register adds t under its Tool.Name. It returns an error on a nil Tool, an empty name, or a duplicate name — registration failures are startup bugs the caller should surface, not silently overwrite.
func (*Registry) Tools ¶
Tools returns every registered Tool sorted by Tool.Name. This is the full available-Tools catalog — NOT grant-stripped, unlike GrantSet.Declarations — so the grant editor can list every Tool an Agent could be granted with its current grant state (#117). The stable sort keeps the rendered list order deterministic across process restarts.
type RememberKnowledge ¶
type RememberKnowledge struct {
// contains filtered or unexported fields
}
RememberKnowledge is the first side-effecting built-in (#300, ADR-0052): an Agent's proposal that a new fact, relationship, or entry be added to the Knowledge Graph. Its ONLY effect is a pending Knowledge Proposal row for the GM to review — nothing touches campaign canon until the GM approves. It is therefore ProposalMediated and runs inline in the loop despite ReadOnly being false.
The write authority is narrowed per Agent Role via the ADR-0029 grant scope, enforced HERE, never by the LLM:
- own_node (a Character NPC's default, and the fail-closed default): may propose facts on its OWN linked Node and Edges FROM it. The subject and anchor are the caller's own Node (resolved from the turn ctx, not the args), so a crafted subject cannot make an innkeeper propose facts about the distant war. Creating a new entry (kind=node) is refused.
- campaign (the Butler's grant): may propose facts, edges, and brand-new entries anywhere in the Campaign; the subject is taken from the args.
func NewRememberKnowledge ¶
func NewRememberKnowledge(dst KGWriter) *RememberKnowledge
NewRememberKnowledge builds the Tool over dst. A nil dst registers the Tool (the grant editor's catalog is identical in every mode) but its Execute reports it is unavailable rather than panicking.
func (*RememberKnowledge) Description ¶
func (*RememberKnowledge) Description() string
Description implements Tool. It is hardened against the #411 proposal flood: the model is told to remember only genuinely NEW facts and never to re-remember something it already proposed this session (the tool echoes the target's pending proposals back on every call, and silently suppresses exact/normalized repeats).
func (*RememberKnowledge) Execute ¶
func (rk *RememberKnowledge) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
Execute implements Tool. It resolves the effective scope from grantConfig (never the args, fail-closed to own_node), validates the per-kind arguments, builds the ProposedWrite, and records it as a pending proposal. A nil writer reports unavailable; a misconfigured grant fails loudly; a bad argument or a scope violation returns an error result the LLM can read, and — for own_node refusals and unlinked callers — the writer is NEVER called.
func (*RememberKnowledge) InputSchema ¶
func (*RememberKnowledge) InputSchema() json.RawMessage
InputSchema implements Tool.
func (*RememberKnowledge) ProposalMediated ¶
func (*RememberKnowledge) ProposalMediated() bool
ProposalMediated implements ProposalMediated: the only effect is a GM-reviewed proposal, so the loop runs it inline despite ReadOnly=false (ADR-0052).
func (*RememberKnowledge) ReadOnly ¶
func (*RememberKnowledge) ReadOnly() bool
ReadOnly implements Tool: remember_knowledge writes a proposal, so it is not read-only (ADR-0030). It runs inline anyway via ProposalMediated (ADR-0052).
func (*RememberKnowledge) SupportsScope ¶
func (*RememberKnowledge) SupportsScope() bool
SupportsScope implements Tool: the write authority is narrowed per Agent via the grant scope (own_node vs campaign), so the grant editor renders its scope UI (ADR-0029).
type Role ¶
type Role string
Role tags a Message's author in the conversation handed to the LLM.
const ( // RoleSystem is the system prompt (Persona, instructions). RoleSystem Role = "system" // RoleUser is the human / upstream turn. RoleUser Role = "user" // RoleAssistant is the model's own prior output, including any tool_calls // it emitted. RoleAssistant Role = "assistant" // RoleTool is a tool-role result the loop feeds back after executing a // tool_call (its [Message.ToolResults] carry the payloads). RoleTool Role = "tool" )
type SpatialReader ¶ added in v0.5.0
type SpatialReader interface {
// Locate answers "where is this?" for a named entry: every Map within scope it
// is pinned on. An unknown name yields no places and no error — not knowing
// where something is, is a legitimate answer.
Locate(ctx context.Context, agentID, name string, scope SpatialScope) ([]Place, error)
// Nearby answers "what is around us?": Pins within radius of the Party Marker,
// or of the calling Agent's own pinned Node when no marker is set.
Nearby(ctx context.Context, agentID string, radius float64, limit int, scope SpatialScope) ([]Place, error)
}
SpatialReader is the read seam the spatial Tools need. It is a READ ONLY seam with no write anywhere on it, which is what makes these Tools structurally incapable of changing the world.
Both methods are campaign-scoped from the turn's session, never from LLM arguments, and BOTH filter gm_private Maps, Pins and Nodes in the handler-side read — on the same seam-not-call-site principle kgfacts.PromptKG follows. The scope is likewise applied in the read, so no crafted argument can widen it.
type SpatialScope ¶ added in v0.5.0
type SpatialScope int
SpatialScope is the ADR-0029 narrowing the spatial Tools apply, resolved from the Agent's grant config and NEVER from the model's arguments.
It is a real narrowing, not a label: an innkeeper knowing the town's layout is correct, and the same innkeeper reciting the enemy capital's is not. A Tool that answered SupportsScope() true while ignoring the configured scope would be the silent widening ADR-0029 exists to forbid — the grant editor would offer the narrowing, the GM would set it, and nothing would apply it.
const ( // SpatialScopeCampaign reaches every public Map in the Campaign. It is the // default for an unset grant, on the same reasoning kg_query uses for its read // direction: the read is gm_private-filtered either way, so the wider default // is safe, and a WRITE would fail closed instead. SpatialScopeCampaign SpatialScope = iota // SpatialScopeOwnMaps reaches only the Maps the calling Agent's own linked Node // is pinned on — "where I am and what I can see from here". SpatialScopeOwnMaps )
type StreamingProvider ¶
type StreamingProvider interface {
Provider
GenerateStream(ctx context.Context, messages []Message, tools []Decl, onText func(delta string) error) (AssistantMessage, error)
}
StreamingProvider is the optional streaming extension of Provider: a provider that implements it can forward the assistant's prose deltas to onText as they arrive, while still returning the same complete AssistantMessage Generate would. Loop.RunStream uses it to stream the final answer round to TTS (B1) without changing the non-streaming Provider contract every existing caller relies on (ADR-0028).
GenerateStream must call onText in order on the calling goroutine for each prose delta; an error onText returns (a downstream barge-in cancel) aborts the completion promptly and is returned. Tool-call arguments are NOT forwarded to onText — only spoken prose.
type Tool ¶
type Tool interface {
// Name is the stable identifier the LLM uses to call the Tool and the key
// it is registered under. Must be unique within a [Registry].
Name() string
// Description is the natural-language summary declared to the LLM so it
// knows when to call the Tool.
Description() string
// InputSchema is the JSON Schema for the Tool's arguments, declared to the
// LLM and used (by the model) to shape the args it emits. Returning nil or
// an empty schema means "no arguments".
InputSchema() json.RawMessage
// ReadOnly reports whether the Tool only reads state (ADR-0030). Read-only
// Tools (dice, future query_knowledge) execute inline during generation and
// are safe to speculate. A Tool that is *not* read-only must defer its
// effect to turn-commit; that machinery is not built in v1.0, so the loop
// refuses to execute a non-read-only Tool inline rather than mutate state
// from a possibly-discarded draft.
ReadOnly() bool
// SupportsScope reports whether a per-grant Config can NARROW this Tool's
// authority for one Agent (ADR-0029) — the bit the grant editor keys its
// scope UI off. A Tool that supports a scope (a future remember_knowledge
// granted "only about yourself" vs campaign-wide) exposes a scope editor; one
// that does not (dice carries no config) is a plain on/off grant. It is a
// declaration ABOUT the grant config, independent of ReadOnly: the LLM never
// sees the scope, and the handler still enforces whatever Config it receives.
SupportsScope() bool
// Execute runs the Tool with the LLM-supplied args and the caller's
// per-grant config (ADR-0029). grantConfig narrows the Tool's authority for
// this Agent and is enforced here, in the handler, never by the LLM — the
// model cannot widen its scope by crafting clever args. grantConfig is nil
// when the grant carries no config (dice's always is). The returned string
// is fed back to the LLM as the tool-role result. ctx is the turn's
// context: honoring its cancellation lets barge-in tear down an in-flight
// call (ADR-0030).
Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
}
Tool is the single internal interface every callable presents, regardless of backing (ADR-0028). The LLM is shown Tool.Name, Tool.Description, and Tool.InputSchema; the loop calls Tool.Execute when the model emits a matching tool_call.
type ToolCall ¶
type ToolCall struct {
ID string
Name string
Input json.RawMessage
}
ToolCall is one tool invocation the LLM emitted: the Tool to call (Name), the arguments (Input, raw JSON validated against the Tool's input schema by the model), and an ID the provider assigns so the matching ToolResult can be correlated back. The field is named Input to match the Anthropic-native wording and the llm.ToolCall shape on the provider side of the seam (task #2).
type ToolResult ¶
ToolResult is the outcome of executing one ToolCall, fed back to the LLM as part of a RoleTool Message. CallID echoes the ToolCall.ID it answers. IsError marks a failed execution so the model can react rather than treat the error text as data.
type TranscriptHit ¶
TranscriptHit is one persisted transcript Line the knowledge adapter surfaces to transcript_search — a storage-free projection so pkg/tool never sees a storage type. Who is the speaker's display name, Kind the line kind (human utterance vs Agent reply), Text the spoken words, At the wall-clock time.
type TranscriptSearch ¶
type TranscriptSearch struct {
// contains filtered or unexported fields
}
TranscriptSearch is the read-only transcript_search built-in (#296): a relevance search over the active Campaign's persisted transcript (ADR-0011 tsvector path), campaign-scoped inside the adapter. It executes inline (ReadOnly) — the model needs the recalled lines to keep talking ("earlier you promised…"). It carries no per-grant scope: every Agent that holds the grant searches its own Campaign's transcript, no narrowing (SupportsScope false).
A nil source means the Tool is registered but transcript retrieval is not wired in this mode; Execute then reports it is unavailable rather than panic.
func NewTranscriptSearch ¶
func NewTranscriptSearch(src TranscriptSearcher) *TranscriptSearch
NewTranscriptSearch builds the Tool over src. A nil src is allowed — the Tool registers but reports unavailable at Execute time (the standalone bench path).
func (*TranscriptSearch) Description ¶
func (*TranscriptSearch) Description() string
Description implements Tool.
func (*TranscriptSearch) Execute ¶
func (ts *TranscriptSearch) Execute(ctx context.Context, args json.RawMessage, _ any) (string, error)
Execute implements Tool. It searches the active Campaign's transcript for the query and renders the matches as numbered lines the LLM can read back. The Campaign is resolved inside the adapter (from the active session), never from the args — the model cannot search another Campaign. grantConfig is ignored (no scope). A nil source yields the unavailable error; no matches yields a friendly "none" line, not an error.
func (*TranscriptSearch) InputSchema ¶
func (*TranscriptSearch) InputSchema() json.RawMessage
InputSchema implements Tool.
func (*TranscriptSearch) ReadOnly ¶
func (*TranscriptSearch) ReadOnly() bool
ReadOnly implements Tool: a transcript search mutates no state (ADR-0030).
func (*TranscriptSearch) SupportsScope ¶
func (*TranscriptSearch) SupportsScope() bool
SupportsScope implements Tool: transcript_search is campaign-scoped for everyone (the Campaign comes from the active session, not a grant), so it carries no narrowing config.
type TranscriptSearcher ¶
type TranscriptSearcher interface {
SearchTranscript(ctx context.Context, query string, limit int) ([]TranscriptHit, error)
}
TranscriptSearcher is the narrow read transcript_search needs: a relevance search over the active Campaign's persisted transcript. The Campaign is resolved INSIDE the adapter from the active Voice Session (never passed by the LLM), so the model cannot search another Campaign's transcript. A limit ≤ 0 is the adapter's default. *knowledge.Store satisfies it.
type WhatsNearby ¶ added in v0.5.0
type WhatsNearby struct {
// contains filtered or unexported fields
}
WhatsNearby answers "what is around us?" from the Party Marker, or from the calling Agent's own pinned Node when no marker is set (#539).
func NewWhatsNearby ¶ added in v0.5.0
func NewWhatsNearby(src SpatialReader) *WhatsNearby
NewWhatsNearby builds the Tool over the spatial read seam.
func (*WhatsNearby) Description ¶ added in v0.5.0
func (*WhatsNearby) Description() string
Description implements Tool.
func (*WhatsNearby) Execute ¶ added in v0.5.0
func (t *WhatsNearby) Execute(ctx context.Context, args json.RawMessage, grantConfig any) (string, error)
Execute implements Tool.
func (*WhatsNearby) InputSchema ¶ added in v0.5.0
func (*WhatsNearby) InputSchema() json.RawMessage
InputSchema implements Tool.
func (*WhatsNearby) ReadOnly ¶ added in v0.5.0
func (*WhatsNearby) ReadOnly() bool
ReadOnly implements Tool.
func (*WhatsNearby) SupportsScope ¶ added in v0.5.0
func (*WhatsNearby) SupportsScope() bool
SupportsScope implements Tool.