Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .cortexkit/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# >>> cortexkit:magic-context
magic-context/
# <<< cortexkit:magic-context
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
"""add subagent prompt-cache TTL and sticky is_subagent flag

Revision ID: 20260713_080000_add_subagent_prompt_cache_ttl
Revises: 20260717_000000_optimize_dashboard_hot_path_indexes
Create Date: 2026-07-13
"""

from __future__ import annotations

import sqlalchemy as sa
from alembic import op

revision = "20260713_080000_add_subagent_prompt_cache_ttl"
down_revision = "20260717_000000_optimize_dashboard_hot_path_indexes"
branch_labels = None
depends_on = None


def _columns(bind, table_name: str) -> set[str]:
inspector = sa.inspect(bind)
if not inspector.has_table(table_name):
return set()
return {column["name"] for column in inspector.get_columns(table_name)}


def upgrade() -> None:
bind = op.get_bind()

settings_columns = _columns(bind, "dashboard_settings")
if settings_columns and "http_responses_session_bridge_subagent_prompt_cache_ttl_seconds" not in settings_columns:
with op.batch_alter_table("dashboard_settings") as batch_op:
batch_op.add_column(
sa.Column(
"http_responses_session_bridge_subagent_prompt_cache_ttl_seconds",
sa.Integer(),
nullable=True,
server_default=None,
)
)

sticky_columns = _columns(bind, "sticky_sessions")
if sticky_columns and "is_subagent" not in sticky_columns:
with op.batch_alter_table("sticky_sessions") as batch_op:
batch_op.add_column(
sa.Column(
"is_subagent",
sa.Boolean(),
nullable=False,
server_default=sa.false(),
)
)


def downgrade() -> None:
bind = op.get_bind()

settings_columns = _columns(bind, "dashboard_settings")
if settings_columns and "http_responses_session_bridge_subagent_prompt_cache_ttl_seconds" in settings_columns:
with op.batch_alter_table("dashboard_settings") as batch_op:
batch_op.drop_column("http_responses_session_bridge_subagent_prompt_cache_ttl_seconds")

sticky_columns = _columns(bind, "sticky_sessions")
if sticky_columns and "is_subagent" in sticky_columns:
with op.batch_alter_table("sticky_sessions") as batch_op:
batch_op.drop_column("is_subagent")
12 changes: 12 additions & 0 deletions app/db/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,12 @@ class StickySession(Base):
onupdate=func.now(),
nullable=False,
)
is_subagent: Mapped[bool] = mapped_column(
Boolean,
default=False,
server_default=false(),
nullable=False,
)


class DashboardSettings(Base):
Expand Down Expand Up @@ -733,6 +739,12 @@ class DashboardSettings(Base):
server_default=text("3600"),
nullable=False,
)
http_responses_session_bridge_subagent_prompt_cache_ttl_seconds: Mapped[int | None] = mapped_column(
Integer,
default=None,
server_default=None,
nullable=True,
)
http_responses_session_bridge_gateway_safe_mode: Mapped[bool] = mapped_column(
Boolean,
default=False,
Expand Down
116 changes: 116 additions & 0 deletions app/modules/proxy/_service/http_bridge/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -1851,6 +1851,119 @@ def _http_bridge_previous_response_owner_unavailable_error() -> ProxyResponseErr
)


_SUBAGENT_HEADER_NAMES = ("x-parent-session-id", "x-openai-subagent", "x-codex-parent-thread-id")
Comment thread
Komzpa marked this conversation as resolved.


def _detect_subagent_session(
headers: Mapping[str, str],
dashboard_settings: Any,
) -> tuple[bool, int | None]:
ttl = getattr(
dashboard_settings,
"http_responses_session_bridge_subagent_prompt_cache_ttl_seconds",
None,
)
if ttl is not None and ttl <= 0:
ttl = None
is_subagent = any(headers.get(name, "").strip() for name in _SUBAGENT_HEADER_NAMES)
return is_subagent, ttl


