Schema & Data Model¶
Two reference layers: the template schema (the ProjectTemplate / TeamConfig shape a Workflow Template is authored in) and the data model (the EF Core entities). The template authoring view is in §08 — Templates; this page is the field-by-field enumeration.
The camelCase ↔ snake_case boundary¶
The single most common authoring mistake is casing. The rule: the outer ProjectTemplate object is camelCase; everything inside the team block is snake_case. The mechanism is two-fold:
ProjectTemplatecarries no[JsonPropertyName]attributes, so its casing is driven by the serializer'sJsonNamingPolicy.CamelCase→id,name,defaultTier,seedKnowledgeBase, …TeamConfigand its sub-types carry explicit[JsonPropertyName("snake_case")]on every field, which overrides the naming policy →base_image,is_supervisor,system_prompt,circuit_breaker, …
The clone step (TemplateService.BuildCreateRequest) round-trips the team block through JSON with the naming policy turned off, so the explicit snake_case attributes drive serialization and deserialization symmetrically — the team block deep-clones losslessly. Write seed_knowledge_base (snake) on the outer object or modelId (camel) inside the team block and the field is silently ignored — the deserializer keeps the default rather than erroring.
One exception inside the team block
Two of the container sub-objects referenced from TeamConfig — VolumeMount and NetworkConfig — carry camelCase [JsonPropertyName] attributes (hostPath, containerPath, readOnly; isolateNetwork, allowedHosts, networkMode), so mounted_paths is snake_case but its bodies are camelCase. ResourceLimits was the third until it was corrected to snake_case (cpu_limit, memory_limit, disk_quota, cpu_shares) — every shipped template had always written the snake_case spelling, so default_resources bound to nothing and silently fell back to the defaults.
Template schema¶
ProjectTemplate¶
The outer blueprint — camelCase. File: Aetherium.Shared/Models/ProjectTemplate.cs.
| Property | JSON | Type | Default |
|---|---|---|---|
| Id | id |
string | "" |
| Name | name |
string | "" |
| Description | description |
string | "" |
| Icon | icon |
string | "bi-collection" |
| Category | category |
string | "General" |
| DefaultTier | defaultTier |
string | "Staging" |
| Team | team |
TeamConfig |
new() (key camelCase → body snake_case) |
| Directories | directories |
List<string> |
new() |
| ProjectConfigTemplate | projectConfigTemplate |
string? | null |
| KnowledgeFolders | knowledgeFolders |
List<string>? |
null |
| SeedKnowledgeBase | seedKnowledgeBase |
bool | true |
| IsCore | (ignored) | bool | [JsonIgnore] runtime flag |
TeamConfig¶
The team block — snake_case. File: Aetherium.Shared/Models/TeamConfig.cs.
| Property | JSON | Type | Default |
|---|---|---|---|
| Name | name |
string | "" |
| Description | description |
string | "" |
| Version | version |
string | "1.0.0" |
| Config | config |
TeamGlobalConfig |
new() |
| Nodes | nodes |
List<TeamNode> |
new() |
| Edges | edges |
List<TeamEdge> |
new() |
| Topology | topology |
string | "hierarchical" |
| Recipe | recipe |
string? | null — the preset form's shape (single, hierarchical, sequential, roundtable, dynamic-parallel); the resolver sets topology from it, generates edges when empty, and fills circuit_breaker from the recipe's defaults when unset |
| BaseImage | base_image |
string | "python:3.11-slim" |
| PersistentContainer | persistent_container |
bool | false |
| DefaultResources | default_resources |
ResourceLimits |
new() |
| ExposedPorts | exposed_ports |
Dictionary<string,string>? |
null |
| ContainerPorts | container_ports |
List<string>? |
null |
| CircuitBreaker | circuit_breaker |
CircuitBreakerConfig? |
null |
| KnowledgePacks | knowledge_packs |
List<string>? |
null — the packs the team retrieves from: null detects them at session start (base image, tools, prompts), [] means none, a list is explicit |
| AutoDetectKnowledge | auto_detect_knowledge |
bool | false — adds detected packs to an explicit knowledge_packs list |
| MountAetheriumSource | mount_aetherium_source |
bool | false |
| ContainerInitCommands | container_init_commands |
List<string>? |
null — each line runs once, as root, when the project container is first created. A non-zero exit fails the run before any agent runs, with the command and its output in the run's error (since 2026-09-16; before that a failure was logged as completed) |
| NetworkPolicy | network_policy |
ContainerNetworkPolicy |
Unrestricted (serialized as string) |
| AllowedDomains | allowed_domains |
List<string>? |
null |
| SubagentDefaults | subagent_defaults |
SubagentDefaults? |
null |
| TargetCollections | target_collections |
List<string>? |
null |
| ToolRequests | tool_requests |
string? | null — deny (the default), grant or ask: what happens when an agent calls a tool its node does not hold |
| StepRounds | step_rounds |
int? | null — fresh attempts per plan step for this team; overrides the AI_StepRounds setting |
| VerifierInstance | verifier_instance |
string? | null — the slot for the step verifier, written by a run override's verifierInstance (omitted from JSON when null). It beats verification.verifier_preferred_instance, applies whether verification comes from the team's block or the global switch, and places the trajectory judge too unless the block names trajectory_judge_preferred_instance. It lives on the team rather than in the block because creating a block would change whether verification runs |
| EnforceNodeRequiredOutput | enforce_node_required_output |
bool | false — when a plan step declares a strict subset of the paths its node declares in required_output, replace the step's list with the node's instead of only logging the difference — and, since 2026-09-16, when the step declares no required_outputs at all (an empty declaration used to switch the gate off). Only ever asserts paths the node already declares, so it cannot hold a step to a sibling step's file. restaurant-kitchen is the one shipped template that opts in |
| PlanSerialSteps | plan_serial_steps |
bool | false — when true, create_plan makes every step depend on the one before it, whatever the planner declared (explicit extra dependencies are kept), so the wave scheduler runs one step at a time. Applied to the plan before it is stored, on both the tool path and the hierarchical Phase 1 native call (2026-09-16) |
| PlanSteps | plan_steps |
list of {agent_id, description} |
null — when set, the declared list is the plan (order, assignment, serial deps, node outputs); the planner only lends descriptions (2026-09-17) |
| GpuServices | gpu_services |
string[]? | null — registered GPU services (comfyui, comfyui-qwen, comfyui-zimage, comfyui-video, qwen-tts, qwen-tts-design) the run needs resident: they ride on the run's own inference lease, so the arbiter starts each unit before the run, budgets its VRAM beside the run's models, and stops it after the run. When a team declares none, the composition resolver derives the list from the tools its nodes hold (comfyui_* → the ComfyUI of the team's image_model, comfyui_video → comfyui-video, tts_* → qwen-tts); a declared list is taken as written |
| TemplateId | template_id |
string? | null — the template the team was created from, set when a project is created from a template, since the team's own name becomes the project's (omitted from JSON when null). tts_render keeps the template's designed voices under it, so every project made from one template speaks with the same cast |
| ImageModel | image_model |
string? | null — qwen renders the team's images on Qwen-Image 2.1 through the comfyui-qwen service, and makes the team eligible for the post-run 2K re-render when Aetherium:ImageFinishing:Enabled is on (off by default); z-image renders them on Z-Image Turbo through the comfyui-zimage service; unset is SDXL |
| StarterKitDir | starter_kit_dir |
string? | null — a workspace folder (site/kit) the Engine copies its canvas game kit into when the project is created, with a paragraph in project-config.md saying where it is; see §08 |
| FilmMotion | film_motion |
bool | false — the film a team cuts from its stills moves: each still sits on a blurred fill of itself and slowly zooms in over its share of the narration. Off, every still is held for its share. A template that sets it has its slideshow command swapped for the moving one when a project is created from it. No shipped template sets it. Omitted from JSON when false |
| FinishingPhase | finishing_phase |
int | 0 — 1 or 2 on the team a finishing pass runs, set by the Engine and never authored in a template (omitted from JSON when 0); the plan path holds Phase 2 to its own rules by it, such as an owed file having to be written in that run |
| ProjectBrief | project_brief |
string? | null — set only on a team the Quick Create generator made, to the request it was given (omitted from JSON when null). Creating the project writes it to the project root and the workspace as project-config.md when no config exists, so the roles told to read that file first find the brief there |
| GenerationNotes | generation_notes |
List<string>? |
null — what the Quick Create generator could not settle, one note per line: an owed file no tool in the workspace makes, a page owing a folder no role fills, a role missing the tool its deliverable needs, a ticked need no role covers. The Designer shows them under its header. Null for a template's team and for a design with nothing to report |
topology is a string. The four canonical topologies are Hierarchical, Sequential, Roundtable, Single (only Hierarchical reads edges). A fifth value, dynamic-parallel, selects the runtime subagent-spawning mode and pairs with subagent_defaults — see §08. ContainerNetworkPolicy values: Unrestricted, SetupOnly, Isolated, AllowlistOnly (unknown values tolerate to Unrestricted).
TeamGlobalConfig¶
The inner config object — snake_case.
| Property | JSON | Type | Default |
|---|---|---|---|
| Model | model |
string | "" |
| Temperature | temperature |
double | 0.7 |
| MaxTokens | max_tokens |
int | 4096 |
| ContextWindow | context_window |
int | 16384 |
| ReasoningEffort | reasoning_effort |
string? | null — low/medium/high/xhigh turns a thinking model's reasoning on for every worker dispatch of this team — hierarchical delegates, single-agent and sequential-stage turns, roundtable rounds, dynamic-parallel subagents and the lead's synthesis; anything else leaves it off. Planning turns follow AI_ReasoningEffort instead |
| DiscussionRounds | discussion_rounds |
int | 2 (Roundtable only; 1 = single round) |
TeamNode¶
One agent — snake_case.
| Property | JSON | Type | Default |
|---|---|---|---|
| Id | id |
string | "" |
| Name | name |
string | "" |
| Role | role |
string | "" |
| SystemPrompt | system_prompt |
string | "" |
| Tools | tools |
List<string> |
new() — the runtime allowlist |
| Profiles | profiles |
List<string>? |
null — named tool profiles (reader, writer, reporter, coder, qa, researcher, planner, orchestrator, knowledge, imaging, audio, video, comms) expanded into tools on create. reporter is reader plus write_file — a role that produces a report and repairs nothing |
| ExcludeTools | exclude_tools |
List<string>? |
null — removed after profile expansion |
| RequiresEvidence | requires_evidence |
bool? | null — set from the role library for reporting roles; a plan step this node runs fails the evidence gate unless it called web_search or web_fetch, wrote at least one source URL, and every URL it wrote appeared in a search result or a fetch during the step (only calls that succeeded count) |
| RoleId | role_id |
string? | null — a role-library entry (Templates/composition/roles.json); every empty field on the node is filled from it on resolve, and it is kept as provenance |
| McpServers | mcp_servers |
List<string>? |
null (derived from tools when null) |
| Description | description |
string | "" |
| IsSupervisor | is_supervisor |
bool | false |
| ModelId | model_id |
string? | null |
| PreferredInstance | preferred_instance |
string? | null |
| Temperature | temperature |
double? | null |
| MaxTokens | max_tokens |
int? | null |
| KnowledgeFilter | knowledge_filter |
string? | null |
| KnowledgeFilters | knowledge_filters |
List<string>? |
null |
| MaxTurns | max_turns |
int? | null |
| Reflection | reflection |
ReflectionConfig? |
null |
| AgentType | agent_type |
string? | null |
TeamEdge¶
| Property | JSON | Type | Default |
|---|---|---|---|
| From | from |
string | "" |
| To | to |
string | "" |
SubagentDefaults¶
The subagent_defaults block that shapes spawn_subagents children — snake_case.
| Property | JSON | Type | Default |
|---|---|---|---|
| ModelId | model_id |
string? | null |
| Tools | tools |
List<string> |
["read_file","write_file","edit_file","list_files","web_search","web_fetch","write_knowledge","search_knowledge"] |
| MaxTokens | max_tokens |
int? | null |
| MaxTurns | max_turns |
int | 15 |
| PreferredInstance | preferred_instance |
string? | null — pins every spawned child to that slot, and its model_id, when concrete, is made resident there by the run's lease; unset, each child is smart-routed. Read since 2026-10-06; before that it was declared and never read |
| McpServers | mcp_servers |
List<string>? |
null |
Run overrides (modelId, preferredInstance, maxTokens, contextWindow, reasoningEffort) are applied to this block as well as to the nodes, so spawned children run on the overridden model; subagentInstance sets only its preferred_instance, creating the block with these defaults when the team has none. Shipped templates routinely override the default tool list — e.g. the research template uses [web_search, web_fetch, download_pdf, write_file, read_file, write_knowledge] with explicit mcp_servers: [workspace, research, knowledge].
CircuitBreakerConfig¶
Per-team, session-scoped ceilings — snake_case. File: Aetherium.Shared/Models/CircuitBreakerConfig.cs.
There are no tool-call or token caps. per_tool_limits, max_tool_calls_per_session, max_turns_per_step, max_tool_calls_per_step and max_session_tokens were removed on 2026-10-08: the only checks that read them had no caller, so a template that set one capped nothing. A template that still carries those keys loads without error and they are ignored.
| Property | JSON | Type | Default |
|---|---|---|---|
| MaxTurnsPerAgent | max_turns_per_agent |
int | 10 |
| StepTimeoutSeconds | step_timeout_seconds |
int | 1800 — wall-clock budget for one plan step; 0 disables. On expiry the step fails with the reason and is re-planned like any failed step (its re-run is told it timed out), and the run continues to whatever is still executable. Enforced since 2026-09-16; before that the value (then 120) was declared and never read |
| SessionTimeoutSeconds | session_timeout_seconds |
int | 600 |
| InfiniteLoopThreshold | infinite_loop_threshold |
int | 3 |
| MaxReplansPerSession | max_replans_per_session |
int | 2 |
| ThinkingModelOverrides | thinking_model_overrides |
ThinkingModelOverrides? |
new() |
ThinkingModelOverrides¶
Applied when the team's default model is classified as a reasoning model — those models get wider ceilings. snake_case. The classification is not configured here: it comes from the Engine's model-capability table, which is keyed on the model ID and carries the measurement behind each entry. (Before 2026-09-08 the opt-in was a thinking_model_pattern regex on this config; that property is gone, and the capability table is the only classifier.)
| Property | JSON | Type | Default |
|---|---|---|---|
| MaxTurnsPerAgent | max_turns_per_agent |
int | 35 |
| StepTimeoutSeconds | step_timeout_seconds |
int | 0 — 0 inherits the team's step_timeout_seconds; the other override values replace the team's |
| SessionTimeoutSeconds | session_timeout_seconds |
int | 1800 |
ReflectionConfig¶
Optional per-node self-critique pass — snake_case. Off by default; no shipped template enables it.
| Property | JSON | Type | Default |
|---|---|---|---|
| Enabled | enabled |
bool | false |
| Criteria | criteria |
List<string> |
["completeness","clarity"] |
| MaxRefinements | max_refinements |
int | 1 (max 3) |
| UseSeparateModel | use_separate_model |
bool | false |
| CriticModelId | critic_model_id |
string? | null |
| CriticPreferredInstance | critic_preferred_instance |
string? | null |
| MinOutputLengthForReflection | min_output_length_for_reflection |
int | 200 |
Supporting container types (camelCase)¶
Referenced from the snake_case team block but themselves camelCase:
SharedWorkspaceConfig—enabled(bool,true),mountPath("/workspace/shared"),readOnlyAgents(List<string>),hostPath(string?).ResourceLimits—cpuLimit("2.0"),memoryLimit("4g"),diskQuota("10g"),cpuShares(long,1024).AgentContainerSettings—isolationMode("shared"),requiresIsolation(false),baseImage(null),resources,network,privateMounts,shareWorkspace(true),poolSettings(null),environmentVariables({}),workingDirectory("/workspace").
mcp_servers derivation¶
When a node's mcp_servers is null, it is computed from the node's tools: each tool name is mapped through McpToolRouter.ToolToServerGroup, unmapped tools are dropped, and the distinct set of groups becomes the node's authorized servers. Layer-1 filtering then includes only tools whose group is in that set. Declaring mcp_servers explicitly keeps the allowlist under your control (§07, §08).
The tools list itself is settled by the composition resolver on every creation path (Quick Create, template create, role sync, a Studio edit) and at template load: a node's role_id fills every field it left empty from the role library (name, role, description, system_prompt, tools, mcp_servers, requires_evidence, and model_id from the role's slot in Templates/composition/model-policy.json, with the slot's instance as the node's preferred_instance and its max_tokens where the node sets neither; a node pinned to a different instance keeps its pin and gets no model), a team's recipe sets the topology and generates the edges, then a node's profiles expand into their tool sets (minus exclude_tools), a hierarchical or sequential team's supervisor gets delegate and reply (and read_plan + update_plan when it has create_plan), workers get reply, and a content, code or test role that has write_file gets edit_file unless its effective profiles include reporter. Every tool the resolver adds is logged with its reason. A Studio edit (the Designer's save) is the exception: an agent that was already on the team, or that arrives with its tools listed (added from the library, or generated), keeps the tools and model_id it was saved with. It gets only the topology wiring (delegate, reply and the planner grant), plus requires_evidence and agent_type from its role, which the Designer does not send. An agent that arrives with no tools is resolved in full. So a tool removed in the Designer, or "use the team model", is not undone by the save. The shipped templates are presets that resolve to the same teams they listed in full before the rewrite (a golden test pins each one), so resolving them changes nothing. The role library, the recipes and the model policy live beside the profile vocabulary in Templates/composition/ (roles.json, recipes.json, model-policy.json, tool-profiles.json), and the Engine refuses to start when any of them names a tool the router cannot route, a profile a role's tools do not cover, or a model slot the policy lacks. The profile vocabulary lives in Templates/composition/tool-profiles.json, which also names, per profile, the tools an agent may be granted at run time through the tool-request gate (tool_requests).
Data model¶
AetheriumDbContext declares 27 DbSet<T>. Core entities use explicit [Table("snake_case")] + [Column("snake_case")] (Postgres convention); a few lean ones (SystemSetting, ToolSecurityAuditEntry) use EF default PascalCase. Several columns are Postgres jsonb, stored as string? in C#.
DbSets¶
| DbSet | Entity | Purpose |
|---|---|---|
| Prompts | Prompt |
Reusable prompt library |
| Roles | Role |
Agent-role definition (FK → Project) |
| Projects | Project |
Top-level project; team config stored as jsonb |
| IdempotencyRecords | IdempotencyRecord |
API idempotency cache |
| SystemSettings | SystemSetting |
Key-value config table |
| InteractionLogs | InteractionLog |
LLM interaction analytics (tokens, timing) |
| TeamConfigVersions | TeamConfigVersion |
Historical versions of a project's team config |
| ContainerCommandLogs | ContainerCommandLog |
Shell commands run in containers |
| ContainerSnapshots | ContainerSnapshot |
Docker-commit snapshots (soft-delete) |
| ConversationMessages | ConversationMessage |
Persisted chat history per session |
| ConversationSessions | ConversationSession |
Session entity grouping messages |
| AgentMemories | AgentMemory |
Working / long-term agent memory |
| SessionSummaries | SessionSummary |
LLM summaries of completed sessions |
| WorkflowStates | WorkflowState |
Orchestrator state for checkpoint/resume |
| AgentLessons | AgentLesson |
Lessons extracted from sessions; project_id is nullable with ON DELETE SET NULL, so a lesson outlives the project that produced it |
| ProjectPlans | ProjectPlan |
Execution plan (from create_plan) |
| PlanSteps | PlanStep |
Plan steps (FK → ProjectPlan) |
| PortAllocations | PortAllocation |
Dynamic host-port allocations |
| BuildRuns | BuildRun |
One user-message → orchestrator cycle. ReviewNotes (text, nullable; JSON reviewNotes) is why a run that finished Completed still needs the owner's eyes, one reason per line: a step that completed although the step verifier failed it or could not decide (the newest attempt at each plan step); a file a team node owes (required_output) that is not on disk when the run ends, read by the step gate's own tests (files an earlier run made count as present); a page owing links whose links, re-read at the end of the run, miss a file of the set or name one that does not exist; and a page owing shows DIR/ that at the end of the run shows fewer of DIR/'s files than it owes or links one that does not resolve. Null when there is nothing to review. A verifier's failure does not fail the run; it is recorded here. On the plan path CurrentStep is the plan step being worked and TotalSteps the plan's step count, so a retried step does not advance the counter. SourceAgent records who launched the run: ui for the Studio, otherwise the sourceAgent the caller sent to run-from-template or POST /api/orchestrator/run |
| BuildSteps | BuildStep |
An agent's work within a BuildRun; Status (Pending / Running / Completed / Failed / Skipped — Skipped is what an evidence gap and a stopped swarm thread record) and VerificationStatus (passed / failed / inconclusive, null when the step was not verified). Absence records why a step produced nothing a consumer reads, as a value rather than as prose: None / NoToolCalls / ToolCallsNoWrites / WroteOutsideWatchedPaths (a successful write that landed where the consumer does not read) / WritesFailContentCheck / NoOwningProcess (reserved — nothing is classified as this yet). PlanStepNumber (nullable) is the plan step a row is an attempt at, on the plan path only: a plan step that runs again gets a new row under the same number, and the run is judged on the newest one |
| WebhookTargets | WebhookTarget |
Registered webhook endpoints |
| WebhookDeliveries | WebhookDelivery |
Webhook delivery attempts |
| ToolSecurityAuditEntries | ToolSecurityAuditEntry |
Tool-security audit table |
| KnowledgeSeedJobs | KnowledgeSeedJob |
Per-project KB seeding tracker |
| SessionEvents | SessionEvent |
Durable AG-UI event log — one row per stream event |
| DecisionLogs | DecisionLog |
One row per decision-layer call, including refusals and timeouts |
| FinishingGates | FinishingGate |
One finishing pass per project: its phase, its two run GUIDs, and the timestamps that mark the operator's answer |
Key relationships: Project → Roles (project_id, cascade; ProjectId nullable for library roles; unique (Name, ProjectId)); ProjectPlan → PlanStep, BuildRun → BuildStep, WebhookTarget → WebhookDelivery (one-to-many, cascade); InteractionLog → BuildRun/BuildStep (set-null).
Project¶
Table projects.
| Field | Column | Type | Notes |
|---|---|---|---|
| Id | id | int | PK |
| Name | name | string(100) | required |
| Description | description | string? | |
| LocalPath | local_path | string?(500) | |
| WorkspaceTier | workspace_tier | string(20) | default Staging |
| GithubRepo | github_repo | string?(200) | |
| GithubConfig | github_config | string? | jsonb |
| DockerConfig | docker_config | string? | jsonb |
| TeamComposition | team_composition | string? | jsonb |
| MassTopology | mass_topology | string? | jsonb — serialized TeamConfig |
| GeminiConfig | gemini_config | string? | jsonb |
| KnowledgeConfig | knowledge_config | string? | jsonb |
| Status | status | string(50) | default active |
| CreatedAt / UpdatedAt | created_at / updated_at | DateTime |
Role¶
Table roles — the agent record.
| Field | Column | Type | Notes |
|---|---|---|---|
| Id | id | int | PK |
| Name | name | string(100) | required; unique with ProjectId |
| Description | description | string? | |
| RoleType | role_type | string?(255) | |
| Category | category | string(50) | default General |
| State | state | string(50) | default draft |
| IsTechnical | is_technical | bool | false |
| PromptTemplate | prompt_template | string? | |
| Specializations / TechnologyStack / Prerequisites | … | string? | jsonb |
| PromptId | prompt_id | int? | → Prompt |
| ProjectId | project_id | int? | FK → projects.id (cascade) |
| AgentType | agent_type | string(50) | default individual |
| IsSupervisor | is_supervisor | bool | false |
| DeploymentConfig | deployment_config | string? | backs [NotMapped] Config (RoleConfiguration) |
| DeploymentStatus | deployment_status | string(50) | default pending |
| Provenance | provenance | string? |
RoleConfiguration (nested, from DeploymentConfig): Provider ("LLM Studio"), ModelId ("default"), Temperature (0.7), MaxTokens (2048), Tools, PreferredInstance?, KnowledgeFilter?.
Identify team agents by ProjectId, not AgentType
ProjectId.HasValue marks a team agent reliably; AgentType is often unset. This is a load-bearing convention across the services.
SystemSetting¶
PascalCase mapping. Key (PK), Value (string), Description (string?), Group (string, default General). Seeded rows and their groups are in Configuration.
AgentMemory¶
Table agent_memories.
| Field | Column | Type | Notes |
|---|---|---|---|
| Id | id | int | PK |
| ProjectId | project_id | int | FK → Project, indexed |
| AgentId | agent_id | string?(100) | null = project-shared; set = per-agent |
| MemoryType | memory_type | string(50) | working / long_term |
| Content | content | string | required |
| SourceSessionId | source_session_id | string?(100) | |
| RelevanceScore | relevance_score | float | 1.0 |
| TokenCount | token_count | int | |
| CreatedAt / UpdatedAt | … | DateTime | |
| ExpiresAt | expires_at | DateTime? | null = never |
ToolSecurityAuditEntry¶
PascalCase mapping. Every gated tool call. No retention policy (Configuration).
| Field | Type | Notes |
|---|---|---|
| Id | int | PK |
| Timestamp | DateTime | indexed |
| ProjectId | int? | indexed (composite with SecurityLevel) |
| TeamName | string?(200) | |
| AgentId / AgentName | string(100) / string(200) | |
| ToolName | string(100) | |
| SecurityLevel | string(20) | default Standard |
| ArgsJson | string? | JSON args |
| ResultSummary | string?(500) | first 500 chars |
| ResultFull | string? | Elevated+ only |
| ApprovedBy | string?(200) | Restricted only |
| Allowed | bool | true |
| ExecutionTimeMs | long |

