AI Connectors
Zenvara treats an LLM call the same way it treats a database query: a typed step with declared inputs and outputs, logged prompt and response, composing with retries, secrets, and audit. There is one connector for chat completions — ai.* — and the model vendor is a connection field, not a connector choice: the environment decides whether Provider means Anthropic, OpenAI, Google, or a self-hosted model, the same way it decides which database db resolves to.
Connectors
Section titled “Connectors”| Connector | invoke: | What it does |
|---|---|---|
| AI | ai.* | call — a chat-completion request to the configured provider; typed response with token counts and, for tool-using flows, toolCalls. |
| AI Agent | ai-agent.* | invoke, start-session — run an autonomous coding agent (Claude Code, OpenCode, Gemini CLI, Codex CLI) as a flow step, or spawn an interactive session with quota management and human-assist. |
| Knowledge | knowledge.* | index, retrieve, plan-sync, forget — chunk, embed, and query content through a swappable vector index (RAG). |
ai and ai-agent answer different questions: ai is a token-billed, conversational API call — the shape of a single completion. ai-agent is process-based and agentic — it drives a whole coding session, with its own working directory, turn limit, and quota management.
ai — example
Section titled “ai — example”using: - environment/prod - zenvara/ai
output: content: !str
steps: - $summary: invoke: ai.call on: ai with: SystemPrompt: "You are a concise analyst." Prompt: "Summarise these orders in two paragraphs:\n${orders.rows}"
- return: content: "${summary.content}"Outputs: content (generated text), finishReason, inputTokens, outputTokens, totalTokens, toolCalls (when function-calling), cacheCreationInputTokens / cacheReadInputTokens (Anthropic prompt-cache accounting — 0 for other providers). Reference them as ${summary.content}, ${summary.outputTokens}.
The Provider switch
Section titled “The Provider switch”The model vendor lives on the connection, not in the flow. A zenvara/ai connection carries a Provider field — anthropic, openai, google, or local — plus whatever that vendor needs:
type: zenvara/aikind: productionProvider: anthropicApiKey: "${secret:ai/api-key}"Model: "claude-sonnet-4-6"MaxTokens: 4096TimeoutSeconds: 120EnableCaching: true # Anthropic prompt-cache breakpoints; ignored by other providersFlip Provider to openai, point Model at gpt-4.1, and reference an OpenAI key instead — same fields, same secret-reference shape. Nothing in the ai worked example above changes: invoke: ai.call is the verb whichever vendor answers it. See Environments for how an alias like ai picks which connection is live, and Secrets for the ${secret:...} form.
Provider: local — self-hosted models
Section titled “Provider: local — self-hosted models”The strongest form of the switch: point Provider at local and the same flow runs against Ollama, LM Studio, vLLM, llama.cpp --server, or an OpenRouter gateway — anything speaking the OpenAI-compatible chat-completions wire format.
type: zenvara/aiProvider: localBaseUrl: "http://localhost:11434/v1" # Ollama default; LM Studio: http://localhost:1234/v1ApiKey: "" # Ollama needs none — a placeholder is substituted internallyModel: "qwen2.5:7b"local requires BaseUrl — the manifest enforces it. A Provider: local connection with no BaseUrl is rejected before any request is built, not discovered later at run time.
ai-agent — autonomous coding agents
Section titled “ai-agent — autonomous coding agents”ai calls are single-shot completions; ai-agent runs a full coding agent as a subprocess — Claude Code, OpenCode, Gemini CLI, or Codex CLI — with quota management, stuck-detection, and human-assist when it needs a decision. Same provider-transparency shape, one level up: Provider picks the agent (claude, opencode, gemini, codex).
using: - environment/prod - zenvara/ai-agent
output: output: !str
steps: - $task: invoke: ai-agent.invoke on: agent with: Task: "Refactor the auth module to use the new session store" WorkingDirectory: "/repo" TimeoutMinutes: 30
- return: output: "${task.output}"Outputs: success, status, output, errorOutput, exitCode, durationSeconds, provider — plus Claude-only fields (sessionId, quotaWaitSeconds, stats) when Provider: claude.
start-session spawns the agent as a long-running interactive process instead of waiting for completion — it returns immediately with a sessionId and a terminalUrl for live attach through Studio’s terminal.
Knowledge — retrieval (RAG)
Section titled “Knowledge — retrieval (RAG)”Two constraints worth flagging before the actions:
- Embeddings use a separate connection from the chat
aiconnection. The embedding-provider enum isopenai | google | local— Anthropic is intentionally absent, since it ships no embeddings API. - An index is pinned to one embedding model. Querying it with a different model is an error — vectors from different models aren’t comparable.
using: - environment/prod - zenvara/knowledge
output: chunks: !obj-list
steps: - $ingest: invoke: knowledge.index on: embeddings with: Content: "${page.body}" Collection: "handbook" SourceSystem: "confluence" SourceId: "${page.id}" SourceUri: "${page.url}"
- $hits: invoke: knowledge.retrieve on: embeddings with: Query: "What did we decide about retention policy?" Collection: "handbook" TopK: 5
- return: chunks: "${hits.chunks}"Outputs: index returns chunksIndexed; retrieve returns chunks — a ranked array of { content, score, collection, source }, each carrying its provenance for citation. plan-sync reconciles a cheap live source manifest against what’s already indexed (toIndex / toDelete) for incremental re-index jobs; forget drops every chunk for one source.
Web search
Section titled “Web search”Four single-action connectors, one shape (.search) — and a different category (Search, not AI):
| Connector | invoke: | What it does |
|---|---|---|
| Web Search (DuckDuckGo) | web-search-duckduckgo.search | DuckDuckGo’s lite HTML endpoint. No API key. |
| Web Search (Google) | web-search-google.search | Google Custom Search JSON API. Needs ApiKey + SearchEngineId (the Custom Search Engine’s cx id). |
| Web Search (Ollama) | web-search-ollama.search | Ollama’s hosted web-search API at ollama.com. Needs ApiKey. |
| Web Search (StackOverflow) | web-search-stackoverflow.search | Stack Exchange API v2.3. ApiKey optional (raises rate limits); Site selects a Stack Exchange community shortname (security, math, …) — not an arbitrary domain. |
using: - zenvara/web-search-duckduckgo
output: data: !obj-list
steps: - $hits: invoke: web-search-duckduckgo.search with: Query: "F# FS0039 value not defined"
- return: data: "${hits.data.data}"Outputs are nested: results at ${hits.data.data[*]} (title, url, snippet), pagination at ${hits.data.pagination} (totalItems, page, pageSize, itemCount, hasMore) — not a flat ${hits.data[*]}.
Tool / function calling
Section titled “Tool / function calling”ai.call supports structured content blocks (text, tool_use, tool_result) and a Tools list of tool definitions (Name, Description, InputSchema). This is provider-transparent too: InputSchema maps to whatever the provider calls it (input_schema for Anthropic, parameters for OpenAI/Gemini) at request build. A flow drives a full agentic tool loop by feeding each tool result back as a typed message — the model’s toolCalls output steers the next step.
Treating AI output as data
Section titled “Treating AI output as data”Because the response is a typed value, the next step consumes it like any other:
- $tickets: invoke: jira.search-issues with: jql: "${summary.content}" # AI output → next connector's inputThat composition — AI output flowing into a database query, an HTTP call, or a filter: — is the point of a typed AI connector. See Authoring Flows for how values flow between steps.