Severity: Medium
Affected repos:middleware-python
Component boundary: middleware (local mode) → on-disk file
Motivation
_LocalTransport in recost/_transport.py defaults to local mode when api_key is missing and opens ws://127.0.0.1:9847. The VS Code extension does not host that server and has decided not to — see recost-dev/extension#91. Every default-configured SDK in local mode reconnects forever to nothing.
A file-based transport gives users a working "no cloud account" path without a localhost listener: the SDK writes WindowSummary frames to disk; tooling can read them later.
Proposal
Add a local-file transport mode alongside the existing WS mode.
- Config:
RecostConfig.local_transport: Literal["ws", "file"]. Default: "file". The WS target does not exist (recost-dev/extension#91), so the new default writes telemetry to disk instead of silently dropping it. "ws" remains as an opt-in. Behavior change for anyone currently using local mode by accident, but their telemetry was going nowhere — so this is strictly an improvement. - Wire format: append-only NDJSON. Each line is exactly one
json.dumps(window_summary.to_dict()). Every frame carries a top-level protocol_version: "1.0" (covers recost-dev/extension#99 for this transport). - Location:
os.environ.get("RECOST_LOCAL_DIR") or pathlib.Path.home() / ".recost" / "local-telemetry" then f"{project_id}.jsonl". - Permissions:
0o600 on POSIX; document the Windows ACL limitation. - Failure modes: disk-full /
PermissionError drops the oldest queued frame, fires on_error once per overflow episode. Never raises. - Lifecycle: flush + close stream on
dispose().
Open for the implementer
- Rotation (per-day? per-size? both?) and retention.
- Multi-process concurrency on the same
project_id (POSIX O_APPEND atomicity vs Windows). - Whether the existing WS transport is eventually removed (separate issue).
Verification
init(project_id="proj_x") (no local_transport set → uses new default "file") + 3 requests.get() calls writes 3 NDJSON lines with protocol_version: "1.0".init(project_id="proj_x", local_transport="ws") still attempts the existing WS path.dispose() flushes within 1s.- File mode
0o600 on POSIX.
Out of scope
- Reading the files (no consumer exists — extension explicitly opted out per
recost-dev/extension#91). - Removing the existing WebSocket transport.
- Encryption / signing of on-disk frames.
Related
recost-dev/extension#91, recost-dev/extension#99- Node mirror: cross-referenced in a follow-up comment.
Severity: Medium
Affected repos:
middleware-pythonComponent boundary: middleware (local mode) → on-disk file
Motivation
_LocalTransportinrecost/_transport.pydefaults to local mode whenapi_keyis missing and opensws://127.0.0.1:9847. The VS Code extension does not host that server and has decided not to — seerecost-dev/extension#91. Every default-configured SDK in local mode reconnects forever to nothing.A file-based transport gives users a working "no cloud account" path without a localhost listener: the SDK writes
WindowSummaryframes to disk; tooling can read them later.Proposal
Add a
local-filetransport mode alongside the existing WS mode.RecostConfig.local_transport: Literal["ws", "file"]. Default:"file". The WS target does not exist (recost-dev/extension#91), so the new default writes telemetry to disk instead of silently dropping it."ws"remains as an opt-in. Behavior change for anyone currently using local mode by accident, but their telemetry was going nowhere — so this is strictly an improvement.json.dumps(window_summary.to_dict()). Every frame carries a top-levelprotocol_version: "1.0"(coversrecost-dev/extension#99for this transport).os.environ.get("RECOST_LOCAL_DIR") or pathlib.Path.home() / ".recost" / "local-telemetry"thenf"{project_id}.jsonl".0o600on POSIX; document the Windows ACL limitation.PermissionErrordrops the oldest queued frame, fireson_erroronce per overflow episode. Never raises.dispose().Open for the implementer
project_id(POSIXO_APPENDatomicity vs Windows).Verification
init(project_id="proj_x")(nolocal_transportset → uses new default"file") + 3requests.get()calls writes 3 NDJSON lines withprotocol_version: "1.0".init(project_id="proj_x", local_transport="ws")still attempts the existing WS path.dispose()flushes within 1s.0o600on POSIX.Out of scope
recost-dev/extension#91).Related
recost-dev/extension#91,recost-dev/extension#99