Skip to Content
Developer DocsServicesAgent Config Service

Agent Config Service

The Agent Config Service is the runtime source of truth for agent behavior in AEGIS: skill definitions, skill routers, rule definitions, prompt templates, platform settings, agent definitions, and entity-type definitions all live here as tenant-configurable data, not code. This is the service that makes horizontalization possible — an agent’s skills, routers, rules, prompts, and LLM settings are edited and versioned through this API (and the /configuration admin UI), never by shipping orchestration-engine code. It provides a centralized, auditable system with Jinja2 templating, role-based access control, an HMAC-signed append-only audit trail, and Redis-cached runtime resolution.

Overview

Instead of hardcoding system prompts in the orchestration engine, prompts are managed as versioned templates organized into namespaces. Each template goes through a controlled lifecycle:

  1. Draft — Authors create and edit drafts privately. One draft per user per template.
  2. Pre-production — Promoted drafts are immutable and can be targeted to specific users (target_users) and/or platform roles (target_roles, R42e) for testing.
  3. Active — The production version served to all users at runtime.
  4. Archived — Superseded versions preserved for audit and rollback.

Every lifecycle transition is recorded in an HMAC-signed, append-only audit trail.

The orchestration engine calls this service at conversation start to render the agent’s persona (when it uses a prompt_template_ref) and each loaded skill’s context_templates (R35 P3b), which system_prompt_node assembles into the base system message. These runtime calls send X-Tenant-Id (always) and X-User-Id (when a real end user is present) as headers; /resolve + /render accept identity optionally (get_optional_user). A best-effort soft-fail degrades prompt context (but never the skill’s executable surface) if the service is unreachable.

Port & Language

PropertyValue
Port8010
LanguagePython 3.12
FrameworkFastAPI
Entry pointsrc/agent_config/main.py

Key Endpoints

