- No database-level foreign keys. Use Rails associations (
belongs_to,has_many) withdependent: :destroyfor referential integrity. Do not useadd_foreign_keyin migrations.
- Never use
render json:in controllers. All JSON responses go through jbuilder views inapp/views/<namespace>/<controller>/. - For data responses, create a
.json.jbuilderview matching the action name. Use partials (_model.json.jbuilder) for reusable shapes. - For simple responses, use the helpers from
ApplicationController:render_error(message, status:)→ rendersshared/error.json.jbuilderrender_success→ rendersshared/success.json.jbuilderrender_message(text)→ rendersshared/message.json.jbuilder
- Never use camelCase in Ruby or Python. All hash keys, JSON response keys, method names, and variable names must be snake_case.
- The frontend converts automatically at the boundary:
- Responses (snake_case → camelCase): Axios response interceptor in
frontend/lib/axios.tsusescamelcase-keys. - Requests (camelCase → snake_case): Axios request interceptor converts outgoing bodies with
snakecase-keys. - WebSocket events:
frontend/composables/useWebSocket.tsconverts incoming events withtoCamelCase()and outgoing sends withtoSnakeCase(). - Shared helpers live in
frontend/lib/case-converter.ts.
- Responses (snake_case → camelCase): Axios response interceptor in
- When adding new API endpoints or WebSocket events, just use snake_case on the backend — the frontend conversion layer handles the rest.
- Never read
ENVdirectly. All environment access goes through a centralized config class. - Ruby:
AppConfigmodule inconfig/app_config.rb— access viaAppConfig.method_name(e.g.AppConfig.redis_url). Add new env vars as methods here. - Python:
AppConfigclass inagent_service/agent/config.py— access viaapp_config.field_name(e.g.app_config.openai_model). Add new env vars as fields here. - Both read from the shared root
.envfile. See.env.examplefor all available variables.