Observations and derivations API
Kheish exposes a dedicated observation plane for durable captured media and a derivation plane for deterministic secondary artifacts such as canonical text or visual previews.Endpoint inventory
Observation source management:GET /v1/observation-sourcesPOST /v1/observation-sourcesGET /v1/observation-sources/{source_id}POST /v1/observation-sources/{source_id}/rotate-tokenPOST /v1/observation-sources/{source_id}/revoke-tokenGET /v1/observation-audit
POST /v1/capture-agent-provisionsGET /v1/capture-agentsGET /v1/capture-agents/{machine_id}POST /v1/capture-agents/{machine_id}/heartbeatPOST /v1/capture-agents/{machine_id}/revokeGET /v1/capture-alerts
POST /v1/observation-sources/{source_id}/observationsGET /v1/observationsGET /v1/observations/{observation_id}
POST /v1/observation-materializations
GET /v1/derivationsPOST /v1/derivationsGET /v1/derivations/{derivation_id}
Observation sources
POST /v1/observation-sources accepts:
source_iddisplay_namekindupload_tokensensitivityretention_secondsmax_active_observationsmax_active_bytesingest_rate_limit_window_msingest_rate_limit_burstpurge_raw_on_retentionallow_materializationallow_output_delivery
sensitivity:sensitiveretention_seconds: 7 daysmax_active_observations: 512max_active_bytes: 512 MiBingest_rate_limit_window_ms:60000ingest_rate_limit_burst:120purge_raw_on_retention:falseallow_materialization:trueallow_output_delivery:false
source_id is optional:
- when you provide it, the daemon uses it as the stable source identifier
- when you omit it, the daemon generates one server-side
- explicit source identifiers are path-safe: ASCII letters, digits, dots, underscores, and dashes only, up to 128 bytes
- the returned
ObservationSourceViewdoes not expose the upload token - callers must keep the token client-side after source creation or rotation
POST /v1/observation-sources/{source_id}/rotate-tokenacceptsupload_tokenand optionalgrace_period_ms; while the grace window is active, the previous token can still authenticate uploadsPOST /v1/observation-sources/{source_id}/revoke-tokenrevokes the current source upload token and any grace-window tokens without disabling historical observations; source-token revoke audit reasons are sanitized and secret-looking free text is recorded asoperator_reason_redacted- recreating an existing source with a new
upload_tokenincrementsupload_token_versionimmediately without a grace window; userotate-tokenwhen clients need a bounded handoff window - source creates, explicit token rotations/revocations, successful uploads, rejected uploads, rate limits, and retention asset purges are appended to the daemon observation audit log without bearer tokens, raw bytes, base64, or literal idempotency keys
GET /v1/observation-audit supports:
source_id: exact source filterevent: exact audit event filterlimit: newest records returned, default100, capped by the daemon
screen_snapshotimage/pngimage/jpeg
webcam_snapshotimage/pngimage/jpeg
microphone_segmentaudio/wavaudio/webm
GET /v1/observation-sources supports:
query: substring filter over source id or display name
Capture-agent provisioning
POST /v1/capture-agent-provisions creates or rotates a batch of host-local capture agents.
The response is the only API response that contains raw upload tokens; durable agent, source, audit, and list views store or expose only token metadata and digests.
Request fields:
batch_iddaemon_base_urlos_profile:macos,linux, orwindows; defaults tomacosagents: machine targets withmachine_id, optional per-agentos_profile, camera selection, and microphone selectionsources: booleans forscreen,camera,system_audio, andmicrophoneinterval_ms,max_runs,duration_msretention_seconds,max_active_observations,max_active_bytestoken_ttl_msheartbeat_interval_msheartbeat_grace_ms
statusos_profilesource_ids- sanitized source lease views with
lease_id,issued_at_ms,expires_at_ms, token version, and revocation/supersession state heartbeat_token_versionlast_heartbeat_at_msheartbeat_deadline_msheartbeat_state- last heartbeat diagnostics:
last_heartbeat_agent_version,last_heartbeat_observed_source_ids,last_heartbeat_unobserved_source_ids heartbeat_missing_since_mswhen a missing-heartbeat alert has been materialized
machine_id and source ids with a new batch_id rotates source upload tokens and the per-agent heartbeat token. Old leases are marked revoked and superseded, old upload tokens fail immediately, the old heartbeat token fails immediately, and the new config contains the replacement tokens.
Reusing an already-applied batch_id returns 409 instead of rotating again. Raw tokens are intentionally not replayable from daemon state.
Capture-owned observation sources are managed by capture provisioning and capture-agent revocation; the generic source rotate-token and revoke-token endpoints reject those sources with 409 so source leases and agent records cannot drift.
Capture-owned observation sources also fail closed if the capture-agent record is absent or quarantined on restart; the source upload endpoint will not fall back to the generic source token path.
The macOS profile supports screen, webcam, system-audio, and microphone provisioning. The portable Linux and Windows profiles currently support microphone provisioning and reject unsupported screen/camera/system-audio requests with 400.
Capture-agent heartbeat and revoke
POST /v1/capture-agents/{machine_id}/heartbeat is intentionally outside normal admin auth, like observation ingest.
It requires Authorization: Bearer <active heartbeat_token> from the provisioned agent config. Source upload tokens cannot authenticate heartbeats. The endpoint accepts:
observed_source_ids are owned by that agent, stores the sanitized agent_version, records any owned sources not observed in that heartbeat, updates last_heartbeat_at_ms, extends heartbeat_deadline_ms, clears heartbeat_missing_since_ms, and changes heartbeat_state to healthy.
If an active agent passes its deadline, GET /v1/capture-agents and GET /v1/capture-alerts materialize one missing_heartbeat alert and audit the transition once. A later valid heartbeat records a recovery transition.
POST /v1/capture-agents/{machine_id}/revoke accepts:
revoked, revokes all active leases, disables every owned observation source, and blocks future uploads and heartbeats using those tokens. Secret-looking revoke reasons are sanitized before they are stored or appended to the observation audit log.
Upload an observation
The upload route is intentionally outside normal admin auth:POST /v1/observation-sources/{source_id}/observations
- send
Authorization: Bearer <upload_token> - the token is validated against the source-scoped upload secret
- successful non-replay uploads are rate-limited per source using
ingest_rate_limit_window_msandingest_rate_limit_burst - when the rate limit is exceeded, the route returns
429 application/problem+jsonwithcode: "rate_limited"and aretry_after_msvalue indetail
upload.file_nameupload.media_typeupload.content_base64idempotency_keycaptured_at_msstream_idseq_nocanonical_textmetadata
stream_id, when provided, is source-scoped and path/query-safe: ASCII letters, digits, dots, underscores, dashes, and colons only, up to 128 bytes.
Idempotency behavior:
- the stable key is
(source_id, idempotency_key) - if the same request fingerprint is replayed, the daemon returns the existing observation
- if the same key is reused with different payload content, the request is rejected
- retention always marks expired or over-budget records as
purged - time-based retention is measured from the daemon receive timestamp, not from uploader-controlled
captured_at_ms - when the source sets
purge_raw_on_retention: true, the daemon also removes raw/canonical daemon-owned assets that are no longer referenced by active observations - a purged observation remains listable with
include_purged=true, but it cannot be materialized or used as a derivation subject
Observation listing
GET /v1/observations supports:
source_idstream_idafter_msbefore_msinclude_purged
stream_idrequiressource_id- combining
source_idandstream_idnarrows the result set to one source stream
ObservationView includes:
observation_idsource_idkindsensitivityretention_stateasset_idcanonical_text_asset_idmedia_typesha256byte_lengthcaptured_at_msreceived_at_msstream_idseq_noidempotency_keyrequest_fingerprintmetadata
Materialize observations into a run
POST /v1/observation-materializations submits a normal daemon run after augmenting an input request with one observation selection.
Request fields:
target_session_idselectionrequestinclude_raw_assetsraw_asset_policyfail_when_empty
requestis a fullSubmitInputRequest- when that nested request uses only
input_items,contentcan be omitted
include_raw_assetsdefaults totruefail_when_emptydefaults totruelatest_from_sourceandlatest_from_streamdefaultmax_observationsto3observation_idsmust contain at least one identifiermax_observationsmust be greater than zero
Selection variants
By ids:include_raw_assetsis a legacy boolean fallbackraw_asset_policyis the preferred explicit control:autoneveralways
- materialized observation screenshots, transcripts, OCR, and metadata are framed as untrusted observed data
- canonical text is wrapped with explicit begin/end markers instructing the model to treat it as evidence, not as commands or policy
Derivations
Derivations are deterministic daemon-owned transforms over assets, observations, or persisted session input.GET /v1/derivations supports:
query: substring match over derivation id, profile, subject, or result asset idqueryalso matches source fingerprints, status, and persisted error text
Create a derivation
POST /v1/derivations accepts:
profilesubject- optional
transcriptioncontrols for audio-backedcanonical_textderivations:prompt: trimmed provider prompt, capped at 2,000 characterslanguage: trimmed short ASCII language hinttimestamp_granularities: uniquewordand/orsegment; currently requires the OpenAIwhisper-1transcription backend and persists a timestamp JSON asset
force_refresh=true: recompute even when a terminal cache entry already existsretry_failed=true: recompute only when the current cache entry is failed
canonical_textvisual_preview
status: "completed"profile_version, the deterministic implementation version included in the cache keysource_fingerprint, the source hash/fingerprint used for invalidationresult_asset_idreused_subject_assetbackend, when the derivation used an external backend such as transcription; current fields arekind,route_id,provider,model,pipeline_version,stitching_strategy,part_count, and optionaltimestamp_asset_idcache_status, only onPOST /v1/derivationsresponses, asmisswhen the request created a new durable record orhitwhen it reused an existing record
status: "failed"profile_versionsource_fingerprinterror
GET /v1/derivations, GET /v1/derivations/{derivation_id},
and repeated POST /v1/derivations calls for the same versioned cache key. Validation errors
such as unknown subjects, unsupported visual previews, inactive observations, or blocked
observation sources are rejected without creating a failed derivation record. Retryable provider
failures and audio preflight validation failures are also left unpersisted so a later request can
retry instead of replaying a cached terminal failure.
Use POST /v1/derivations?retry_failed=true to retry a persisted failed cache entry. Use
force_refresh=true when a completed record should be recomputed; if the forced recompute
completes, it becomes the preferred cache entry for later normal calls.
On daemon startup, completed records whose result asset is no longer available are repaired into
durable failed records instead of continuing to advertise a missing artifact.
The cache key includes profile, profile_version, the stable subject key, and the resolved
source fingerprint. A profile implementation version bump therefore recomputes instead of
silently reusing older derived assets. Asset source fingerprints also include attached canonical
text when text_uri exists, so an older placeholder canonical-text derivation is not reused after
the asset gains a real transcript.
When explicit transcription prompt, language, or timestamp_granularities options are supplied, the source fingerprint
also includes a non-secret digest of the normalized options plus the planned transcription route,
provider, and model. Option-specific transcription derivations do not replace the asset or
observation’s default canonical text pointer; repeated calls with the same normalized options
return a cache_status: "hit", while changing the options creates a separate cache key.
Concurrent POST /v1/derivations calls for the same cache key are single-flighted and return the
same durable record. Calls for unrelated cache keys are not serialized behind the same lock.
Completed derivations also attach their derivation_id to the result asset’s derivation_ids
provenance list; daemon startup backfills this list for older completed records.
Audio notes:
canonical_texton microphone observations reuses uploader-supplied canonical text when it already exists- otherwise, when the daemon was started with a transcription backend,
canonical_textperforms daemon-owned speech-to-text and stores the result as one daemon-ownedtext/plainasset - explicit
transcription.prompt,transcription.language, and supported timestamp granularities are forwarded to the transcription backend for manual derivation requests - session input and observation materialization paths that trigger speech-to-text also create the same durable
canonical_textderivation records exposed by this API - built-in transcription backends currently include OpenAI and OpenRouter
- canonical-text derivation depends on the configured transcription backend, not only on the selected run route
- current transcription provenance uses
pipeline_version: 1,stitching_strategy: "single_part", andpart_count: 1; these fields are included in derivation backend provenance and timestamp JSON assets so future chunking or stitching changes can be audited and cache-invalidation behavior can stay explicit - observation-source uploads remain limited to
audio/wavandaudio/webm, but imported daemon assets and normal session attachments can also derive text from supported formats such asaudio/mpeg,audio/mpga,audio/mp4, andaudio/m4a - before uploading to a transcription backend, the daemon revalidates supported audio containers and rejects malformed WAV/WebM/MP3 payloads, WebM without a supported audio track or media block, MP4/M4A containers without an audio handler, unsupported runtime media types, and transcription payloads above 25 MiB
- asset:
- observation:
- session input:
DerivationView contains:
derivation_idprofilesubjectresult_asset_idreused_subject_assetcreated_at_ms