MethodPathDescription
GET/prompts/namespacesList namespaces (filtered by user access)
POST/prompts/namespacesCreate a namespace (tenant admin only)
GET/PUT/prompts/namespaces/{id}Get or update namespace config
GET/POST/prompts/namespaces/{id}/tiersList or create budget tiers
GET/POST/prompts/templatesList or create prompt templates
GET/PUT/DELETE/prompts/templates/{id}Template CRUD
GET/POST/prompts/templates/{id}/versionsList versions or create a draft
GET/prompts/templates/{id}/my-draftGet current user’s draft
PUT/prompts/versions/{id}/save-draftUpdate draft in place, re-validate
DELETE/prompts/versions/{id}/discardHard-delete a draft
POST/prompts/versions/{id}/promote-pre-prodPromote draft to pre-production. Accepts target_users: string[] and (R42e) target_roles: string[] — the version is served when the caller is pinned in target_users OR holds a role overlapping target_roles; the /override and /merge conflict resolvers accept both fields too
POST/prompts/versions/{id}/promote-activePromote pre-prod to active
POST/prompts/versions/{id}/approveApprove a pending promotion
POST/prompts/versions/{id}/rejectReject with reason
POST/prompts/versions/{id}/restoreRestore an archived version
GET/prompts/resolve/{namespace}:{slug}Resolve best version (hot path)
POST/prompts/render/{namespace}:{slug}Resolve + render with variables
GET/prompts/pending-approvalsList pending approvals for current user
POST/prompts/audit/verifyVerify HMAC signatures on audit entries
GET/POST/PUT/DELETE/prompts/namespaces/{id}/accessNamespace access control CRUD
GET/POST/skillsList or create skill definitions. R38 — create accepts optional as_draft (default false): when true, the skill is created with review_status='draft' regardless of caller role (admin included) — the multi-step wizard path. Drafts are stomp-proof (subsequent writes don’t change status); POST /skills/{id}/submit is the sole exit
GET/PUT/DELETE/skills/{id}Skill detail / update / soft-delete. Create/update accept context_mode (reason_alongside|compute_and_return, R35 §11.4), response_mode (llm_synthesis|verbatim, migration 019 — verbatim = the platform renders the skill’s tool results to the user verbatim as the turn’s primary response; the LLM is instructed not to restate them) and version_check_on_turn (R36 — compliance-grade skills check their loaded version every turn). Create/update responses include a warnings list (e.g. an active non-dependency skill with no approved router mapping is unreachable via selection — warn, never block).
POST/skills/{id}/submitR38a — the ONLY exit from draft: draft → pending_review. Author-or-admin (ownership-derived); any other current status → 409 with current_status in the body. Pairs with the reopen_review draft-guard: a draft survives any number of writes until explicitly submitted
POST/skills/{id}/reviewApprove/reject a skill under review (admin, R37a). R38 — approve accepts optional router_id (UUID): if the skill has no approved mapping under an active router, the approval transaction also creates an approved skill_router_map entry under that router and (if none exists) an approved skill_description_versions row from the skill’s description column (falls back to display_name) — “publish-on-approve”, making the skill reachable via R36 selection. Idempotent (approving an already-mapped skill is a no-op on the mapping); unknown router_id404 and the approval rolls back; ignored on reject and on the revision-promote path. Approve responses include unmapped: booleantrue when the skill ends the review unreachable via selection
POST/skills/{id}/revisionsR38a — clone an approved, active skill into a private draft revision (the GitHub-clone model). Deep-copies code blocks, personas, outgoing dependencies, and skill-sourced advisory rules (rule keys copy byte-identically — rule uniqueness is per-skill since migration 015); owner_id = the cloner, parent_skill_id = the source row, same skill_key (legal under the partial approved+active unique index). Admin approve on a revision = atomic PROMOTE: old row → is_active=false + superseded_by_skill_id, revision → approved, router mappings + description history remap, surviving sibling revisions reparent. A superseded row can never be re-activated (409 skill_superseded)
GET/skills/{id}/revisionsR38a — list revisions cloned from this row (newest first)
GET/POST/skills/{id}/code-blocksList or create code blocks. Create/update accept llm_visible (R35 §11.3, default true).
GET/PUT/DELETE/skills/{id}/code-blocks/{block_key}Code block CRUD
GET/POST/skills/{id}/personasR35 P2 — list or create skill personas (voice/identity; exactly one of prompt_text / prompt_template_ref; at most one is_default)
GET/PUT/DELETE/skills/{id}/personas/{persona_key}R35 P2 — skill persona CRUD
GET/skill-routersR36 — list skill routers
POST/skill-routersR36 — create a router (admin)
PUT/skill-routers/{id}R36 — update a router (admin). Retirement = is_active=false; there is no DELETE route — routers are soft-deactivated only, so the manifest-delta watermark cannot miss a change
GET/skill-routers/manifestR36 — derived manifest read (router_key + recruitment_blurb per active router) — what system_prompt_node renders into general-mode prompts
GET/skill-routers/disambiguation?router_keys=...R36 — derived disambiguation payload: the pulled routers’ approved skills with latest-approved descriptions (backs pull_routers)
GET/skill-routers/unrouted-skillsR36 — active non-dependency skills with no approved router mapping (unreachable via selection)
GET/skill-routers/{id}/contentsR36 — router-contents admin view
GET/POST/skill-routers/{id}/mappingsR36 — list / create router↔skill mappings (POST admin). Mappings carry a promotion status (draft/pending_review/approved/rejected); admin submissions auto-approve; only approved mappings render
DELETE/skill-routers/{id}/mappings/{skill_id}R36 — remove a mapping (admin)
GET/skills/{skill_id}/router-mappingsR38 — skill-side mapping view (any authenticated user, read-only): the skill’s skill_router_map rows enriched with router_key and router_is_active. 404 if the skill doesn’t exist; empty list if unmapped. Backs the ReviewDialog’s decision on whether to offer a router at approval
GET/POST/skills/{skill_id}/description-versionsR36 — list / submit disambiguation description versions (POST admin; append-only, latest approved renders)
GET/rulesList rules (filters: trigger, source_level, source_id, is_active, and R37d enforcementadvisory|mandatory)
GET/rules/effective?skill_id=...R37d — a skill’s effective rule set: {own, mandatory}. own = its source_level=skill rules; mandatory = every mandatory floor rule whose applies_to selector matches, each with a provenance (matched_viacategory|domain_tag|skill + detail). Non-admin readable (feeds the skill editor RulesTab on both surfaces). R37e adds lint[] — review-time warnings for flow-changing/state-mutating own rules on a skill carrying a mandatory gate ({rule_key, code, message, mandatory_rule_keys}); rendered in the ReviewDialog and RulesTab
POST/PUT/DELETE/skills/{skill_id}/rules, /skills/{skill_id}/rules/{rule_id}R37e — the author (advisory) rule write path, skill-nested. Open to the skill’s owner or an admin; the server derives source_level='skill' + source_id from the path and forces enforcement='advisory' + empty applies_to for every caller (explicit 400 on mandatory/selector/foreign-source attempts; 403 for a non-owner; mandatory rules are untouchable here even when homed on the skill). A non-admin write calls reopen_review(skill) — the R37a load gate then keeps the skill (and its rules) dormant until an admin re-approves. Write responses carry the lint warnings and (R38) the same namespace-lint warnings[] as /rules/validate
POST/rules/validateR37e — non-admin condition validation (stateless parse-only) → {valid, error, ast}. Backs the author surface’s live validation. R38 — accepts optional trigger (one of the 8 rule triggers) and the response gains warnings[] (each {code, field, message}): a namespace lint flagging field references whose root namespace the evaluator can never resolve (warn-never-block — such conditions silently never match). With trigger, checks against that trigger’s exact namespace table; without it, only namespaces no trigger populates are flagged. Codes: namespace_trigger_mismatch, unknown_namespace
POST/PUT/DELETE/admin/rules, /admin/rules/{id}Rule CRUD (admin). Create/update accept R37d enforcement + applies_to ({category_ids[], domain_tags[], skill_ids[]}); a mandatory rule with an empty selector is rejected
POST/admin/rules/validateValidate a condition_text expression without persisting → {valid, error, ast} (admin twin of /rules/validate; R38 — same optional trigger + warnings[] namespace lint)
POST/admin/rules/match-previewR37d — blast radius of an applies_to selector → {count, skills[]} (each matched_via). Resolved by the same selector helper the runtime fan-out uses, so preview and enforcement agree
POST/rules/evaluateInternal (service-to-service, not gateway-routed) — the orchestration engine’s per-trigger rule evaluation. Mandatory rules scope by applies_to against the loaded skills and sort before advisory rules (un-bypassable precedence)
GET/settingsTenant platform settings (LLM runtime config): stored tenant values + effective values + a sources map (tenant|default) + the calling user’s user_override if any. Any authenticated identity — the orchestration engine calls it at every turn start with X-Tenant-Id/X-User-Id headers
PUT/settingsAdmin-only upsert of tenant settings. Nullable fields clear back to inherit; models are validated against the curated list; writes an HMAC-signed audit row (platform_settings.update) and DELs the settings:{tenant_id} Redis cache
GET/settings/model-optionsCurated model picker for the settings UI (LiteLLM model ids)
GET/settings/user-overridesAdmin — list per-user daily budget overrides
PUT/settings/user-overrides/{user_id}Admin — upsert a per-user override (at least one of max_tokens_per_day / max_cost_usd_per_day); audit action platform_settings.user_override.upsert
DELETE/settings/user-overrides/{user_id}Admin — remove a per-user override (204); audit action platform_settings.user_override.delete
GET/healthHealth check

