Authoring templates
Build one agent. Deploy it to every customer with one command.
Why templates
An agency typically sells the same agent to many customers: an SDR for SaaS startups, an SEO manager for e-commerce, a support triage bot. Templates let you prototype one agent on your own account, snapshot it into a reusable definition, and deploy it to each customer with a single command. Parameters (founder name, domain, brand color) are passed at deploy time so each customer gets a personalized copy.
POST /api/templates, fork, edit) is restricted to agency or @oya.ai accounts. Deploy is open to all. Non-agency users can only deploy to their own account.Lifecycle
The full happy path:
# 1. Prototype the agent on your own account
oya account use --clear # operate as yourself, not a customer
# … build the agent in the web app or via CLI …
# 2. Snapshot the agent into a new template
oya template create \
--name "Acme SDR" \
--category sales \
--display-name "AI SDR for SaaS" \
--from-agent agt_abc123
# 3. (Optional) edit the template content in $EDITOR
oya template edit acme-sdr
# 4. Pick a customer and deploy
oya account use acct_8f3c…
oya template deploy acme-sdr \
--input founder_name="Alex" \
--input domain="acme.com" \
--name "Acme Sales Bot"
# 5. Wire gateways and deploy
oya agent gateway connect <agent_id> slack
oya agent deploy <agent_id>create --from-agent
The fastest way to author a template: point at an existing agent and Oya copies its soul (persona, mission, brand, behavior_rules, welcome_message), every skill attached to it (by skill_id), and every routine (name, prompt, schedule).
oya template create \
--name "Acme SDR" \
--from-agent agt_abc123 \
--include-kb # optional, embeds KB entries into the templateFlags
- --name: slugified into the template ID. Required only when neither --from-template nor --from-agent supplies one. Collisions get a numeric suffix.
- --display-name: pretty name shown in the gallery. Defaults to --name.
- --description, --category, --icon, --color: gallery metadata.
- --from-template <path|url|->: load a YAML template (path, http(s) URL, or - for stdin).
- --from-agent <id>: copy soul + skill_ids + routines from an existing agent.
- --include-kb: with --from-agent, also embed the agent's KB entries (large). Note: KB embedding is preserved in content but not yet auto-replayed on deploy. Customers upload KB after deploy for v1.
- --restrict-domain example.com: limit who can see the template (repeatable).
create --from-template
For full control over the schema, author a YAML file and load it directly. The shape matches the 12 built-in templates at backend/app/templates/catalog/<id>/TEMPLATE.yaml, the same files the Claude Code skill ships as references.
# From a file
oya template create --from-template ./acme-sdr.yaml
# From stdin
cat acme-sdr.yaml | oya template create --from-template -
# From a public URL (gist, GitHub raw, etc.)
oya template create --from-template https://gist.githubusercontent.com/you/.../acme-sdr.yamlThe canonical YAML schema:
id: acme-sdr
display_name: "Acme SDR"
tagline: "Sends 300 personalized cold emails per day"
description: |
Runs a real outbound motion every weekday for SaaS startups…
icon: target
category: sales
color: "#f59e0b"
soul:
name: "Acme SDR"
chat_model: "gemini/gemini-2.5-flash"
persona: |
You are an experienced Sales Development Representative…
behavior_rules:
- "Always personalize outreach with company-specific context."
- "Research the lead's company before drafting any message."
welcome_message: |
I'm your AI SDR. Tell me about your ICP and I'll start sourcing leads.
skills:
- web-search
- fetch-url
- brevo
- gmail-read
- memory
gateways_required:
- platform: apollo
label: "Apollo.io"
description: "Search leads and enrich contact data"
deploy_inputs:
- field: company_name
label: "Company Name"
type: text
required: true
placeholder: "Acme Corp"
knowledge_base:
- filename: "outreach-playbook.md"
content: |
# SDR Outreach Playbook
## Email Structure
…
routines:
- name: "Daily Lead Search"
schedule_cron: "0 9 * * 1-5"
schedule_human: "every weekday at 9am"
prompt: |
Populate today's raw candidate pool…Read ~/.claude/skills/oya/templates/ai-sdr.yaml, then mirror its shape. Every key you see in the example is supported; keys you don't see are not.fork & edit
Built-in templates (the curated gallery shipped with Oya) are read-only. To customize one, fork it into your account first.
# Fork a built-in
oya template fork ai-sdr --name "Acme SDR"
# → forked_from: ai-sdr
# → new id: acme-sdr
# Edit in $EDITOR (YAML round-trip)
oya template edit acme-sdr
# Print the current YAML (e.g. for piping into git)
oya template get acme-sdrdeploy
oya template deploy <template_id> creates a new agent owned by the target account, applies the template's soul, attaches every skill the template references, and installs every routine.
oya template deploy acme-sdr \
--account acct_8f3c… \
--name "Acme Sales Bot" \
--input founder_name="Alex" \
--input domain="acme.com"Deploy target resolution
The deploy target (which account owns the new agent) resolves in this order:
- 1. The --account flag, if passed.
- 2. The active target from `oya account use <id>`, if pinned.
- 3. Your own account.
deploy_inputs
--input KEY=VALUE values are persisted on the new agent's config.deploy_inputs map. Skill scripts and routines that reference oya_runtime.config() can read them at runtime. Full string interpolation in soul / skills / routines text is a v2 follow-up. Today, deploy_inputs are passed to scripts but not auto-substituted into soul copy.
Visibility restrictions
Set --restrict-domain on create (or edit it later) to limit a template to users from specific email domains. Useful when a template carries customer-specific copy or branded assets.
oya template create \
--name "Acme Internal" \
--from-agent agt_internal \
--restrict-domain acme.com \
--restrict-domain acme-agency.com