Skip to content

Latest commit

 

History

History
411 lines (305 loc) · 16.7 KB

File metadata and controls

411 lines (305 loc) · 16.7 KB

Agent Profile Guide

This document explains how to configure and deploy a Rowan agent for your use case. All deployment-specific configuration lives in a single YAML file (agent.yaml by default). The binary never needs to be rebuilt to change an agent's behaviour, tools, or languages.

How It Works

agent.yaml  ──►  internal/agentconfig  ──►  profile.Profile  ──►  dialog manager
                 (loads & validates)         (internal type)        (unchanged)

At startup, the agent reads the YAML file, validates it, and wires up HTTP webhook clients in place of any business logic. The dialog manager is generic — it never knows whether tools came from a YAML file or compiled Go code.

Point the agent at a profile file via the environment variable:

ROWAN_PROFILE_FILE=/path/to/agent.yaml

Default: ./agent.yaml (relative to the working directory).

YAML File Structure

agent:
  name: "Rowan"           # Agent name used in the system prompt
  domain: "order enquiries"  # Business domain, e.g. "insurance claims"

languages:
  - code: "en-US"         # BCP-47 language code
    name: "English"
    fallback: "..."        # Spoken on error
    farewell: "..."        # Spoken when max turns reached
    transfer: "..."        # Spoken before human transfer
    welcome_prompt: "..."  # LLM prompt to generate the opening greeting
    response_instruction: "Respond in English."

verification:             # omit entirely to disable identity verification
  tool:
    name: "verify_identity"
    description: "..."
    parameters: { ... }   # JSON Schema object
  webhook:
    url: "https://api.example.com/verify"
    secret: "bearer-token"  # optional
  guideline: "Before calling any sensitive tool, ask for..."

tools:
  - name: "query_order_status"
    description: "..."
    sensitive: true        # requires identity verification before use
    webhook:
      url: "https://api.example.com/orders/{order_id}"
      method: GET          # GET POST PUT PATCH DELETE — default POST
      secret: "bearer-token"  # optional
    parameters: { ... }   # JSON Schema object

policy:
  - "Return policy: Items may be returned within 30 days of delivery in original packaging."
  - "Shipping: Standard delivery takes 3-5 business days."

custom_guidelines:
  - "Always ask for the order ID before calling any order tool."

Webhook Contract

Each tool has a webhook block that controls how the agent calls it. The method field selects the HTTP method (default POST). Path parameters in {braces} in the URL are substituted from the tool params before the request is sent.

webhook:
  url: "https://api.example.com/orders/{order_id}"
  method: GET          # GET POST PUT PATCH DELETE — default POST
  secret: "bearer-token"  # optional

GET and DELETE

Params not consumed by URL path substitution are appended as query string parameters. No request body is sent.

GET https://api.example.com/orders/123456
Authorization: Bearer <secret>

POST, PUT, PATCH

Params not consumed by URL path substitution are sent as a JSON body with the standard tool envelope.

POST https://api.example.com/orders
Content-Type: application/json
Authorization: Bearer <secret>

{"tool": "create_order", "params": {"product_id": "ABC", "quantity": 2}}

Response format (all methods)

Success (2xx):

{"result": "Order 123456 is shipped and arriving tomorrow."}

Error (any non-2xx, or "error" field present):

{"error": "Order not found."}

The result string is passed directly to the LLM, which reads it to the customer in natural language. Error text is also surfaced to the LLM so it can inform the customer and decide how to proceed.

There are no retries — if a webhook call fails the error is surfaced immediately. Retry and circuit-breaker logic belongs in the customer backend or their infrastructure.

Verification

Verification always uses POST regardless of any method field — sensitive identity data must not appear in URLs or query strings.

POST https://api.example.com/verify
Content-Type: application/json

{"tool": "verify_identity", "params": {"phone_last4": "1234"}}

Success:

{"ok": true}

Failure:

{"ok": false, "error": "Phone number does not match our records."}

Demo / Mock Mode

Set mock: true on any webhook to skip the HTTP call entirely. Useful for demos and local testing with no customer backend running.

tools:
  - name: "query_order_status"
    ...
    webhook:
      mock: true
      mock_response: "Order is shipped and on its way."

For verification, mock: true always returns success:

verification:
  ...
  webhook:
    mock: true

When switching from demo to production, replace mock: true (and mock_response) with url: and optionally secret:.

Scope and Behavioural Guardrails

The agent enforces scope and accuracy at two layers: code and prompt.

What the code enforces (hard guarantees)

Behaviour Where it is enforced
Unknown tool names are rejected dispatchTool returns an error for any tool not in the profile
Sensitive tools blocked without verification dispatchTool checks state.IdentityVerified before calling the tool
Max 5 tool calls per turn maxToolRounds constant in the dialog manager