Authoring Authorization (R42d)

Authoring writes are permission-gated via aegis_shared.auth (a deliberate change from R37’s open-authoring posture — authoring is now a granted capability per the R42 Appendix C sign-off):

Write pathRequired permission
POST /skills, code-block writes, POST /skills/{id}/submit, POST /skills/{id}/revisions, skill-rule writesskills.propose (held by operator, power_user, admin — the operator escape hatch: propose a draft, an author/admin takes it from there)
PUT /skills/{id}skills.author (power_user, admin)
Prompt template and version writesprompts.author (power_user, admin)

Ownership and review_status governance (draft → pending_review → approved) are unchanged and sit on top of these gates. Checks are mode-gated by AEGIS_AUTH_MODE (warn-and-pass in log, 403 in enforce) — see Auth Flow — Service-Side Enforcement.

Architecture

Module Breakdown

src/agent_config/ ├── main.py # FastAPI app, lifespan, route registration ├── config.py # Settings from environment variables ├── database.py # AsyncPG connection pool (shared PostgresPool) ├── dependencies.py # Binds the STRICT aegis_shared.auth variant (R42d): get_current_user = get_strict_user, so a header-less request 401s in BOTH auth modes (stamped-empty-perms = revocation); plus get_optional_user, require_permission, get_redis ├── seed.py # Namespace/tier/prompt seeder + migration runner ├── models/ │ ├── namespaces.py # Pydantic models for namespace CRUD │ ├── tiers.py # Budget tier models │ ├── templates.py # Template CRUD models │ ├── versions.py # Version CRUD + lifecycle models │ └── audit.py # Audit log models ├── routes/ │ ├── namespaces.py # Namespace + tier endpoints │ ├── templates.py # Template CRUD endpoints │ ├── versions.py # Version CRUD + lifecycle + pending approvals │ ├── resolution.py # Runtime resolve + render (hot path) │ ├── audit.py # Audit log queries + HMAC verification │ ├── access.py # Namespace access control │ └── routers.py # R36: skill-router CRUD, manifest/disambiguation reads, description versions ├── services/ │ ├── validation.py # 5-step save-time validation pipeline │ ├── injection_scan.py # Prompt injection pattern detector │ ├── lifecycle.py # Promote, approve, reject, restore, conflict │ ├── renderer.py # Jinja2 SandboxedEnvironment with timeout │ ├── resolution.py # Redis-cached runtime resolution │ └── audit_service.py # HMAC signing + audit log writes └── migrations/ ├── 001_initial_schema.sql ├── ... ├── 010_skill_routers.sql # R36: skill_routers, skill_router_map, │ # skill_description_versions, version_check_on_turn ├── 016_platform_settings.sql # platform_settings, user_budget_overrides, │ # prompt_audit_log.action widened to VARCHAR(64) ├── 020_identity_normalization.sql # R42e: uuid retypes on identity columns, │ # sentinel → real-admin skill ownership remap └── 021_prompt_target_roles.sql # R42e: prompt_versions.target_roles TEXT[]

