Context
Needed for the Intuno Personal product surface (wisdom-agents#62 epic, ADR at wisdom-agents#71). Frontend users authenticate against wisdom (JWT), but entity state lives in wisdom-agents. Today there's no auth bridge — wisdom-agents only accepts a shared X-API-Key: AGENTS_API_KEY, which a user-facing UI can't safely hold.
This ticket creates the proxy tier in wisdom so the frontend talks to exactly one public origin (wisdom), which validates JWTs, enforces quotas/billing, and forwards to wisdom-agents over the private network with the shared key + X-User-Id header.
Architecture
Frontend (wisdom JWT)
→ wisdom POST /personal/entities [validate JWT, enforce quotas]
→ wisdom-agents POST /entities [trusts X-API-Key + X-User-Id]
wisdom-agents remains a private internal service. wisdom is the only public API surface for Personal.
Scope — new routes under src/routes/personal.py
Proxy routes (JWT-protected, current_user injected):
POST /personal/entities — forward body to wisdom-agents POST /entities; attach X-User-Id: {current_user.id}. Enforce per-user entity-count quota before forwarding.
GET /personal/entities — forward; wisdom-agents scopes by X-User-Id.
GET /personal/entities/{name} — forward + ownership check.
PATCH /personal/entities/{name} — forward + ownership check.
DELETE /personal/entities/{name} — forward + ownership check.
POST /personal/entities/{name}/pause — forward.
POST /personal/entities/{name}/resume — forward.
POST /personal/entities/{name}/messages — forward to wisdom-agents POST /entities/{name}/chat (new — see wisdom-agents trust ticket).
GET /personal/entities/{name}/messages — forward to wisdom-agents GET /entities/{name}/chat.
Implementation notes
- New service:
src/services/personal.py — one PersonalAgentsClient class with an httpx.AsyncClient configured with INTUNO_AGENTS_BASE_URL + INTUNO_AGENTS_API_KEY env vars.
- Routes are thin: validate ownership, forward, return response. No business logic in the proxy layer.
- Errors: 4xx from
wisdom-agents pass through with the same status code; 5xx gets logged and returns 502 with a scrubbed message.
- Timeouts: 30s default; callback-sensitive endpoints (POST .../messages) can bump higher when we add streaming.
Quota enforcement (MVP)
Reject POST /personal/entities with 429 if the user already has N entities (default 1 on Free, 3 on Pro — fetch from users.plan once that exists; for MVP, env-configurable PERSONAL_FREE_TIER_ENTITY_CAP).
Per-message rate limits come later (#60 on wisdom-agents side).
Environment variables
INTUNO_AGENTS_BASE_URL — e.g., http://wisdom-agents:8001
INTUNO_AGENTS_API_KEY — the AGENTS_API_KEY value from the wisdom-agents deployment
Acceptance criteria
Blocked by
- wisdom-agents
[personal-trust] — needs the X-User-Id trust model live on the other side
Unblocks
Files (anticipated)
src/routes/personal.py (new)
src/services/personal.py (new) — PersonalAgentsClient
src/core/settings.py — add the two env vars
src/main.py — mount the router
Context
Needed for the Intuno Personal product surface (wisdom-agents#62 epic, ADR at wisdom-agents#71). Frontend users authenticate against
wisdom(JWT), but entity state lives inwisdom-agents. Today there's no auth bridge —wisdom-agentsonly accepts a sharedX-API-Key: AGENTS_API_KEY, which a user-facing UI can't safely hold.This ticket creates the proxy tier in
wisdomso the frontend talks to exactly one public origin (wisdom), which validates JWTs, enforces quotas/billing, and forwards towisdom-agentsover the private network with the shared key +X-User-Idheader.Architecture
wisdom-agentsremains a private internal service.wisdomis the only public API surface for Personal.Scope — new routes under
src/routes/personal.pyProxy routes (JWT-protected, current_user injected):
POST /personal/entities— forward body towisdom-agents POST /entities; attachX-User-Id: {current_user.id}. Enforce per-user entity-count quota before forwarding.GET /personal/entities— forward;wisdom-agentsscopes byX-User-Id.GET /personal/entities/{name}— forward + ownership check.PATCH /personal/entities/{name}— forward + ownership check.DELETE /personal/entities/{name}— forward + ownership check.POST /personal/entities/{name}/pause— forward.POST /personal/entities/{name}/resume— forward.POST /personal/entities/{name}/messages— forward towisdom-agents POST /entities/{name}/chat(new — see wisdom-agents trust ticket).GET /personal/entities/{name}/messages— forward towisdom-agents GET /entities/{name}/chat.Implementation notes
src/services/personal.py— onePersonalAgentsClientclass with anhttpx.AsyncClientconfigured withINTUNO_AGENTS_BASE_URL+INTUNO_AGENTS_API_KEYenv vars.wisdom-agentspass through with the same status code; 5xx gets logged and returns 502 with a scrubbed message.Quota enforcement (MVP)
Reject
POST /personal/entitieswith 429 if the user already has N entities (default 1 on Free, 3 on Pro — fetch fromusers.planonce that exists; for MVP, env-configurablePERSONAL_FREE_TIER_ENTITY_CAP).Per-message rate limits come later (#60 on wisdom-agents side).
Environment variables
INTUNO_AGENTS_BASE_URL— e.g.,http://wisdom-agents:8001INTUNO_AGENTS_API_KEY— theAGENTS_API_KEYvalue from the wisdom-agents deploymentAcceptance criteria
Blocked by
[personal-trust]— needs theX-User-Idtrust model live on the other sideUnblocks
Files (anticipated)
src/routes/personal.py(new)src/services/personal.py(new) —PersonalAgentsClientsrc/core/settings.py— add the two env varssrc/main.py— mount the router