These cannot be bypassed by the LLM regardless of what it generates.

What the prompt enforces (behavioural guidance)

The system prompt instructs the agent to:

  • Only help with configured topics. The capabilities list tells the LLM what it can do; the scope restriction sentence immediately after tells it to politely redirect for anything else.
  • Never answer account/order questions without calling a tool first. This prevents the LLM from guessing or inventing account details.
  • Report tool results faithfully. The LLM is told to convey exactly what the tool returned and not add information from general knowledge.
  • Only answer general questions from the policy items. The policy section is injected as explicit reference context; the LLM is told not to draw on general knowledge for topics not covered there.

These are instructions to the LLM — they are highly effective but not absolute. No prompt-based instruction can offer a 100% guarantee against hallucination. For truly sensitive data (account numbers, balances, PII), the correct defence is to ensure that information only arrives via tool results, never in the system prompt or conversation history where the LLM could repeat it out of context.

The policy field

Use policy items for factual information the agent should be able to recite without calling a tool — return policy, shipping times, support hours, accepted payment methods, etc.

policy:
  - "Return policy: Items may be returned within 30 days of delivery in original packaging."
  - "Shipping: Standard delivery takes 3-5 business days. Express (1-2 days) is available at extra cost."
  - "Support hours: Monday to Friday, 08:00–18:00 CET."

Keep each item as a single, self-contained statement. The agent will use these to answer general questions and will decline to speculate on anything not listed.

policy vs custom_guidelines:

Field Purpose Example
policy Factual knowledge the agent may recite "Return window is 30 days."
custom_guidelines Behavioural rules for how the agent should act "Always collect the order ID before calling any tool."

Limitations

  • The LLM can still produce imprecise paraphrases of tool results. If exact wording matters (e.g., legal disclosures), review and constrain the tool result string itself in your backend rather than relying solely on the prompt instruction.
  • Verification protects tool calls, not free-text responses. If a customer mentions account details in conversation, the LLM may echo them back. Avoid injecting sensitive data into the conversation history; keep it inside tool responses.
  • Out-of-scope redirection depends on the LLM correctly classifying requests. Adding a custom_guidelines entry like "If the customer asks about topics not covered by your tools or policy, say 'I can only help with [domain]' and list what you can do" reinforces the system prompt instruction for edge cases.

Integration Patterns

Tool chaining

The dialog manager supports up to five sequential tool calls within a single turn. This lets the LLM complete multi-step workflows — for example, verify identity, then query the database, then send a notification — without extra round trips from the customer.

Order is driven entirely by the LLM, so make the intended sequence obvious in tool descriptions:

tools:
  - name: "fetch_account"
    description: "Retrieve the customer's account details. Always call this before any account-modification tool."
    sensitive: true
    ...
  - name: "update_email"
    description: "Update the email address on a verified account. Requires fetch_account to have been called first."
    sensitive: true
    ...

When the customer says "update my email", the LLM calls fetch_account, reads the result, then calls update_email — all in one turn.

CRM integration (Salesforce, HubSpot, Dynamics)

You can call CRM APIs directly using the appropriate HTTP method and URL template. For APIs that don't match the agent's response envelope ({"result": "..."}) you still need a thin integration layer to translate the response. For those that do, or where you control the API, no middleware is needed.

tools:
  - name: "get_contact"
    description: "Retrieve the customer's contact record from the CRM using their verified phone number."
    sensitive: true
    webhook:
      url: "https://integration.example.com/crm/contacts/{phone}"
      method: GET
      secret: "bearer-token"
    parameters:
      type: object
      properties:
        phone:
          type: string
          description: "Customer phone number, digits only."
      required: [phone]

  - name: "create_case"
    description: "Create a support case in the CRM on behalf of the customer."
    sensitive: true
    webhook:
      url: "https://integration.example.com/crm/cases"
      method: POST
      secret: "bearer-token"
    parameters:
      type: object
      properties:
        subject:
          type: string
          description: "One-line summary of the issue."
        description:
          type: string
          description: "Full description as described by the customer."
      required: [subject, description]

get_contact resolves to GET /crm/contacts/0701234567 — the phone param is substituted into the path and nothing goes in the query string. create_case sends POST /crm/cases with a JSON body envelope.

Ticketing systems (Jira, ServiceNow, Zendesk)

Map each operation to the correct HTTP method:

Agent tool Method URL template What it does
create_ticket POST /tickets Creates a new issue
get_ticket_status GET /tickets/{ticket_id} Looks up an issue by ID
add_comment POST /tickets/{ticket_id}/comments Appends a comment
close_ticket DELETE /tickets/{ticket_id} Closes/deletes an issue

