Discovery: How an Agent Finds Your APIs Neander, a purpose-built language for agentic systems, makes API discovery a first-class language feature through its 'discover' verb. The language unifies finding and calling APIs into a single 'submitProgram' operation, eliminating the need for separate tool catalogs or documentation scraping. This design allows agents to discover available functions at runtime using the same mechanism as executing work. Last time https://dev.to/newadventuresinit/neander-and-grotto-beyond-code-mode-21im I made the case for why Neander and Grotto exist at all — a purpose-built, safe-by-construction language instead of a sandboxed general-purpose one. That was the argument for the whole language. From here, Field Notes from the Grotto takes it apart one feature at a time, and the first one is the feature that explains why the rest exists: discovery. Classical integration had a shape we all recognize: a developer reads one system's API documentation, then writes integration code that calls it in the right order with the right data. Two steps, one human. Make the calling system agentic — let it decide at runtime what it needs — and the human in the middle vanishes. The two steps do not. They have to land somewhere. The writing step is the half everyone talks about: the agent writes the program instead of the developer. It is the reading step that gets skipped over. Before you can write a line against an API you have to find out what the API even is — what exists, what it takes, what it returns. That was the developer with a browser tab open on the documentation. When the developer leaves, that finding-out does not leave with them; something has to inherit it. That something is discovery. The eager answer is the tool catalog: hand the agent every function definition up front and let it pick. That is discovery too — just total, and paid in advance. It does not scale. Hundreds of definitions clutter the context before the agent has done anything, every intermediate result piles on top, and latency and cost climb with them. Take the pragmatic route from my post Source Code as the Seam Between Systems https://dev.to/newadventuresinit/source-code-as-the-seam-between-systems-5b1p — a general-purpose language the model already writes fluently, its APIs behind a sandbox — and you inherit a quieter awkwardness. That language and those API packages were built for human developers, and human developers read documentation: a reference site, a PDF, a README, a page of generated typedocs. But a running agent is not a human developer. It may not have unrestricted access to the web to search for and reach that reference page at all — and even where it does, the page is shaped for a person: prose, worked examples, a layout to skim, none of it meant to be consumed by an agent. So every system that takes this route has to bolt something on — a meta-tool that lists the available functions, or a search endpoint the agent queries before it writes anything. It works. But it is an appendage: out of band, and reinvented for every stack. Neander makes discovery part of the language itself. discover is a verb you write into a program and submit exactly the same way you submit a program that does real work — and that is the unification worth stressing. In Neander, everything is a program . Finding out what exists and calling it are the same kind of act, in the same language, through the same entry point. The runtime exposes one operation to the agent — submitProgram — and every interaction, whether the agent is asking what is available or getting something done, flows through it. With this, the finding-out moved in-band. What the developer used to do with a browser tab, the agent now does with a program. Almost everything in Neander exists to glue two verbs together. call invokes one of the registered API functions. discover asks the runtime what there is to call. discover has six forms — three things you can look for namespaces, functions, documents crossed with two ways to look search a list, or get one by exact name : discover namespaces "payment" // Namespace — search discover namespace "shipping" // Namespace? — exact lookup of a namespace discover functions ns "estimate", "intl" // Function — search the namespace's functions discover function ns "estimateBatch" // Function? — exact lookup of a function discover documents ns "format" // Document — search the namespace's documents discover document ns "requestFormat" // Document? — exact lookup of a document Search terms are case-insensitive substrings, AND-combined, matched against each candidate's name and description. The empty list matches everything. That is the entire discovery surface. A cold agent does not know your API, so it works inward. The power lies in the sequential writing and execution of code. One program to list namespaces, a second to look for functions inside a namespace, a third to make the function call — reading and learning from the response envelopes the runtime returns as the result of every program submission. php neander 1 { types {} main - Namespace { return discover namespaces } } The runtime answers with a standard envelope — a success flag, the result , and a meta block of telemetry. The return value of type Namespace is the fully serialized list of available namespaces: { "success": true, "result": { "name": "bookings", "description": "Booking management — retrieve, list, confirm, and escalate reservations for the hospitality backend" }, { "name": "guests", "description": "Guest profile lookup — retrieve and list guests for the hospitality backend" }, { "name": "payments", "description": "Payment processing — charge bookings, issue refunds, and look up prior charges" } , "meta": { "thalersConsumed": 1, "memoryConsumedKb": 1, "durationMs": 2, "apiCalls": } } With this list in its context the agent can now further inspect the namespace of interest: php neander 1 { types {} main - Function { let ns: Namespace =? discover namespace "bookings" return discover functions ns } } This time the result contains the list of available functions in this namespace, each function serialized in full only one function shown here : { "success": true, "result": { "qualifiedName": "bookings.get", "description": "Get a booking by its numeric id", "params": { "id": "int" }, "returnType": "bookings.Booking", "errors": { "404": "Booking not found" }, "types": { "bookings.Booking": { "description": "A reservation record with id, fare amount, state, and the guest who placed it", "fields": { "id": "int", "fare": "decimal 2, half away ", "state": "string", "guestId": "int" } } } } , "meta": { "thalersConsumed": 2, "memoryConsumedKb": 1, "durationMs": 3, "apiCalls": } } Every function name listed is the exact string to write next — qualifiedName is what you pass to call , and the types side-table is transitively complete, so the shape of bookings.Booking arrives with the function that returns it. The agent reads it straight out of the JSON response envelope, then writes the program that does the work: php neander 1 { types {} main - decimal 2, half away { let booking: bookings.Booking =? call bookings.get id: 8821 return booking.fare } } Discover, call, return. Everything else in the language is detail. The design choice worth pointing out is that the values discover returns — handles of type Namespace , Function , Document — are opaque . A program can hold one and return it, but it cannot read its fields. The envelope contains what the runtime serializes when the discovery handle is returned from main . Inside the program the handle is a sealed token. Only on the way out does it become readable. What might sound like a restriction is, in fact, a guarantee. A discovery handle can only ever come from discover — you cannot forge one out of a record literal or return it from an API function. So a discovery handle the agent sees is always a handle the runtime minted, pointing at something the runtime actually registered. Discovery is not a convention, it is the only way in. Discovery answers what APIs exist — but one question remains open: how does an agent discover discovery? The disadvantage of a new, purpose-built language like Neander is that a cold-start agent has never heard of it in stark contrast to any well-established general-purpose language. So before the agent can write even its first three-line namespace discovery program, it has to learn the language itself with nothing more at its disposal than the submitProgram operation. If you do not know the language, what is the only program you can write which is not guesswork? The empty program, exactly In the meantime, read the Neander https://newadventuresinit.github.io/neander/ spec, embed Grotto https://github.com/newadventuresinit/grotto in your own app, and let me know what you discovered.