ContainerCommandLog¶
Table container_command_logs — dual attributes ([Column("snake_case")] for DB, [JsonPropertyName("camelCase")] for API). Key fields: Id, ContainerId(100, indexed), ContainerName?, ProjectId? (FK), Command(500), Arguments?, StdOut?, StdErr?, ExitCode?, AgentName? (null for a command run by an operator), ExecutionTimeMs (double), WorkingDirectory?, Timestamp (indexed).
ProjectPlan / PlanStep¶
project_plans: Id, ProjectId (FK), SessionId?, Goal, Status (default active), timestamps, Steps (nav). plan_steps: Id, PlanId (FK → project_plans, cascade), StepNumber, Description, AssignedAgentId?, Status (default pending; completed, failed, or skipped when a step wrote files but failed the evidence gate), DependsOn (jsonb), Result?, StartedAt?/CompletedAt?, RequiredTools (jsonb), RequiredOutputs (jsonb — a JSON array of workspace-relative paths the step owes; when present the required-output gate enforces exactly that list, and when absent it falls back to narrowing the node's declared outputs by the words of the step's description. An entry ending in /, /* or /** is a directory obligation, satisfied by any file beneath it; an entry may carry a " >= N" suffix, satisfied only by N files of distinct content. A team with enforce_node_required_output replaces a step's list with its node's whenever the step declared a strict subset), ClaimedBy?/ClaimedAt?, Priority (PlanStepPriority { High=0, Medium=1, Low=2 }; lower = higher priority).
Conversation & summaries¶
ConversationMessage(conversation_messages):Id,ProjectId(FK),SessionId(100),Role(user/orchestrator/agent name),MessageType(message/tool_call/tool_rejected/tool_output/thought),Content,Metadata(jsonb: tokens, model, latency,isError, the tool name, arguments and result card, and since 2026-10-10kind—verdict/plan/system/error/delegationon a thought,heldon a guard-refused tool call,steeron an operator steer — withbuildRunIdandstepNumber),AgentId?,SequenceNumber,CreatedAt. Unique(ProjectId, SessionId, SequenceNumber).ConversationSession(conversation_sessions):Id,SessionId(unique),ProjectId(FK),Title?,Topology?,AgentCount,MessageCount,CreatedAt,LastMessageAt,IsArchived.SessionSummary(session_summaries):Id,ProjectId?(FK,ON DELETE SET NULL— a summary outlives its project),SessionId(unique),Summary,KeyDecisions?(jsonb),TokenCount,CreatedAt.
InteractionLog¶
Table interaction_logs — analytics, distinct from chat history: Id, ProjectId?, RoleId?, SessionId(100), AgentName?, InteractionType (user/assistant/tool/error), Content?, InputTokens, OutputTokens, plus further token/timing fields and set-null FKs to BuildRun/BuildStep. Indexed on Timestamp, SessionId, ProjectId, and composites.
FinishingGate¶
Table finishing_gates — dual attributes ([Column("snake_case")] for DB, [JsonPropertyName("camelCase")] for API), indexed on project_id and on status. Fields: Id, ProjectId, SourceRunGuid?(36 — the build run whose completion opened the gate), Status, Phase1RunGuid?(36), Phase1StartedAt, BriefWrittenAt?, AnsweredAt?, Phase2RunGuid?(36), Phase2DoneAt?, Note?.
Status is FinishingGateStatus { Phase1 = 0, AwaitingAnswer = 1, Phase2 = 2, Done = 3, Failed = 4 }. The three nullable timestamps are the record of the pass: BriefWrittenAt is set only when the brief actually reached disk (a completed run that wrote none does not advance the gate), AnsweredAt when the operator's answer was written into the workspace, and Phase2DoneAt when the applied pass closed.
SessionEvent¶
Table session_events — the durable copy of the AG-UI event stream, one row per event: Id (bigint), ProjectId, Seq (the stream's per-project sequence number; unique with ProjectId), EventType(120; the AG-UI type, or the custom event's name), BuildRunId?, BuildStepId? (indexed), At, Payload (jsonb — the event exactly as the SSE endpoint sends it). Written behind the stream; sequence numbers resume from this table after a restart.
DecisionLog¶
Table decision_logs — one row per call into the decision layer's IDecisionModel, written whatever the outcome, so the cases a model could not answer are kept beside the ones it could: Id, Primitive (choice / score / noul), QuestionHash (SHA-256 of the question, 16 hex chars), Question?, OptionsCount, InputExcerpt? (the head of the context, capped), ChosenValue?, Probability? (renormalised over the continuations the schema allowed; null when the backend returns no logprobs), Calibrated, Backend, ModelId?, Slot?, LatencyMs, ProjectId?, BuildRunId?, BuildStepId?, AgentName?, Round?, Outcome (ok / refused / timeout / error), Action? (bounced / passed, or shadow when the layer was only observing) and CreatedAt. Nothing is written while Aetherium:Decision:Enabled and :Shadow are both off, which is the default.
Where to go next¶
- §08 — Templates — authoring templates against this schema.
- Tools — the
tools/mcp_serverslists each node declares. - Configuration —
circuit_breaker,context_window, andSystemSettings. - API · Projects, Teams & Agents — the endpoints that exchange these entities.