# Gotchas

> Silent failure footguns to know about before you ship.

A reference for behaviors that won't show up in type signatures or API docs but will silently break your agents in production. Skim before you ship.

## Silent Failure Gotchas

### 1. PolicyConfig has no priority field

`PolicyConfig` accepts `resource`, `actions`, and `effect` — that's it. There is no `priority` field. Adding one does nothing because policy evaluation always uses deny-overrides-allow: any matching `deny` policy wins regardless of order.

```typescript
{ resource: "payment", actions: ["read"], effect: "deny" }
```

If you want a more permissive rule on the same resource, narrow the deny by `actions` or `resource` — don't try to outrank it.

### 2. Scope rule operators

Scope rule operators are `eq`, `neq`, `in`, and `contains`. Using `ne` (common in MongoDB and other systems) silently fails — the rule loads, validates, and never matches. There's no error.

```typescript
{ entityType: "session", field: "data.teacherId", operator: "neq", value: "actor.userId" }
```

If your scope rule is unexpectedly returning everything (or nothing), check the operator spelling first.

### 3. Entity link/unlink params

The entity link and unlink operations use `fromId` and `toId`. Passing `fromEntityId` and `toEntityId` (the more verbose names you might assume) silently fails — the call returns successfully but no link is created.

```typescript
struere.entity.link({ fromId: "...", toId: "...", relationType: "owner" })
```

### 4. Model IDs use OpenRouter format

Model IDs must be in `provider/model-name` format — write `openai/gpt-5-mini`, not `gpt-5-mini`. Bare model names are rejected at runtime, but configuration sync may accept them. Always include the provider prefix:

```typescript
model: { model: "anthropic/claude-sonnet-4" }
```

### 5. Default model

When `model` is omitted from agent config, the platform uses `openai/gpt-5-mini` with `temperature: 0.7` and `maxTokens: 4096`. If you're seeing unexpected output style or token costs, check whether your agent has a model set at all.

### 6. Field masks use allowlist strategy

Field masks are fail-safe: if you define any field mask for an entity type, only the fields explicitly allowed (not hidden, not redacted) are exposed. New fields you add to the data type later will be hidden by default until you update the mask. This protects against accidental leaks but means schema changes need a mask review.

### 7. Template variables that fail

Template variables that fail to resolve render as `[TEMPLATE_ERROR: variableName not found]` in the compiled prompt. The agent then sees that literal string and gets confused. Always test prompts before shipping:

```bash
bunx struere compile-prompt <agent-slug>
bunx struere compile-prompt <agent-slug> --phone +1234567890
```

### 8. Custom tool fetch is unrestricted

Custom tool handlers receive a `fetch` function with no domain allowlist or rate limit. Handlers can call any URL on any port. Treat custom tool code as production network egress — review it like you would a webhook receiver, and never paste handler code from untrusted sources.

### 9. entity.query default limit

`entity.query` has a default `limit` of 100 and a max of 100. Agents that need to reason over more data should paginate explicitly or use a `templateOnly` tool that pre-aggregates data into the system prompt. Asking for `limit: 500` does not raise the cap.

### 10. agent.chat depth limit

`agent.chat` enforces a max depth of 3 with cycle detection. A calling B calling A is blocked at the second hop. Design agent graphs to be shallow — orchestrator at the top, specialists at the leaves, no diamond shapes.

### 11. Fixture sync deletes entities

When you sync a fixture file in the eval environment, the platform deletes all existing entities of the listed types and recreates them from YAML. This is a full reset every sync, not an incremental upsert. Don't keep production-shaped data in the eval environment expecting it to persist between fixture syncs.

### 12. whatsappOwnedTemplates is org-scoped

Most resources are environment-scoped (development, production, eval). `whatsappOwnedTemplates` is the exception — templates you register are shared across all environments in the same org. If you delete a template in dev, it disappears in prod too.
