struere-developer

Installation
SKILL.md

Struere Developer Guide

Struere is a full-stack AI agent platform. You define agents, data types, roles, triggers, and custom tools as TypeScript code — then sync to development and deploy to production.

How to Use This Guide

You are working inside a Struere project. Before writing or modifying any Struere code, ALWAYS fetch the relevant documentation first. Never work from memory alone.

Documentation Routing

When you need to do something, fetch the matching URL before writing code:

Task Fetch
Define an agent https://docs.struere.dev/sdk/define-agent.md
Define a data type https://docs.struere.dev/sdk/define-data.md
Define a role / permissions https://docs.struere.dev/sdk/define-role.md
Define a trigger / automation https://docs.struere.dev/sdk/define-trigger.md
Define custom tools https://docs.struere.dev/sdk/define-tools.md
Full SDK reference https://docs.struere.dev/llms-sdk.txt
System prompt templates https://docs.struere.dev/tools/system-prompt-templates.md
Built-in tools list https://docs.struere.dev/tools/built-in-tools.md
Custom tools guide https://docs.struere.dev/tools/custom-tools.md
Chat API integration https://docs.struere.dev/llms-api.txt
OpenAPI spec https://docs.struere.dev/openapi.yaml
Set up RBAC https://docs.struere.dev/knowledge-base/how-to-set-up-rbac.md
Debug permission denied https://docs.struere.dev/knowledge-base/how-to-debug-permission-denied.md
Handle tool errors https://docs.struere.dev/knowledge-base/how-to-handle-tool-errors.md
Manage environments https://docs.struere.dev/knowledge-base/how-to-manage-environments.md
Test with evals https://docs.struere.dev/knowledge-base/how-to-test-with-evals.md
Use multiple agents https://docs.struere.dev/knowledge-base/how-to-use-multiple-agents.md
Use template variables https://docs.struere.dev/knowledge-base/how-to-use-template-variables.md
Add chatbot to website https://docs.struere.dev/knowledge-base/how-to-add-chatbot-to-website.md
WhatsApp integration https://docs.struere.dev/integrations/whatsapp.md
Google Calendar integration https://docs.struere.dev/integrations/google-calendar.md
Airtable integration https://docs.struere.dev/integrations/airtable.md
Email (Resend) integration https://docs.struere.dev/integrations/resend.md
Flow Payments integration https://docs.struere.dev/integrations/flow-payments.md
Embeddable widget https://docs.struere.dev/integrations/embeddable-widget.md
All integrations https://docs.struere.dev/llms-integrations.txt
CLI commands https://docs.struere.dev/llms-cli.txt
Define a router https://docs.struere.dev/sdk/define-router.md
Debug triggers / automations https://docs.struere.dev/platform/triggers.md
Platform concepts https://docs.struere.dev/llms-platform.txt
Model configuration https://docs.struere.dev/reference/model-configuration.md
Error codes https://docs.struere.dev/reference/error-codes.md
Rate limits https://docs.struere.dev/reference/rate-limiting.md
Project structure https://docs.struere.dev/reference/project-structure.md
Best practices https://docs.struere.dev/knowledge-base/best-practices.md
Full documentation https://docs.struere.dev/llms-full.txt
Docs index https://docs.struere.dev/llms.txt

Behavioral Rules

ALWAYS follow these rules when working in a Struere project:

  1. Fetch docs before writing code. Look up the task in the routing table above and fetch the relevant documentation. Do not write Struere definitions from memory.

  2. Ask before assuming. Before creating an agent, ask what it should do, who it serves, and what tools it needs. Before creating a role, ask what access level is needed. Do not guess.

  3. Validate after changes. Run bunx struere sync after editing resource files to validate and sync to development. Check the output for errors. Use struere triggers logs to verify trigger executions succeeded.

  4. Use bun, never npm. For package installs and script execution, always use bun install, bun run, bunx.

  5. One export per file. Each file in agents/, entity-types/, roles/, triggers/ exports a single default. Only tools/index.ts exports all custom tools.

  6. Slugs are identities. Resources upsert by slug. Renaming a slug creates a new resource — it does not rename the existing one. To rename, delete the old slug and create the new one.

  7. Environments are isolated. struere dev syncs to development + eval. struere deploy syncs to production. Data, configs, and API keys are fully isolated per environment.

  8. Keep tools under 5 per agent. Agent performance degrades with more tools. If an agent needs more, split into specialist agents connected with agent.chat.

