Task — engineering-spec@1

"synthesis-author@1 skill — nightly multi-memory auto-evolve composes derived memories from clustered raw captures (P3 — stub scaffold in P1)"

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

§1 — Description (BCP-14 normative)

The synthesis-author@1 skill MUST scaffold the nightly multi-memory synthesis surface; full logic ships P3. The contract:

  1. MUST ship signed bundle with SKILL.md frontmatter allowed_tools: [MemoryEmit, MemoryRead, MemorySearch]; sync_class shareable (synthesised memories may sync); tenant_scope any.
  2. MUST expose Rust API request_synthesis(scope: SynthesisScope, dry_run: bool) -> Result<SynthesisOutcome, SynthesisError> where SynthesisScope ∈ Tenant | Engagement | Custom { paths: Vec<String> }.
  3. MUST in P1 return SynthesisOutcome::DeferredToP3 { reason } regardless of arguments.
  4. MUST emit memory audit memory.synthesis_requested per invocation.
  5. MUST be invokable via Rust SDK + bash CLI cyberos-synthesis-author run --scope tenant --dry-run.
  6. MUST exit Ok in P1.
  7. MUST emit OTel skill_synthesis_requests_total{scope, outcome}.
  8. SHOULD P3 slice-1 implementation:

§2 — Why this design

Same reasoning as TASK-SKILL-106 stub: reserve ID, capture UX, audit invocations, defer logic to module-owner task (here: the eventual P3 synthesis pipeline). Synthesis is high-leverage but expensive (LLM compose chains); shipping the stub now lets early adopters experiment with the surface API without compute cost.


§3 — API contract

// skills/synthesis-author/src/lib.rs
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SynthesisScope {
    Tenant,
    Engagement { engagement_id: uuid::Uuid },
    Custom { paths: Vec<String> },
}

#[derive(Clone, Debug, serde::Serialize)]
pub enum SynthesisOutcome {
    DeferredToP3 { reason: String },
}

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

pub async fn request_synthesis(scope: SynthesisScope, dry_run: bool) -> Result<SynthesisOutcome, SynthesisError> {
    emit_memory_row("memory.synthesis_requested", serde_json::json!({
        "scope": scope, "dry_run": dry_run,
        "slice_version": "p1-stub", "trace_id": current_trace_id(),
    })).await;
    metrics::counter!("skill_synthesis_requests_total",
        "scope" => format!("{scope:?}"), "outcome" => "deferred_p3").increment(1);
    Ok(SynthesisOutcome::DeferredToP3 {
        reason: "Synthesis ships in P3; this is the scaffold reservation.".into(),
    })
}

§4 — Acceptance criteria

  1. DeferredToP3 returned regardless of args.
  2. memory audit emitted with slice_version: "p1-stub".
  3. OTel counter increments.
  4. CLI prints P3-deferral warning.
  5. SKILL.md validates.
  6. Signature verified at release.
  7. Broker enforces narrow allowed_tools.

§5 — Verification

#[tokio::test]
async fn returns_deferred() {
    let outcome = request_synthesis(SynthesisScope::Tenant, false).await.unwrap();
    assert!(matches!(outcome, SynthesisOutcome::DeferredToP3 { .. }));
}

#[tokio::test]
async fn audit_emitted() {
    let _ = request_synthesis(SynthesisScope::Tenant, true).await.unwrap();
    let row = memory_test_helper::latest("memory.synthesis_requested").await;
    assert_eq!(row["payload"]["slice_version"], "p1-stub");
}

§6 — Implementation skeleton

(API above.)


§7 — Dependencies


§8 — Example payloads

{
  "kind": "memory.synthesis_requested",
  "payload": {
    "scope": "tenant",
    "dry_run": true,
    "slice_version": "p1-stub",
    "trace_id": "0af..."
  }
}

§9 — Open questions

All resolved. Deferred to P3: cluster algorithm choice, LLM cost budget, sync_class for synthesised memories.


§10 — Failure modes inventory

FailureDetectionOutcomeRecovery
Broker downUnixStream ErrSynthesisError::BrokerDownOperator restores
Audit emit failssev-2Stub still returns OkOperator restores memory
Invalid scopeserde Err422Caller fixes

§11 — Implementation notes


End of TASK-SKILL-107.