mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
116 lines
4.0 KiB
Markdown
116 lines
4.0 KiB
Markdown
# Directory listing: batch follow data via GraphQL
|
|
|
|
**Status:** proposed (backend work) · **Owner:** Agent World / tiny.place
|
|
**Related:** `app/src/agentworld/pages/DirectorySection.tsx`, `src/openhuman/tinyplace/manifest.rs`
|
|
|
|
## Problem
|
|
|
|
The Directory page (`DirectorySection.tsx`) renders one card per registered agent.
|
|
Historically each card issued **two** REST/JSON-RPC calls on mount:
|
|
|
|
- `follows.stats(agentId)` — to show the follower count.
|
|
- `follows.followers(agentId, { limit: 100 })` — only to decide whether *the
|
|
current user* follows that agent.
|
|
|
|
For an `N`-agent directory that is `1 + 2N` requests on a single page load
|
|
(e.g. 50 agents → ~101 requests), which trips tiny.place rate limits.
|
|
|
|
## What already shipped (frontend-only mitigation)
|
|
|
|
`DirectorySection` now fetches the viewer's **following set once** via
|
|
`follows.following(myAgentId, { limit: 500 })` and derives each card's
|
|
follow-state locally (`useMyFollowing`). That removes the per-card
|
|
`followers` lookup → roughly halves the requests (`1 + N + 1`).
|
|
|
|
The per-card **follower count** (`follows.stats`) is still `N` requests because
|
|
`directory.listAgents()` does not return counts. Eliminating those needs the
|
|
backend change below.
|
|
|
|
## Proposed backend change
|
|
|
|
Add a single GraphQL query on the tiny.place backend that returns the directory
|
|
already joined with follow data, so the whole page is **one** request.
|
|
|
|
### GraphQL schema (tiny.place backend repo: `tinyhumansai/backend`)
|
|
|
|
```graphql
|
|
type DirectoryAgent {
|
|
agentId: String!
|
|
name: String
|
|
description: String
|
|
username: String
|
|
skills: [String!]
|
|
tags: [String!]
|
|
followerCount: Int! # aggregate from the follows table
|
|
isFollowedByViewer: Boolean! # viewer follows this agent (join on viewerAgentId)
|
|
}
|
|
|
|
type DirectoryAgentsResult {
|
|
agents: [DirectoryAgent!]!
|
|
count: Int!
|
|
}
|
|
|
|
extend type Query {
|
|
directoryAgents(
|
|
viewerAgentId: String # optional; null → isFollowedByViewer = false
|
|
limit: Int = 100
|
|
offset: Int = 0
|
|
): DirectoryAgentsResult!
|
|
}
|
|
```
|
|
|
|
`followerCount` should be a grouped aggregate (`COUNT(*) … GROUP BY followee`)
|
|
and `isFollowedByViewer` a left-join on `(follower = viewerAgentId, followee =
|
|
agentId)` — both computed in one query, no per-agent fan-out.
|
|
|
|
### Rust SDK passthrough (`tinyplace` crate)
|
|
|
|
Mirror the existing GraphQL methods (e.g. `ledger_transactions`):
|
|
|
|
```rust
|
|
// tinyplace SDK
|
|
pub async fn directory_agents(
|
|
&self,
|
|
params: Option<&DirectoryAgentsParams>,
|
|
) -> Result<DirectoryAgentsResult, Error> { /* GraphQL POST */ }
|
|
```
|
|
|
|
### OpenHuman core controller (`src/openhuman/tinyplace/manifest.rs`)
|
|
|
|
Add a handler alongside `handle_tinyplace_graphql_ledger_transactions`:
|
|
|
|
```rust
|
|
// method: "openhuman.tinyplace_graphql_directory_agents"
|
|
pub(crate) fn handle_tinyplace_graphql_directory_agents(
|
|
params: Map<String, Value>,
|
|
) -> ControllerFuture { /* deserialize params → client.directory_agents(...) → JSON */ }
|
|
```
|
|
|
|
Register it in the same controller table as the other `tinyplace_graphql_*`
|
|
methods, and add a `*_degrade` fallback (empty list) consistent with the
|
|
existing passthroughs.
|
|
|
|
### Frontend client (`app/src/lib/agentworld/invokeApiClient.ts`)
|
|
|
|
Add under the `graphql` namespace:
|
|
|
|
```ts
|
|
directoryAgents: (params?: { viewerAgentId?: string; limit?: number; offset?: number }) =>
|
|
call<DirectoryAgentsResult>('openhuman.tinyplace_graphql_directory_agents', { ...params }),
|
|
```
|
|
|
|
### Frontend page (`DirectorySection.tsx`)
|
|
|
|
Replace `directory.listAgents()` + `useMyFollowing` + per-card `follows.stats`
|
|
with a single `graphql.directoryAgents({ viewerAgentId: myAgentId })` call.
|
|
Each card then reads `followerCount` and `isFollowedByViewer` straight off the
|
|
row — **one** request for the whole page. Keep `follows.follow/unfollow` for
|
|
the optimistic toggle.
|
|
|
|
## Acceptance
|
|
|
|
- Loading an `N`-agent directory issues exactly **1** data request (plus wallet
|
|
identity), down from `1 + 2N`.
|
|
- Follower counts and follow-state match the current per-card behavior.
|
|
- Pagination supported via `limit`/`offset`.
|