Session settings are codex-manager's shared per-session state store for automation and policy. The same values are visible to Web UI, API, CLI, extensions, and Python workflows.
Settings are persisted inside the session controls tuple (controls.settings) alongside:
modelapprovalPolicynetworkAccessfilesystemSandbox
Two scopes are available:
session: only this sessiondefault: baseline for future/new sessions
Default-scope writes can be locked by SESSION_DEFAULTS_LOCKED=true. In that case, write attempts return 423 locked.
Codex-manager supports two mutation styles for POST /api/sessions/:sessionId/settings:
- object update:
{settings, mode}wheremodeismerge(default) orreplace - single-key update:
{key, value}
Rules:
- use either
{settings}or{key,value}; never both {key,value}requires both fieldsmode=replacereplaces the entire settings map for the target scopemode=mergepreserves existing keys and updates only provided keys
DELETE /api/sessions/:sessionId/settings/:key removes one top-level key and returns removed: true|false.
POST /api/sessions/:sessionId/session-controls can include controls.settings. If controls.settings is omitted, codex-manager preserves existing settings for the selected scope.
All write routes support optional actor and source fields for audit provenance.
For settings and controls operations, handle these statuses as first-class outcomes:
200: success or unchanged payload400: invalid request payload403: system-owned session (orchestrator worker session)404: session not found410: session deleted/purged423: default scope locked by harness configuration
Session-scoped wrapper (chat = cm.session(session_id)) exposes:
chat.controls.get()chat.controls.apply(controls=..., scope="session" | "default", actor=..., source=...)chat.settings.get(scope=..., key=...)chat.settings.set(scope=..., settings=..., mode="merge"|"replace", key=..., value=..., actor=..., source=...)chat.settings.unset(key, scope=..., actor=..., source=...)chat.settings.namespace("dot.path").get()/set()/merge()/unset()
Namespace helpers only shape payload ergonomics in Python. Storage remains a top-level settings object in codex-manager.
from codex_manager import CodexManager
with CodexManager.from_profile("local") as cm:
session_id = cm.sessions.create(cwd=".")["session"]["sessionId"]
chat = cm.session(session_id)
# Merge one top-level key.
chat.settings.set(
key="team",
value={"definitionOfDone": "tests green + docs updated"},
source="python-script",
)
# Namespace ergonomics for nested data.
policy = chat.settings.namespace("team.review")
policy.merge({"autoApprove": {"enabled": True, "threshold": "low"}})
# Replace the full settings map for this session scope.
chat.settings.set(
settings={
"team": {
"definitionOfDone": "tests green + docs updated",
"review": {"autoApprove": {"enabled": False, "threshold": "low"}},
}
},
mode="replace",
source="python-script",
)
# Read one key and remove it.
print(chat.settings.get(key="team"))
chat.settings.unset("team")Apply controls while preserving settings:
chat.controls.apply(
controls={
"model": None,
"approvalPolicy": "on-request",
"networkAccess": "restricted",
"filesystemSandbox": "workspace-write",
# Omit "settings" to preserve existing settings map.
},
scope="session",
source="policy-update",
)- Python introduction:
introduction.md - API domain map:
api-surface.md - Streaming handlers:
streaming-and-handlers.md - Remote skills:
remote-skills.md