Semantic Synthesis Engine
Pricing
from $0.15 / actor start
Semantic Synthesis Engine
RAG Pipeline, Agentic Infrastructure, Idempotent Ingestion — deterministic-first LLM synthesis over Delta-Sync Sentinel manifests, gated by a separated Critic/canary assertion step.
Pricing
from $0.15 / actor start
Rating
0.0
(0)
Developer
Nathan Carter
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
5 days ago
Last modified
Categories
Share
Actor 2 of 3. Takes a committed manifest from ../delta-sync-sentinel and produces a synthesis result gated by a deterministic Critic — a "canary" step that decides ok vs critic_rejected before an agent ever sees the payload.
Status: implemented (Task 2).
synthesize()calls Claude (temperature 0) to produce{summary, keyFacts, citedChunkIds}.runCritic()runs four real deterministic checks.main.tschecks the KV idempotency cache before calling either. See "Task 2 implementation notes" below for where this deviates from the original brief, and why.
Task 2 implementation notes
Three places where Task 2's instructions conflicted with what Task 1 already shipped. Resolved, not silently picked:
- Status vocabulary. The brief said status is
'completed'or'needs_human_review'. The already-published contract (types.ts, both output schemas) says'ok'/'critic_rejected'/'error'. Kept the original enum values —completedandneeds_human_revieware the human-readable meaning ofokandcritic_rejected, not a field rename, so nothing that already reads this contract breaks. determinism_self_testredefined. Originally specified (Task 1) as: re-runsynthesize()and hash-compare, fail on any diff. Task 2 makessynthesize()a real LLM call — temperature 0 reduces but does not guarantee bit-exact output, so a strict re-run-and-diff would reject a large share of good runs and doubles LLM cost per verification. It now checks internal self-consistency of the single payload instead: no duplicatecitedChunkIds, and zero citations despite available source chunks is treated as a failure (a vacuous-result guard), not a valid "nothing to say."- Error shape. Task 1's own infra rule banned prose errors. Task 2 rule 4 asks for
{ error: "CODE", message: "..." }.errorCodestays the field to branch on (closed enum, matches the published schema);messagewas added as a supplementary string alongside it, not a replacement for the code.
Verification gap, stated plainly: this environment has no ANTHROPIC_API_KEY — only Claude Code's own OAuth session, not a usable API key for a direct SDK call. synthesize() is implemented against the real Anthropic SDK and typechecks, but the live LLM call itself has not been executed end-to-end. What has been verified for real:
npm run harness— 8/8 passing, covering all four critic checks against fixture data (good payload, hallucinated citation, low coverage, duplicate citations, oversized output, vacuous zero-citation result).- The cache-key fix (
analysis-<synthesisId>, notanalysis-<manifestId>as literally written in the brief) — confirmed the same manifestId produces different ids per mode, and the same id across repeats of the same mode, so anarrative_summaryrequest can no longer incorrectly hit astructured_extractcache entry. npx tsc --noEmit— clean across the full implementation.
Infrastructure-grade guarantees
Idempotency. synthesisId = sha256(manifestId + synthesisMode + engineVersion). Same three inputs, same id — call this actor twice on the same manifest and you get the same identity back, so an agent can treat repeat calls as free.
Versioning. engineVersion is on every result, not just in a changelog. A cached synthesisId is only trustworthy alongside the engineVersion it was produced under — if the engine bumps, the id space is allowed to change, and callers are expected to check this rather than assume forever-stability.
Canary verification. Synthesis and verification are two separate modules (synthesis.ts, critic.ts) called from two separate steps in main.ts, on purpose — the Critic never trusts synthesis's own opinion of itself. Its checks are deterministic by design (set/schema operations, not model calls), so a narrative_summary synthesis can be non-deterministic while its verification stays reproducible. status: "critic_rejected" is a first-class outcome, not an error — the pipeline is expected to reject some fraction of runs, and that's the point of having it.
Structured errors only. Every failure path returns { status: "error", errorCode: <enum>, message?: <string> }. errorCode — drawn from a closed enum (MANIFEST_NOT_FOUND, SENTINEL_UNREACHABLE, COST_CEILING_EXCEEDED, VALIDATION_ERROR, SYNTHESIS_TIMEOUT) — is the field to branch on, matching the Sentinel's own typed-error convention (ValidationError, CostCeilingError, etc. in errors.ts) one layer up the stack. message is a supplementary human-readable detail string; treat it as a log line, not a contract.
Integration: how an agent triggers this from a Sentinel manifest
The Sentinel emits committed manifests with a manifestId (see its README, "Lifecycle example"). This actor's whole input surface exists to consume exactly that value:
{"sentinelDatasetId": "<the Sentinel run's defaultDatasetId>","manifestId": "9697071a4e2c...","synthesisMode": "structured_extract","maxCostUsd": "1.00"}
Recommended agent loop:
- Poll the Sentinel's health endpoint (or watch its dataset) for
last_cursorto advance. - Read the new manifest's
manifestIdand the Sentinel run'sdefaultDatasetId. - Call this actor with both, plus a cost ceiling.
- Branch only on
status:ok→ useresult;critic_rejected→ inspectcriticVerdict.checksto see which assertion failed, do not use a partialresult(there isn't one);error→ branch onerrorCode, never on message text.
Agent-native schemas
.actor/input_schema.json— every field'sdescriptionis written to explain intent, not just type, so an agent building the call payload from the schema alone (function-calling style) has enough context to fill it in correctly.output_schema.json(project root) — the full function-calling return contract. Kept separate from.actor/output_schema.json, which is Apify's own link-template convention and isn't meant to carry field-level documentation..actor/dataset_schema.json— same field descriptions as the root schema, in the shape Apify's Console actually renders as a table.
Module boundaries
types.ts— shared contract. No logic.synthesis.ts— candidate generation only. Must never decide acceptability.critic.ts— deterministic acceptance checks only. Must never generate content.main.ts— orchestration: fetch Sentinel chunks → synthesize → critic → structured outcome. Mirrors the Sentinel'sActor.init()/log/KV-first conventions.
Local development
npm installnpm run typechecknpm run harness # critic.ts invariants against fixtures — no API key neededANTHROPIC_API_KEY=sk-... npm run start:dev # real end-to-end run, requires a real key