Agent tools

The memory operations as agent tools: descriptions, schemas, rendered results.

Handing these operations to an LLM takes three things beyond the calls themselves — text telling a model what a tool does and what to pass it, a JSON Schema for its arguments, and results rendered as text a model can read. None of that is specific to an agent framework, and all of it is the SDK’s to get right: the descriptions are the service’s copy, the schemas are this package’s own types, and a caller who writes their own rendering has to keep it in step with every field the contract adds.

So it lives here, reached from the session the tools are bound to, and wiring it into a framework is the only work left:

from memcoai import Memco

with Memco() as client:
    with client.memory.with_session("coding") as session:
        for tool in session.tools():
            tool.name, tool.description, tool.parameters, tool.call

Each tool is bound to the session it was built from, so nothing a model sends can change which session a call is recorded under. What a model sends is untrusted: arguments are validated against the same types the schema was built from, and a bad one is reported the way a bad request is.

examples/langchain_agent.py is the whole of the LangChain wiring.

AGENT_RECOVERABLE: tuple[type[MemcoInvalidRequestError], type[MemcoNotFoundError]] = (<class 'memcoai.errors.MemcoInvalidRequestError'>, <class 'memcoai.errors.MemcoNotFoundError'>)

The failures a model can act on, and the only ones a tool reports as text.

A malformed request it can correct, and a handle that resolves to nothing it can look for elsewhere. Everything else — a rejected or unscoped credential, a timeout, an unreachable service, a spent quota — is raised, because no wording a model reads will fix it and letting it read the failure only invites it to try again.

class AsyncTool(name, description, parameters, call)[source]

Bases: object

One memory operation on an asyncio client. Mirrors Tool.

Variables:
  • name (str) – The name a model calls, such as memco_search.

  • description (str) – What the operation is for, as a model should read it.

  • parameters (dict[str, Any]) – A JSON Schema object describing the arguments.

  • call (collections.abc.Callable[[...], collections.abc.Awaitable[str]]) – Runs the operation and renders the result as text, awaitably.

Parameters:

Example

>>> for tool in session.tools():
...     print(tool.name, tool.parameters["required"])
name: str
description: str
parameters: dict[str, Any]
call: Callable[[...], Awaitable[str]]
class AsyncToolset(iterable=(), /)[source]

Bases: tuple[AsyncTool, …]

Every memory operation as an awaitable tool. Mirrors Toolset.

Example

>>> toolset = session.tools()
>>> runnable = create_agent(model, tools=toolset.to_langchain())
to_langchain()[source]

Hand these tools to LangChain, bound as coroutines.

Returns:

One langchain_core.tools.StructuredTool per operation, each reached with ainvoke.

Raises:

ImportError – If LangChain is not installed.

Return type:

list[Any]

Example

>>> create_agent(model, tools=session.tools().to_langchain())
to_anthropic()[source]

Describe these tools for the Anthropic Messages API.

Returns:

One tool definition per operation, identical to the synchronous ones: a definition says nothing about how the host calls it.

Return type:

list[dict[str, Any]]

Example

>>> await client.messages.create(tools=toolset.to_anthropic(), ...)
to_openai()[source]

Describe these tools for the OpenAI function-calling API.

Returns:

One tool definition per operation.

Return type:

list[dict[str, Any]]

Example

>>> await client.chat.completions.create(tools=toolset.to_openai(), ...)
async call(name, arguments)[source]

Run the tool a model named, and render what it returned.

Parameters:
  • name (str) – The tool name the model used, which is untrusted.

  • arguments (str | Mapping[str, object]) – The arguments it sent, which are untrusted. An object, or the JSON text OpenAI hands back.

Returns:

The result as text, or what the model got wrong. Nothing a model can send reaches the caller as an exception.

Return type:

str

Example

>>> await toolset.call("memco_search", {"query": "how does X work"})
class Tool(name, description, parameters, call)[source]

Bases: object

One memory operation, ready to hand to an agent framework.

