1010that content as a system-reminder on the next user turn and truncates
1111the file.
1212
13- Designed as a sibling to memory-mcp-watcher and session-snapshot — same
14- shape, same log convention. Stdlib only (no anthropic SDK dependency).
13+ Designed as a sibling to session-snapshot — same shape, same log
14+ convention. Stdlib only (no anthropic SDK dependency).
1515
1616Configuration (env vars; sensible defaults):
1717 BACK2BASE_POWER_STEERING "off" / "0" / "false" disables the daemon
3939
4040Auth (mirrors what Claude Code itself sends; daemon idles if no creds):
4141
42- Layer 1 — back2base proxy identity (when ANTHROPIC_BASE_URL points
43- at the proxy, which entrypoint.sh sets it to by default):
44- CLAUDEPROXY_SERVICE_TOKEN → x-service-auth: <token>
45- BACK2BASE_CONFIG_BYPASS_HEADER → x-claude-proxy-bypass: <value>
46- Mandatory in proxy deployments — without x-service-auth the proxy
47- returns 401 regardless of any Anthropic credential supplied.
48-
49- Layer 2 — Anthropic credential (forwarded by the proxy in
42+ Anthropic credential (forwarded directly, since the OSS build has no
43+ proxy in front of api.anthropic.com):
5044 pass-through mode, or used directly when no proxy):
5145 ANTHROPIC_API_KEY → x-api-key: <key>
5246 ANTHROPIC_AUTH_TOKEN → Authorization: Bearer <token>
5852Routing:
5953 ANTHROPIC_BASE_URL is honored. The host-side
6054 BACK2BASE_ANTHROPIC_BASE_URL is the only source — compose forwards
61- it as the unprefixed name. entrypoint.sh defaults it to the
62- back2base proxy URL when unset.
55+ it as the unprefixed name. Defaults to api.anthropic.com when unset.
6356
6457Runtime overrides (hot-reloaded each poll):
6558 ~/.claude/power-steering/config.json may set any of {model,
8679import uuid
8780from pathlib import Path
8881
89- # Identifies this daemon to back2base's gateway-proxy worker, which
90- # attaches it to the OTel span emitted for every supervisor /v1/messages
91- # call. Bumped only when the daemon's request shape or telemetry schema
92- # changes — not on every back2base release.
82+ # Identifies this daemon in OTel spans (when an endpoint is configured).
83+ # Bumped only when the daemon's request shape or telemetry schema changes.
9384TELEMETRY_SOURCE = "power-steering"
9485TELEMETRY_VERSION = "1"
9586
10091SESSION_ID = (os .environ .get ("BACK2BASE_TELEMETRY_SESSION_ID" , "" ).strip ()
10192 or uuid .uuid4 ().hex )
10293
103- # OTel: ship spans to otel.back2base.net via OTLP/HTTP + mTLS. Cert + key
104- # arrive in the image at /opt/back2base/certs/ from the release pipeline
105- # (see .github/workflows/release.yml + back2base-container/certs/README.md).
106- # Disabled gracefully when:
107- # - opentelemetry packages aren't installed (older base image)
108- # - cert/key files are missing (local builds without CI secrets)
109- # - BACK2BASE_OTEL=off
110- # Override paths for tests via BACK2BASE_OTEL_CERT / BACK2BASE_OTEL_KEY.
111- OTEL_CERT_PATH = Path (
112- os .environ .get ("BACK2BASE_OTEL_CERT" , "/opt/back2base/certs/client.cert.pem" ),
113- )
114- OTEL_KEY_PATH = Path (
115- os .environ .get ("BACK2BASE_OTEL_KEY" , "/opt/back2base/certs/client.key.pem" ),
116- )
94+ # OTel: optional. Set BACK2BASE_OTEL_ENDPOINT to an OTLP/HTTP collector URL
95+ # to enable. Optional mTLS via BACK2BASE_OTEL_CERT / BACK2BASE_OTEL_KEY.
96+ # Disabled gracefully when opentelemetry packages aren't installed, when
97+ # no endpoint is set, or when BACK2BASE_OTEL=off.
98+ OTEL_CERT_PATH = Path (os .environ .get ("BACK2BASE_OTEL_CERT" , "" ))
99+ OTEL_KEY_PATH = Path (os .environ .get ("BACK2BASE_OTEL_KEY" , "" ))
117100OTEL_ENDPOINT = os .environ .get ("BACK2BASE_OTEL_ENDPOINT" , "" )
118101_TELEMETRY_INITIALIZED = False
119102
@@ -172,9 +155,8 @@ def init_telemetry():
172155 if not OTEL_ENDPOINT :
173156 log ("otel: no BACK2BASE_OTEL_ENDPOINT set; telemetry disabled" )
174157 return False
175- if not OTEL_CERT_PATH .exists () or not OTEL_KEY_PATH .exists ():
176- log (f"otel: cert/key missing under { OTEL_CERT_PATH .parent } ; telemetry disabled" )
177- return False
158+ mtls_enabled = bool (str (OTEL_CERT_PATH )) and bool (str (OTEL_KEY_PATH )) \
159+ and OTEL_CERT_PATH .exists () and OTEL_KEY_PATH .exists ()
178160 try :
179161 from opentelemetry import trace as otel_trace
180162 from opentelemetry .sdk .resources import Resource
@@ -187,15 +169,14 @@ def init_telemetry():
187169 log (f"otel: SDK import failed ({ e } ); telemetry disabled" )
188170 return False
189171 try :
190- exporter = OTLPSpanExporter (
191- endpoint = f" { OTEL_ENDPOINT . rstrip ( '/' ) } /v1/traces" ,
192- client_certificate_file = str (OTEL_CERT_PATH ),
193- client_key_file = str (OTEL_KEY_PATH ),
194- )
172+ exporter_kwargs = { "endpoint" : f" { OTEL_ENDPOINT . rstrip ( '/' ) } /v1/traces" }
173+ if mtls_enabled :
174+ exporter_kwargs [ " client_certificate_file" ] = str (OTEL_CERT_PATH )
175+ exporter_kwargs [ " client_key_file" ] = str (OTEL_KEY_PATH )
176+ exporter = OTLPSpanExporter ( ** exporter_kwargs )
195177 resource = Resource .create ({
196178 "service.name" : TELEMETRY_SOURCE ,
197179 "service.version" : TELEMETRY_VERSION ,
198- "back2base.source" : TELEMETRY_SOURCE ,
199180 "back2base.session" : SESSION_ID ,
200181 "back2base.user_id" : os .environ .get ("MEMORY_USER_ID" , "" ),
201182 })
@@ -386,44 +367,22 @@ def build_request_target():
386367 rides the exact same auth path the user's interactive session uses.
387368 Two layers, stacked when both are available:
388369
389- 1. back2base proxy identity (when ANTHROPIC_BASE_URL points at the
390- proxy — which entrypoint.sh sets it to by default):
391- x-service-auth: <CLAUDEPROXY_SERVICE_TOKEN>
392- x-claude-proxy-bypass: <BACK2BASE_CONFIG_BYPASS_HEADER or "back2base">
393- The proxy worker rejects /v1/messages without `x-service-auth`,
394- so this layer is mandatory in proxy deployments.
395-
396- 2. Anthropic credential (forwarded as-is by the proxy in
397- pass-through mode, or used directly when ANTHROPIC_BASE_URL is
398- unset). Probed in order:
399- ANTHROPIC_API_KEY → x-api-key
400- ANTHROPIC_AUTH_TOKEN → Authorization: Bearer
401- CLAUDE_CODE_OAUTH_TOKEN → Authorization: Bearer
402- Each name is read from the container's process env (the
403- unprefixed form), populated by docker-compose from the matching
404- BACK2BASE_<NAME> host-side variable. The BACK2BASE_ prefix is
405- the only source the host honors.
406-
407- Returns (url, headers, mode) on success or (None, None, None) if
408- no auth is configured. `mode` is "proxy" when x-service-auth is
409- sent, "direct" otherwise — informational only, used for logging.
370+ Anthropic credential probed in order:
371+ ANTHROPIC_API_KEY → x-api-key
372+ ANTHROPIC_AUTH_TOKEN → Authorization: Bearer
373+ CLAUDE_CODE_OAUTH_TOKEN → Authorization: Bearer
374+ Each name is read from the container's process env (the unprefixed
375+ form), populated by docker-compose from the matching BACK2BASE_<NAME>
376+ host-side variable.
377+
378+ Returns (url, headers, mode) on success or (None, None, None) if no
379+ auth is configured. `mode` is always "direct" in the OSS build.
410380 """
411381 base = (_env_with_prefix ("ANTHROPIC_BASE_URL" )
412382 or DEFAULT_API_BASE ).rstrip ("/" )
413383 url = f"{ base } /v1/messages"
414384 headers = {}
415385
416- # Layer 1: back2base proxy identity.
417- svc = _env_with_prefix ("CLAUDEPROXY_SERVICE_TOKEN" )
418- if svc :
419- headers ["x-service-auth" ] = svc
420- # BACK2BASE_CONFIG_BYPASS_HEADER is back2base-owned (no upstream
421- # counterpart), so a plain getenv is correct.
422- bypass = os .environ .get ("BACK2BASE_CONFIG_BYPASS_HEADER" , "back2base" ).strip ()
423- if bypass :
424- headers ["x-claude-proxy-bypass" ] = bypass
425-
426- # Layer 2: Anthropic credential.
427386 api_key = _env_with_prefix ("ANTHROPIC_API_KEY" )
428387 if api_key :
429388 headers ["x-api-key" ] = api_key
@@ -436,16 +395,7 @@ def build_request_target():
436395 if not headers :
437396 return None , None , None
438397
439- # Layer 3: telemetry identity. Read by the gateway-proxy worker and
440- # attached to the OTel span it emits for every /v1/messages call.
441- # Set unconditionally — even on auth misconfiguration we want the
442- # 4xx visible in observability tagged correctly.
443- headers ["x-back2base-source" ] = TELEMETRY_SOURCE
444- headers ["x-back2base-version" ] = TELEMETRY_VERSION
445- headers ["x-back2base-session" ] = SESSION_ID
446-
447- mode = "proxy" if "x-service-auth" in headers else "direct"
448- return url , headers , mode
398+ return url , headers , "direct"
449399
450400
451401# --- logging ---------------------------------------------------------
@@ -1139,8 +1089,8 @@ def main():
11391089 return
11401090 url , headers , mode = build_request_target ()
11411091 if headers is None :
1142- log ("no auth env var set (CLAUDEPROXY_SERVICE_TOKEN / ANTHROPIC_API_KEY / "
1143- "ANTHROPIC_AUTH_TOKEN / CLAUDE_CODE_OAUTH_TOKEN); daemon idle" )
1092+ log ("no auth env var set (ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN / "
1093+ "CLAUDE_CODE_OAUTH_TOKEN); daemon idle" )
11441094 return
11451095 if not NS :
11461096 log ("MEMORY_NAMESPACE not set; daemon idle" )
0 commit comments