molecule-core/workspace/initial_prompt.py
Hongming Wang 479a027e4b chore: open-source restructure — rename dirs, remove internal files, scrub secrets
Renames:
- platform/ → workspace-server/ (Go module path stays as "platform" for
  external dep compat — will update after plugin module republish)
- workspace-template/ → workspace/

Removed (moved to separate repos or deleted):
- PLAN.md — internal roadmap (move to private project board)
- HANDOFF.md, AGENTS.md — one-time internal session docs
- .claude/ — gitignored entirely (local agent config)
- infra/cloudflare-worker/ → Molecule-AI/molecule-tenant-proxy
- org-templates/molecule-dev/ → standalone template repo
- .mcp-eval/ → molecule-mcp-server repo
- test-results/ — ephemeral, gitignored

Security scrubbing:
- Cloudflare account/zone/KV IDs → placeholders
- Real EC2 IPs → <EC2_IP> in all docs
- CF token prefix, Neon project ID, Fly app names → redacted
- Langfuse dev credentials → parameterized
- Personal runner username/machine name → generic

Community files:
- CONTRIBUTING.md — build, test, branch conventions
- CODE_OF_CONDUCT.md — Contributor Covenant 2.1

All Dockerfiles, CI workflows, docker-compose, railway.toml, render.yaml,
README, CLAUDE.md updated for new directory names.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-18 00:24:44 -07:00

52 lines
2.1 KiB
Python

"""Helpers for the workspace's one-shot initial_prompt.
Kept as a standalone module (no heavy imports like uvicorn) so the marker
logic is unit-testable without standing up the full workspace runtime.
Background: the workspace runtime supports an `initial_prompt` that runs once
on first boot (clone the repo, set git hooks, read CLAUDE.md, commit_memory).
A marker file `.initial_prompt_done` prevents the prompt from re-running on
subsequent boots.
Prior behaviour wrote the marker AFTER the prompt completed successfully. If
the prompt crashed mid-execution (e.g. ProcessError from a stale Claude
session), the marker was never written; every subsequent container boot
replayed the same failing prompt, cascading into "every message crashes until
an operator intervenes." See GitHub issue #71.
Fix (2026-04-12): write the marker BEFORE firing the prompt. If the prompt
fails, operators re-send it manually via chat — cheap and available — instead
of trapping the workspace in a crash loop.
"""
from __future__ import annotations
import os
def resolve_initial_prompt_marker(config_path: str) -> str:
"""Return the path where the `.initial_prompt_done` marker should live.
Prefers ``<config_path>/.initial_prompt_done`` when the directory is
writable; falls back to ``/workspace/.initial_prompt_done`` for containers
where ``/configs`` is read-only.
"""
if os.access(config_path, os.W_OK):
return os.path.join(config_path, ".initial_prompt_done")
return "/workspace/.initial_prompt_done"
def mark_initial_prompt_attempted(marker_path: str) -> bool:
"""Write the marker best-effort. Return True on success, False on I/O error.
Called BEFORE the initial-prompt self-message is sent. If the attempt
later fails, the marker is still present — so the next container boot
does NOT replay the same failing prompt. Operators retry manually via
the chat interface instead of relying on auto-replay.
"""
try:
with open(marker_path, "w") as f:
f.write("attempted")
return True
except OSError:
return False