Silent Failure Gotchas

These mistakes cause no visible errors but produce wrong behavior:

  1. PolicyConfig has NO priority field. Deny always overrides allow automatically. Adding priority does nothing.

  2. Scope rule operators: eq, neq, in, contains. Using ne (common in other systems) silently fails to match.

  3. Entity link/unlink uses fromId/toId. Using fromEntityId/toEntityId silently fails.

  4. Model IDs use OpenRouter format: provider/model-name. Write openai/gpt-5-mini, not gpt-5-mini. Write anthropic/claude-sonnet-4, not claude-sonnet-4.

  5. Default model is openai/gpt-5-mini (temperature 0.7, maxTokens 4096) when model is omitted from agent config.

  6. Field masks use allowlist strategy. New fields added to a data type are hidden by default if a field mask exists for that entity type.

  7. Template variables that fail resolve to [TEMPLATE_ERROR: variableName not found]. Always test prompts with struere compile-prompt <agent-slug>.

  8. Custom tool fetch (4th parameter) is unrestricted — it can reach any domain. However, for structured web scraping with markdown conversion, use struere.web.fetch() (3rd parameter SDK) instead — it routes through Jina and handles HTML-to-markdown conversion.

  9. entity.query default limit is 100, max is 100. Agents that need more data should paginate or use template-only tools for prompt injection.

  10. agent.chat has depth limit 3 with cycle detection. A calling B calling A is blocked. Design agent graphs to be shallow.

  11. Fixture sync deletes all existing entities in eval and recreates from YAML. This is a full reset every sync — not incremental.

  12. whatsappOwnedTemplates is org-scoped, not env-scoped. Templates are shared across environments.

  13. web.fetch returns html field (not content) when using returnFormat: "html". The default markdown mode returns content. HTML mode returns html. Always check the right field.

  14. Trigger execution errors are only visible via struere triggers logs. Immediate triggers (no schedule) don't create triggerRuns records — they emit events. Use triggers logs [slug] to see execution history, triggers log <event-id> for details.

  15. router.transfer only works inside a router context. If an agent calls router.transfer but the thread was not created via a router, the tool call throws an error. Only add router.transfer to agents that are part of a router.

  16. agent.chat in triggers may show "success" while having internal tool errors. The agent self-corrects, so the step succeeds. Use triggers log <event-id> --verbose to see the full tool call timeline including retries and errors.

Decision Frameworks

Choosing a Model

Need Model
Cost-sensitive / high-volume openai/gpt-5-mini or openai/gpt-5-nano
Balanced reasoning anthropic/claude-sonnet-4 or openai/gpt-5
Complex reasoning anthropic/claude-opus-4 or openai/o3
Fast + cheap xai/grok-4-1-fast or google/gemini-2.5-flash

Fetch https://docs.struere.dev/reference/model-configuration.md for full pricing and options.

Built-in vs Custom Tools

Use built-in tools when:

  • Agent needs simple CRUD on entities (entity.create, entity.query, etc.)
  • Agent needs to send messages (whatsapp.send, email.send)
  • Agent needs calendar or payment operations

Use custom tools when:

  • Agent needs multi-step workflows (create + notify + update in one call)
  • Agent needs external API calls (custom tool fetch is unrestricted, or use struere.web.fetch() for markdown conversion)
  • Agent needs HTML parsing or web scraping (use struere.web.fetch({url, returnFormat: "html"}))
  • Agent needs data transformation or computation
  • You want to reduce tool count by consolidating multiple operations
  • Automations need to call custom logic (tools are org-level in customTools table, no agent registration needed)

