Skip to main content

Projects

The Projects module provides multi-tenant namespaces in SOAT. Every resource (document, file, actor, conversation) belongs to a project. Projects are identified by an id prefixed with proj_.

Overview

A Project is a top-level container that scopes all resources. Users access projects through policy-based authorization — there is no separate membership table. Whether a user can access a project is determined entirely by the policies attached to their account and the SRN patterns those policies contain. For a project creation walkthrough, see Chat with an LLM - Step 2 (Create a project).

See the Permissions Reference for the IAM action strings for this module.

Data Model

FieldTypeDescription
idstringPublic identifier prefixed with proj_
namestringHuman-readable project name
guardrail_idsarrayGuardrails attached at the project scope — the baseline governing every tool call by every agent in the project. See Guardrails — Attachment
default_model_route_idstring | nullModel route inherited by consumers in this project that bind neither model_route_id nor ai_provider_id. null (default) means no default, so every consumer must bind explicitly. Settable/clearable via update-project.
max_concurrent_runsinteger | nullMaximum orchestration runs of this project driven at once. null (default) means unlimited; otherwise an integer ≥ 1. Settable/clearable via update-project.
max_chain_generationsinteger | nullGenerations one continuation chain in this project may hold before the platform stops resuming it. null (default) means no project ceiling, leaving the deployment-wide one; otherwise an integer ≥ 1. The effective budget is the smallest of the deployment's ceiling, this one, and the agent's own maxChainGenerations. Settable/clearable via update-project.
max_orchestration_run_depthinteger | nullloop / sub_orchestration nesting levels a run tree in this project may reach before the engine refuses to start the next child. null (default) means no project bound, leaving the deployment-wide one; otherwise an integer ≥ 1. The effective bound is the smaller of the two. Settable/clearable via update-project.
audit_reads_enabledbooleanOpts the project into read auditing: when true, GET requests naming this project are recorded in the audit log alongside mutations. false by default. Settable via update-project.
trace_content_retention_daysinteger | nullDays of trace/generation content retention before the daily sweep purges it. null (default) disables retention; otherwise an integer ≥ 1. Settable/clearable via update-project.
trace_content_modestringfull (default) or none. none is zero-retention: trace and generation content is never written for any agent in the project. Settable via update-project.
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Key Concepts

Project Access via Policies

Project access is entirely policy-driven; there is no membership list to maintain. Access is granted by attaching a Policy to the user (or their API key) that contains an Allow statement covering the relevant project's SRN pattern:

{
"statement": [
{
"effect": "Allow",
"action": ["projects:GetProject", "files:ListFiles", "files:GetFile"],
"resource": ["srn:proj_ABC:*:*"]
}
]
}

For a complete scoped-access walkthrough, see Permissions in Practice - Step 3 (Create the Analytics project).

To grant a user access to all projects, use a wildcard project segment:

{ "resource": ["srn:*:*:*"] }

Visibility Rules

  • Admin users see all projects.
  • API key callers scoped to a project see only that project.
  • Regular users see only the projects covered by the SRN patterns in their attached policies.

Authorization is policy-only: all access decisions are evaluated through the policy engine against the requested action and resource SRN, and a project-scoped grant is honored by every project endpoint, including GET /projects/{id}. See IAM for details.

Default Model Route

default_model_route_id names the model route every consumer in the project inherits when it binds neither model_route_id nor ai_provider_id — a single project-scoped switch that gives agents, chats, and memory completions provider failover without editing each one.

soat update-project --project-id proj_… --default_model_route_id route_…

The route must belong to this project. An explicit binding on a consumer always wins, so the default can never override a deliberate pin. Repointing the default to another route is free; clearing it returns 409 PROJECT_DEFAULT_ROUTE_INHERITED while any consumer inherits it, and deleting the route itself returns 409 MODEL_ROUTE_HAS_DEPENDENTS. Governed by projects:UpdateProject.

Deletion

Deleting a project that has any dependent resource returns 409 Conflict with error code PROJECT_HAS_DEPENDENTS. "Any" is literal — every project-scoped resource counts, including the ones a project accumulates on its own while it runs:

Pass ?force=true to delete all dependents along with the project in a single transaction — including the billing/usage history, and the stored bytes of the project's files, so a force-deleted project leaves nothing behind in storage.

The audit log is the one deliberate exception: its entries outlive the project, keeping the record of who did what with project_id cleared instead of the row deleted.

Common Errors

StatusBodyCauseWhat to do
403{ "error": "Forbidden" }Caller isn't the admin role — creating, renaming, and deleting a project are admin-onlyAuthenticate as the admin user, or have an admin perform the operation
403{ "error": "Forbidden" }GET /projects/{id} (or a nested resource route) with a policy/API key that doesn't cover this project's SRN — e.g. a project key created for a different projectCheck the caller's attached policies cover srn:<this-project-id>:*:*, or use a key scoped to this project — see Project Access via Policies
404The project ID doesn't exist, or the caller can't see it because no policy grants access to it (existence isn't leaked)Verify the ID; if it should exist, confirm a policy grants visibility — see Visibility Rules
409{ "error": { "code": "PROJECT_HAS_DEPENDENTS" } }Deleting a project that still has dependent resourcesPass ?force=true, or delete the dependent resources first — see Deletion
409{ "error": { "code": "PROJECT_DEFAULT_ROUTE_INHERITED" } }Clearing default_model_route_id while consumers that bind nothing inherit it — they would be left with no resolvable modelBind those consumers explicitly (meta.sample names some), or repoint the default to another route, which is always allowed — see Project default route

Examples

Create a project

soat create-project --name "My Project"

Get a project

soat get-project --project-id proj_ABC

Delete a project

soat delete-project --project-id proj_ABC

# Force-delete a project along with all of its dependent resources
soat delete-project --project-id proj_ABC --force true