Variables:
  • name (str) – The name a model calls, such as memco_search.

  • description (str) – What the operation is for, as a model should read it.

  • parameters (dict[str, Any]) – A JSON Schema object describing the arguments, with a description on each. Every framework this SDK has been used with takes one of these directly.

  • call (collections.abc.Callable[[...], str]) – Runs the operation and renders the result as text. Takes the arguments the schema describes, by keyword.

Parameters:

Example

>>> for tool in session.tools():
...     print(tool.name, tool.parameters["required"])
name: str
description: str
parameters: dict[str, Any]
call: Callable[[...], str]
class Toolset(iterable=(), /)[source]

Bases: tuple[Tool, …]

Every memory operation as a tool, in the shape a framework wants it.

A tuple of Tool, so it iterates and indexes like one. The to_* methods hand the same tools to a particular framework, and call() runs the one a model named — which is what the definition-only shapes need, since there the host dispatches rather than the framework.

Example

>>> toolset = session.tools()
>>> runnable = create_agent(model, tools=toolset.to_langchain())
>>> # or, driving the loop yourself:
>>> response = anthropic.messages.create(tools=toolset.to_anthropic(), ...)
>>> toolset.call(block.name, block.input)
to_langchain()[source]

Hand these tools to LangChain.

LangChain takes a JSON Schema directly, so nothing is restated.

Returns:

One langchain_core.tools.StructuredTool per operation, ready for create_agent(tools=...).

Raises:

ImportError – If LangChain is not installed.

Return type:

list[Any]

Example

>>> create_agent(model, tools=session.tools().to_langchain())
to_anthropic()[source]

Describe these tools for the Anthropic Messages API.

Definitions only — pair them with call() to run what the model asks for.

Returns:

One tool definition per operation.

Return type:

list[dict[str, Any]]

Example

>>> client.messages.create(tools=session.tools().to_anthropic(), ...)
to_openai()[source]

Describe these tools for the OpenAI Chat Completions API.

Definitions only — pair them with call() to run what the model asks for, which also takes the JSON string that API hands back. Anything speaking the same shape takes these too; the Responses API wants a flatter one and is not what this emits.

Returns:

One tool definition per operation.

Return type:

list[dict[str, Any]]

Example

>>> client.chat.completions.create(tools=session.tools().to_openai(), ...)
call(name, arguments)[source]

Run the tool a model named, and render what it returned.

Parameters:
  • name (str) – The tool name the model used, which is untrusted.

  • arguments (str | Mapping[str, object]) – The arguments it sent, which are untrusted. An object, or the JSON text OpenAI hands back.

Returns:

The result as text, or what the model got wrong. Nothing a model can send reaches the caller as an exception.

Return type:

str

Example

>>> toolset.call("memco_search", {"query": "how does X work"})
>>> toolset.call(call.function.name, call.function.arguments)  # OpenAI
briefing(domain, instructions)[source]

Render what the service says about a domain, for a model to be told.

Every word of this comes from list_domains() and from opening the session — what the domain holds, when to draw on it, what belongs in it, and the tag vocabulary it uses. Supply it up front rather than leaving a model to ask: guidance behind a tool only steers the models that reach for it.

Parameters:
  • domain (DomainEntry) – The domain the session was opened in, from list_domains.

  • instructions (Instructions) – What the service said when the session was opened, as carried by the scope’s instructions.

Returns:

The guidance as text, ready to be a system prompt or part of one.

Return type:

str

Example

>>> from memcoai import agent
>>> entry = next(d for d in client.memory.list_domains().domains
...              if d.slug == "coding")
>>> with client.memory.with_session("coding") as session:
...     print(agent.briefing(entry, session.instructions))
render(value)[source]

Render an operation’s result as text a model can read.

Every result carries Instructions — the service’s own guidance on what to do with what came back — and that is included, because a model that never sees it is left to guess.

Parameters:

value (SearchResult[Any] | Memory | AsyncMemory | WriteResult | FeedbackResult | RevertResult) – Any result a memory operation returns.

Returns:

The result as text.

Raises:

TypeError – If the value is not a result type this renders.

Return type:

str

Example

>>> from memcoai import agent
>>> print(agent.render(session.search("how does X work")))

The tools themselves are built from the session they are bound to: memcoai.operations.Session.tools() and memcoai.operations.AsyncSession.tools().