Use templateOnly: true on custom tools when:

  • You need dynamic data in the system prompt without adding to the runtime tool list
  • You want to inject computed context at prompt compile time
  • The tool should be available to ALL agents' system prompts automatically (no need to list in any agent's tools array)

Routers vs Multi-Agent with agent.chat

Use routers when:

  • You have a shared entry point (e.g., WhatsApp number, widget) serving multiple agents
  • You want zero-LLM-cost routing via deterministic rules
  • You need sticky sessions (user stays with assigned agent across turns)
  • You want automatic handoffs between agents mid-conversation

Use agent.chat when:

  • An agent needs to delegate a subtask and get a response back inline
  • You need real-time orchestration within a single conversation turn
  • The calling agent needs to process the response before replying

Structuring Multi-Agent Systems

  • Split agents by audience (customer-facing, admin-facing), not by function (booking, notification)
  • Use routers for entry-point routing when multiple agents share a channel
  • Use triggers for decoupled async communication between agents (retried, tracked) or cron-based recurring tasks
  • Use agent.chat for real-time delegation within a conversation
  • Each agent gets its own system prompt, tools, and permission scope
  • Keep the agent graph shallow (max depth 3)

Accessing Thread Context in Agents and Tools

Threads carry channel metadata that agents can use to identify senders and personalize behavior.

In system prompts:

  • {{threadContext.channel}} — the channel (whatsapp, api, widget, dashboard)
  • {{threadContext.params.phoneNumber}} — sender's phone number (auto-populated for WhatsApp)
  • {{threadContext.params.contactName}} — sender's WhatsApp display name

In custom tool handlers via the context parameter:

  • context.channel"whatsapp" | "api" | "widget" | "dashboard"
  • context.channelParams.phoneNumber — sender's phone number
  • context.threadId — current thread ID
  • context.threadContext — custom params from the caller

WhatsApp phone number pattern: When a WhatsApp message arrives, the thread stores channelParams = { phoneNumber, contactName?, lastInboundAt? }. Use context.channelParams.phoneNumber in a custom tool to query the sender's entity (e.g., struere.entity.query({ type: "teacher", filter: { field: "phone", operator: "eq", value: phone } })).

System Prompt Structure

Order your system prompt sections by priority:

  1. Security — never-do rules, boundaries
  2. Data integrity — required fields, validation rules
  3. Intent detection — what the agent should do for different requests
  4. Conversation flows — multi-turn patterns, handoff rules

Always include {{currentTime}} and {{organizationName}}. Test with struere compile-prompt <agent-slug> before deploying.

Workflow Checklists

Creating a New Agent

  1. Ask: what should this agent do? Who is the audience? What data does it need?
  2. Fetch: https://docs.struere.dev/sdk/define-agent.md
  3. Scaffold: bunx struere add agent <name>
  4. Define: name, slug, version, systemPrompt, model, tools (keep under 5)
  5. Include {{currentTime}} and {{organizationName}} in prompt
  6. Sync: bunx struere dev or bunx struere sync
  7. Test: struere compile-prompt <slug> then struere chat <slug>

Setting Up Permissions

  1. Ask: who needs access to what? What should be hidden?
  2. Fetch: https://docs.struere.dev/sdk/define-role.md and https://docs.struere.dev/knowledge-base/how-to-set-up-rbac.md
  3. Scaffold: bunx struere add role <name>
  4. Define policies (deny overrides allow, NO priority field)
  5. Add scope rules for row-level filtering (eq, neq, in, contains)
  6. Add field masks for column-level hiding (allowlist strategy)
  7. Sync and test with a development API key

Integrating via Chat API

  1. Fetch: https://docs.struere.dev/llms-api.txt or https://docs.struere.dev/openapi.yaml
  2. Create API key in dashboard (Settings > API Keys, select environment)
  3. Use slug endpoint: POST /v1/agents/:slug/chat with Bearer sk_dev_... or sk_prod_...
  4. Pass threadId for multi-turn conversations
  5. Pass externalThreadId for idempotent mapping from your system
  6. Handle errors: 401 (bad key), 429 (rate limit), 500 (agent error)