Template Organization

Templates are organized in a two-level hierarchy: Namespace > Slug. Each namespace has its own budget tiers, access control, and approval settings.

NamespacePurposeAuto-Approve
agentsAgent system prompts (Rule 37, Rule 32, etc.)No
ontologyKnowledge graph ontology generationYes
detectionEvent detection rule formulasYes
notificationsNotification text generationYes

Version Lifecycle

Draft (private, mutable) ├── Promote to Pre-prod ──┬── No conflict → Pre-production │ └── Conflict (409) → Override or Merge Pre-production (immutable, targeted) ├── auto_approve = true → Active (immediate) ├── auto_approve = false → Pending approval │ ├── Approver approves → Active │ └── Approver rejects → Stays Pre-prod (author revises) Active (one per template) └── Superseded → Archived (restorable)

Validation Pipeline

Every draft creation and save runs a 5-step validation pipeline:

  1. Jinja2 Syntax Check — Catches template syntax errors with line numbers
  2. Restricted Subset Enforcement — Blocks macro, import, set, call constructs and non-whitelisted filters
  3. Variable Extraction — Detects undeclared variables, warns on unknowns
  4. Injection Scan — Regex-based detection of instruction override, role confusion, sandbox escape, and data exfiltration patterns
  5. Token Budget Check — Estimates rendered tokens using tiktoken, rejects if over budget tier limit

