feat: read agent_id from ctx (SDK now plumbs it)

Plexum-sdk-go now unpacks `_meta.agent_id` from host's MCP tools/call
frames into the ctx. Plugin reads it via plugin.AgentIDFromContext
and uses it for backend api-key resolution (agentKeys map lookup +
bearer token) and HF on_call pre-check.

config.defaultAgentID demoted from "v1 stop-gap for the missing SDK
plumbing" to "fallback for operator-driven plugin-call". Normal turn
loops carry the real caller id through ctx now; defaultAgentID only
fires on host-driven dispatch paths.

Also: log every CallTool dispatch with {tool, agent_id} at info level
so operators can see which agent is hitting which tool without
debug-level chatter.

E2E in sim: PLEXUM_ECHO_FORCE_TOOL_USE drives echo provider →
dialectic_list_topics dispatched via host → plugin sees
agent_id=test-agent in ctx + logs it correctly.
This commit is contained in:
h z
2026-06-03 12:54:53 +01:00
parent c5593e3961
commit 3e02a73c05
2 changed files with 23 additions and 13 deletions

View File

@@ -59,18 +59,20 @@ Restart the host afterwards: `systemctl --user restart plexum`.
| `agentKeys` | `{}` | Per-agent bearer token overrides. |
| `defaultAgentID` | — | Agent id reported to the backend when host hasn't surfaced one via tool ctx. |
## How agent identity is resolved (v1 limitation)
## How agent identity is resolved
`Dialectic.OpenclawPlugin` got the calling agent's id via the OpenClaw
framework's `ctx.agentId`. The Plexum SDK doesn't yet surface this on
tool dispatch — same constraint `HarborForge.PlexumPlugin` hits. v1
falls back to `config.defaultAgentID`. Multi-agent claws can configure
`agentKeys` but the bearer token used per call is selected against the
config default (not the true caller), which is fine for a homogeneous-
role claw but won't sort signed verdicts apart by agent.
framework's `ctx.agentId`. The Plexum SDK now propagates the same
information: the host attaches `_meta.agent_id` to every MCP
`tools/call` frame it dispatches on an agent's behalf, and SDK
serve.go unpacks it into the ctx. Plugins read it via
`plugin.AgentIDFromContext(ctx)`.
The fix (deferred): plumb `AgentContext` through `ToolPlugin.CallTool`
in `Plexum-sdk-go`. Once landed, swap `AgentIDFromCtx` to read it.
`config.defaultAgentID` remains as a fallback used only on host paths
that genuinely carry no agent context — operator-driven
`plexum plugin-call` invocations for debugging, etc. Leave it empty
in a normal deployment; tool calls coming from real turn loops will
always carry the caller agent id.
## HF on_call coverage pre-check
@@ -86,7 +88,6 @@ to make the skip explicit (matches the OpenClaw plugin escape hatch).
## Deferred items
- **Per-call agent id** — see "How agent identity is resolved" above.
- **HF window-coverage check** — needs a backend-side endpoint or a
Plexum cross-plugin contract for `harbor-forge` to surface
`HasOnCallCovering(agentID, from, to)`.

View File

@@ -56,9 +56,14 @@ func (p *dialecticPlugin) Init(ctx context.Context, host sdkplugin.HostAPI) erro
Config: p.cfg,
Host: host,
AgentIDFromCtx: func(ctx context.Context) string {
// v1: SDK doesn't surface the calling agent on tool ctx.
// Fall back to the per-claw default. Multi-agent claws will
// need SDK ctx plumbing — tracked upstream.
// Host attaches the caller agent id via tools/call
// `_meta.agent_id`; SDK unpacks it into ctx.
if id := sdkplugin.AgentIDFromContext(ctx); id != "" {
return id
}
// Fallback for host paths that don't carry an agent
// (CLI plugin-call against this plugin from an operator
// debugging the deployment). Empty when not set.
return p.cfg.DefaultAgentID
},
}
@@ -66,6 +71,10 @@ func (p *dialecticPlugin) Init(ctx context.Context, host sdkplugin.HostAPI) erro
}
func (p *dialecticPlugin) CallTool(ctx context.Context, name string, input json.RawMessage) (sdkplugin.ToolResult, error) {
p.host.Log("info", "tools/call dispatched", map[string]any{
"tool": name,
"agent_id": sdkplugin.AgentIDFromContext(ctx),
})
return tools.Dispatch(ctx, p.deps, name, input)
}