Task — engineering-spec@1

"memory-sync@1 skill bundle — operator-facing sync trigger that defers to Stage 4 orchestrator (slice-3 stub; full sync ships P2)"

doneTASK-SKILL-106
module skill · class product · priority p1 · created 2026-05-16 · shipped null
depends on TASK-SKILL-105 · blocks TASK-SKILL-107

§1 — Description (BCP-14 normative)

The memory-sync@1 skill bundle MUST scaffold the sync-trigger surface; full orchestration deferred to TASK-MEMORY-103 + P2. The contract:

  1. MUST ship a signed bundle with SKILL.md frontmatter allowed_tools: [MemoryEmit]; sync_class private; tenant_scope pinned.
  2. MUST expose Rust API request_sync(direction: SyncDirection, dry_run: bool) -> Result<SyncOutcome, SyncError> where SyncDirection ∈ Push | Pull | Both.
  3. MUST in slice-3 return SyncOutcome::DeferredToP2 { reason: "full orchestration ships in P2 via TASK-MEMORY-103" } regardless of arguments.
  4. MUST emit memory audit row memory.sync_requested per invocation with payload {direction, dry_run, by_subject_id, slice_version, trace_id}.
  5. MUST be invokable via:
  1. MUST exit with ExitCode::Ok (slice-3) even though no actual sync occurs; bash CLI prints "⚠ memory-sync@1 slice-3: deferred to P2 — see TASK-MEMORY-103".
  2. MUST emit OTel metric skill_memory_sync_requests_total{direction, dry_run, outcome} (counter; outcome ∈ deferred_p2 | error).
  3. SHOULD when P2 ships, this task's slice-4 version will delegate to TASK-MEMORY-103's sync daemon via Unix socket call.

§2 — Why this design (rationale for humans)

Why ship a stub (DEC-400)? Reserves the skill ID (memory-sync) in the OCI registry; downstream skills can declare depends_on: memory-sync@^1. Slice-3 users learn the UX; full logic lands without breaking their muscle memory.

Why audit stub invocations (DEC-401)? Tells the operator "the skill was called but didn't do anything yet" — visible signal that drives P2 prioritisation. Also surfaces accidental-test invocations.

Why DeferredToP2 not Error? Error implies bug; Deferred implies known-limitation. Caller code can pattern-match cleanly without try/catch ceremony.


§3 — API contract

SKILL.md

---
id: memory-sync
version: 1.0.0
description: memory multi-device sync trigger (slice-3 stub; full orchestration in P2 via TASK-MEMORY-103).
allowed_memory_scopes: []
allowed_tools: [MemoryEmit]
sync_class: private
tenant_scope: pinned
effort_minutes: 1
tags: [memory, sync, p2-pending]
signature:
  algo: ed25519
  public_key_hex: "<release-populated>"
  signature_hex:  "<release-populated>"
---

# memory-sync@1 (slice-3 stub)

```rust
use cyberos_memory_sync::{request_sync, SyncDirection}; let outcome = request_sync(SyncDirection::Both, false).await?; // outcome = SyncOutcome::DeferredToP2 { reason: "..." }

### Rust API

```rust
// skills/memory-sync/src/lib.rs
use serde::Serialize;

#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SyncDirection { Push, Pull, Both }

#[derive(Clone, Debug, Serialize)]
pub enum SyncOutcome {
    DeferredToP2 { reason: String },
}

#[derive(Debug, thiserror::Error)]
pub enum SyncError {
    #[error("broker down")] BrokerDown,
}

pub async fn request_sync(direction: SyncDirection, dry_run: bool) -> Result<SyncOutcome, SyncError> {
    let trace_id = current_trace_id();
    emit_memory_row("memory.sync_requested", serde_json::json!({
        "direction": direction,
        "dry_run": dry_run,
        "slice_version": "slice-3-stub",
        "trace_id": trace_id,
    })).await;
    metrics::counter!("skill_memory_sync_requests_total",
        "direction" => format!("{direction:?}"),
        "dry_run" => dry_run.to_string(),
        "outcome" => "deferred_p2").increment(1);
    Ok(SyncOutcome::DeferredToP2 {
        reason: "full sync orchestration ships in P2 via TASK-MEMORY-103".into(),
    })
}

Bash CLI

#!/usr/bin/env bash
# skills/memory-sync/cli/cyberos-memory-sync
set -euo pipefail
DIRECTION="${1:-both}"   # push | pull | both
DRY_RUN=""
if [[ "${2:-}" == "--dry-run" ]]; then DRY_RUN="--dry-run"; fi
echo "⚠  memory-sync@1 slice-3 — request acknowledged; full orchestration ships in P2 (TASK-MEMORY-103)"
exec cyberos-memory-sync-main --direction "$DIRECTION" $DRY_RUN

§4 — Acceptance criteria

  1. request_sync returns DeferredToP2 — any direction + dry_run combo → DeferredToP2 outcome.
  2. memory audit row emittedmemory.sync_requested row with slice_version: "slice-3-stub".
  3. OTel counter incrementsskill_memory_sync_requests_total{outcome="deferred_p2"} after invoke.
  4. Bash CLI prints warningcyberos-memory-sync → stderr contains "slice-3" + "TASK-MEMORY-103".
  5. SKILL.md validatescyberos skill validate skills/memory-sync/ → exit 0.
  6. Signature verified — release sign-bundle → cyberos skill verify passes.
  7. Bundle published — OCI tag oci://registry.cyberos.world/skills/memory-sync:1.0.0.
  8. Broker enforces — skill attempts non-allowed tool → broker denial.

§5 — Verification

#[tokio::test]
async fn returns_deferred() {
    let outcome = request_sync(SyncDirection::Both, false).await.unwrap();
    assert!(matches!(outcome, SyncOutcome::DeferredToP2 { .. }));
}

#[tokio::test]
async fn audit_emitted() {
    let _ = request_sync(SyncDirection::Push, true).await.unwrap();
    let row = memory_test_helper::latest("memory.sync_requested").await;
    assert_eq!(row["payload"]["direction"], "push");
    assert_eq!(row["payload"]["dry_run"], true);
    assert_eq!(row["payload"]["slice_version"], "slice-3-stub");
}

§6 — Implementation skeleton

(API above is the skeleton.)


§7 — Dependencies


§8 — Example payloads

{
  "kind": "memory.sync_requested",
  "payload": {
    "direction": "both",
    "dry_run": false,
    "by_subject_id": "7e57c0de-...",
    "slice_version": "slice-3-stub",
    "trace_id": "0af..."
  }
}

§9 — Open questions

All resolved. Deferred:


§10 — Failure modes inventory

FailureDetectionOutcomeRecovery
Broker downUnixStream ErrSyncError::BrokerDownOperator restores
Audit emit failssev-2 alarmNone (stub completes regardless)Operator restores memory
OTel exporter downmetric bufferedLoggedRestore TASK-OBS-001
Bash CLI misuse (typo direction)clap rejectexit 1User fixes
Concurrent invocationsindependentEach gets own audit rowNone

§11 — Implementation notes


End of TASK-SKILL-106.