feat: align agent/microdao model - add is_orchestrator, is_platform, hierarchy
This commit is contained in:
139
docs/internal/architecture/DAARION_AGENT_MICRODAO_MODEL_v1.md
Normal file
139
docs/internal/architecture/DAARION_AGENT_MICRODAO_MODEL_v1.md
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
# DAARION Agent & MicroDAO Model v1
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
This document describes the unified data model for Agents and MicroDAOs in the DAARION ecosystem.
|
||||||
|
|
||||||
|
## Core Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Node → Agent → MicroDAO
|
||||||
|
↓
|
||||||
|
Platform (District)
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Node**: Physical/virtual infrastructure where agents run (NODE1, NODE2)
|
||||||
|
- **Agent**: AI entity with identity, capabilities, and affiliations
|
||||||
|
- **MicroDAO**: Organization/community of agents with shared goals
|
||||||
|
- **Platform/District**: Top-level MicroDAO that acts as a category/district
|
||||||
|
|
||||||
|
## Agent Model
|
||||||
|
|
||||||
|
### Key Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `id` | text | Unique identifier |
|
||||||
|
| `slug` | text | URL-friendly identifier |
|
||||||
|
| `display_name` | text | Human-readable name |
|
||||||
|
| `kind` | text | Type: orchestrator, security, marketing, etc. |
|
||||||
|
| `node_id` | text | Home node where agent runs |
|
||||||
|
| `is_public` | boolean | Visible in public Citizens catalog |
|
||||||
|
| `visibility_scope` | text | Access level: global, microdao, private |
|
||||||
|
| `is_orchestrator` | boolean | Can create/manage microDAOs |
|
||||||
|
| `primary_microdao_id` | text | Primary organization affiliation |
|
||||||
|
|
||||||
|
### Visibility Scope Values
|
||||||
|
|
||||||
|
- **global**: Visible to everyone in the city
|
||||||
|
- **microdao**: Visible only to MicroDAO members
|
||||||
|
- **private**: Visible only to owner/admin
|
||||||
|
|
||||||
|
### Agent Types by Kind
|
||||||
|
|
||||||
|
- `orchestrator`: MicroDAO leaders, can manage organizations
|
||||||
|
- `security`: Security and audit agents
|
||||||
|
- `marketing`: Marketing and communication agents
|
||||||
|
- `developer`: Development and technical agents
|
||||||
|
- `research`: Research and analysis agents
|
||||||
|
- `finance`: Financial management agents
|
||||||
|
- `system`: Infrastructure and monitoring agents
|
||||||
|
|
||||||
|
## MicroDAO Model
|
||||||
|
|
||||||
|
### Key Fields
|
||||||
|
|
||||||
|
| Field | Type | Description |
|
||||||
|
|-------|------|-------------|
|
||||||
|
| `id` | text | Unique identifier |
|
||||||
|
| `slug` | text | URL-friendly identifier |
|
||||||
|
| `name` | text | Display name |
|
||||||
|
| `is_public` | boolean | Visible in public services |
|
||||||
|
| `is_platform` | boolean | Is a platform/district (top-level) |
|
||||||
|
| `orchestrator_agent_id` | text | Main orchestrator agent |
|
||||||
|
| `parent_microdao_id` | text | Parent for hierarchy |
|
||||||
|
| `district` | text | District/category name |
|
||||||
|
|
||||||
|
### MicroDAO Types
|
||||||
|
|
||||||
|
- **Platform** (`is_platform = true`): Top-level organizational unit (district)
|
||||||
|
- **Regular** (`is_platform = false`): Standard MicroDAO under a platform
|
||||||
|
|
||||||
|
### Hierarchy
|
||||||
|
|
||||||
|
```
|
||||||
|
Platform (District)
|
||||||
|
├── MicroDAO 1
|
||||||
|
│ ├── Agent A (orchestrator)
|
||||||
|
│ ├── Agent B (member)
|
||||||
|
│ └── Agent C (member)
|
||||||
|
└── MicroDAO 2
|
||||||
|
├── Agent D (orchestrator)
|
||||||
|
└── Agent E (member)
|
||||||
|
```
|
||||||
|
|
||||||
|
## UI Mapping
|
||||||
|
|
||||||
|
### Agent Console (`/agents`)
|
||||||
|
- Technical view of all agents
|
||||||
|
- Shows: node_id, visibility_scope, is_orchestrator
|
||||||
|
- Filters: kind, node_id, microdao_id, is_public
|
||||||
|
|
||||||
|
### Citizens (`/citizens`)
|
||||||
|
- Public view of agents (`is_public = true`)
|
||||||
|
- Shows: display_name, title, tagline, skills
|
||||||
|
- Filters: district, kind, search
|
||||||
|
|
||||||
|
### MicroDAO Dashboard (`/microdao`)
|
||||||
|
- Organization management
|
||||||
|
- Shows: member_count, orchestrator, channels
|
||||||
|
- Filters: district, is_platform, is_public
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### Agents
|
||||||
|
```
|
||||||
|
GET /city/agents
|
||||||
|
?kind=orchestrator
|
||||||
|
&node_id=node-1-hetzner-gex44
|
||||||
|
µdao_id=dao_daarion
|
||||||
|
&is_public=true
|
||||||
|
&visibility_scope=global
|
||||||
|
&include_system=false
|
||||||
|
```
|
||||||
|
|
||||||
|
### MicroDAOs
|
||||||
|
```
|
||||||
|
GET /city/microdao
|
||||||
|
?district=Core
|
||||||
|
&is_public=true
|
||||||
|
&is_platform=false
|
||||||
|
&q=search
|
||||||
|
|
||||||
|
GET /city/microdao/{slug}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Important Notes
|
||||||
|
|
||||||
|
1. **Citizen ≠ separate entity**: Citizens are just public agents (`is_public = true`)
|
||||||
|
2. **Every agent needs MicroDAO**: Active agents must belong to at least one MicroDAO
|
||||||
|
3. **Orchestrators**: Agents with `is_orchestrator = true` can manage MicroDAOs
|
||||||
|
4. **Soft delete**: Use `is_archived`, `is_test`, `deleted_at` instead of hard delete
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- Models: `services/city-service/models_city.py`
|
||||||
|
- Repository: `services/city-service/repo_city.py`
|
||||||
|
- Routes: `services/city-service/routes_city.py`
|
||||||
|
- Migrations: `migrations/026_align_agent_microdao_model.sql`
|
||||||
|
|
||||||
91
migrations/026_align_agent_microdao_model.sql
Normal file
91
migrations/026_align_agent_microdao_model.sql
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
-- Migration: Align Agent/MicroDAO model
|
||||||
|
-- Purpose: Standardize fields for Agent Console, Citizens, MicroDAO Dashboard
|
||||||
|
-- Date: 2025-11-28
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- AGENTS TABLE
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Add is_orchestrator flag (agent can create/manage microDAOs)
|
||||||
|
ALTER TABLE agents
|
||||||
|
ADD COLUMN IF NOT EXISTS is_orchestrator boolean NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- Create index for orchestrator lookup
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agents_is_orchestrator ON agents(is_orchestrator) WHERE is_orchestrator = true;
|
||||||
|
|
||||||
|
-- Update existing orchestrators based on kind
|
||||||
|
UPDATE agents
|
||||||
|
SET is_orchestrator = true
|
||||||
|
WHERE kind = 'orchestrator'
|
||||||
|
AND is_orchestrator = false;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- MICRODAOS TABLE
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
-- Add is_platform flag (microDAO is a platform/district)
|
||||||
|
ALTER TABLE microdaos
|
||||||
|
ADD COLUMN IF NOT EXISTS is_platform boolean NOT NULL DEFAULT false;
|
||||||
|
|
||||||
|
-- Add orchestrator_agent_id as alias/copy of owner_agent_id for clarity
|
||||||
|
-- (keeping owner_agent_id for backward compatibility)
|
||||||
|
ALTER TABLE microdaos
|
||||||
|
ADD COLUMN IF NOT EXISTS orchestrator_agent_id text;
|
||||||
|
|
||||||
|
-- Copy owner_agent_id to orchestrator_agent_id where not set
|
||||||
|
UPDATE microdaos
|
||||||
|
SET orchestrator_agent_id = owner_agent_id
|
||||||
|
WHERE orchestrator_agent_id IS NULL
|
||||||
|
AND owner_agent_id IS NOT NULL;
|
||||||
|
|
||||||
|
-- Add parent_microdao_id for hierarchy (platform -> child microDAOs)
|
||||||
|
ALTER TABLE microdaos
|
||||||
|
ADD COLUMN IF NOT EXISTS parent_microdao_id text;
|
||||||
|
|
||||||
|
-- Create indexes
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_microdaos_is_platform ON microdaos(is_platform) WHERE is_platform = true;
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_microdaos_orchestrator ON microdaos(orchestrator_agent_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_microdaos_parent ON microdaos(parent_microdao_id);
|
||||||
|
|
||||||
|
-- Add foreign key for parent_microdao_id (self-reference)
|
||||||
|
-- Note: Using DO block to handle if constraint already exists
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'microdaos_parent_fk'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE microdaos
|
||||||
|
ADD CONSTRAINT microdaos_parent_fk
|
||||||
|
FOREIGN KEY (parent_microdao_id)
|
||||||
|
REFERENCES microdaos(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- Add foreign key for orchestrator_agent_id
|
||||||
|
DO $$
|
||||||
|
BEGIN
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1 FROM pg_constraint WHERE conname = 'microdaos_orchestrator_agent_fk'
|
||||||
|
) THEN
|
||||||
|
ALTER TABLE microdaos
|
||||||
|
ADD CONSTRAINT microdaos_orchestrator_agent_fk
|
||||||
|
FOREIGN KEY (orchestrator_agent_id)
|
||||||
|
REFERENCES agents(id)
|
||||||
|
ON DELETE SET NULL;
|
||||||
|
END IF;
|
||||||
|
END $$;
|
||||||
|
|
||||||
|
-- ============================================================================
|
||||||
|
-- COMMENTS
|
||||||
|
-- ============================================================================
|
||||||
|
|
||||||
|
COMMENT ON COLUMN agents.is_orchestrator IS 'Agent can create and manage microDAOs';
|
||||||
|
COMMENT ON COLUMN agents.visibility_scope IS 'Visibility: global (everyone), microdao (members only), private (owner only)';
|
||||||
|
COMMENT ON COLUMN agents.is_public IS 'Agent visible in public Citizens catalog';
|
||||||
|
COMMENT ON COLUMN agents.primary_microdao_id IS 'Primary microDAO affiliation';
|
||||||
|
|
||||||
|
COMMENT ON COLUMN microdaos.is_platform IS 'MicroDAO is a platform/district (top-level organization)';
|
||||||
|
COMMENT ON COLUMN microdaos.orchestrator_agent_id IS 'Main orchestrator agent for this microDAO';
|
||||||
|
COMMENT ON COLUMN microdaos.parent_microdao_id IS 'Parent microDAO for hierarchy (platform -> child)';
|
||||||
|
|
||||||
@@ -233,6 +233,8 @@ class MicrodaoBadge(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
slug: Optional[str] = None
|
slug: Optional[str] = None
|
||||||
role: Optional[str] = None # orchestrator, member, etc.
|
role: Optional[str] = None # orchestrator, member, etc.
|
||||||
|
is_public: bool = True
|
||||||
|
is_platform: bool = False
|
||||||
|
|
||||||
|
|
||||||
class AgentSummary(BaseModel):
|
class AgentSummary(BaseModel):
|
||||||
@@ -251,11 +253,12 @@ class AgentSummary(BaseModel):
|
|||||||
node_label: Optional[str] = None # "НОДА1" / "НОДА2"
|
node_label: Optional[str] = None # "НОДА1" / "НОДА2"
|
||||||
home_node: Optional[HomeNodeView] = None
|
home_node: Optional[HomeNodeView] = None
|
||||||
|
|
||||||
# Visibility
|
# Visibility & roles
|
||||||
visibility_scope: str = "city" # city, microdao, owner_only
|
visibility_scope: str = "city" # global, microdao, private
|
||||||
is_listed_in_directory: bool = True
|
is_listed_in_directory: bool = True
|
||||||
is_system: bool = False
|
is_system: bool = False
|
||||||
is_public: bool = False # backward compatibility
|
is_public: bool = False
|
||||||
|
is_orchestrator: bool = False # Can create/manage microDAOs
|
||||||
|
|
||||||
# MicroDAO
|
# MicroDAO
|
||||||
primary_microdao_id: Optional[str] = None
|
primary_microdao_id: Optional[str] = None
|
||||||
@@ -354,12 +357,27 @@ class MicrodaoSummary(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
district: Optional[str] = None
|
district: Optional[str] = None
|
||||||
|
|
||||||
|
# Visibility & type
|
||||||
|
is_public: bool = True
|
||||||
|
is_platform: bool = False # Is a platform/district
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
# Orchestrator
|
||||||
orchestrator_agent_id: Optional[str] = None
|
orchestrator_agent_id: Optional[str] = None
|
||||||
is_active: bool
|
orchestrator_agent_name: Optional[str] = None
|
||||||
|
|
||||||
|
# Hierarchy
|
||||||
|
parent_microdao_id: Optional[str] = None
|
||||||
|
parent_microdao_slug: Optional[str] = None
|
||||||
|
|
||||||
|
# Stats
|
||||||
logo_url: Optional[str] = None
|
logo_url: Optional[str] = None
|
||||||
agents_count: int
|
member_count: int = 0 # alias for agents_count
|
||||||
rooms_count: int
|
agents_count: int = 0 # backward compatibility
|
||||||
channels_count: int
|
room_count: int = 0 # alias for rooms_count
|
||||||
|
rooms_count: int = 0 # backward compatibility
|
||||||
|
channels_count: int = 0
|
||||||
|
|
||||||
|
|
||||||
class MicrodaoChannelView(BaseModel):
|
class MicrodaoChannelView(BaseModel):
|
||||||
@@ -385,13 +403,25 @@ class MicrodaoDetail(BaseModel):
|
|||||||
name: str
|
name: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
district: Optional[str] = None
|
district: Optional[str] = None
|
||||||
|
|
||||||
|
# Visibility & type
|
||||||
|
is_public: bool = True
|
||||||
|
is_platform: bool = False
|
||||||
|
is_active: bool = True
|
||||||
|
|
||||||
|
# Orchestrator
|
||||||
orchestrator_agent_id: Optional[str] = None
|
orchestrator_agent_id: Optional[str] = None
|
||||||
orchestrator_display_name: Optional[str] = None
|
orchestrator_display_name: Optional[str] = None
|
||||||
is_active: bool
|
|
||||||
is_public: bool
|
# Hierarchy
|
||||||
|
parent_microdao_id: Optional[str] = None
|
||||||
|
parent_microdao_slug: Optional[str] = None
|
||||||
|
child_microdaos: List["MicrodaoSummary"] = []
|
||||||
|
|
||||||
|
# Content
|
||||||
logo_url: Optional[str] = None
|
logo_url: Optional[str] = None
|
||||||
agents: List[MicrodaoAgentView]
|
agents: List[MicrodaoAgentView] = []
|
||||||
channels: List[MicrodaoChannelView]
|
channels: List[MicrodaoChannelView] = []
|
||||||
public_citizens: List[MicrodaoCitizenView] = []
|
public_citizens: List[MicrodaoCitizenView] = []
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -295,6 +295,8 @@ async def get_rooms_for_map() -> List[dict]:
|
|||||||
async def list_agent_summaries(
|
async def list_agent_summaries(
|
||||||
*,
|
*,
|
||||||
node_id: Optional[str] = None,
|
node_id: Optional[str] = None,
|
||||||
|
microdao_id: Optional[str] = None,
|
||||||
|
is_public: Optional[bool] = None,
|
||||||
visibility_scope: Optional[str] = None,
|
visibility_scope: Optional[str] = None,
|
||||||
listed_only: Optional[bool] = None,
|
listed_only: Optional[bool] = None,
|
||||||
kinds: Optional[List[str]] = None,
|
kinds: Optional[List[str]] = None,
|
||||||
@@ -322,6 +324,14 @@ async def list_agent_summaries(
|
|||||||
params.append(node_id)
|
params.append(node_id)
|
||||||
where_clauses.append(f"a.node_id = ${len(params)}")
|
where_clauses.append(f"a.node_id = ${len(params)}")
|
||||||
|
|
||||||
|
if microdao_id:
|
||||||
|
params.append(microdao_id)
|
||||||
|
where_clauses.append(f"EXISTS (SELECT 1 FROM microdao_agents ma WHERE ma.agent_id = a.id AND ma.microdao_id = ${len(params)})")
|
||||||
|
|
||||||
|
if is_public is not None:
|
||||||
|
params.append(is_public)
|
||||||
|
where_clauses.append(f"COALESCE(a.is_public, false) = ${len(params)}")
|
||||||
|
|
||||||
if visibility_scope:
|
if visibility_scope:
|
||||||
params.append(visibility_scope)
|
params.append(visibility_scope)
|
||||||
where_clauses.append(f"COALESCE(a.visibility_scope, 'city') = ${len(params)}")
|
where_clauses.append(f"COALESCE(a.visibility_scope, 'city') = ${len(params)}")
|
||||||
@@ -359,6 +369,7 @@ async def list_agent_summaries(
|
|||||||
COALESCE(a.is_listed_in_directory, true) AS is_listed_in_directory,
|
COALESCE(a.is_listed_in_directory, true) AS is_listed_in_directory,
|
||||||
COALESCE(a.is_system, false) AS is_system,
|
COALESCE(a.is_system, false) AS is_system,
|
||||||
COALESCE(a.is_public, false) AS is_public,
|
COALESCE(a.is_public, false) AS is_public,
|
||||||
|
COALESCE(a.is_orchestrator, false) AS is_orchestrator,
|
||||||
a.primary_microdao_id,
|
a.primary_microdao_id,
|
||||||
pm.name AS primary_microdao_name,
|
pm.name AS primary_microdao_name,
|
||||||
pm.slug AS primary_microdao_slug,
|
pm.slug AS primary_microdao_slug,
|
||||||
@@ -1246,17 +1257,26 @@ async def get_microdaos(district: Optional[str] = None, q: Optional[str] = None,
|
|||||||
m.name,
|
m.name,
|
||||||
m.description,
|
m.description,
|
||||||
m.district,
|
m.district,
|
||||||
m.owner_agent_id as orchestrator_agent_id,
|
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||||
|
oa.display_name as orchestrator_agent_name,
|
||||||
m.is_active,
|
m.is_active,
|
||||||
|
COALESCE(m.is_public, true) as is_public,
|
||||||
|
COALESCE(m.is_platform, false) as is_platform,
|
||||||
|
m.parent_microdao_id,
|
||||||
|
pm.slug as parent_microdao_slug,
|
||||||
m.logo_url,
|
m.logo_url,
|
||||||
COUNT(DISTINCT ma.agent_id) AS agents_count,
|
COUNT(DISTINCT ma.agent_id) AS agents_count,
|
||||||
|
COUNT(DISTINCT ma.agent_id) AS member_count,
|
||||||
COUNT(DISTINCT mc.id) AS channels_count,
|
COUNT(DISTINCT mc.id) AS channels_count,
|
||||||
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count
|
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count,
|
||||||
|
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS room_count
|
||||||
FROM microdaos m
|
FROM microdaos m
|
||||||
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
|
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
|
||||||
LEFT JOIN microdao_channels mc ON mc.microdao_id = m.id
|
LEFT JOIN microdao_channels mc ON mc.microdao_id = m.id
|
||||||
|
LEFT JOIN agents oa ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = oa.id
|
||||||
|
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||||
WHERE {where_sql}
|
WHERE {where_sql}
|
||||||
GROUP BY m.id
|
GROUP BY m.id, oa.display_name, pm.slug
|
||||||
ORDER BY m.name
|
ORDER BY m.name
|
||||||
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||||
"""
|
"""
|
||||||
@@ -1269,6 +1289,93 @@ async def get_microdaos(district: Optional[str] = None, q: Optional[str] = None,
|
|||||||
return [dict(row) for row in rows]
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def list_microdao_summaries(
|
||||||
|
*,
|
||||||
|
is_public: Optional[bool] = None,
|
||||||
|
is_platform: Optional[bool] = None,
|
||||||
|
district: Optional[str] = None,
|
||||||
|
q: Optional[str] = None,
|
||||||
|
limit: int = 50,
|
||||||
|
offset: int = 0
|
||||||
|
) -> List[dict]:
|
||||||
|
"""
|
||||||
|
Unified method to list microDAOs.
|
||||||
|
Wraps get_microdaos with additional filtering.
|
||||||
|
"""
|
||||||
|
pool = await get_pool()
|
||||||
|
|
||||||
|
params = []
|
||||||
|
where_clauses = [
|
||||||
|
"COALESCE(m.is_archived, false) = false",
|
||||||
|
"COALESCE(m.is_test, false) = false",
|
||||||
|
"m.deleted_at IS NULL",
|
||||||
|
"m.is_active = true"
|
||||||
|
]
|
||||||
|
|
||||||
|
if is_public is not None:
|
||||||
|
params.append(is_public)
|
||||||
|
where_clauses.append(f"COALESCE(m.is_public, true) = ${len(params)}")
|
||||||
|
|
||||||
|
if is_platform is not None:
|
||||||
|
params.append(is_platform)
|
||||||
|
where_clauses.append(f"COALESCE(m.is_platform, false) = ${len(params)}")
|
||||||
|
|
||||||
|
if district:
|
||||||
|
params.append(district)
|
||||||
|
where_clauses.append(f"m.district = ${len(params)}")
|
||||||
|
|
||||||
|
if q:
|
||||||
|
params.append(f"%{q}%")
|
||||||
|
where_clauses.append(f"(m.name ILIKE ${len(params)} OR m.description ILIKE ${len(params)})")
|
||||||
|
|
||||||
|
where_sql = " AND ".join(where_clauses)
|
||||||
|
|
||||||
|
query = f"""
|
||||||
|
SELECT
|
||||||
|
m.id,
|
||||||
|
m.slug,
|
||||||
|
m.name,
|
||||||
|
m.description,
|
||||||
|
m.district,
|
||||||
|
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||||
|
oa.display_name as orchestrator_agent_name,
|
||||||
|
m.is_active,
|
||||||
|
COALESCE(m.is_public, true) as is_public,
|
||||||
|
COALESCE(m.is_platform, false) as is_platform,
|
||||||
|
m.parent_microdao_id,
|
||||||
|
pm.slug as parent_microdao_slug,
|
||||||
|
m.logo_url,
|
||||||
|
COUNT(DISTINCT ma.agent_id) AS agents_count,
|
||||||
|
COUNT(DISTINCT ma.agent_id) AS member_count,
|
||||||
|
COUNT(DISTINCT mc.id) AS channels_count,
|
||||||
|
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS rooms_count,
|
||||||
|
COUNT(DISTINCT CASE WHEN mc.kind = 'city_room' THEN mc.id END) AS room_count
|
||||||
|
FROM microdaos m
|
||||||
|
LEFT JOIN microdao_agents ma ON ma.microdao_id = m.id
|
||||||
|
LEFT JOIN microdao_channels mc ON mc.microdao_id = m.id
|
||||||
|
LEFT JOIN agents oa ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = oa.id
|
||||||
|
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||||
|
WHERE {where_sql}
|
||||||
|
GROUP BY m.id, oa.display_name, pm.slug
|
||||||
|
ORDER BY m.name
|
||||||
|
LIMIT ${len(params) + 1} OFFSET ${len(params) + 2}
|
||||||
|
"""
|
||||||
|
|
||||||
|
params.append(limit)
|
||||||
|
params.append(offset)
|
||||||
|
|
||||||
|
rows = await pool.fetch(query, *params)
|
||||||
|
return [dict(row) for row in rows]
|
||||||
|
|
||||||
|
|
||||||
|
async def get_microdao_detail(slug: str) -> Optional[dict]:
|
||||||
|
"""
|
||||||
|
Get detailed microDAO info including agents, channels, children.
|
||||||
|
Alias for get_microdao_by_slug with clearer naming.
|
||||||
|
"""
|
||||||
|
return await get_microdao_by_slug(slug)
|
||||||
|
|
||||||
|
|
||||||
async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
||||||
"""Отримати детальну інформацію про MicroDAO"""
|
"""Отримати детальну інформацію про MicroDAO"""
|
||||||
pool = await get_pool()
|
pool = await get_pool()
|
||||||
@@ -1281,14 +1388,21 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
|||||||
m.name,
|
m.name,
|
||||||
m.description,
|
m.description,
|
||||||
m.district,
|
m.district,
|
||||||
m.owner_agent_id as orchestrator_agent_id,
|
COALESCE(m.orchestrator_agent_id, m.owner_agent_id) as orchestrator_agent_id,
|
||||||
|
a.display_name as orchestrator_display_name,
|
||||||
m.is_active,
|
m.is_active,
|
||||||
m.is_public,
|
COALESCE(m.is_public, true) as is_public,
|
||||||
m.logo_url,
|
COALESCE(m.is_platform, false) as is_platform,
|
||||||
a.display_name as orchestrator_display_name
|
m.parent_microdao_id,
|
||||||
|
pm.slug as parent_microdao_slug,
|
||||||
|
m.logo_url
|
||||||
FROM microdaos m
|
FROM microdaos m
|
||||||
LEFT JOIN agents a ON m.owner_agent_id = a.id
|
LEFT JOIN agents a ON COALESCE(m.orchestrator_agent_id, m.owner_agent_id) = a.id
|
||||||
WHERE m.slug = $1 AND COALESCE(m.is_archived, false) = false
|
LEFT JOIN microdaos pm ON m.parent_microdao_id = pm.id
|
||||||
|
WHERE m.slug = $1
|
||||||
|
AND COALESCE(m.is_archived, false) = false
|
||||||
|
AND COALESCE(m.is_test, false) = false
|
||||||
|
AND m.deleted_at IS NULL
|
||||||
"""
|
"""
|
||||||
|
|
||||||
dao_row = await pool.fetchrow(query_dao, slug)
|
dao_row = await pool.fetchrow(query_dao, slug)
|
||||||
@@ -1308,6 +1422,9 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
|||||||
FROM microdao_agents ma
|
FROM microdao_agents ma
|
||||||
JOIN agents a ON ma.agent_id = a.id
|
JOIN agents a ON ma.agent_id = a.id
|
||||||
WHERE ma.microdao_id = $1
|
WHERE ma.microdao_id = $1
|
||||||
|
AND COALESCE(a.is_archived, false) = false
|
||||||
|
AND COALESCE(a.is_test, false) = false
|
||||||
|
AND a.deleted_at IS NULL
|
||||||
ORDER BY ma.is_core DESC, ma.role
|
ORDER BY ma.is_core DESC, ma.role
|
||||||
"""
|
"""
|
||||||
agents_rows = await pool.fetch(query_agents, dao_id)
|
agents_rows = await pool.fetch(query_agents, dao_id)
|
||||||
@@ -1327,6 +1444,20 @@ async def get_microdao_by_slug(slug: str) -> Optional[dict]:
|
|||||||
channels_rows = await pool.fetch(query_channels, dao_id)
|
channels_rows = await pool.fetch(query_channels, dao_id)
|
||||||
result["channels"] = [dict(row) for row in channels_rows]
|
result["channels"] = [dict(row) for row in channels_rows]
|
||||||
|
|
||||||
|
# 4. Get child microDAOs
|
||||||
|
query_children = """
|
||||||
|
SELECT id, slug, name, COALESCE(is_public, true) as is_public,
|
||||||
|
COALESCE(is_platform, false) as is_platform
|
||||||
|
FROM microdaos
|
||||||
|
WHERE parent_microdao_id = $1
|
||||||
|
AND COALESCE(is_archived, false) = false
|
||||||
|
AND COALESCE(is_test, false) = false
|
||||||
|
AND deleted_at IS NULL
|
||||||
|
ORDER BY name
|
||||||
|
"""
|
||||||
|
children_rows = await pool.fetch(query_children, dao_id)
|
||||||
|
result["child_microdaos"] = [dict(row) for row in children_rows]
|
||||||
|
|
||||||
public_citizens = await get_microdao_public_citizens(dao_id)
|
public_citizens = await get_microdao_public_citizens(dao_id)
|
||||||
result["public_citizens"] = public_citizens
|
result["public_citizens"] = public_citizens
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,9 @@ class MicrodaoMembershipPayload(BaseModel):
|
|||||||
async def list_agents(
|
async def list_agents(
|
||||||
kind: Optional[str] = Query(None, description="Filter by agent kind"),
|
kind: Optional[str] = Query(None, description="Filter by agent kind"),
|
||||||
node_id: Optional[str] = Query(None, description="Filter by node_id"),
|
node_id: Optional[str] = Query(None, description="Filter by node_id"),
|
||||||
visibility_scope: Optional[str] = Query(None, description="Filter by visibility: city, microdao, owner_only"),
|
microdao_id: Optional[str] = Query(None, description="Filter by microDAO id"),
|
||||||
|
is_public: Optional[bool] = Query(None, description="Filter by public status"),
|
||||||
|
visibility_scope: Optional[str] = Query(None, description="Filter by visibility: global, microdao, private"),
|
||||||
include_system: bool = Query(True, description="Include system agents"),
|
include_system: bool = Query(True, description="Include system agents"),
|
||||||
limit: int = Query(100, le=200),
|
limit: int = Query(100, le=200),
|
||||||
offset: int = Query(0, ge=0)
|
offset: int = Query(0, ge=0)
|
||||||
@@ -78,6 +80,8 @@ async def list_agents(
|
|||||||
kinds_list = [kind] if kind else None
|
kinds_list = [kind] if kind else None
|
||||||
agents, total = await repo_city.list_agent_summaries(
|
agents, total = await repo_city.list_agent_summaries(
|
||||||
node_id=node_id,
|
node_id=node_id,
|
||||||
|
microdao_id=microdao_id,
|
||||||
|
is_public=is_public,
|
||||||
visibility_scope=visibility_scope,
|
visibility_scope=visibility_scope,
|
||||||
kinds=kinds_list,
|
kinds=kinds_list,
|
||||||
include_system=include_system,
|
include_system=include_system,
|
||||||
@@ -126,6 +130,7 @@ async def list_agents(
|
|||||||
is_listed_in_directory=agent.get("is_listed_in_directory", True),
|
is_listed_in_directory=agent.get("is_listed_in_directory", True),
|
||||||
is_system=agent.get("is_system", False),
|
is_system=agent.get("is_system", False),
|
||||||
is_public=agent.get("is_public", False),
|
is_public=agent.get("is_public", False),
|
||||||
|
is_orchestrator=agent.get("is_orchestrator", False),
|
||||||
primary_microdao_id=agent.get("primary_microdao_id"),
|
primary_microdao_id=agent.get("primary_microdao_id"),
|
||||||
primary_microdao_name=agent.get("primary_microdao_name"),
|
primary_microdao_name=agent.get("primary_microdao_name"),
|
||||||
primary_microdao_slug=agent.get("primary_microdao_slug"),
|
primary_microdao_slug=agent.get("primary_microdao_slug"),
|
||||||
@@ -1320,6 +1325,8 @@ async def get_agents_presence_snapshot():
|
|||||||
@router.get("/microdao", response_model=List[MicrodaoSummary])
|
@router.get("/microdao", response_model=List[MicrodaoSummary])
|
||||||
async def get_microdaos(
|
async def get_microdaos(
|
||||||
district: Optional[str] = Query(None, description="Filter by district"),
|
district: Optional[str] = Query(None, description="Filter by district"),
|
||||||
|
is_public: Optional[bool] = Query(None, description="Filter by public status"),
|
||||||
|
is_platform: Optional[bool] = Query(None, description="Filter by platform status"),
|
||||||
q: Optional[str] = Query(None, description="Search by name/description"),
|
q: Optional[str] = Query(None, description="Search by name/description"),
|
||||||
limit: int = Query(50, le=100),
|
limit: int = Query(50, le=100),
|
||||||
offset: int = Query(0, ge=0)
|
offset: int = Query(0, ge=0)
|
||||||
@@ -1328,10 +1335,19 @@ async def get_microdaos(
|
|||||||
Отримати список MicroDAOs.
|
Отримати список MicroDAOs.
|
||||||
|
|
||||||
- **district**: фільтр по дістрікту (Core, Energy, Green, Labs, etc.)
|
- **district**: фільтр по дістрікту (Core, Energy, Green, Labs, etc.)
|
||||||
|
- **is_public**: фільтр по публічності
|
||||||
|
- **is_platform**: фільтр по типу (платформа/дістрікт)
|
||||||
- **q**: пошук по назві або опису
|
- **q**: пошук по назві або опису
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
daos = await repo_city.get_microdaos(district=district, q=q, limit=limit, offset=offset)
|
daos = await repo_city.list_microdao_summaries(
|
||||||
|
district=district,
|
||||||
|
is_public=is_public,
|
||||||
|
is_platform=is_platform,
|
||||||
|
q=q,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset
|
||||||
|
)
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
for dao in daos:
|
for dao in daos:
|
||||||
@@ -1341,10 +1357,17 @@ async def get_microdaos(
|
|||||||
name=dao["name"],
|
name=dao["name"],
|
||||||
description=dao.get("description"),
|
description=dao.get("description"),
|
||||||
district=dao.get("district"),
|
district=dao.get("district"),
|
||||||
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
is_public=dao.get("is_public", True),
|
||||||
|
is_platform=dao.get("is_platform", False),
|
||||||
is_active=dao.get("is_active", True),
|
is_active=dao.get("is_active", True),
|
||||||
|
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
||||||
|
orchestrator_agent_name=dao.get("orchestrator_agent_name"),
|
||||||
|
parent_microdao_id=dao.get("parent_microdao_id"),
|
||||||
|
parent_microdao_slug=dao.get("parent_microdao_slug"),
|
||||||
logo_url=dao.get("logo_url"),
|
logo_url=dao.get("logo_url"),
|
||||||
|
member_count=dao.get("member_count", 0),
|
||||||
agents_count=dao.get("agents_count", 0),
|
agents_count=dao.get("agents_count", 0),
|
||||||
|
room_count=dao.get("room_count", 0),
|
||||||
rooms_count=dao.get("rooms_count", 0),
|
rooms_count=dao.get("rooms_count", 0),
|
||||||
channels_count=dao.get("channels_count", 0)
|
channels_count=dao.get("channels_count", 0)
|
||||||
))
|
))
|
||||||
@@ -1405,16 +1428,31 @@ async def get_microdao_by_slug(slug: str):
|
|||||||
primary_room_slug=citizen.get("public_primary_room_slug")
|
primary_room_slug=citizen.get("public_primary_room_slug")
|
||||||
))
|
))
|
||||||
|
|
||||||
|
# Build child microDAOs list
|
||||||
|
child_microdaos = []
|
||||||
|
for child in dao.get("child_microdaos", []):
|
||||||
|
child_microdaos.append(MicrodaoSummary(
|
||||||
|
id=child["id"],
|
||||||
|
slug=child["slug"],
|
||||||
|
name=child["name"],
|
||||||
|
is_public=child.get("is_public", True),
|
||||||
|
is_platform=child.get("is_platform", False)
|
||||||
|
))
|
||||||
|
|
||||||
return MicrodaoDetail(
|
return MicrodaoDetail(
|
||||||
id=dao["id"],
|
id=dao["id"],
|
||||||
slug=dao["slug"],
|
slug=dao["slug"],
|
||||||
name=dao["name"],
|
name=dao["name"],
|
||||||
description=dao.get("description"),
|
description=dao.get("description"),
|
||||||
district=dao.get("district"),
|
district=dao.get("district"),
|
||||||
|
is_public=dao.get("is_public", True),
|
||||||
|
is_platform=dao.get("is_platform", False),
|
||||||
|
is_active=dao.get("is_active", True),
|
||||||
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
orchestrator_agent_id=dao.get("orchestrator_agent_id"),
|
||||||
orchestrator_display_name=dao.get("orchestrator_display_name"),
|
orchestrator_display_name=dao.get("orchestrator_display_name"),
|
||||||
is_active=dao.get("is_active", True),
|
parent_microdao_id=dao.get("parent_microdao_id"),
|
||||||
is_public=dao.get("is_public", True),
|
parent_microdao_slug=dao.get("parent_microdao_slug"),
|
||||||
|
child_microdaos=child_microdaos,
|
||||||
logo_url=dao.get("logo_url"),
|
logo_url=dao.get("logo_url"),
|
||||||
agents=agents,
|
agents=agents,
|
||||||
channels=channels,
|
channels=channels,
|
||||||
|
|||||||
Reference in New Issue
Block a user