Conflict Resolution

When promoting to pre-production and another user’s pre-prod already exists, the API returns 409 with two options:

OptionBehavior
OverrideArchives the existing pre-prod, promotes your draft
MergeArchives both, creates a new version from a merged body

Runtime Resolution (Hot Path)

The resolve endpoint checks in order:

  1. Redis cache for a pre-prod version targeting this caller (60s TTL). Targeting matches target_users (pinned user) OR target_roles role overlap (R42e); the cache key carries a sorted-roles component so a role-based match caches per role set (the …:preprod:* invalidation SCAN pattern still covers it)
  2. Redis cache for active version (5-min TTL)
  3. Database query on cache miss

Access Control

Namespace access uses a four-level role hierarchy:

RolePermissions
viewerRead templates and versions
authorCreate templates, drafts, promote
approverApprove/reject promotions (cannot approve own versions)
adminManage tiers, access control, namespace settings

Unauthorized access returns 404 (not 403) to avoid leaking namespace existence.

Platform Settings (Tenant LLM Runtime Config)

The /settings* endpoints back the admin Platform Settings page (/configuration/platform-settings in the frontend) — tenant-level LLM runtime configuration that replaces editing env vars on the box. Nine nullable fields:

FieldGoverns
default_modelPrimary LLM model for all agent turns
fallback_modelOne retry on primary-model errors (all LLM call sites)
max_tokens_per_execution / max_cost_usd_per_executionPer-execution budget
max_output_tokens_per_callmax_tokens ceiling for a single LLM call (thinking + visible output; validated 8k–128k — below ~8k, Sonnet 5’s adaptive thinking can consume the whole ceiling and return an empty message)
thinking_modeadaptive | disabled — sent explicitly to Anthropic models (omitted for models that don’t accept it)
max_tokens_per_day / max_cost_usd_per_dayTenant-aggregate daily caps
tenant_brandingE0.3 — free-text label shown in the sidebar footer for every user (max 200 chars; blank/whitespace normalizes to NULL = no footer line). Unlike the LLM knobs it has no env fallback tier: resolution is tenant setting → NULL (neutral chrome). The sidebar reads it via the non-admin GET /settings path

NULL means inherit: the effective value falls back to the orchestration engine’s env defaults (DEFAULT_LLM_MODEL, DEFAULT_MAX_TOKENS_PER_EXECUTION, DEFAULT_MAX_COST_PER_EXECUTION, DEFAULT_MAX_OUTPUT_TOKENS_PER_CALL, DEFAULT_THINKING_MODE), which remain the fallback floor. GET /settings returns the stored values alongside the resolved effective values and a sources map (tenant | default) so the UI can show where each value comes from. Model choices are validated against the curated GET /settings/model-options list.

Reads are Redis-cached at settings:{tenant_id} (TTL 300s, DEL on write). Every write lands an HMAC-signed row in prompt_audit_log (platform_settings.update, platform_settings.user_override.upsert, platform_settings.user_override.delete; migration 016 widens action to accommodate these).

Per-user daily budget overrides (/settings/user-overrides*) cap individual users; the tenant daily caps remain the aggregate ceiling. The settings page’s user picker is fed by the auth service’s admin-only GET /auth/users directory.

Database Tables

