Agent code export¶
Status: Current | Last reviewed: 2026-07-08
TrueServe can export selected agents as credential-free code packages for external runtimes. Three code targets exist: Strands (Python), Genkit (TypeScript), and Google ADK (Python). Workflows have their own code target — see Workflow code export.
Entry point¶
GET /api/pipelines/[pipelineId]/export?format=<target> returns application/zip:
format |
Target | Flag |
|---|---|---|
strands |
Strands Python package | ENABLE_STRANDS_EXPORT / NEXT_PUBLIC_ENABLE_STRANDS_EXPORT |
genkit |
Firebase Genkit TypeScript package | ENABLE_CODE_EXPORT / NEXT_PUBLIC_ENABLE_CODE_EXPORT |
adk |
Google ADK Python package | ENABLE_CODE_EXPORT / NEXT_PUBLIC_ENABLE_CODE_EXPORT |
The route uses the same version-aware lookup as the existing pipeline export endpoint:
agent-id:draftresolves the draft version.agent-id:vNresolves the requested published version.agent-idfalls back through the current export lookup behavior.
Translation and copilot agents return 422 for every code target. ADK additionally requires a Google-stack conversational stage (see below). The 422 body carries {error, issues[]}; the editor's export modal (components/ExportModal.tsx, opened from the agent editor's Export button) surfaces the issue messages in a toast. The modal presents every enabled format as a selectable card with a description and a "what's included" line, grouped into Configuration (portable / JSON / AgentCore — re-importable) and Code packages (Strands / Genkit / ADK — one-way) — the fetch-based code-package handlers keep the 422 issue list visible.
Deferred: AgentCore SDK.
format=agentcorealready exists and means raw-config JSON for the config-sync-service (DynamoDB sync path) — it is NOT a code package. A future Bedrock AgentCore SDK code export must use a new format name (e.g.agentcore-sdk); do not repurpose the existing branch.
Shared core¶
All agent code targets share apps/ai-manager/lib/agent-code-export/core/:
types.ts— target-agnostic analysis/report types +AgentExportUnsupportedError(status 422) base class every target error extends.config.ts— pipeline traversal (iterateAgentStagesrecurses swimlanes + subagent inline pipelines + translation flows), agent-kind inference, model-stage selection, topology warnings, voice-session analysis (analyzeVoiceSession: voice/greeting/VAD params), subagent analysis (analyzeSubagents).prompt.ts— prompt template extraction (pickPromptTemplateWithSourcereports which config key supplied the prompt, so agreeting_messageused as the prompt fallback is not also emitted as a greeting), mustache-variable +{{TOOL:}}/{{SUBAGENT:}}/{{COMPONENT:}}token collection, preview rendering.tools.ts— saved-tool (MCP/knowledge) + inline-tool (API/Python) analysis with per-target labels. The MCP branch carriestool_filters,parameter_bindings, andcached_tool_schemasthrough totool.config;mcpBindingFromTool()is the typed accessor.secrets.ts— env-var registration, header scrubbing,secret_report.jsonassembly. Credential policy is alwaysno_credentials_exported.db.ts—tool_configs/integrationsrow loading scoped by the route's tenant context, plusloadComponentRows/collectComponentRowsForExport: A2UI catalog components (name, description,props_schema) for the agent'smultimodality.catalogs[], matched by trailing slug (same rule as the runtime's token resolver — built-ins were rescoped shared→tenant, so a storedv1/<slug>URI must still match) and deduped preferring tenant rows. RLS scopes the query; a missing catalog degrades to a warning, never a failed export.analysis.ts—analyzeAgentExport(), the shared analysis pipeline each target parameterizes (targetLabel,modelEnvName,modelLabel). When the caller passescomponentRows,{{COMPONENT:}}handling switches to component-aware mode: oneAnalyzedComponentToolper catalog component (real description + props schema, function names likeshow_appointmentsummary), tokens resolve to the tool name, and the genericsend_ui_eventtool is emitted only for tokens with no catalog row (a2ui_component_unresolved). WithoutcomponentRows(Genkit/ADK today) the legacy single-send_ui_eventbehavior is unchanged.emit-python.ts—pyJsonLiteral/pyJsonLoads, plus readable emitters:pyDictLiteral(hand-editable Python dict literals),pyTripleQuoted(prompts),pyDocArgsFromSchema(schema → doc lines).
Genkit, ADK, the LangGraph workflow export, and Strands (since format v2) all consume the core directly.
Common package contract¶
Every target zip contains manifest.json (target id, source version, runtime mode, generated file list), secret_report.json, unsupported.json, .env.example, README.md, and the generated source tree. No credentials, tenant settings, conversation history, UI assets, telephony config, or runtime logs are ever exported — required credential surfaces are represented only as env-var names.
Prompt rendering preserves TrueServe token order in every target: {{TOOL:}} → {{SUBAGENT:}} → {{COMPONENT:}} → mustache merge fields, with system built-ins (current date/time, session id, agent id, tenant id) injected per render.
Strands (Python) — format v2¶
Format v2 (2026-07-08) rewrote the target for fidelity: purpose-built, hand-editable code instead of a generic JSON-interpreting runtime. The old tools/runtime.py interpreter (including its exec() path) and data/tool_specs.json are gone — everything is baked into readable modules.
- Eligibility: chat + voice agents. Full voice is limited to stages that map to a Strands bidi provider (OpenAI Realtime →
BidiOpenAIRealtimeModel, AWS Nova Sonic →BidiNovaSonicModel); cascaded STT→LLM→TTS exports a runnable chat core + arun_voice.pyshell (degraded, warned). - Split runtimes. Voice agents get a
BidiAgentcore (build_bidi_agent()) andrun_voice.pyas the only entrypoint (--textswaps the microphone for keyboard I/O — the no-audio-hardware smoke test); the realtime model id and configured voice are baked in. Chat agents get a persistent multi-turnAgent(build_agent()), a REPLrun_chat.py, and a per-session FastAPIserver.py; the provider model (OpenAIModel/AnthropicModel/BedrockModel) is emitted with only that agent's branch, temperature included. Voice session params Strands cannot set (vad_threshold, silence_duration_ms, temperature, transcription language) are emitted as a documented comment block inagent.pywith the source values. - Prompt (
prompt.py): the system prompt is a triple-quoted constant with{{TOOL:}}/{{SUBAGENT:}}/{{COMPONENT:}}tokens pre-resolved at export time to the registered tool function names; only{{variable}}merge fields remain (render(variables),missing_variables()warns at startup). A greeting instruction rides the prompt as a final "Conversation opening" section. - UI components (
tools/ui.py): one module-styleTOOL_SPECtool per catalog component with the component's reala2ui_components.descriptionand verbatimprops_schema(readable dict literal + prop-summary comments). All component tools funnel intoemit_ui_event(component, props)— the single marked host-integration point (prints JSON envelopes by default;UI_EVENT_HANDLERhook for real UIs).post_send_instructionis appended to the tool result when configured. - MCP (
tools/mcp_tools.py): when a saved MCP/Knowledge tool hascached_tool_schemas, each (filtered) method becomes a named wrapper tool — advertised schema minus the bound params,parameter_bindingsmerged server-side on every call, so{{TOOL:}}prompt tokens resolve to real registered names. Without cached schemas theMCPClientis attached directly (withtool_filters={"allowed": [...]}). Clients are lazy singletons withatexitcleanup. - Other tools: inline API → self-contained
httpxmodule with url/method/headers/response_path baked as literals (secret headers scrubbed to env vars); inline Python → verbatim source + typed entry wrapper (inline_python_runtime_changedwarning);builtin:end_session→ functional (tools/session.pysetsrequest_state["stop_event_loop"] = True, which both the chat event loop and the bidi voice loop honor); enabled inline subagents on chat agents →tools/subagents.py(one tool per subagent constructing a nestedAgentwith the inline prompt/model); disabled/ref/voice subagents → explicitunsupported.jsonentries; telephony built-ins →unsupported.json; unresolved{{COMPONENT:}}tokens → genericsend_ui_eventfallback. - Deps: voice pins
strands-agents[bidi,bidi-io,bidi-openai]>=1.41.0(PyAudio needs PortAudio — documented in the README); chat uses the provider extra;mcp/httpx/fastapi only when used. - Manifest extras:
components[](component → tool mapping) andmcp_tools[](mode, filtered functions, bound params).
Genkit (TypeScript)¶
- Eligibility: chat + voice; voice is always degraded (Genkit has no bidi live-audio API) — chat core +
genkit_voice_degradedwarning, never a 422. - Package:
package.json(ESM,genkit+ model plugin +@genkit-ai/mcpwhen needed),tsconfig.json,src/{agent,prompting,chat,server}.ts,src/tools/(oneai.defineToolmodule per local tool). - Model mapping: google →
@genkit-ai/googleai; google-vertex →@genkit-ai/vertexai; openai/anthropic/other →@genkit-ai/compat-oai(anthropic flagged withanthropic_via_compat_oai). Model id ridesGENKIT_MODEL. - Tools: input schemas are converted to zod source text; inline Python tools cannot run in TS and export as stubs (
inline_python_not_portable); MCP/knowledge tools becomemcpClientplugin entries with env-var URL/auth. - Module:
apps/ai-manager/lib/agent-code-export/genkit/.
Google ADK (Python)¶
- Eligibility gate (422): the primary conversational stage must be Google-stack — provider
google/google-vertexor service in {google-adk,google-adk-agent,google-gemini,google-vertex}. Everything else raisesadk_requires_google_stack. - Package:
pyproject.toml(google-adk),src/trueserve_exported_agent/{agent,prompting}.py,tools/,run_chat.py,run_voice.py.agent.pyexposesroot_agent, soadk run/adk webwork out of the box. - Instruction is an
InstructionProvidercallable, never a plain string — ADK'scanonical_instructionruns{var}session-state templating on string instructions, which collides with TrueServe's literal{{mustache}}placeholders (same trick as the runtime's adk.py integration). The callable wraps the shippedprompting.pyrenderer. - Tools: MCP/knowledge →
McpToolsetwith env-var URL/auth; inline API →httpxfunction tools; inline Python → local code + warning; end_session/send_ui_event→ plain function tools. - Voice: cascaded topologies degrade with
adk_voice_degraded; the model id ridesADK_MODEL. - Module:
apps/ai-manager/lib/agent-code-export/adk/.
Testing¶
Each target has focused tests appended to the ai-manager package.json test list (an explicit file list — a test file not listed never runs):
lib/agent-code-export/strands/index.test.ts— chat/voice mapping, 422s, token order, zip inventory, secret-free output, component/MCP-binding/voice-session assertions, plus refactor-safety pins: golden SHA-256 byte comparisons of the chat AND voice fixture zips, aniterateAgentStagestraversal fixture, and a secret-scrub determinism check. Regenerate the golden maps after an intentional output change withREGENERATE_GOLDEN=1 npm test(prints the new map literals to paste in).lib/agent-code-export/strands/route.test.ts— DI route tests (tenant scoping, flag-off 404, 422 passthrough, version fallback).lib/agent-code-export/genkit/index.test.ts— package shape, provider plugin mapping, degraded voice, 422, zod generation, secret scrubbing, python-stub warnings.lib/agent-code-export/adk/index.test.ts— package shape, Google-stack gate,InstructionProvideremission, vertex acceptance, degraded voice, secret scrubbing.