Project Structure
Organization-centric project layout and configuration
Struere uses an organization-centric project layout where all agents, entity types, roles, triggers, and tools are defined as code in a single project directory. The CLI watches these files and syncs them to the Convex backend.
Directory Layout
my-org/
├── struere.json
├── agents/
│ ├── scheduler.ts
│ ├── support.ts
│ └── coordinator.ts
├── entity-types/
│ ├── teacher.ts
│ ├── student.ts
│ ├── guardian.ts
│ ├── session.ts
│ ├── payment.ts
│ └── entitlement.ts
├── roles/
│ ├── admin.ts
│ ├── teacher.ts
│ └── guardian.ts
├── triggers/
│ └── notify-on-session.ts
└── tools/
└── index.ts
Directory Descriptions
agents/
Each file exports a single agent definition using defineAgent. The file name is conventionally the agent's slug, though the slug is determined by the slug field in the definition.
import { defineAgent } from 'struere'
export default defineAgent({
name: "Scheduler",
slug: "scheduler",
version: "0.1.0",
systemPrompt: "You are a scheduling assistant for {{organizationName}}.",
model: { provider: "anthropic", name: "claude-sonnet-4" },
tools: ["entity.create", "entity.query", "event.emit"],
})
entity-types/
Each file exports a single entity type definition using defineEntityType. Entity types define the schema for structured data that agents can create, query, and manage.
import { defineEntityType } from 'struere'
export default defineEntityType({
name: "Teacher",
slug: "teacher",
schema: {
type: "object",
properties: {
name: { type: "string" },
email: { type: "string", format: "email" },
subjects: { type: "array", items: { type: "string" } },
hourlyRate: { type: "number" },
},
required: ["name", "email"],
},
searchFields: ["name", "email"],
})
roles/
Each file exports a single role definition using defineRole. Roles include policies (allow/deny rules), scope rules (row-level security), and field masks (column-level security).
import { defineRole } from 'struere'
export default defineRole({
name: "teacher",
description: "Tutors who conduct sessions",
policies: [
{ resource: "session", actions: ["list", "read", "update"], effect: "allow" },
{ resource: "student", actions: ["list", "read"], effect: "allow" },
{ resource: "payment", actions: ["*"], effect: "deny" },
],
scopeRules: [
{ entityType: "session", field: "data.teacherId", operator: "eq", value: "actor.userId" },
],
fieldMasks: [
{ entityType: "session", fieldPath: "data.paymentId", maskType: "hide" },
{ entityType: "student", fieldPath: "data.guardianNotes", maskType: "hide" },
],
})
triggers/
Each file exports a single trigger definition using defineTrigger. Triggers define automations that fire when entities are created, updated, or deleted.
import { defineTrigger } from 'struere'
export default defineTrigger({
name: "Notify on New Session",
slug: "notify-on-session",
on: {
entityType: "session",
action: "created",
condition: { "data.status": "scheduled" },
},
actions: [
{
tool: "entity.get",
args: { id: "{{trigger.data.teacherId}}" },
as: "teacher",
},
{
tool: "event.emit",
args: {
eventType: "session.notification",
entityId: "{{trigger.entityId}}",
payload: { teacher: "{{steps.teacher.data.name}}" },
},
},
],
})
tools/
Contains a single index.ts file that exports all custom tool definitions using defineTools. These tools are shared across all agents in the organization.
import { defineTools } from 'struere'
export default defineTools([
{
name: "send_email",
description: "Send an email to a recipient",
parameters: {
type: "object",
properties: {
to: { type: "string" },
subject: { type: "string" },
body: { type: "string" },
},
required: ["to", "subject", "body"],
},
handler: async (args, context, fetch) => {
const response = await fetch("https://api.sendgrid.com/v3/mail/send", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SENDGRID_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
personalizations: [{ to: [{ email: args.to }] }],
from: { email: "noreply@example.com" },
subject: args.subject,
content: [{ type: "text/plain", value: args.body }],
}),
})
return { success: response.ok }
},
},
])
struere.json
The root configuration file identifies the organization and project version. Created automatically by struere init.
{
"version": "2.0",
"organization": {
"id": "org_abc123",
"slug": "acme-corp",
"name": "Acme Corp"
}
}
| Field | Type | Description |
|---|---|---|
version |
string |
Configuration schema version. Currently "2.0". |
organization.id |
string |
The Clerk organization ID |
organization.slug |
string |
The organization's URL slug |
organization.name |
string |
The organization's display name |
Credentials File
Authentication credentials are stored at ~/.struere/credentials.json after running struere login. This file is user-specific and should not be committed to version control.
{
"token": "eyJhbGciOiJSUzI1NiIs...",
"refreshToken": "...",
"expiresAt": 1710500000000
}
Environment Variables
| Variable | Default | Description |
|---|---|---|
STRUERE_CONVEX_URL |
your-deployment.convex.cloud |
Convex deployment URL for API calls |
STRUERE_API_KEY |
— | API key for production deployments |
STRUERE_AUTH_URL |
app.struere.dev |
Authentication callback URL |
Database Schema Overview
The Convex backend stores all platform data across the following table categories:
| Category | Tables | Description |
|---|---|---|
| User & Org | organizations, users, userOrganizations, apiKeys |
User accounts, organizations, memberships, and API keys (env-scoped) |
| Agents | agents, agentConfigs |
Agent definitions (shared) and environment-specific configurations |
| Conversation | threads, messages |
Conversation threads and message history |
| Business Data | entityTypes, entities, entityRelations |
Structured data types, instances, and relations (all env-scoped) |
| Events & Audit | events, executions |
Event log and agent execution tracking (env-scoped) |
| Triggers | triggers, triggerRuns |
Automation rules and execution records (env-scoped) |
| RBAC | roles, policies, scopeRules, fieldMasks, toolPermissions, userRoles, pendingRoleAssignments |
Access control definitions (roles are env-scoped) |
| Integrations | integrationConfigs, whatsappConnections, whatsappMessages, providerConfigs, calendarConnections |
External service configurations and integration data |
| Billing | creditBalances, creditTransactions |
Organization credit balances and transaction history |
| Evals | evalSuites, evalCases, evalRuns, evalResults |
Agent evaluation and testing |
Environment-Scoped vs Shared Tables
Most tables are scoped by environment, meaning development and production data is fully isolated:
Environment-scoped: entityTypes, entities, entityRelations, roles, agentConfigs, threads, messages, events, executions, triggerRuns, apiKeys, integrationConfigs, whatsappConnections
Shared across environments: agents, users, organizations, userOrganizations, toolPermissions
The agents table stores the agent name, slug, and description which are shared. The environment-specific configuration (system prompt, model, tools) lives in agentConfigs, looked up via the by_agent_env index on agentId and environment.
CLI Sync Mechanism
When you run struere dev, the CLI:
- Loads all resource files from
agents/,entity-types/,roles/,triggers/, andtools/ - Builds a sync payload containing all definitions
- Sends the payload to the Convex backend via the
syncOrganizationmutation - Watches all directories with chokidar for file changes
- Re-syncs on any file change (add, modify, or delete)
The sync payload structure:
{
agents: AgentConfig[]
entityTypes: EntityTypeConfig[]
roles: RoleConfig[]
triggers: TriggerConfig[]
}
Resources are upserted by slug or name, so renaming a slug creates a new resource rather than updating the existing one. The dev command syncs to the development environment. Use struere deploy to promote to production.