TablePurpose
prompt_namespacesNamespace definitions with approval config
prompt_budget_tiersToken budget limits per namespace
prompt_templatesTemplate metadata with active version pointer
prompt_versionsVersion bodies with status, validation, targeting (target_users + target_roles since R42e; author_id/draft_owner_id/approved_by are uuid since migration 020)
prompt_audit_logAppend-only, HMAC-signed lifecycle events. actor_id stays TEXT permanently (R42e — rows are never rewritten); the audit-log list endpoints resolve actor_resolved (the actor’s email, via identity_map) at read time
namespace_access_controlRole-based access per namespace per user
skill_definitionsSkill metadata (domain tags, context_templates, required_capabilities, context_mode, response_mode — verbatim output contract, R36 version_check_on_turn)
skill_code_blocksSandboxed code blocks per skill (input_binding, llm_visible, mode)
skill_personasR35 P2 — a skill’s voice/identity personas (one default per skill)
agent_definitionsR35 P1 — agent → root skill + persona + model config (read by the loader from R35 P3a)
skill_routersR36 — tenant-scoped, admin-curated recruitment taxonomy (router_key, recruitment_blurb); soft-deactivate only, no hard delete
skill_router_mapR36 — router↔skill mappings with promotion status (draft/pending_review/approved/rejected; one row per pair, multi-homing = multiple pairs)
skill_description_versionsR36 — append-only disambiguation descriptions per skill; the latest approved version renders in selection payloads
rule_definitionsCondition→action rules (trigger, condition/condition_text, action_capability, source_level, priority). R37d adds enforcement (advisory|mandatory, default advisory) + applies_to JSONB selector ({category_ids[], domain_tags[], skill_ids[]}) — the admin-owned compliance floor. Migration 013 is the sole owner of the enforcement CHECK (the 012 single-owner lesson)
platform_settingsTenant-level runtime config (migrations 016/017/018): tenant_id PK + the nullable value columns (NULL = inherit the env default; tenant_branding from 018 has no env tier — NULL = no branding) + updated_by and timestamps
user_budget_overridesPer-user daily budget overrides (migration 016): (tenant_id, user_id) PK, max_tokens_per_day, max_cost_usd_per_day, updated_by, timestamps

Key constraints:

  • One draft per user per template (partial unique index on status = 'draft')
  • One pre-prod per template (partial unique index)
  • One active per template (partial unique index)
  • Audit log is append-only (triggers reject UPDATE/DELETE)
  • One default persona per skill (idx_one_default_persona_per_skill, partial unique on is_default); each persona sets exactly one of prompt_text / prompt_template_ref (app-enforced)

Dependencies

Python Packages

PackageVersionPurpose
fastapi^0.115Web framework
uvicorn^0.34ASGI server
asyncpg^0.29PostgreSQL async driver
redis^5.0Redis client for cache
jinja2^3.1Template rendering (SandboxedEnvironment)
tiktoken^0.7Token estimation (cl100k_base encoding)
aegis-sharedlocalShared DB helpers (PostgresPool)

Infrastructure Dependencies

DependencyPurpose
PostgreSQL 15Template/version storage, audit trail
Redis 7Runtime resolution cache

Configuration

Environment VariableDefaultDescription
AGENT_CONFIG_HOST0.0.0.0Bind address
AGENT_CONFIG_PORT8010Bind port
DATABASE_URLpostgresql://aegis:aegis_local@localhost:5432/aegisPostgreSQL connection
REDIS_URLredis://localhost:6379Redis connection
HMAC_SIGNING_KEYaegis-local-hmac-key-change-in-productionKey for audit trail HMAC signatures
DEFAULT_TENANT_ID00000000-0000-0000-0000-000000000001Default tenant for local dev

The HMAC_SIGNING_KEY must be kept secret and consistent. Changing the key invalidates all existing audit log signatures. Use HashiCorp Vault in production.

Running Locally

cd services/agent-config-service poetry install poetry run uvicorn agent_config.main:app --reload --port 8010

Seed Data

Run the seeder to create default namespaces, budget tiers, and migrate agent prompts from the orchestration engine:

cd services/agent-config-service poetry run python -m agent_config.seed

This creates:

  • 4 namespaces (agents, ontology, detection, notifications)
  • 5 budget tiers
  • 4 agent system prompt templates with active version 1