Keep result strings short and voice-friendly — they will be read aloud on the voice WebSocket channel.

ERP / order management

tools:
  - name: "query_order"
    description: "Look up the status of an order by order ID."
    sensitive: true
    webhook:
      url: "https://erp.example.com/api/orders/{order_id}"
      method: GET
      secret: "erp-token"
    parameters:
      type: object
      properties:
        order_id:
          type: string
          description: "The order ID as stated by the customer."
      required: [order_id]

  - name: "request_return"
    description: "Initiate a return for an order. Always call query_order first to confirm the order exists."
    sensitive: true
    webhook:
      url: "https://erp.example.com/api/orders/{order_id}/returns"
      method: POST
      secret: "erp-token"
    parameters:
      type: object
      properties:
        order_id:
          type: string
        reason:
          type: string
          description: "Return reason as described by the customer."
      required: [order_id, reason]

query_order resolves to GET /api/orders/123456order_id is substituted into the path, no query string needed. request_return sends POST /api/orders/123456/returns with {"tool": "request_return", "params": {"reason": "..."}}order_id is consumed by the path template, only reason remains in the body.

Authentication and security

The secret field adds Authorization: Bearer <token> to every webhook request. Store the token in your infrastructure secrets manager and inject it at deploy time — never commit secrets to the YAML file.

For stronger security, your webhook endpoint can additionally validate the source IP against the agent's egress IP range, or require mutual TLS.

Error handling

The agent surfaces webhook errors to the LLM, which then tells the customer. Return customer-readable messages — avoid internal stack traces or system codes:

{"error": "We could not find that order number. Please check the number and try again."}

Use HTTP status codes correctly: 2xx for success, 4xx for user-correctable errors (wrong ID, missing field), 5xx for transient backend failures. The agent does not retry — add retry and circuit-breaker logic in your integration layer.

Testing your integration

  1. Mock mode first — set mock: true on all webhooks while authoring the YAML. Verify the LLM invokes the right tools in the right order before touching any real backend.
  2. Integration layer staging — point webhook URLs at a staging environment with real data.
  3. Production cut-over — swap URLs and secrets, restart the agent. No rebuild needed.

Use the test/ws/ WebSocket voice client and the POST /chat endpoint to exercise tool flows end-to-end.

Onboarding a New Customer

  1. Copy agent.yaml to a new file, e.g. insurance-customer.yaml.
  2. Set agent.name and agent.domain to match the customer's brand and use case.
  3. Define languages — list every language the agent should support. The first entry is the default.
  4. Define tools — one entry per business operation. Set sensitive: true if identity verification must be done first.
  5. Define verification — describe the identity check tool the LLM will call. Set the webhook to the customer's backend. Omit the verification block entirely if the deployment needs no identity check.
  6. Point the agent at the new file:
    ROWAN_PROFILE_FILE=/path/to/insurance-customer.yaml
  7. Restart the agent — no rebuild needed.

Updating an Existing Customer

Adding a new tool

  1. Add a new entry to the tools list in the YAML file with a name, description, sensitive flag, webhook, and parameters.
  2. Implement the corresponding endpoint in the customer backend.
  3. Restart the agent.

Removing a tool

  1. Delete the tool entry from the tools list.
  2. Restart the agent.

Changing the verification method

  1. Replace the verification block with the new tool definition and webhook URL.
  2. Restart the agent.

Adding a language

  1. Add a new entry to the languages list.
  2. Restart the agent. The system prompt, switch_language tool, and ASR language detection all update automatically.

Changing the agent persona

  1. Update agent.name and/or agent.domain.
  2. Restart the agent.

Updating tool descriptions or guidelines

  1. Edit the relevant description, guideline, or custom_guidelines fields.
  2. Restart the agent. The new wording takes effect for all new sessions.

What You Should NOT Change

  • internal/profile/profile.go — the internal Profile struct. Only change this if you need new platform capabilities.
  • internal/dialog/manager.go — the generic dialog engine. This reads from the profile; never edit it for customer changes.
  • Meta-tools (end_call, transfer_to_human, switch_language) — these are platform-level and built into the dialog manager. Do not define them in the YAML file.

File Reference

File Purpose When to edit
agent.yaml Default customer profile (demo) When updating the demo profile
<customer>.yaml Customer-specific profile When onboarding or updating that customer
internal/agentconfig/agentconfig.go YAML schema + loader + validation Only to add new profile fields
internal/agentconfig/webhook.go HTTP webhook client Only to change the webhook protocol
internal/profile/profile.go Internal Profile types Only to add new platform capabilities
internal/dialog/manager.go Generic dialog engine Never for customer changes