Repository navigation
.NET: [Bug]: CodeAct/Hyperlight: host tool parameter names/schema are never surfaced to the model, calling contract must be hand-written into the tool [Description] #8125
Description
Description
When host tools are registered on HyperlightCodeActProviderOptions.Tools, the model reaches them via call_tool("name", { ... }) inside execute_code. But the instructions injected for execute_code include only each tool's name and description, never its parameter names or JSON schema. As a result, the model cannot know what argument names to pass unless the developer manually encodes the calling contract inside the tool's [Description]. When the contract is omitted, call_tool fails at runtime with a "missing required parameter" error.
This differs from normal function/MCP tool calling, where the full parameter schema (names + per-parameter descriptions) is sent to the model.
Repro (.NET, Microsoft.Agents.AI.Hyperlight 1.15.0-preview.260722.1)
public static class InventoryTools
{
[Description("Gets the stock level (units on hand) of a spare part.")]
public static int GetStock(string partNumber) => partNumber switch
{
"BRG-4410" => 12, "SEAL-220" => 3, "FLT-901" => 47, _ => 0
};
[Description("Gets the unit price in EUR of a spare part.")]
public static decimal GetPrice(string partNumber) => partNumber switch
{
"BRG-4410" => 89.50m, "SEAL-220" => 14.25m, "FLT-901" => 6.80m, _ => 0m
};
}
var options = HyperlightCodeActProviderOptions.CreateForJavaScript();
options.Tools =
[
AIFunctionFactory.Create(InventoryTools.GetStock, "get_stock"),
AIFunctionFactory.Create(InventoryTools.GetPrice, "get_price"),
];
using var codeFunction = new HyperlightExecuteCodeFunction(options);
Console.WriteLine(codeFunction.BuildInstructions()); // inspect what the model sees
The generated execute_code description lists the tools like this. note there is no parameter information at all.
The following host tools are available inside the sandbox via `call_tool("<name>", **kwargs)`:
- `get_stock`: Gets the stock level (units on hand) of a spare part.
- `get_price`: Gets the unit price in EUR of a spare part.
Running a prompt that needs these tools then fails, because the model can't guess the parameter name.
tool 'get_stock': The arguments dictionary is missing a value for the required parameter 'partNumber'.
Workaround — bake the calling contract into the description:
[Description("""
Gets the stock level (units on hand) of a spare part.
Call as: call_tool("get_stock", { partNumber: "BRG-4410" })
""")]
With this, the model emits the correct argument name and the calls succeed.
Code Sample
public static class InventoryTools
{
// NOTE: no calling contract in the description -> model cannot learn the param name
[Description("Gets the stock level (units on hand) of a spare part.")]
public static int GetStock(string partNumber) => partNumber switch
{
"BRG-4410" => 12, "SEAL-220" => 3, "FLT-901" => 47, _ => 0
};
[Description("Gets the unit price in EUR of a spare part.")]
public static decimal GetPrice(string partNumber) => partNumber switch
{
"BRG-4410" => 89.50m, "SEAL-220" => 14.25m, "FLT-901" => 6.80m, _ => 0m
};
}
var options = HyperlightCodeActProviderOptions.CreateForJavaScript();
options.Tools =
[
AIFunctionFactory.Create(InventoryTools.GetStock, "get_stock"),
AIFunctionFactory.Create(InventoryTools.GetPrice, "get_price"),
];
// Inspect exactly what the model is told about the tools:
using var codeFunction = new HyperlightExecuteCodeFunction(options);
Console.WriteLine(codeFunction.BuildInstructions());
// -> lists get_stock / get_price with NAME + DESCRIPTION only. No parameter info.
// Running a prompt that needs these tools then fails (see Error Messages).Error Messages / Stack Traces
tool 'get_stock': The arguments dictionary is missing a value for the required parameter 'partNumber'. (Parameter 'arguments')Package Versions
Microsoft.Agents.AI: 1.15.0 Microsoft.Agents.AI.OpenAI: 1.15.0 Microsoft.Agents.AI.Hyperlight: 1.15.0-preview.260722.1
.NET Version
.NET 10.0
Additional Context
Suspected root cause (from decompiling the shipped 1.15.0-preview.260722.1 DLL): InstructionBuilder.BuildExecuteCodeDescription, in its foreach (var tool in tools) loop, appears to append only tool.Name and tool.Description per tool — I don't see tool.JsonSchema read anywhere in the method. If that's correct, parameter-level [Description] attributes are dropped entirely, leaving the tool-level description as the only channel that reaches the model.
There also seems to be no fallback schema channel: HyperlightExecuteCodeFunction.JsonSchema returns a hardcoded static declaring only execute_code's own code parameter.
{ "type": "object", "properties": { "code": { "type": "string" } }, "required": ["code"] }
so nothing structured about the host tools' parameters would reach the model. Please correct me if I'm misreading the decompiled source.
What the model actually receives from BuildInstructions().
The following host tools are available inside the sandbox via `call_tool("<name>", **kwargs)`:
- `get_stock`: Gets the stock level (units on hand) of a spare part.
- `get_price`: Gets the unit price in EUR of a spare part.
Workaround: encode the calling contract in the description, e.g. Call as: call_tool("get_stock", { partNumber: "BRG-4410" }). With this present, the model emits the right argument name and calls succeed.
Minor doc note: the Hyperlight docs limitation #6 mentions "Tool descriptions, parameter annotations, and return shapes matter more here…", which I initially read as parameter annotations being passed to the model. If the behavior above is correct, that line might be worth a small clarification. The samples all happen to use a single self-evident parameter, so this case doesn't surface in them.
Is the schema is left out deliberately for prompt-size reasons or anything else. Then I'm happy to prepare a focused fix with test coverage serializing each host tool's parameter schema into the execute_code listing if the maintainers are comfortable with that. Please let me know whether this can be assigned to me.
Related: this came out of feedback on my article; a maintainer asked me to file it. See also Discussion #5328.
Metadata
Metadata
Labels
Type
Projects
- StatusShow more project fieldsNo status
Activity
github-actions commentedon Sep 7, 2026
🤖 Automated triage reproduction notes (agent-authored — trust but verify)
Agent analysis
The failure occurs in
dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs::BuildExecuteCodeDescriptionlines 58-75 when a hostAIFunctionwith required parameters is registered. Minimal repro: createget_stockwith a describedpartNumberparameter, confirm itsJsonSchemacontains that metadata, then assert the generated execute-code description contains it; the latter assertion fails.dotnet/tests/Microsoft.Agents.AI.Hyperlight.UnitTests/InstructionBuilderTests.cs1.15.0-preview.260722.11.20.0-previeweavanvalkenburg commentedon Sep 7, 2026
Thanks Pasindu Madusha (@epcm18) I indeed asked for this! We'll have a look and fix!
atty57 commentedon Sep 7, 2026
Eduard van Valkenburg (@eavanvalkenburg) - I'd like to pick this one up if it isn't already in progress on your side.
Scope looks contained: InstructionBuilder.BuildExecuteCodeDescription (dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs) currently emits only tool.Name + tool.Description for each host tool. Each AIFunction already exposes JsonSchema, so the parameter metadata is available at that point , it just isn't rendered. Fix plus a case in InstructionBuilderTests should be a small diff, no public API change.
One call I'd want your steer on before writing it on how the parameters should be rendered:
get_stock(partNumber: string): Gets the stock level... - much cheaper on tokens, but drops per-parameter descriptions and richer constraints.Scope looks contained: InstructionBuilder.BuildExecuteCodeDescription (dotnet/src/Microsoft.Agents.AI.Hyperlight/Internal/InstructionBuilder.cs) currently emits only tool.Name + tool.Description for each host tool. Each AIFunction already exposes JsonSchema, so the parameter metadata is available at that point ,it just isn't rendered. Fix plus a case in InstructionBuilderTests should be a small diff, no public API change.
One call I'd want your steer on before writing it - how the parameters should be rendered
get_stock(partNumber: string): Gets the stock level... drops per-parameter descriptions and richer constraints.I'd lean towards 3 as the default, since it keeps the token cost near 2 while not losing the per-parameter descriptions that developers write. Happy to go with whichever you prefer, or to leave it to you if you've already started.