Handle Syntax

Secret handles use a simple template syntax embedded in JSON strings.

Format

{{secret:<name>}}
  • {{ and }} are the delimiters
  • secret: is the literal prefix
  • <name> is the exact name used when registering the secret with ash secrets add

Valid Examples

{{secret:openai-key}}
{{secret:db-password}}
{{secret:analytics-api-key-prod}}

Placement in Tool Input

Handles can appear:

  • As the entire value of a JSON string field
  • As a substring within a JSON string field (embedded in a URL, header value, etc.)
{
  "api_key": "{{secret:stripe-key}}",
  "url": "https://api.example.com?key={{secret:api-key}}&format=json",
  "headers": {
    "Authorization": "Bearer {{secret:openai-key}}",
    "X-Custom": "prefix-{{secret:token}}-suffix"
  }
}

Multiple Handles

A single tool call can reference multiple secrets:

{
  "username": "{{secret:db-user}}",
  "password": "{{secret:db-password}}"
}

Each handle is resolved independently. All referenced secrets must have matching pre-approval policies.

Handle Resolution Errors

If resolution fails, the tool call returns a ToolFailed error:

CauseError Message
Secret not registeredsecret 'name' not found
Capability missingagent lacks secret.use:name capability
No matching policyno pre-approval policy matches (name, tool, host)
Policy expiredpre-approval policy expired
Policy max_uses reachedpre-approval policy exhausted

Security Notes

  • Handles are never logged by agentd - only the secret name appears in audit entries
  • The plaintext substitution happens in memory, immediately before calling the tool handler
  • The substituted input is discarded after the handler returns
  • [REDACTED:<name>] in output indicates a secret value was found and scrubbed