def _subagent_prompt_cache_bridge_key(
key: _HTTPBridgeSessionKey,
affinity: _AffinityPolicy,
is_subagent: bool,
forwarded_request: bool,
idle_ttl_seconds: float,
request_scope_id: str,
subagent_prompt_cache_ttl_seconds: int | None,
) -> tuple[_HTTPBridgeSessionKey, _AffinityPolicy, float, int | None]:
prompt_cache_affinity_key = affinity.key if affinity.kind is StickySessionKind.PROMPT_CACHE else None
is_prompt_cache = bool(prompt_cache_affinity_key)
effective_subagent_prompt_cache_ttl_seconds = subagent_prompt_cache_ttl_seconds if is_prompt_cache else None
if (
not is_subagent
or forwarded_request
or key.affinity_key.startswith("subagent:")
or (affinity.key is not None and affinity.key.startswith("subagent:"))
):
return key, affinity, idle_ttl_seconds, effective_subagent_prompt_cache_ttl_seconds

if is_prompt_cache and subagent_prompt_cache_ttl_seconds is not None:
assert prompt_cache_affinity_key is not None
subagent_affinity_key = f"subagent:{prompt_cache_affinity_key}"
subagent_affinity_kind = StickySessionKind.PROMPT_CACHE.value
effective_idle_ttl_seconds = float(subagent_prompt_cache_ttl_seconds)
effective_max_age_seconds = subagent_prompt_cache_ttl_seconds
else:
subagent_affinity_key = f"subagent:{request_scope_id}:{prompt_cache_affinity_key or key.affinity_key}"
subagent_affinity_kind = key.affinity_kind
effective_idle_ttl_seconds = idle_ttl_seconds
effective_max_age_seconds = affinity.max_age_seconds
return (
_HTTPBridgeSessionKey(
subagent_affinity_kind,
subagent_affinity_key,
key.api_key_id,
strength=None if subagent_affinity_kind == StickySessionKind.PROMPT_CACHE.value else key.strength,
),
_AffinityPolicy(
key=subagent_affinity_key,
kind=affinity.kind,
reallocate_sticky=affinity.reallocate_sticky,
max_age_seconds=effective_max_age_seconds,
),
effective_idle_ttl_seconds,
effective_subagent_prompt_cache_ttl_seconds,
)


async def _delete_completed_subagent_sticky_mapping(
repo_factory: Callable[..., Any],
session: Any,
) -> None:
if not (
getattr(session, "is_subagent", False)
and getattr(session, "subagent_prompt_cache_ttl_seconds", None) is None
and getattr(getattr(session, "affinity", None), "kind", None) is StickySessionKind.PROMPT_CACHE
):
return
affinity = getattr(session, "affinity", None)
if affinity is None:
return
try:
async with repo_factory() as repositories:
keys_to_delete = {affinity.key}
if isinstance(affinity.key, str) and affinity.key.startswith("subagent:"):
parts = affinity.key.split(":", 2)
if len(parts) == 3 and parts[2]:
keys_to_delete.add(f"subagent:{parts[2]}")
for key in keys_to_delete:
await repositories.sticky_sessions.delete(
key,
kind=StickySessionKind.PROMPT_CACHE,
is_subagent=True,
)
except Exception:
logger.warning("Failed to delete completed subagent sticky mapping", exc_info=True)


def _sync_reused_http_bridge_session(
session: Any,
metadata: tuple[Any, str | None, str | None, float, bool, int | None],
) -> None:
api_key, request_model, request_service_tier, idle_ttl_seconds, is_subagent, subagent_ttl = metadata
session.api_key = api_key
session.request_model = request_model
session.request_service_tier = request_service_tier
session.last_used_at = _service_time().monotonic()
if not (getattr(session, "is_subagent", False) or is_subagent):
return
session.idle_ttl_seconds = idle_ttl_seconds
session.is_subagent = True
session.subagent_prompt_cache_ttl_seconds = subagent_ttl


def _http_bridge_should_attempt_local_previous_response_recovery(exc: ProxyResponseError) -> bool:
payload = exc.payload
if not isinstance(payload, dict):
Expand Down Expand Up @@ -2182,6 +2295,9 @@ def _wrapper(*args: Any, **kwargs: Any) -> Any:
"_http_bridge_previous_response_error_envelope",
"_http_bridge_continuity_lost_error_envelope",
"_http_bridge_owner_lookup_unavailable_error_envelope",
"_detect_subagent_session",
"_subagent_prompt_cache_bridge_key",
"_delete_completed_subagent_sticky_mapping",
"_http_bridge_should_attempt_local_previous_response_recovery",
"_http_bridge_is_previous_response_owner_unavailable",
"_http_bridge_should_attempt_soft_affinity_reroute",
Expand Down
Loading
Loading