Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions .github/workflows/outreach-sequencer-dogfood.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
name: outreach-sequencer dogfood

on:
workflow_dispatch:
push:
branches:
- outreach-sequencer
paths:
- "skills/outreach-sequencer/**"
- ".github/workflows/outreach-sequencer-dogfood.yml"

permissions:
contents: write

jobs:
dogfood:
if: ${{ !contains(github.event.head_commit.message, '[skip dogfood]') }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Install runx 0.6.14
run: |
set -euo pipefail
curl -L -s https://github.com/runxhq/runx/releases/download/cli-v0.6.14/runx-0.6.14-x86_64-unknown-linux-musl.tar.gz -o /tmp/runx.tar.gz
mkdir -p /tmp/runx
tar -xzf /tmp/runx.tar.gz -C /tmp/runx
install -m 0755 "$(find /tmp/runx -type f -name runx | head -n 1)" /tmp/runx/runx
/tmp/runx/runx --version
echo "/tmp/runx" >> "$GITHUB_PATH"

- name: Clean install published package
run: |
set -euo pipefail
mkdir -p skills/outreach-sequencer/evidence
runx add vidshidden/outreach-sequencer@sha-8248a4585211 \
--registry https://api.runx.ai \
--to skills/outreach-sequencer/evidence/clean-install \
--json | tee skills/outreach-sequencer/evidence/clean-install.json

- name: Dogfood published package
env:
RUNX_RECEIPT_SIGN_KID: runx-runtime-prod-fixture-key
RUNX_RECEIPT_SIGN_ED25519_SEED_BASE64: QkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkJCQkI=
RUNX_RECEIPT_SIGN_ISSUER_TYPE: hosted
run: |
set -euo pipefail
mkdir -p skills/outreach-sequencer/evidence/dogfood-receipts
SEQUENCE="$(jq -c '.sequence_definition' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
CONTACT="$(jq -c '.contact_ref' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
ENGAGEMENT="$(jq -c '.engagement_projection' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
AGGREGATE_ID="$(jq -r '.aggregate_id' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
CURRENT_TOUCH_INDEX="$(jq -r '.current_touch_index' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
STORE_ID="$(jq -r '.store_id' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
EXPECTED_VERSION="$(jq -r '.expected_version' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
IDEMPOTENCY_KEY="$(jq -r '.idempotency_key' skills/outreach-sequencer/fixtures/happy-next-touch.json)"
runx skill vidshidden/outreach-sequencer@sha-8248a4585211 \
--registry https://api.runx.ai \
--input-json sequence_definition "$SEQUENCE" \
-i "aggregate_id=$AGGREGATE_ID" \
--input-json contact_ref "$CONTACT" \
--input-json current_touch_index "$CURRENT_TOUCH_INDEX" \
-i "store_id=$STORE_ID" \
-i "idempotency_key=$IDEMPOTENCY_KEY" \
--input-json expected_version "$EXPECTED_VERSION" \
--input-json engagement_projection "$ENGAGEMENT" \
--json \
-R skills/outreach-sequencer/evidence/dogfood-receipts \
| tee skills/outreach-sequencer/evidence/dogfood-output.json

- name: Verify dogfood receipt
env:
RUNX_RECEIPT_VERIFY_KID: runx-runtime-prod-fixture-key
RUNX_RECEIPT_VERIFY_ED25519_PUBLIC_KEY_BASE64: IVL40Zt5HSRFMkLhXy6rbLfP+ntqXtMAl5YOBpiB2xI=
run: |
set -euo pipefail
runx verify \
--receipt-dir skills/outreach-sequencer/evidence/dogfood-receipts \
--json | tee skills/outreach-sequencer/evidence/dogfood-verify.json

- name: Build evidence packet
run: |
set -euo pipefail
node skills/outreach-sequencer/evidence/build-evidence.mjs

- name: Commit dogfood evidence
run: |
set -euo pipefail
receipt_file="$(find skills/outreach-sequencer/evidence/dogfood-receipts -type f -name '*.json' | head -n 1)"
if [ -n "$receipt_file" ]; then
cp "$receipt_file" skills/outreach-sequencer/evidence/dogfood-receipt.json
fi
git config user.name "VidsHidden"
git config user.email "zhengxinyu515@gmail.com"
git add skills/outreach-sequencer/evidence/clean-install.json \
skills/outreach-sequencer/evidence/dogfood-output.json \
skills/outreach-sequencer/evidence/dogfood-verify.json \
skills/outreach-sequencer/evidence/dogfood-receipt.json \
skills/outreach-sequencer/evidence/evidence.json \
skills/outreach-sequencer/evidence/verification.json \
skills/outreach-sequencer/evidence/report.md
if git diff --cached --quiet; then
exit 0
fi
git commit -m "Add outreach-sequencer dogfood evidence [skip dogfood]"
git push
5 changes: 5 additions & 0 deletions crates/runx-cli/src/official_skills.rs
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,11 @@ pub(crate) const OFFICIAL_SKILLS: &[OfficialSkillLockEntry] = &[
version: "sha-a2f4840047ee",
digest: "f32f21b6ed6c03f5623bc98f2823365ebcabc721cedf64e8a67c556613a35f59",
},
OfficialSkillLockEntry {
skill_id: "runx/outreach-sequencer",
version: "sha-4fa79af84905",
digest: "e1860ab37decf86bcacd30b8665f7b91bde369c7f503a73ba38a3d8dc7495ae0",
},
OfficialSkillLockEntry {
skill_id: "runx/overlay-generator",
version: "sha-b5dc11a7088d",
Expand Down
7 changes: 7 additions & 0 deletions packages/cli/src/official-skills.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@
"catalog_visibility": "public",
"catalog_role": "canonical"
},
{
"skill_id": "runx/outreach-sequencer",
"version": "sha-4fa79af84905",
"digest": "e1860ab37decf86bcacd30b8665f7b91bde369c7f503a73ba38a3d8dc7495ae0",
"catalog_visibility": "public",
"catalog_role": "canonical"
},
{
"skill_id": "runx/overlay-generator",
"version": "sha-b5dc11a7088d",
Expand Down
83 changes: 83 additions & 0 deletions skills/outreach-sequencer/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
---
name: outreach-sequencer
description: Decide the next eligible outreach touch from durable engagement state, append the decision event, and emit a handoff-only send packet.
source:
type: cli-tool
command: node
args:
- run.mjs
runx:
tags:
- outreach
- sequencing
- data-store
links:
composes:
- registry:runx/data-store@0.1.2
- send-as
---

# Outreach Sequencer

This skill reads a bounded outreach sequence definition and the contact's
durable engagement projection for one sequence/contact aggregate. It decides
whether the next touch is eligible, records the decision as an ungated
data-store append event, and emits a typed handoff packet for a downstream
governed `send-as` run.

## Inputs

- `sequence_definition`: object with `touches[]` and `rules`.
- `aggregate_id`: sequence/contact entity id used for data-store reads and
appends.
- `contact_ref`: bounded contact reference with `principal`, `audience`, and
optional `channel`.
- `current_touch_index`: optional number representing the last completed touch.
- `store_id`: pinned data-store id for outreach sequence state.
- `idempotency_key`: caller-provided idempotency key for the decision append.
- `expected_version`: CAS version read from the engagement projection.
- `engagement_projection`: bounded data-store read projection containing
`operation_result`, `events[]`, and `version`.

## Outputs

The skill emits `runx.outreach.sequencer.v1`:

- `decision`: `{ eligible, reason }`.
- `append_event`: an ungated CAS append event shaped for
`registry:runx/data-store@0.1.2`.
- `next_touch_packet`: present only when eligible. It has schema
`runx.outreach.next_touch.v1` and binds `send_class`, `principal`, `channel`,
`audience`, `content_digest`, and the dispatch `idempotency_key`.
- `escalation`: present when a missing sequence definition or unreadable state
needs a human approval lane.
- `stop_state`: present when a reply, unsubscribe, or too-recent prior touch
stops dispatch.

## Safety Boundaries

The skill does not send email, post messages, mint authority, or emit a proposal
envelope. The next-touch packet is a handoff only: a separate governed
downstream driver or operator must run `send-as` with its own preflight and
approval to deliver the touch. Sequence progress is committed only as an
ungated `append_event(idempotency_key, expected_version)` against the pinned
`store_id` and aggregate id.

The skill refuses to emit a touch packet after a reply or unsubscribe event,
refuses when the prior touch was sent less than `min_days_apart` ago, and never
invents an engagement event it cannot link to the supplied data-store
`operation_result`.

## Procedure

1. Validate the sequence definition, aggregate id, contact reference, store id,
idempotency key, expected version, and engagement projection.
2. Read the supplied engagement projection for the sequence/contact aggregate.
3. Stop if a reply or unsubscribe event appears in the durable engagement
stream.
4. Stop if the prior sent touch is inside the configured `min_days_apart`
window.
5. Select the next touch after `current_touch_index`, or infer it from the
latest sent event when not supplied.
6. Append an outreach decision event with CAS version movement.
7. Emit exactly one `runx.outreach.next_touch.v1` handoff packet when eligible.
Loading