Skip to content

Expose tool invocation identity through delegating-agent middleware #949

Description

@hlgone

Summary

Expose the current tool invocation identity, including the function call ID, through the delegating-agent middleware design proposed by the maintainers in #638.

This should preserve the idiomatic FuncTool.Call(context.Context, args) API introduced by #205. The goal is not to restore the removed custom tool.Context, but to provide a supported per-invocation interception contract for policy, approval, audit, and gateway integrations.

Motivation

The automatic tool-call loop already has the originating message.FunctionCallContent and uses its CallID to correlate approval requests, function results, and OpenTelemetry spans. However, application middleware and tool wrappers do not currently have a supported public contract for that identity.

Enterprise tool gateways commonly need to bind:

  • a policy decision to the exact logical tool call;
  • an approval request and its resumed execution;
  • normalized argument digests and consume-once records;
  • audit events and function results;
  • parallel calls to separate correlation records.

Passing this identity as a model-generated tool argument is not appropriate because it mixes framework metadata with model-controlled business arguments.

Existing direction

PR #638 demonstrated the need for per-function interception. It was closed with maintainer guidance that the capability should be implemented as an agent.Middleware that replaces real tools with lifecycle-aware wrappers, similar to the .NET delegating-agent design.

This proposal follows that direction.

Proposed contract

A delegating-agent middleware should be able to observe a per-invocation context containing at least:

  • the invoked tool;
  • the original function-call content or an explicit CallID;
  • the normalized invocation arguments;
  • the invocation result or error;
  • the current context.Context.

The exact API shape can follow the wrapper approach preferred in #638. Two possible non-exclusive surfaces are:

  1. a public function-invocation context passed to the wrapper callback;
  2. a typed accessor such as tool.InvocationFromContext(ctx) when the wrapped FuncTool itself needs the metadata.

The second option should keep using standard context.Context; it should not reintroduce a custom context parameter.

Semantics to document

  • The call ID identifies one logical tool invocation.
  • The same approved invocation should expose the same call ID when execution resumes.
  • Parallel invocations must expose distinct IDs.
  • Provider behavior when no call ID is supplied should be documented, including whether the framework creates a synthetic ID.
  • The call ID is a correlation identifier, not a cross-run idempotency key.
  • Authorization must not rely on the call ID alone.

Suggested acceptance tests

  1. A delegating middleware observes the call ID before invoking a normal tool.
  2. An approval-required call exposes the same call ID before approval and during approved execution.
  3. Two parallel calls to the same tool expose distinct call IDs.
  4. A value added to context.Context by the wrapper reaches FuncTool.Call.
  5. With no delegating middleware configured, current tool-call behavior remains unchanged.
  6. Function results retain the same call ID observed by the middleware.

I would be happy to contribute a focused implementation and tests after the API shape is confirmed.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions