gabriel / musehub public
derived_agent_provisioner.py python
105 lines 3.4 KB
Raw
sha256:8a1389ae62d0a688d5e027763249bd8faedfc39ab00857d65da6620d1ab8a689 Merge 'feat/9a-4-f7-overseer-provenance' into 'dev' — propo… Human 4 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 15 commits
sha256:8a1389ae62d0a688d5e027763249bd8faedfc39ab00857d65da6620d1ab8a689 Merge 'feat/9a-4-f7-overseer-provenance' into 'dev' — propo… Human 4 days ago
sha256:bee12c5cbde2334f98421c6c209d768fa6b8004d6705c9ea798ce6c1651bc11f Merge 'infra/database-phase3-4-cleanup' into 'dev' — propos… Human 4 days ago
sha256:fc04e4cae9e1774d6a21b65c45daeed0e6787eb581d13aa1b03bfe9384a34226 Merge branch 'fix/two-column-scroll-layout' into dev Human 67 days ago
sha256:408916fc5973ba59c6e4eebaa80ebdcc801c0a63205651e25009d11548f79454 chore: bump version to 0.2.0.dev2 — nightly.2, matching muse Sonnet 4.6 patch 70 days ago
sha256:d035733f21ccff27735fddebfbbe0ed24565a32a22db8de5885402262671ecd2 chore: bump version to 0.2.0rc15 for musehub#113 fix release Sonnet 4.6 patch 73 days ago
sha256:0032d6cfa33bc3c8367436ad768e7dd0e339b4332153160247da8266cb5fa352 Merge branch 'task/version-tags-phase3-server' into dev Human 76 days ago
sha256:4669620efda9ff41c55bdefd1f7bfe1c239d468428744c84ead9957e5a003a53 merge: rescue snapshot-recovery hardening (c00aa21d) into d… Opus 4.8 minor 88 days ago
sha256:a59da49c4611b970fc4b6ae48678ce4943261c213a07ddbd73ce9201df869b4a fix: remove false-positive proposal_comments index drop fro… Sonnet 4.6 patch 92 days ago
sha256:0a240d6dbff234f07d98a28a4a9a68db702f3f9ff9260196f24219bdb1c0b6f3 feat: render markdown mists as HTML with heading anchor links Sonnet 4.6 patch 93 days ago
sha256:24a7d47486ebc4ebd1832830580e177ec6f877b48dced8c000e198cdec4ce9d6 Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 94 days ago
sha256:b9ff931d147e0114a1f17060f415b89ed551c170a91ff226c70437aa5c85f9ee Merge 'task/bump-version-rc12' into 'dev' — proposal: Bump … Human 94 days ago
sha256:d1122d21e73471879b460037b22c0b50fded7c423444a176f248428f75dac39c Merge 'task/fix-issue-pagination-cursor' into 'dev' — propo… Human 94 days ago
sha256:01e18975e73d2b3cd5b6db7929c895bef9aa6e0d4391dc5b2adfc548b41318dd Merge 'feat/adding-debug-logs-to-staging' into 'dev' — prop… Human 94 days ago
sha256:6b1949fc2797ca4c1936a637a4cbfec828ef56cf52398a2e74ca3c4f494e728f fix: use wire_bytes not mpack_bytes_raw in compute_object_b… Sonnet 4.6 patch 107 days ago
sha256:b99f2455dc346966d040133f5203297e6e3ef5803a93728a2c30568d0a0f7583 rename: delta_add → delta_upsert across wire format, models… Sonnet 4.6 patch 109 days ago