adk-samples / genmedia-for-commerce
AGENTS.md11k starsChanged 44 days agoApache-2.0View on GitHub
How the code works and the rules for changing it. README.md is the only user document (setup, run, deploy, configuration); CLAUDE.md and GEMINI.md only point here. A full-stack ADK app that makes product media for online shops with Gemini: Photo Try-On (a person wearing one product or a whole outfit), Video Try-On (the try-on animated into short videos), 360° Spin (a looping animated WebP of any product) and catalogue search. Each generation makes several variations (GENMEDIA_NUM_VARIATIONS, default 3, in parallel…
- Reads credentials
1 other project uses a version of this.
Trending
More talk lately.about the whole repository
Most recently taken up by
- google/adk-recipesword for word, first seen Oct 13, 2026
Who's talking about google/adk-samples
- @stephanjanssen.be“Google just open sourced a Python Agent Development Kit @ github.com/google/adk-p... Native Streaming and …”Bluesky · Apr 9, 2025 · 12 likes
- google/adk-python“stream_query and HTTP Streaming to Vertex AI Agent Return Empty/Exhausted Responses (Works via ADK Web)”GitHub · Jul 8, 2025 · 5 reactions
- google/adk-recipes“Support running agents fully locally without Vertex AI (offline mode)”GitHub · Apr 14, 2025 · 4 reactions
- google/adk-recipes“[BUG]: Deployment of Data Science agent to Agent Engine fails ”GitHub · Oct 1, 2025 · 3 reactions
- A Hacker News story“CaMeL-Powered Secure Agent Demo with ADK”Hacker News · Aug 9, 2025 · 3 points
The biggest 5 of 32 mentions linking to the repository that our crawl found. Each line is their own headline.
See all 32
Talked about on GitHub, Bluesky, Hacker News and Stack Overflow (32 times).
On GitHub · 23
- Jul 8, 2025stream_query and HTTP Streaming to Vertex AI Agent Return Empty/Exhausted Responses (Works via ADK Web) — google/adk-python5 reactions
- Apr 14, 2025Support running agents fully locally without Vertex AI (offline mode) — google/adk-recipes4 reactions · 7 comments
- Oct 1, 2025[BUG]: Deployment of Data Science agent to Agent Engine fails — google/adk-recipes3 reactions · 1 comment
- Oct 28, 2026docs: fix 5 broken internal anchor links and 1 dead external link — google-gemini/gemini-cli1 reaction · 4 comments
- Oct 20, 2026Update adk-samples references and python/agents discovery paths for google/adk-recipes restructure — google/agents-cli1 reaction · 2 comments
18 more on GitHub
- Oct 12, 2026list: scan the template dir we cloned, not the clone root — GoogleCloudPlatform/agent-starter-pack1 reaction
- Feb 12, 2026interrupted sometimes didn't work — google/adk-python1 reaction · 10 comments
- Jan 8, 2026Implement 5 ADK agent projects that can be easily run by newbies — addyosmani/adk-product-engineers1 reaction
- Nov 20, 2025[BUG]: deep-search/gemini-fullstack: Wrong prompt to access session state — google/adk-recipes1 reaction
- Jun 21, 2025unable to import StdioServerParameters — google/adk-recipes1 reaction · 2 comments
- Jun 17, 2025VertexAiRagRetrieval needs llama-index package — google/adk-recipes1 reaction · 3 comments
- Jun 5, 2025Bug: display_name in file uploads causes error with Google.GenAI backend (non-Vertex) — google/adk-python1 reaction · 6 comments
- May 18, 2025Missing Image — google/adk-recipes1 reaction
- Apr 11, 2025Duplicate key name — google/adk-recipes1 reaction · 1 comment
- Oct 18, 2026Fix(audit,#17387): Day4 Track2 tranche 1 -- Lab8/Lab9 reformulations honnetes, cures accents — jsboige/CoursIA0 reactions · 14 comments
- Sep 17, 2026Rename sample references to recipe across repository — google/adk-recipes0 reactions
- Sep 12, 2026docs(deep-search): fix cloud deployment instructions and scaffold path (#2575) — google/adk-recipes0 reactions · 4 comments
- Sep 12, 2026docs: fix 404 link to relocated Deep Search sample — google/adk-docs0 reactions · 1 comment
- Sep 9, 2026Add repo skills instructions and documentation links to AI issue response workflow — google/adk-recipes0 reactions
- Jul 24, 2026Fix broken adk-samples links from deleted sample agents and notebooks — google/adk-docs0 reactions · 1 comment
- Jun 12, 2026ci: run the spam-detection bot via the adk-samples reusable workflow — google/adk-go0 reactions · 1 comment
- May 27, 2026Add Deep Search Agent project to portfolio showcase — asafsilner/silner-showcase-prime0 reactions
- Feb 12, 2026fix: path typo in story teller example — google/adk-recipes0 reactions · 3 comments
On Bluesky · 4
- Apr 9, 2025Google just open sourced a Python Agent Development Kit @ github.com/google/adk-p... Native Streaming and #MCP support out of the box 👀… — @stephanjanssen.be12 likes and reposts
- Jul 12, 2026Exploring the ADK Samples repo from Google—it’s like a guidebook for building intelligent agents. Seeing multi-language support reflects… — @iam.slys.dev1 likes and repost
- Dec 9, 2025📦 google / adk-samples ⭐ 6,900 (+67) 🗒 Python A collection of sample agents built with Agent Development Kit (ADK) — @github-trending.bsky.social1 likes and repost
- May 20, 2025Explore practical use-cases & build! 🧪 Hands-on Patent Search Codelab: codelabs.developers.google.com/patent-searc... 📚 Check out sample… — @kweinmeister.bsky.social0 likes and reposts · 1 reply
On Hacker News · 3
- Aug 9, 2025CaMeL-Powered Secure Agent Demo with ADK3 points · 1 comment
- Jun 13, 2025A collection of sample agents built with Agent Development (ADK)2 points
- Aug 13, 2025CaMeL agent to protect against indirect prompt injection1 point · 1 comment
On Stack Overflow · 2
- Aug 30, 2025Google ADK SequentialAgent sub_agents not waiting for user input1 vote
- Aug 29, 2025Issue with deploying ADK Agent on Google Agent Engine0 votes
Other versions1 project uses a version of this.
Word for word · 1
- google/adk-recipes★ 11k100% same
Edited · 0
None found.
Wrote their own version · 0
None found.
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
Where files are headingNew AGENTS.md files have more rules about keeping secrets out of the code, and fewer about code style.
Of the AGENTS.md files we read, the 31,843 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.
In this repository43 other files it gives its agents.
- AGENTS.mdAGENTS.md
- contrib/kotlin/llm-auditor/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/sandbox/home/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/tests/eval/fixtures/alex/repos/portland-trip/AGENTS.mdAGENTS.md
- core/python/ambient-expense-agent/AGENTS.mdAGENTS.md
- core/python/ambient-quality-agent/AGENTS.mdAGENTS.md
- core/python/cross-session-memory/AGENTS.mdAGENTS.md
- core/python/deep-search/AGENTS.mdAGENTS.md
- core/python/long-horizon-harness/AGENTS.mdAGENTS.md
- core/python/oauth-user-consent-flow/AGENTS.mdAGENTS.md
- core/python/rag-agent-search/AGENTS.mdAGENTS.md
- core/python/rag-vector-search/AGENTS.mdAGENTS.md
- core/python/safety-plugins/AGENTS.mdAGENTS.md
- core/python/adk-voice-agent/sandbox/home/CLAUDE.mdCLAUDE.md
- core/python/genmedia-for-commerce/CLAUDE.mdCLAUDE.md
- align-recipe-pyprojectSkill
- extract-python-environment-variablesSkill
- generate-manifestSkill
- generate-python-runnability-testSkill
- github-pr-reviewSkill
- make-python-recipe-deployableSkill
- prepare-python-recipeSkill
- repo-oracleSkill
- scaffold-python-recipeSkill
- blog-writerSkill
- content-research-writerSkill
- approvalsSkill
- coding-workflowSkill
- connected-apps-mcpSkill
- deliverablesSkill
- filesystemSkill
- recommendationsSkill
- agents-cli-aquaSkill
- custom-investigationSkill
- rcaSkill
- bootstrap-google-toolsSkill
- find-skillsSkill
- google-workspaceSkill
- policySkill
- routinesSkill
- retail-product-searchSkill
- retail-virtual-tryonSkill
The file
We split this AGENTS.md into the rules it gives an agent
7 rules. About 0 are common, 1 is distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈1
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Very specific: line 20
All the app's Python is under genmedia4commerce/.
- Very specific: line 107
Run options' variations default to 3 on Photo and Video Try-On and 1 on 360° Spin (patches.VARIATIONS, the panel's and the web app's …
- Distinctive: line 171
Delete before you build: standard library first, then the SDK, then one shared helper.
- Very specific: line 180
Every new code file starts with the Apache 2.0 header of the existing files (tests/test_license_headers.py checks it).
- Very specific: line 191
Before a commit: make lint, make typecheck, make secrets and make test.
- Very specific: line 203
Run ADK from the repository root, as the Makefile does (adk web .).
- Very specific: line 205
Keep the system instruction identical from call to call and the stored conversation append-only, so Gemini's prefix cache hits (Studio …
≈ “Common” is an estimate: at least 1 in 5 of the 40,246 AGENTS.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
GenMedia for Commerce: guide for coding agents
How the code works and the rules for changing it. README.md is the only user document (setup, run, deploy, configuration); CLAUDE.md and GEMINI.md only point here.
What the app does
A full-stack ADK app that makes product media for online shops with Gemini: Photo Try-On (a person wearing one product or a whole outfit), Video Try-On (the try-on animated into short videos), 360° Spin (a looping animated WebP of any product) and catalogue search. Each generation makes several variations (GENMEDIA_NUM_VARIATIONS, default 3, in parallel by default), checks each one against the product photos (and the person's face) with a judge model, ranks them and streams them as they finish.
One FastAPI app, deployed as one Cloud Run service, serves every entry point:
- the web app (
frontend/), one page: the Studio, with the agent's chat beside it; - the Studio MCP App at
/mcp, an interactive panel inside Gemini Enterprise; - the Studio agent, the app's one ADK agent, through those two and through the ADK API (
/run,/run_sse);make playgroundruns it in the ADK web UI; - REST routes that stream one generation as Server-Sent Events (
POST /api/tryon,/api/video-tryon,/api/spin).
The agent never handles image bytes: it fills a "studio" (person, products, spin photos, settings) with image references and starts background jobs. The server owns the studio state; it, the agent's ADK sessions, the jobs, the uploads and the results all live in one Cloud Storage bucket, so any instance can answer any call.
Module map
All the app's Python is under genmedia4commerce/. Read first: core/gen.py, core/runner.py, requests.py, workflows/tryon/pipeline.py, studio/agent.py and api/app.py.
| Path | What it holds |
__init__.py | Loads config.env from the repository root when it exists (environment values win); exposes agent lazily |
agent/ | Re-exports root_agent and app from studio/agent.py, where ADK looks for them. Keep it a package: agent-starter-pack writes its own sample agent.py into the agent directory (agent_directory in pyproject.toml), and the package wins the import |
fast_api_app.py | The uvicorn entry point (genmedia4commerce.fast_api_app:app) |
core/ | The shared layer, with no import from the rest of the package: settings.py and models.py (environment), clients.py (the one genai.Client factory and HTTP retry policy), gen.py (every model call), retry.py (deadlines, stalls, transport retries, ContextPool), runner.py (N variations, evaluate, rank), events.py (the event types), runs.py (run folders and output delivery), _parts.py and _json.py (request parts, lenient JSON for gen), gcs.py (Cloud Storage, resolve_image, signed URLs), media.py, _fit.py, video.py (image and frame helpers, Cloud Vision face boxes), catalog.py and catalog_search.py, cache.py, _codec.py and degraded.py (the preprocessing cache), trace.py (local debug trace), errors.py, types.py, testing.py (test helpers) |
eval/ | The checks, depending only on core/: outfit.py (the one-call outfit judge used by both try-ons), clip_check.py (one call over a whole video: the same person, nothing no reference shows), orientation.py (view of each frame), motion.py (spin direction by optical flow, glitch call), video_fidelity.py (each reference view against its best frame, shoe or product judge). garment.py, garment_detector.py and garment_prompts.py (per-reference detector, verifier, aggregator) are reachable only as video_fidelity's judge="garment", which no workflow uses |
workflows/tryon/ | Photo Try-On (below) |
workflows/video_tryon/ | Video Try-On (below) |
workflows/spin/ | 360° Spin (below) |
workflows/details.py | Close-ups of a product's small details (lettering, logos, hardware): one judge call finds them, each is cropped from the full-resolution photo and can be upscaled by the image model. Used by all three workflows |
requests.py | Builds and validates every workflow request and starts runs (start_run), for both tools.py and the REST routes |
tools.py | The four tool functions (try_on, video_try_on, spin_360, catalog_search): a generation tool folds its run's events into one JSON result; stream returns the events instead (the Studio jobs use it) |
describe.py | One-sentence description of an uploaded image (the agent gets text and a gs:// reference, never pixels) |
api/ | FastAPI: app.py (builds the app), one router per capability (tryon.py, video_tryon.py, spin.py, sharing sse.py), uploads.py, catalog.py, media.py, status.py, studio.py (the Studio for the web app), and the middlewares message_limit.py and security_headers.py. adk_home/ is the near-empty agents folder ADK puts on sys.path |
studio/ | The Studio, with no transport: the agent (agent.py, prompt.py, agent_tools.py, agent_settings.py, agent_spin.py, agent_spec.py, tool_order.py, turn.py, headless.py), its sessions (sessions.py, session_cache.py, session_limits.py, history.py, events.py), the state (store.py, state.py, patches.py), jobs (jobs.py, job_events.py, job_call.py, quota.py), images (media.py, picks.py, direct_upload.py) and service.py, the operations both clients share |
mcp/ | studio.py, the MCP App adapter over studio/service.py; static/studio.html, the panel (plain HTML and JavaScript, no build step) |
app_utils/telemetry.py | OpenTelemetry setup; by default prompts and replies are kept out of ADK's spans |
Outside the package:
frontend/: React, Vite and Tailwind.src/App.tsxroutes every path to the Studio (src/pages/Studio/):useStudio.tsandstore.ts(the state the server owns, actions, the agent chat, jobs),model.ts(the server's state as the page shows it, job specs),items.ts(picks and uploads),jobs.ts(follows a job),shrink.ts(an upload over the server's limit or of a type it does not take becomes a JPEG in the browser).src/api/client.tsis the only module that callsfetch;src/api/studio.tsholds the/api/studiocalls.src/test/mockServer.tsandstudioMock.tsare the fixture backend of the tests and ofnpm run dev:mock.infra/terraform/: APIs, the media bucket and its lifecycle rules, Artifact Registry, the service accountsgenmedia-cloudrun(runtime),genmedia-build(Cloud Build) andgenmedia-web(identity of themake webproxy), and their roles.infra/terraform.shruns Terraform, installing a pinned release when it is missing.infra/setup_infra.shismake setup-infra's Terraform part: the state in the bucket<project>-genmedia-tfstate, the import of existing resources missing from it, then plan,[Y/n]and apply.infra/web_proxy.shismake web.infra/gemini_enterprise.pywithgemini_enterprise_api.py,gemini_enterprise_app.py,gemini_enterprise_steps.py,gemini_enterprise_licence.py(the free trial when the project has no licence) andgemini_enterprise_policy.py(the organisation policy on custom MCP connectors):make register-gemini-enterpriseandmake allow-mcp-connectors. The Discovery Engine API's quirks are explained in the module docstrings.assets/catalog/products.parquet: the sample catalogue (products, preset models, spin sets, with WebP images and embeddings), copied into the image.services.yaml: registers thestudio://session service foradk web.tests/: one folder per package (below).
Data flow
Every generation
caller: a Studio job (studio/jobs.py, through tools.stream) or a REST route (api/sse.py)
-> requests.py: build and check the request; start_run resolves image refs,
keeps the inputs (input mode gcs) and starts the run
-> workflows/<capability>/pipeline.py: classify, references, prompt
-> core.runner.run_variations: make N variations, evaluate each (eval/), rank
-> core.runs.deliver: first event progress("run"); media inline as base64,
or saved to the run's outputs/ (output mode gcs)
-> events: progress, classified, variation (one per result), then one complete or one errorA workflow never raises to its caller: it ends with one ErrorEvent whose message is meant for the user (core.errors.user_message(exc) for an exception). The REST routes stream the events, the tool functions fold them into a dict, a Studio job folds them into its job document.
Photo Try-On (workflows/tryon/)
pipeline.run_tryon:
inputs.trimmedcrops the product photos to their content. With the cache on (GENMEDIA_USE_CACHE),cache.TryOnCachemay return steps 2 and 3 (and the styling plan) from an earlier run with the same photos and options.classify.classify_products(prompt and schema inclassify_prompt.py): one judge-model call groups the photos into products (category, view, worn or flat, detail photos, the look photo of a whole outfit) and picks the framing. Thenworn.normaliseturns every photo it saw worn (product photos, the look photo, the reference model) into the same photo on a grey mannequin, all at once: one image-model edit (mannequin.py, a standalone module that imports nothing from the package) pasted back onto the photo, so every product pixel stays the original's. Every later step gets these photos and the prompt says "on a mannequin" (ProductRef.mannequin); a photo whose edit fails twice stays as it is (a fallback). Each converted photo is cached on its own, so a cache hit gets them too. The person photo is never touched.- At once:
closeups.search(onedetails.findper product; close-ups of one photo that would show nearly the same area become one,closeup_merge.py) andreference.complete: one box call per photo to crop from (cut.py), the look photo's top and bottom halves as worn examples, each product's hero image (hero.py) and, withcomplete_look, the products the look photo adds. A look item that is no uploaded product but takes one's place (the same category for footwear, bottom, dress, headwear, eyewear and bag,SINGLE_SLOT; for layered items the classifier'sinstead_of) leaves one of the two:GENMEDIA_LOOK_CONFLICT(or the request'slook_conflict)upload, the default, keeps the upload,lookthe look item, and the upload is then set aside (not drawn, judged or recorded as a product);Classification.look_conflictsand a progress line say which. _prepare: the wearer (inputs.wearer: the photo as it is plus its Cloud Vision face crop;GENMEDIA_UPSCALE_FACEupscales the prompt's face) and, for an outfit, the styling plan (outfit.analyze_outfit).- The run record (
record.py) for a later video of these images, then the front variations:prompt.build_promptfor one product,outfit_prompt.build_promptfor several (one card per product,cards.py; how each is worn,styling.py),gen.generate_image, andevaluate.evaluator(theeval/outfit.pyjudge, which also sees the wearer and the face on the front view). WithGENMEDIA_TRYON_FIX(off by default) each rejected image, front or back, gets one fix attempt (fix.py, prompt infix_prompt.py) and is judged again; a fix that passes replaces it, markedfixed. The prompts never leave a bare chest or underwear on show (a plain top or bottom in a neutral colour instead), and the judge checks it on the front view. The body rule makes all visible skin the wearer's own, never a mannequin's grey surface, and each product or crop shown on a mannequin says its body is not part of the product. The judge rejects any part of a mannequin on the wearer (grey, white or plastic skin, mannequin toes, feet, hands, neck or face) as a body defect. - A single product with back photos then gets back views drawn from the best front (
back_view). Onecompleteranks the fronts.
Video Try-On (workflows/video_tryon/)
pipeline.run_video_tryon runs run_tryon first (its events tagged extra.stage="image") and animates one front, in a branch (branch.py): with check_images on, once every image is made and judged, the best that passed (with none passing, the best-scoring one); with it off, the first image made, while the others are still being made (they are shown, not animated). The branch first draws the back view of a single product with a back photo, then builds the references (refs.py: the try-on image, its back, the face, photos of accessories and shoes, close-ups), plans 1 to 3 shots of 8 to 10 seconds in total (plan.py), writes the video model's prompt (prompt.py) and makes GENMEDIA_VIDEOS_PER_IMAGE videos (default 2). Each video is checked by evaluate.evaluate_clip, all calls at once: the products on still frames (checks.py, one outfit judge call per frame in video-frame mode, with every product) and the whole clip (eval/clip_check.py: the same person, no frame showing what no reference shows), which rejects without waiting for the judges. One complete ranks every video. With a tryon_image only that image is animated; its products come from the try-on's run record (source_run) or are classified again.
360° Spin (workflows/spin/)
pipeline.run_spin: classify.py (one judge call: product type, the view of each photo, a description). Shoes: shoe_refs.py builds the reference set (labels, pairs cut apart by shoe_split.py, a mirrored twin by the image model in shoe_twin.py), one video with the tuned shoe prompt, then shoe_check.py (frame labels give the direction, turn-backs, jumps and the loop cut; the shoe judge compares frames and references). Other products: product_refs.py (the best photo per angle, reduced to the product alone), close-ups (details.find, GENMEDIA_SPIN_CLOSEUPS), two half turns of one stored video interaction (prompt.orbit_turns), then product_check.py (optical flow, frame labels and a glitch call; an anticlockwise spin is reversed, a turn-back rejected, the clip cut where it closes) and the product judge. Every spin is delivered as a looping animated WebP (core.video.to_webp); in output mode gcs the video model's MP4 is saved next to it unchanged (outputs/<n>.mp4, the result's video_url, never encoded again) for a download. With the cache on, cache.SpinCache may return the classification, the references and the close-ups from an earlier run with the same photos (kept only when no step took a fallback, core.degraded); the videos are always made again.
Catalogue
core/catalog.py reads the parquet file once, on first use. Its images are catalog:<id> or catalog:<id>/<n> references, which gcs.resolve_image reads, so every image input takes them. catalog_search.search embeds the query with the catalogue's embedding model, ranks by cosine similarity plus a keyword boost and falls back to keywords alone when the embedding call fails; find lists the featured products for an empty query.
A Studio turn
Gemini Enterprise chat -> studio_agent MCP tool (mcp/studio.py) or web app -> POST /api/studio/{id}/agent
-> studio/service.py agent_turn -> studio/agent.py run_agent_turn: a turn scope (studio/turn.py)
holding a working copy of the saved state
-> ADK Runner on the studio's session (studio/sessions.py, the studio:// service)
-> _StudioPlugin rewrites the user's message before it is stored: attachments become [studio] lines;
the current studio and up to six of its images (gs:// file parts) are added when they changed
-> the Studio agent (LlmAgent, instruction in studio/prompt.py, twelve tools in agent_tools.py,
agent_settings.py and agent_spin.py) edits the working copy and records patches
-> state.save_patched applies the turn's patches to the saved state.json; the reply and
the state go back to the panel or the web app- The panel and the web app send each user change as an action (
studio_event,POST /api/studio/{id}/actions):state.apply_actionchecks it with the rules inpatches.py, applies it tostate.jsonand notes it in the session as a[studio]line (history.py,events.py). - Run options' "Check images" and "Check videos" are the settings
check_imagesandcheck_videos(patches.CHECKS, kept with the Photo Try-On settings, off by default) passed to the tools (on by default there and in the REST routes). Off, no judge call is made:runner.skip_checkspasses each result withtypes.unchecked()("Not checked", no score); a spin keeps only the local turn and loop cut (product_check.shape). - Run options' variations default to 3 on Photo and Video Try-On and 1 on 360° Spin (
patches.VARIATIONS, the panel's and the web app'sDEFAULTS). A Video Try-On job shows every try-on image (images) and expectsGENMEDIA_VIDEOS_PER_IMAGEvideos once it picks the one to animate. - Jobs: the agent's
generate,remake_videoandanimate_photoand the clients' job routes all start throughquota.start(saved studio, one running job per studio,GENMEDIA_MAX_JOBS_PER_HOURper studio,GENMEDIA_MAX_JOBS_PER_INSTANCE_HOURper instance).jobs.pyrunstools.streamon that instance, with outputs in the bucket, and rewrites the job document as events arrive (withexpectedandfailedcounts per list, which the clients turn into placeholders); clients poll it. A user action answers once the state is saved; its note for the agent is written right after (history.note_later), and a chat turn waits for pending notes first. When the job ends,state.finish_jobrecords the results and a note goes to the session. generaterefuses withneeds_confirmationwhen the agent changed the inputs, the page or a cost setting in the same turn (tool_order.py), so the agent asks "Shall I generate?" first.- Without a panel (
adk web, the ADK API)_before_agentopens aHeadlessscope for the studio named by the ADK session id;headless.pyturns attached images into uploads and saves finished results as ADK artifacts.
Prompts and model ids
- Model ids live only in
core/models.py: one environment variable per role (GENMEDIA_TEXT_MODEL,GENMEDIA_TEXT_LITE_MODEL,GENMEDIA_JUDGE_MODEL,GENMEDIA_STUDIO_AGENT_MODEL,GENMEDIA_IMAGE_MODEL,GENMEDIA_VIDEO_MODELwithGENMEDIA_VIDEO_API_REVISION,GENMEDIA_EMBED_CATALOG_MODEL) and a default. Code reads them withget_models(). The judge model also does the classifying, boxing and planning calls. - Prompts live next to the code that sends them, as module constants:
workflows/tryon/(prompt.py,outfit_prompt.py,styling.py,skin.py,classify.py,cut.py,outfit.py,fix_prompt.py,mannequin.py),workflows/video_tryon/(plan.py,prompt.py),workflows/spin/(prompt.py,shoe_prompts.py,classify.py,product_check.py),workflows/details.py,eval/(the long ones ineval/prompts/*.md, the others inmotion.py,orientation.py,clip_check.py,video_fidelity.py),studio/prompt.pyand the tool docstrings instudio/agent_tools.py(the agent),describe.py, and the upscale prompts incore/media.py. - The try-on prompts are pinned by golden files in
tests/workflows/tryon/golden/. - Any change to a source or prompt file starts a new preprocessing cache (
core.cache.code_fingerprint).
Settings
core/settings.pyreads the deployment settings once (get_settings(), cached);PROJECT_IDis checked only when first used, so the package imports without it. A few variables are read where they are used:ALLOW_ORIGINS,SESSION_SERVICE_URI,LOGS_BUCKET_NAMEandK_SERVICEinapi/app.py,GENMEDIA_MAX_JOBS_PER_*instudio/quota.py, the telemetry ones inapp_utils/telemetry.py..env.exampledeclares every variable the code reads, with its default: add a new one there (adk-recipes' env-var check fails on anos.getenvoros.environread it does not declare). Do not repeat the list here.config.env(made fromconfig.env.exampleby any target that needs it) holds the values for a project; the Makefile also reads the deploy sizing, region, service name andUSE_IAPfrom it. A deployment passes the same values as environment variables.
Storage
Everything is in the media bucket (MEDIA_BUCKET, default <PROJECT_ID>-gfc-media). Earlier deployments keep their old bucket <PROJECT_ID>-genmedia-for-commerce-media-payloads, with their older runs and studios; new data goes to the new bucket, unless MEDIA_BUCKET names the old one (make setup-infra creates the bucket it names, or adopts it when it exists). Terraform deletes objects under runs/, sessions/, product_cache/ and spin_cache/ 30 days after they were written (media_retention_days).
runs/<date>/<session_id>/<run_id>/: one folder per run (core/runs.py):inputs/withGENMEDIA_INPUT=gcs(orinput=gcson a call) and forPOST /api/uploads,outputs/withGENMEDIA_OUTPUT=gcs(oroutput=gcs; events then carrymedia_uriandmedia_url, served byGET /api/media), and, for a try-on with gcs output, its run recordmanifest.jsonwithrecord/. Workflows never see the modes.runs/studio_catalog/: the Studio's copies of catalogue images; the Studio's own runs use the sessionstudio-<id>.sessions/studio-<id>/:adk/session.jsonand one object per event underadk/events/(the ADK session),state.json(the studio) andresults.json(the latest results).sessions/studio-jobs/<job_id>.json: one job. Writers on different instances go throughstore.update(a generation precondition, retried on conflict); events are written create-only.product_cache/<key>/: the try-on preprocessing cache (core/cache.py,workflows/tryon/cache.py, and each worn photo on a mannequin,workflows/tryon/worn.py), from product photos only; nothing from a person photo is cached. No image reference rule reaches it.spin_cache/<key>/prepared.json: the 360° spin's preprocessing cache (workflows/spin/cache.py): the classification, reference views and close-ups of a set of product photos.- Elsewhere: with
LOGS_BUCKET_NAMEset, ADK artifacts go to that bucket (api/app.py) and GenAI telemetry can be uploaded there (app_utils/telemetry.py); the debug trace (GENMEDIA_DEBUG_DIR,core/trace.py) is on local disk.
Rules the server enforces (keep them)
- Image references: a client or the agent may use only catalogue images, the catalogue copies and the image and video files of that studio's runs (
studio.media.allowed_ref); a job spec is checked bystudio.job_call.check_refsbefore it starts;GET /api/mediaserves run inputs and outputs (core.runs.is_media_file) and the catalogue copies, never a record or a manifest. - Studio and job ids are refused, never rewritten, when invalid (
store.check_id,store.check_job_id). - A user message is at most 4000 characters (
studio/session_limits.py;api/message_limit.pyon ADK's/runand/run_sse). - What a client or the agent's model reads of a failure is
core.errors.user_message(exc): the message ofInputError,GenerationError,EvalErrorandBusyError, a generic one for anything else (log the details). - Browser side: an enforced CSP and security headers outside
/mcp(api/security_headers.py); CORS same-origin unlessALLOW_ORIGINSlists origins (*is refused at startup); ADK's session list and delete routes answer 404; no ADK web UI is served. - Everyone who can invoke the service is one trusted team: there are no per-user accounts.
Commands
| Command | Purpose |
make install | Python dependencies with uv |
make dev | Backend on 127.0.0.1:8000 and the Vite dev server on :3000 (make run-backend / make run-frontend for one) |
make playground | Optional, best effort: the ADK web UI on :8501, the agent without a panel |
make test | The hermetic test suite |
make test-live | tests/live against the real APIs (needs credentials, costs money) |
make lint | codespell, ruff check and ruff format check with the adk-recipes rules; fails when uv.lock is out of date |
make format [FILES=...] | ruff fix and format with the same rules |
make typecheck | mypy on genmedia4commerce/, tests/ and infra/ with the flags of adk-recipes' CI (no mypy config: its CI would not read ours) |
make secrets | detect-secrets on the tracked files when a .secrets.baseline is present, skipped otherwise; mark a false positive with # pragma: allowlist secret |
make check-panel | Syntax check of the script in mcp/static/studio.html |
make tf-check | terraform fmt -check and validate, no Google Cloud access needed |
make setup-infra | Terraform plan and apply after a [Y/n] (APIs, bucket, Artifact Registry, service accounts, IAM; TF_AUTO_APPROVE=1 without a terminal); make tf-plan / make tf-destroy |
make deploy | Build from source and deploy the Cloud Run service; make deploy-agent-engine is a legacy alias |
make web, make share USERS=... | Open the deployed web app through a local proxy (gcloud run services proxy on :8080 with a token of genmedia-web, which only invokes this service: Token Creator for whoever ran make setup-infra, Invoker from make deploy; else your own token; renewed every 50 minutes; ALLOW_ORIGINS lists :8080 and Cloud Shell's Web Preview); let teammates do it (Token Creator on genmedia-web, Cloud Run Viewer on the service). Secret Manager secrets go to make deploy in SECRETS (comma-separated name, secret id and version) |
make register-gemini-enterprise [DRY_RUN=1] | Register the Studio in a Gemini Enterprise app |
make allow-mcp-connectors | Allow custom MCP connectors in the project (the per-project override of the organisation policy) |
cd frontend && npm run lint && npm run build && npm test | Biome (the adk-recipes rules), tsc and the Vite build, Vitest; npm run format fixes formatting |
Run make deploy only after the user confirms. make register-gemini-enterprise changes IAM and a Gemini Enterprise app: run it with DRY_RUN=1 first, and without it only after the user confirms; without a terminal it prints each console-only step and exits. make allow-mcp-connectors changes an organisation policy: run it only after the user confirms.
Code rules
- Delete before you build: standard library first, then the SDK, then one shared helper. One of each: one client factory, one retry policy, one runner. No dead code.
- Keep modules to at most 400 lines and functions to at most 60: split before you add. Line length is 80 (ruff, with the adk-recipes rules passed as flags by the Makefile: there is no ruff config file, and adk-recipes forbids one, as it does a recipe-local Biome config).
- Imports:
from genmedia4commerce.<pkg> import ...; never editsys.path. - Model calls go through the module so tests can fake them:
from genmedia4commerce.core import gen, thengen.generate_image(...). A model id anywhere butcore/models.pyis a bug. - Model parameters (
TextParams,ImageParams,VideoParams) travel from the request to thegen.*call; workflows set the defaults, helpers never hard-code a size, ratio, thinking level or resolution. - No
fastapi,starletteor request models undercore/,eval/orworkflows/.core/imports nothing else of the package andeval/onlycore/. gen,mediaandevalfunctions are synchronous; workflows call them withasyncio.to_thread.- Raise the types in
core/errors.py; workflows yield anErrorEventinstead of raising. - Log with
logging.getLogger(__name__), neverprint. - Every new code file starts with the Apache 2.0 header of the existing files (
tests/test_license_headers.pychecks it). README.mdis the only user document; this file is the guide to the code. Write plain, direct language, one paragraph per line.
How to test
make test(pytest -m "not live", skippingtests/integration,tests/unitandtests/load_test, which agent-starter-pack adds to the projects it creates). Tests are hermetic: no network, no credentials, no Cloud Storage. The autouse fixture intests/conftest.pysetsPROJECT_ID=test-project, a catalogue path with no file, inline input and output, no trace and no cache, and makes any realgenaiclient fail.- Fixtures:
fake_gen(fromcore/testing.py) fakes everycore.gencall and records them;storageis an in-memory bucket;catalog_filewrites a tiny catalogue. - Tests mirror the package:
tests/core,tests/eval,tests/workflows/<capability>,tests/studio,tests/api,tests/mcp,tests/infra,tests/app_utils.tests/test_runnability.pyimports the package, the agent and the app without credentials (adk-recipes requires it). - Goldens: after an intended prompt change, regenerate them with
UPDATE_GOLDEN=1 uv run pytest tests/workflows/tryonand review the diff. - Live tests are in
tests/live/, marked@pytest.mark.live, and run only withGENMEDIA_LIVE=1(make test-live). tests/benchmark/rotation/run_benchmark.pymeasureseval.motion.check_rotationon labelled clips.- Before a commit:
make lint,make typecheck,make secretsandmake test; frontend changes add the npm commands above;studio.htmlchangesmake check-panel; Terraform changesmake tf-check.
Reuse
core/imports nothing else of the package: copy it for any Gemini project that needs typed model parameters, one retry policy and a fan-out runner.eval/needs onlycore/.studio/store.py: JSON documents in Cloud Storage that several instances update safely (generation preconditions, retry on conflict).studio/sessions.py: an ADK session service on Cloud Storage (one object per event, create-only writes), registered under thestudio://scheme forget_fast_api_app(register()) andadk web(services.yaml).mcp/studio.pywithstudio/: an ADK agent behind an MCP App, with a server that owns the panel's state, one chat tool, app-only tools for the panel and background jobs the panel polls.infra/gemini_enterprise.pyregisters an MCP server on Cloud Run (<URL>/mcp) in a Gemini Enterprise app; changeDISPLAY_NAMEandSTUDIO_ACTIONSingemini_enterprise_steps.py.
Gotchas
- Run ADK from the repository root, as the Makefile does (
adk web .). Pointing it atgenmedia4commerce/agentputsgenmedia4commerce/onsys.path, whererequests.py,tools.pyandmcp/shadow the libraries of the same names; the same reason gives the backend its own agents folder,api/adk_home/. - The agent always needs the media bucket, also in
make playground: its sessions, the studio state, uploads and jobs live there. - Keep the system instruction identical from call to call and the stored conversation append-only, so Gemini's prefix cache hits (
Studio model call: <n> prompt tokens, <m> cachedin the logs). - Studio jobs run as background tasks on the instance that started them: the service needs CPU always allocated (
--no-cpu-throttlinginmake deploy), and a job whose instance stops is reported as interrupted, not resumed. - Preview URLs are V4 signed URLs: the runtime account needs Token Creator on itself (Terraform grants it). With user credentials signing fails and previews fall back to inline images.
- The panel puts each original upload straight into the bucket:
studio_upload_urlsigns a create-only PUT URL for a new upload of the studio (studio/direct_upload.py,gcs.signed_put_url), the browser PUTs the file (the resource'sconnectDomainsand the bucket's CORS instorage.tfallow it), thenstudio_upload_savedchecks and describes it asmedia.save_imagedoes. When any step fails, the panel sends a shrunk base64 copy tostudio_uploadfor the rest of the session. These tools are app-only, so the registration's reload check (STUDIO_ACTIONS, model tools only) cannot tell Gemini Enterprise has not read them: reload custom actions once after deploying a version that adds one. make run-backendsetsALLOW_ORIGINSto the Vite dev server: Vite's proxy rewrites the Host but forwards the browser's Origin, which ADK's origin check would refuse.- agent-starter-pack (Agent Garden) renders the
Makefilewith Jinja2: never write{{,{%or{#in it (tests/infra/test_deploy_files.pychecks it). Agent Garden's command ends withmake deploy-agent-engineand fillsconfig.envby replacingyour-gcp-project-idinconfig.env.example: keep that alias, that placeholder andagent_enginein[tool.agent-starter-pack.settings]until it usesmake deploy. The README's agents-cli command reads[tool.agents-cli]instead.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

