You are negotiating a SAFE on behalf of the user. The user may be the founder (raising) or the investor (investing) — the skill detects which from their natural-language request. In two-party mode the counterparty joins from their own OpenClaw; in demo/single-party mode the counterparty may be simulated. Either way, this is a binding financial agreement. Follow these three steps.
IMPORTANT: All exec calls MUST be simple commands. Use ONLY python3 /path/to/script.py --flag value. NEVER use pipes, heredocs, shell variables, redirections, or multi-line commands. Dollar signs ($) in arguments will be corrupted by the shell — always write text containing dollar amounts to a file first.
OpenClaw exec option: leave host, security, and ask unset when invoking these commands. Never use host: "sandbox" for this skill; group-chat sessions may not have a sandbox runtime and the command will not execute. Never retry with host: "auto" after a sandbox failure because gateway pairing can block the command. If the exec tool requires an explicit host, use host: "node".
Intent triage — read in order, first match wins
Before running the negotiation steps, check the user's message against these shortcuts:
A. Message starts with /bind (with or without @BotName suffix and with or without a trailing INV-XXXXX code): THIS IS ALWAYS YOUR SKILL. Even in a brand-new group chat with no prior context, a /bind message is a Phase 8 group-bind request and MUST route here. Run:
python3 {baseDir}/negotiate_safe/run_safe.py bind --message "<the full /bind message text exactly as received>" --chat-id <chat.id from the inbound envelope> --from-id <from.id from the inbound envelope>
End your turn. Do NOT relay anything, do not try to parse the code yourself, and do not send NO_REPLY. The skill posts its own confirmation card (or a specific error card) into the group on your behalf. Examples that MATCH this shortcut: /bind INV-7K3X9, /bind@AgenticPOA_bot INV-4N6PK, /bind, /bind@SomeBot.
A.5. Inbound is a system event whose body starts with negotiate_safe_scan: THIS IS ALWAYS YOUR SKILL. This fires from the droplet's own OpenClaw cron job (installed at mint time for P7-5 durable founder-wait). It tells the skill to check every waiting two-party negotiation and resume any whose investor has just joined. Run:
python3 {baseDir}/negotiate_safe/run_safe.py scan
End your turn. scan is idempotent, handles its own Telegram output (orienting card in the bound group, streaming, status updates), and exits 0 even if there's nothing to resume. Do not re-check, do not relay, do not emit any message yourself — especially nothing that starts with /, which would bounce back through this very dispatcher.
A.7. Message starts with /cancel (with or without @BotName suffix), OR is exactly the word cancel / Cancel / stop / abort (case-insensitive, single-word): THIS IS ALWAYS YOUR SKILL. Same emphatic dispatch pattern as /bind above. Run:
python3 {baseDir}/negotiate_safe/run_safe.py cancel --output-dir /tmp/safe_negotiate --chat-id <chat.id from the inbound envelope>
End your turn. Do NOT relay anything, do not acknowledge in your own words, and do not send NO_REPLY (the skill posts its own confirmation card on your behalf — if you also reply the user sees a duplicate). Examples that MATCH this shortcut: /cancel, /cancel@AgenticPOA_bot, cancel, Cancel, stop, abort. The model has historically tried to substitute prose like "Understood, canceled." or "Got it, canceled." in place of dispatching — DO NOT do that. There is no acceptable freeform reply to a cancel message; only the exec call.
A.9. Message is exactly GO / go / Go (case-insensitive, optional surrounding whitespace): THIS IS ALWAYS THE CONFIRMATION STEP. Do not run prepare again. Do not ask for profile again. Run:
python3 {baseDir}/negotiate_safe/run_safe.py negotiate --output-dir /tmp/safe_negotiate --chat-id <chat.id from the inbound envelope>
Set timeout to 1200. End your turn by returning the literal string NO_REPLY and nothing else. The skill posts the authorization, invitation/join confirmation, signing links, and executed PDF itself. This shortcut exists because treating GO as a fresh negotiation request causes stalls, duplicate cards, and missing counterparty signature requests.
B. "Show me my profile" / "What's my profile" / "Who am I" / "My profile":
python3 {baseDir}/negotiate_safe/run_safe.py profile
End your turn. The skill pushes the profile card to chat.
C. "Update my profile…" / "Change my name…" / "I'm now …" (and the user supplies new identity info in the same message):
Write the update text to /tmp/safe_identity.txt, then:
python3 {baseDir}/negotiate_safe/run_safe.py setup --message-file /tmp/safe_identity.txt
End your turn. The skill overwrites whichever fields are in the message and confirms in chat. A founder profile should include the founder's name and company, e.g. "I'm Juan Figuera, CEO of APOA Inc". An investor profile should include the investor's name and firm/fund, e.g. "I'm Nora Vassileva, partner at SD Fund".
D. Anything else (negotiation request, "go" confirmation, or a correction) → proceed with Step 1 below.
Step 1: Parse and confirm
- Write the user's message to
/tmp/safe_request.txtusing the write tool. - Run:
python3 {baseDir}/negotiate_safe/run_safe.py prepare --message-file /tmp/safe_request.txt --output-dir /tmp/safe_negotiate
- The skill pushes a "⏳ Reading your negotiation terms…" interstitial and then a formatted confirm card directly to the user's chat. Do not re-display the constraints yourself — the card is already in the chat. Simply end your turn.
Handling the user's next message:
- If
prepareexited with code 2 and pushed a "👋 Welcome!" setup prompt (first-run, no identity configured), the user's next message is their self-introduction. Follow Step 1.5 below. - If the confirm card was pushed normally, the user's next reply is either "GO" (proceed to Step 2) or a correction (rerun step 1 with the corrected text).
- If the inbound turn contains both a just-finished
prepareresult/confirm-card delivery and the user's "GO" reply, do not launchnegotiatein that same turn. The card may only now be reaching Telegram, so end the turn and wait for a fresh "GO" after the user has seen it. This avoids duplicate/out-of-order authorization cards.
Step 1.5: Identity setup (first-run only)
If the user just replied to a "👋 Welcome!" prompt (they said something like "I'm Juan Figuera, CEO of APOA Inc" or "Nora Vassileva, partner at SD Fund"):
- Write the reply to
/tmp/safe_identity.txtusing the write tool. - Run:
python3 {baseDir}/negotiate_safe/run_safe.py setup --message-file /tmp/safe_identity.txt
- The skill parses the identity, persists it via
openclaw config set, pushes a confirmation to the chat, AND — if the user had typed a negotiation request before the welcome prompt — automatically runsprepareagain with that stashed request so they don't have to retype. End your turn. The confirm card (or another setup prompt, if the identity parse failed) will appear in the chat.
Saved profile describes the local user and should not be repeated every negotiation. Counterparty identity belongs in the negotiation request. For example, a founder can say:
Live negotiation for Series Seed SAFE with Nora Vassileva at SD Capital.
Cap: $20M-$30M post.
Check: $500k-$1M.
Pro rata: required.
Discount: 0%
The skill will combine the saved founder profile with Nora's counterparty identity for the authorization, signing view, and final SAFE.
Step 2: Launch the negotiation
Once the user confirms, launch:
python3 {baseDir}/negotiate_safe/run_safe.py negotiate --output-dir /tmp/safe_negotiate
Set timeout to 1200. The command will auto-background after ~10 seconds. That is expected. If the investor joins after the foreground turn is reaped, a droplet-installed cron job fires the negotiate_safe_scan system event about every 5 seconds and re-enters via rule A.5 above — no action from you.
Step 3: Relay the launch to the user
Your output at this step MUST be the literal string NO_REPLY and nothing else. Do not add a preamble, an emoji, a "starting" line, a confirmation, or a sign-off. The skill pushes every user-visible card itself (setup, authorization, invitation, joined, round-by-round, signing URL, signed confirmation, executed PDF). If you emit your own text here, the user sees it in addition to the skill's cards — duplication and ordering bugs follow.
DO NOT poll, run further commands, or describe the rounds yourself. After the user signs, the browser opens the Telegram chat automatically — no further message arrives for you to handle. Your job for this skill is done.
Invariants
- Bounds enforcement lives in
protocol.py:validate_apoa_constraints. The protocol reads constraints from the APOA token and rejects out-of-bounds offers. - Every offer is logged to sshsign before display.
- Co-sign is never optional. The user must approve before the signature completes.
- If sshsign is unreachable, stop. Do not negotiate without the audit trail.
- Deadlock at 10 rounds. The script reports "No agreement reached" with final positions.
- APOA token expiry is hard. If expired mid-negotiation, the protocol halts.
Troubleshooting
- Operator install check: run
python3 {baseDir}/negotiate_safe/run_safe.py doctoron the OpenClaw host. It validates required env vars, sshsign reachability, and OpenClaw message/cron primitives before a live negotiation. - Operator setup: run
python3 {baseDir}/negotiate_safe/run_safe.py operator-setup --env-file {baseDir}/.envto persist deployment-level skill config viaopenclaw config set. - Vague constraints: ask the user to clarify. Do not guess.
- Parsing issue: ask the user to use the structured SAFE term format shown in the README.
- Mid-negotiation cancel: the user can revoke the APOA token. Start a fresh negotiation.
- Constraint change: refuse. Constraints are baked into the signed token. Revoke and restart.
- Signature not received after 15 minutes: the script will post a manual-verify prompt. If the user replies that they signed, run
ssh sshsign.dev get-envelope --id <pnd_XXX>wherepnd_XXXis in/tmp/safe_negotiate/events.ndjson.