# Presence Integration

Use this when wiring the skill into OpenClaw jobs or sessions.

当前默认主动链路只有 `companion-presence`。cron 运行在 isolated session 中，只调用 `scripts/companion_presence_tick.py`；该 wrapper 先确定性运行 `scripts/companion_run.py --stage prepare --no-record-pending`，读取当前 `day-schedule.md` 事件。未命中时静默退出，命中后才把准备好的合同交给稳定 companion session 发送 presence story。媒体任务的 OpenClaw completion 必须回到同一个稳定 companion session；事实连续性仍只来自本地状态文件。

## Runtime Pieces

Required local pieces:
- `scripts/companion_run.py`
- materialized `config.local.json`
- `state/character-profile.md`
- `state/day-schedule.md`
- `state/companion-state.json`
- optional continuity file `state/life-log.jsonl`

`config.local.json` may contain real local paths and delivery ids. Do not copy those values into publishable docs, examples, cron templates, or user-visible companion text.

## Presence Cron Shape

Recommended job:

```json
{
  "name": "companion-presence",
  "description": "Owner-only cyber girlfriend presence cron",
  "schedule": {
    "kind": "cron",
    "expr": "0 * * * *",
    "tz": "Asia/Shanghai"
  },
  "sessionTarget": "isolated",
  "payload": {
    "kind": "agentTurn",
    "message": "<PRESENCE_SINGLE_RUNNER_TEMPLATE>"
  },
  "delivery": {
    "mode": "none"
  },
  "enabled": true
}
```

The payload should do only this:

```text
1. 第一个也是唯一业务动作是触发 companion_presence_tick.py --config <CONFIG>。
2. 如果脚本输出 skip、agent_enqueued、notification_sent 或其他已处理状态，都只回复 NO_REPLY。
3. 不要自己读取 day-schedule.md，不要自己判断当前事件，也不要自己处理消息发送或媒体生成。
```

Do not pass `--event-time` in the live cron. Presence reads the real current local time.

## Message Rules

- Final text must be first person from the companion's perspective and must fit the cyber-girlfriend persona.
- Unless the matched required event defines a special structure, write one complete, rich, specific event story.
- Include the companion's current emotion and inner thought.
- Write at least 160 Chinese characters; before sending, self-check the final text and expand with event details or inner thought if it is shorter.
- If the current event contains an interaction entry for the user, express it naturally and do not omit it.
- Use the current event in `life_context`, not stale memories or unrelated technical incidents.
- Do not mention scripts, JSON, cron, tools, models, routing, status values, step names, or diagnostics.
- Keep owner and companion separate; never project the companion's school, room, friends, schedule, or private life onto the owner.
- Use public browsing only as the optional grounding step above; never let it replace the current event.

## Delivery Rules

- External delivery must use explicit channel/account/target from `delivery_contract`.
- `companion-presence` runs in an isolated cron session and only calls the deterministic wrapper.
- The wrapper starts the stable custom companion runtime session such as `session:companion-runtime` only after prepare returns `status = "ok"`.
- Presence sends final text through the prepared delivery contract.
- State commits only after confirmed visible delivery.
- A second tick in the same event should skip because the event was already sent.

## Media Callback Rules

For media events, the text presence turn is allowed to end before media generation completes. The default path uses OpenClaw's native media completion routing by having the wrapper call a stable companion session for matched events:

1. Send the text presence story first.
2. Use `life_context.event.media_info` to start the matching async media generation defined by `media_contract`.
3. Run `state_commit.command` immediately after the text presence story is visibly sent, so the event is complete even if media later fails.
4. When OpenClaw delivers the media completion into the same stable companion session, send the generated media with `delivery_contract`.
5. Do not run `state_commit.command` again in the media completion turn.

The runner contract exposes `media_contract.callback_context.strategy = same_stable_session` and `requires_original_session_context = true` to make this dependency explicit.

## Verification

Before declaring setup or upgrade complete:

1. Run `python3 scripts/validate_release.py --root <SKILL_DIR> --config <CONFIG> --skip-smoke`.
2. Ensure `day-schedule.md` has a current event, or create a temporary validated schedule for testing.
3. Run `python3 scripts/companion_presence_tick.py --config <CONFIG> --dry-run`.
4. Confirm dry-run output is `would_start_agent` for a matched event or `skip` when no event is active.
5. Confirm prepare output does not expose private paths, channel ids, account ids, session ids, `render_spec`, or top-level `primary_goal`.
6. Run one controlled presence delivery.
7. Confirm the owner saw exactly one message.
8. Confirm the next tick in the same event returns a quiet skip.
9. For a media event, run a controlled native completion test: confirm text sends first, state commits after text delivery, media generation can finish after the presence turn ends, and a successful completion returns to the same stable companion session to send generated media with the saved delivery contract.
