gabriel / musehub public
derived_agent_provisioner.py python
105 lines 3.4 KB
Raw
sha256:20b27796ed512236119feef48b4577a8fd9ded05a15267dd4e6da23604ad1f1a docs: update Suggested Ownership Split table to reflect ful… Sonnet 5 28 days ago
1 """Derived-agent auto-provisioning.
2
3 When a profile page is visited for an agent that only exists as an ``agent_id``
4 string in commit metadata (never formally registered), this module:
5
6 1. Computes a deterministic genesis ``identity_id`` via
7 ``compute_derived_agent_id(handle)``.
8 2. Upserts a real ``musehub_identities`` row (idempotent — safe to call on
9 every profile view).
10 3. Returns the updated identity dict so the avatar route can serve the SVG.
11
12 The upsert is a no-op if the row already exists, so repeated profile views are
13 free.
14 """
15
16 from datetime import datetime, timezone
17
18 from sqlalchemy import select
19 from sqlalchemy.ext.asyncio import AsyncSession
20
21 from musehub.core.genesis import compute_derived_agent_id
22 from musehub.db.musehub_identity_models import MusehubIdentity
23 from musehub.db.musehub_repo_models import MusehubCommit
24 from musehub.types.json_types import JSONObject
25
26 _IdentityDict = JSONObject
27
28 async def latest_agent_model_id(session: AsyncSession, agent_id: str) -> str | None:
29 """Most recent commit's ``model_id`` for this agent, across all repos.
30
31 ``model_id`` is self-reported, mutable metadata — an agent's underlying
32 model changes over its lifetime (e.g. an upgrade), so display always
33 reflects the newest commit rather than a value cached on the identity
34 row at first-ever provisioning.
35 """
36 result = await session.execute(
37 select(MusehubCommit.model_id)
38 .where(MusehubCommit.agent_id == agent_id)
39 .order_by(MusehubCommit.timestamp.desc())
40 .limit(1)
41 )
42 row = result.first()
43 return row[0] if row else None
44
45 async def ensure_agent_identity(
46 session: AsyncSession,
47 handle: str,
48 agent_model: str | None,
49 first_seen_at: datetime | None,
50 ) -> MusehubIdentity:
51 """Upsert a ``musehub_identities`` row for a derived agent.
52
53 If the row already exists (idempotent path), return it unchanged.
54 The caller is responsible for committing the session.
55 """
56 identity_id = compute_derived_agent_id(handle)
57
58 existing = (await session.execute(
59 select(MusehubIdentity).where(MusehubIdentity.handle == handle)
60 )).scalar_one_or_none()
61
62 if existing is not None:
63 return existing
64
65 row = MusehubIdentity(
66 identity_id=identity_id,
67 handle=handle,
68 identity_type="agent",
69 agent_model=agent_model,
70 created_at=first_seen_at or datetime.now(timezone.utc),
71 )
72 session.add(row)
73 await session.flush()
74 return row
75
76 async def provision_if_derived(
77 session: AsyncSession,
78 identity: _IdentityDict,
79 ) -> _IdentityDict:
80 """Provision a real identity row for a derived agent, then update the dict.
81
82 Only acts when:
83 - ``identity["type"] == "agent"``
84 - ``identity["user_id"]`` is ``None`` (not yet registered)
85
86 Returns the same dict (mutated in place) with ``user_id`` and
87 ``is_derived`` updated. For humans, orgs, and already-registered agents
88 the dict is returned unchanged.
89 """
90 if identity.get("type") != "agent":
91 return identity
92 if identity.get("user_id") is not None:
93 return identity
94
95 row = await ensure_agent_identity(
96 session,
97 handle=identity["handle"],
98 agent_model=identity.get("agent_model"),
99 first_seen_at=identity.get("member_since"),
100 )
101 await session.commit()
102
103 identity["user_id"] = row.identity_id
104 identity["is_derived"] = False
105 return identity
File History 3 commits
sha256:20b27796ed512236119feef48b4577a8fd9ded05a15267dd4e6da23604ad1f1a docs: update Suggested Ownership Split table to reflect ful… Sonnet 5 28 days ago
sha256:8ea2f75226d5834770ccaddbaa6efda1cc337446fd481bb385de8bec4555ae79 docs: Section 9 CI pipeline — confirm job queue is idempote… Sonnet 5 28 days ago
sha256:80e1a60a39562f6e616aaafbb27487f03abbec7d8ffc6164302b7a0c0bfc63ee docs: check off Section 0 items verified in inventory doc, … Sonnet 5 28 days ago