The service lifespan additionally self-applies its numbered SQL migrations (001021) and runs idempotent seeders on every boot, including the R35 P2 skill seeders: seed_rrc_prompts (the skills namespace + three regulatory-context templates), seed_rrc_skills (rrc_rule37 + rrc_rule32 + the R35-followup flaring_watch monitoring skill — 10 sandboxed code blocks + a default persona each; the flaring_watch blocks share the rrc_rule32 code constants under distinct block keys), and seed_rrc_skill_rules (3 HITL require_hitl skill rules, live since R35 P5 — flaring_watch is read-only and carries none). R36 adds seed_skill_routers: two approved routers, spacingrrc_rule37 and flaringrrc_rule32 + flaring_watch (the watch/file pair), with version_check_on_turn = true set on the two filing skills. Description versions are append-only; the seed may supersede its own (submitted_by IS NULL) text but never an admin edit. The legacy rule37-spacing registry skill is intentionally left unmapped. R37d adds seed_compliance_floor (runs last): a platform “AEGIS Compliance” catalog + “Compliance” category, files rrc_rule37 + rrc_rule32 under it (only when uncategorized — an admin re-filing survives reboot), and seeds one mandatory require_hitl rule scoped to the Compliance category that fires on either skill’s {rule}_filing_assembly step — the compliance floor an author cannot remove. Its reviewer is a soft reference (reviewer_role="compliance_reviewer") that R40’s org hierarchy will resolve. R38 adds seed_creator (after the RRC seed block): one function seeds the platform Skill Creator agent — skill → persona → agent row, in that order (an active agent_definitions row with an unresolvable root_skill_key fails orchestration boot). The skill_creator skill has zero code blocks (its tools are native orchestration handlers, not sandboxed blocks), required_capabilities=[], domain_tags=['authoring'], no catalog category (the compliance floor’s category axis must not fire on the Creator’s own writes), review_status='approved', and is_system=true. Its single default persona (creator_default) embeds the rule-grammar and seed-code contracts, the two-pattern code library, the five-ask wizard dialogue, the fix-loop budgets, and the authoring scope guard in prompt_text — and the persona seed UPSERTS prompt_text on every boot, so prompt revisions propagate to deployed boxes (unlike the get-or-create RRC personas). The agent row is agent_key='creator', root_skill_key='skill_creator'. Seeded agent_definitions rows no longer pin model_config.model_preference (now null), so the tenant’s Platform Settings default_model governs; an explicitly set per-agent model_preference still overrides it. R42e — seeds write real identities: prompt seeds are authored by system@aegis.local (the nil UUID), and seeded skills are owned by the earliest active admin, looked up at runtime (agent_config/identity.py) — if no active admin exists yet, the ownership stamp is skipped fail-closed rather than inventing an id. The pre-R42 admin-author sentinel UUID is retired (migration 020 remaps rows it owned to the real admin).

Gateway Access

All endpoints are proxied through the API gateway — the prompt routes at http://localhost:8000/api/v1/prompts/* and the platform-settings routes at http://localhost:8000/api/v1/settings*.

Integration with Orchestration Engine

The orchestration engine’s system_prompt_node calls this service at conversation start:

result = await render_prompt(namespace="agents", slug=slug, variables={}, user_id=user_id)

If the service is unreachable, the render soft-fails and prompt context degrades (the hardcoded fallback_prompts.py registry was deleted in R35 P6 — the platform default persona lives in orchestration/persona.py). The resolved prompt_version_id is stored in the conversation state for audit trail purposes.

R36: the orchestration engine additionally reads this service’s router layer — GET /skill-routers/manifest at conversation birth (general mode, rendered into the sealed index-0 prompt block) and GET /skill-routers/disambiguation when the LLM calls pull_routers. Both are derived reads; no compiled router block is materialized anywhere.

Platform Settings: at every turn start (both the synchronous /execute path and the SSE stream) the orchestration engine calls GET /settings with X-Tenant-Id/X-User-Id headers and resolves each value as request override → tenant setting → env default. A fetch failure degrades to the env defaults. Workspace assessment skills and the scoped agent go through the same resolution. See Orchestration Engine for the daily-cap counters.

Last updated on