Context
Today wisdom-agents authenticates to wisdom via a user-scoped INTUNO_API_KEY — a key generated by a logged-in operator from /dashboard/integrations. That means every entity wisdom-agents hosts makes its network calls as that one user, breaking multi-tenant attribution:
- User A's entity creates networks owned by user B (whoever made the key)
intuno_discover / network_send runs under a shared identity
- Operator setup requires grabbing a personal key from a logged-in browser
This ticket introduces a proper service-to-service auth mode, mirroring the X-User-Id trust pattern we already use in the other direction (wisdom → wisdom-agents, IntunoAI/wisdom-agents#77).
Scope
1. New env setting
AGENTS_SERVICE_API_KEY: str = "" in src/core/settings.py — infra secret (e.g. a random 32-byte hex), not tied to any user, not stored in the database
2. New auth dependency
src/core/auth.py:
async def get_current_user_or_service(
credentials: Optional[HTTPAuthorizationCredentials] = Depends(security),
x_service_key: Optional[str] = Header(None, alias="X-Service-Key"),
x_on_behalf_of: Optional[UUID] = Header(None, alias="X-On-Behalf-Of"),
auth_service: AuthService = Depends(),
) -> User:
"""Accept either:
- Normal user JWT / API key → returns that user, or
- X-Service-Key matching AGENTS_SERVICE_API_KEY + X-On-Behalf-Of
→ returns the specified user (after verifying they're active)
"""
Rules:
- Service key + no
X-On-Behalf-Of → 400 (must delegate to somebody)
- Wrong service key → 401 regardless of other headers
- Service key + valid
X-On-Behalf-Of → load and return the target user; 404 if target doesn't exist or is inactive
3. Apply to routes wisdom-agents calls
Audit the endpoints wisdom-agents hits via intuno-sdk and switch their dep from get_current_user to get_current_user_or_service:
/registry/discover, /registry/agents/*
/broker/invoke
/networks/* (create, list, participants, context, messages)
/a2a/agents/import (when wisdom-agents auto-imports A2A agents for an entity)
Legacy user-key auth keeps working — this is strictly additive.
Acceptance criteria
Out of scope
- Auditing every endpoint — just the ones wisdom-agents calls. Gradual rollout is fine.
- Rate-limiting per service key (follows later with the quota work).
Relates to
- IntunoAI/wisdom-agents#77 — the other half of the bridge (we already trust X-User-Id going the other way)
- IntunoAI/wisdom-agents#56 — per-entity Intuno registration, unblocked by this
- Paired with intuno-sdk SDK update + wisdom-agents refactor (companion tickets)
Context
Today wisdom-agents authenticates to wisdom via a user-scoped
INTUNO_API_KEY— a key generated by a logged-in operator from/dashboard/integrations. That means every entity wisdom-agents hosts makes its network calls as that one user, breaking multi-tenant attribution:intuno_discover/network_sendruns under a shared identityThis ticket introduces a proper service-to-service auth mode, mirroring the
X-User-Idtrust pattern we already use in the other direction (wisdom → wisdom-agents, IntunoAI/wisdom-agents#77).Scope
1. New env setting
AGENTS_SERVICE_API_KEY: str = ""insrc/core/settings.py— infra secret (e.g. a random 32-byte hex), not tied to any user, not stored in the database2. New auth dependency
src/core/auth.py:Rules:
X-On-Behalf-Of→ 400 (must delegate to somebody)X-On-Behalf-Of→ load and return the target user; 404 if target doesn't exist or is inactive3. Apply to routes wisdom-agents calls
Audit the endpoints wisdom-agents hits via
intuno-sdkand switch their dep fromget_current_usertoget_current_user_or_service:/registry/discover,/registry/agents/*/broker/invoke/networks/*(create, list, participants, context, messages)/a2a/agents/import(when wisdom-agents auto-imports A2A agents for an entity)Legacy user-key auth keeps working — this is strictly additive.
Acceptance criteria
AGENTS_SERVICE_API_KEYsetting wiredget_current_user_or_servicedep added with tests:Out of scope
Relates to