Skip to content

fix: Agents REST list endpoint — merge owned+granted agents, fix N+1 query #994

@chubes4

Description

@chubes4

Summary

GET /datamachine/v1/agents has two bugs in the non-admin code path that block multi-agent frontend chat.

Related: #993 (DB primitives), #972 (per-user toolsets)

Bug 1: Non-admin path omits agents the user OWNS

File: inc/Api/Agents.php lines 312-358

The non-admin branch only queries AgentAccess::get_agent_ids_for_user():

} else {
    $access_repo    = new AgentAccess();
    $accessible_ids = $access_repo->get_agent_ids_for_user($user_id);
    // ...loops through IDs...
}

If an owner's access grant is missing from agent_access (race condition, migration gap, manual DB edit), they won't see their own agent. The owner's relationship lives on datamachine_agents.owner_id, not in agent_access.

Fix

Merge owned agents with access-granted agents:

} else {
    $access_repo    = new AgentAccess();
    $accessible_ids = $access_repo->get_agent_ids_for_user($user_id);
    $owned_agents   = $agents_repo->get_all_by_owner_id($user_id); // from #993
    $owned_ids      = array_column($owned_agents, 'agent_id');
    $all_ids        = array_unique(array_merge(array_map('intval', $owned_ids), $accessible_ids));
    $all_agents     = $agents_repo->get_agents_by_ids($all_ids);   // from #993
    // ...site_scope filter...
}

Bug 2: N+1 query pattern

The non-admin path calls get_agent_ids_for_user() (1 query), then get_agent() in a loop (N queries). For a user with access to 20 agents, that's 21 queries.

Fix

Use get_agents_by_ids() (batch method from #993) for a single query.

Enhancement: Add user_role to list response

shape_list_item() returns agent metadata but not the requesting user's role (viewer/operator/admin). The frontend needs this for UI decisions (show edit button, show config, etc.).

Needed

Enrich the response with the user's role from agent_access:

private static function shape_list_item(array $agent, ?string $user_role = null): array {
    $item = [
        'agent_id'   => (int) $agent['agent_id'],
        'agent_slug' => (string) $agent['agent_slug'],
        'agent_name' => (string) $agent['agent_name'],
        'owner_id'   => (int) $agent['owner_id'],
        'site_scope' => isset($agent['site_scope']) ? (int) $agent['site_scope'] : null,
        'status'     => (string) $agent['status'],
        'created_at' => $agent['created_at'] ?? '',
        'updated_at' => $agent['updated_at'] ?? '',
    ];
    if ($user_role !== null) {
        $item['user_role'] = $user_role;
    }
    return $item;
}

Checklist

  • Merge owned + access-granted agents in non-admin handle_list() path
  • Replace N+1 get_agent() loop with batch get_agents_by_ids()
  • Add user_role to list response shape

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