Building a Trigger Automation

  1. Ask: what should trigger this — an entity event or a cron schedule? What should happen?
  2. Fetch: https://docs.struere.dev/sdk/define-trigger.md
  3. Scaffold: bunx struere add trigger <name>
  4. Define: on — either { entityType, action, condition? } for entity triggers or { schedule, timezone? } for cron triggers (5-field cron expression: minute hour day-of-month month day-of-week)
  5. Actions can chain: use as to name step results, reference with {{steps.<name>}}
  6. Use agent.chat for intelligent processing (LLM-powered filtering, analysis)
  7. Use custom tools for deterministic operations (HTML parsing, data transformation)
  8. Sync: bunx struere sync
  9. Test: create an entity that matches the trigger's on condition
  10. Debug: struere triggers logs <slug> to see execution, struere triggers log <event-id> --verbose for details
  11. Check: struere triggers list shows last run status + errors

Setting Up a Router

  1. Ask: which agents need to share an entry point? What determines which agent handles a request?
  2. Fetch: https://docs.struere.dev/sdk/define-router.md
  3. Scaffold: bunx struere add router <name>
  4. Define: name, slug, agents array, mode (rules or classify)
  5. For rules mode: define rules with conditions array and route (zero LLM cost)
  6. For classify mode: provide classifyModel with { model, temperature?, maxTokens? } (LLM-based)
  7. Available rule fields: phoneNumber, channel, {entityType}.* (any entity type slug as prefix, e.g., contact.tier, teacher.name, student.grade), time.hour, time.dayOfWeek, message.text, message.type
  8. Available rule operators: eq, neq, in, contains, regex, gt, lt, exists
  9. Set fallback for unmatched requests
  10. Sync: bunx struere dev or bunx struere sync
  11. Test: struere chat --router <slug>
  12. Connect to WhatsApp: set routerId on a whatsappConnection

Example router definition:

import { defineRouter } from 'struere'

export default defineRouter({
  name: "Support Router",
  slug: "support-router",
  mode: "rules",
  agents: [
    { slug: "billing-agent", description: "Handles invoices, payments, and billing questions" },
    { slug: "tech-support-agent", description: "Handles bugs, errors, and technical issues" },
    { slug: "general-agent", description: "General customer support" },
  ],
  rules: [
    {
      conditions: [
        { field: "message.text", operator: "contains", value: "invoice" },
      ],
      route: "billing-agent",
    },
    {
      conditions: [
        { field: "message.text", operator: "contains", value: "bug" },
      ],
      route: "tech-support-agent",
    },
  ],
  fallback: "general-agent",
})

Key behaviors:

  • Sticky routing: once a thread is assigned to an agent, subsequent messages go to the same agent until explicitly transferred
  • router.transfer: built-in tool that agents can call to hand off the conversation to another agent in the router
  • Transfer count: tracked per thread via transferCount field, visible in conversation logs
  • Rules are evaluated top-to-bottom; first match wins
  • Classify mode uses an LLM call to pick the best agent based on agent descriptions

Debugging a Trigger

  1. struere triggers list — check if trigger is enabled, see last run status and error
  2. struere triggers logs <slug> — see execution history with step counts and timing
  3. struere triggers log <event-id> — see per-step detail: resolved args, results, errors
  4. struere triggers log <event-id> --verbose — see agent.chat tool call timeline (which tools failed, which succeeded)
  5. struere logs view <thread-id> --exec — full agent conversation transcript (thread ID shown in trigger log output)

Common issues:

  • Custom tool not found → ensure tool is defined in tools/index.ts and synced (tools are org-level, not agent-level)
  • Template variable empty → check field name matches response shape (e.g., agent.chat returns response not message)
  • web.fetch returns empty content → use returnFormat: "html" for HTML parsing, check html field not content

Adding an Integration

  1. Ask: which integration? (WhatsApp, Calendar, Airtable, Email, Payments)
  2. Fetch the integration page from the routing table above
  3. Configure via CLI: struere integration <provider> --<key> <value>
  4. Add the integration's tools to the agent's tool list
  5. Sync and test in development first
Installs
11
First Seen
Mar 30, 2026