Artifacts: the federated library¶
Everything Kwilo generates for a user — question banks, lesson plans, presentations, research reports, practice tests, and (for B2C) generated images — surfaces on one read-only browse page: /artifacts on web, the Artifacts screen on mobile B2C. Domain tables stay the source of truth; the page is a federation, not a store. Viewing is never quota-metered. Related: Practice tests as Artifacts, Chat image generation, Chat Workspace (where artifacts are born).
Retroactive doc: Phase 1 shipped in
MySetu-AI/kwiloai_webapp#1240 without a doc; authored now alongside the B2C visibility/images change, per the retrofit rule.
Why this exists¶
- Who asked — product owner. Users create things in chat and lose them: the only way back was scrolling chat history.
- User pain — "I made a presentation last week in chat and can't find it." Generated outputs were scattered across five tables with no unified list.
- Cost of not doing it — generated content is effectively disposable; repeat generation burns quota and erodes the "Kwilo builds a library for you" value story.
- Validated or a guess — grounded in chat-history scroll behavior; re-open rate is the watch metric (phases 2–3 are gated on it).
- How we'll know it worked — users re-open artifacts from the page instead of re-generating or scrolling chat.
How it works¶
GET /artifacts merges owner-scoped rows from the typed generation stores into one paginated, searchable, type-filterable list. Two design decisions carry the feature:
- Decision 1 — doc-shaped types share one store and one viewer.
question_bank,lesson_plan,assignment,modulelive indocuments(discriminated bydoc_type) and share the read-only viewer (teachers get the editor instead). - Decision 2 — federation, not duplication. Domain tables (
documents,presentations,deep_research_sessions,mock_tests,practice_sessions+ attempts,media_generations) remain canonical; the service only reads and merges. Queries are always scoped to the requesting user — never the org unit — because all B2C users share the synthetic "Individual Kwilo" org and org scoping there would expose strangers' content.
Type → backing store → open action¶
| Type | Store | Open action (web) |
|---|---|---|
question_bank / lesson_plan / assignment / module |
documents |
Teacher → document editor; others → read-only dialog |
presentation |
presentations |
Presentation viewer |
research_report |
deep_research_sessions |
Chat research viewer (/new-chat?research=:id) |
practice_test (source: "mock_test") |
mock_tests |
Disabled (no reopenable route yet) |
practice_test (source: "practice_session") |
practice_sessions |
Review screen /practice/test/:id |
image (B2C only) |
media_generations |
Lightbox (web) / detail screen (mobile) |
Delete is owner-checked per store; the URL's type must match the row (/artifacts/question_bank/{id} cannot delete a lesson plan). Image delete also removes the GCS object (best effort).
Role visibility (2026-07-12)¶
Which types federate depends on the caller's role — enforced server-side (visible_artifact_types_for_role), so a hand-crafted ?type= deep link can't resurface a hidden type, and mirrored in the clients' tab/chip lists:
- B2B roles (instructor, learner, admins…): the Phase-1 seven types. No images yet — teachers' generated images have their own surfaces, and widening B2B artifacts is a separate decision.
- B2C learners (
b2c_user): everything exceptquestion_bankandresearch_report, plusimage. - Question banks are educator-shaped; a B2C learner can't create one, so the tab was permanently-empty noise.
- Research reports stay on the chat/research surface where B2C users actually run and reopen research.
- Images are the chat Image-chip outputs (a metered pool, Pro-unlimited); before this they vanished once the chat scrolled away — no gallery.
Counts follow visibility: the response's counts map only carries visible types, so the "All" tab count never leaks hidden rows.
Images¶
Image artifacts federate from media_generations rows with media_type="image", status="completed", and a storage_path (chat-relay persistence writes these — see Chat image generation). The list response carries a short-lived (1 h) signed GCS URL per item, minted/refreshed at list time; the card shows a thumbnail, and open renders the full image — web in a lightbox dialog, mobile on the artifact detail screen (URL rides along as a route param, same pattern as other non-document types). The prompt doubles as the title (truncated to 120 chars).
Non-goals and rejected alternatives¶
- An
artifactstable (rejected). Federation keeps domain tables canonical; a mirror table would drift and double-write. - Hiding types client-side only (rejected). Tabs are client concern, but data visibility is enforced in the service — a deep link must not bypass the role gate.
- Images for B2B roles (deferred, not rejected). The service supports it (one line in the role map); shipping it is a product decision about teacher image workflows, not a tech gap.
- Editing from the artifacts page (out of scope). The page is read + delete only; editing happens on each type's own surface.
- Server-side subject filter (out of scope). Subject filtering is client-side over the loaded page; the backend contract has no subject param.
Known gaps (deferred)¶
- Mock-test-sourced practice cards are still unopenable (no reopenable route).
- A B2C user's pre-existing research/question-bank rows are hidden, not deleted — they reappear if the role's visibility ever widens.
- Signed image URLs expire after 1 h; a mobile detail screen left open longer than that shows a broken image until reopened from the list.
Changelog¶
- 2026-07-12 — Role-based visibility + images. B2C learners no longer see
question_bank/research_report; their generated chat images now federate asimageartifacts with thumbnails and a lightbox/detail viewer (web + mobile B2C). Backend gatevisible_artifact_types_for_role.MySetu-AI/kwiloai_webapp#1332. - 2026-07-04 → 07-10 — Practice-hub sessions joined the
practice_testtype withsourcediscriminator (#1313); see Practice tests as Artifacts. - 2026-07-02 (retro) — Phase 1:
/artifactsfederation over four stores + chat capture (#1240). Phases 2–3 (pinning, richer viewers) gated on re-open rate.