AnyBio

Policy reference

The rule schema, the condition grammar, every policy action, and the attention formula, bands and defaults exactly as configured in code.

A rule is a policy row. This page is the schema of that row, the grammar of its condition, the behaviour of each action, and the attention arithmetic those actions feed. Numbers here are the defaults in code; a program can override the attention ones in its spec.attention_config.

Routes: GET /api/v1/policies (filters project_id, episode_profile_id, status, trigger_type), POST /api/v1/policies, GET, PUT and DELETE /api/v1/policies/{policy_id}, POST /api/v1/policies/{policy_id}/test, GET /api/v1/policies/{policy_id}/executions. Reads need the Read scope; writes need Write.

The rule schema

FieldTypeMeaning
slug, name, description, versionstringIdentity. slug is unique within its scope.
scopeglobal, project, episode_profileWhich subjects the rule sees. Inferred from episode_profile_id or project_id when omitted. A global rule sees every project in its environment.
project_id, episode_profile_idint, uuidThe scope anchors. A program-scoped rule only evaluates for episodes of that program.
statusdraft, active, paused, deprecatedOnly active rules run. POST defaults to draft.
trigger_typesee belowWhen the rule is evaluated.
trigger_configobjectPer-trigger filter; shape depends on trigger_type.
condition_expressionstringMust evaluate true for the actions to run. Validated on write.
condition_paramsobject of numberNamed thresholds available to the expression as {thresholds.name} or $name.
actionsarrayExecuted in order when the condition matches. Each has a type.
prioritysmall intEvaluation order within a trigger (materialized rules use 50).
cooldown_secondsintMinimum seconds between executions of this rule on the same episode. 0 disables.
max_executions_per_episodeint or nullHard cap per episode.

Rules are environment-bound: a rule authored in the sandbox never evaluates a production observation.

Triggers

trigger_typeFires ontrigger_config
observation_createA new observation linked to an active episodebiosignal_ids: [int], loinc_codes: [string] (empty means any), latency_classes: [string] (realtime, near_realtime, delayed, historical; empty means any), max_data_age_minutes: int or null, context_only: bool
event_createA new timeline eventevent_types: [string]
episode_start, episode_updateEpisode lifecyclenone
scheduleA cron grid, evaluated by the policy workercron: string, timezone: string (default UTC)
inactivityNo data for a period, evaluated by the policy workerthreshold_hours: int
manualNever automaticallynone

An observation only reaches observation_create rules after it is linked to an active episode, and it is linked only when the episode's program lists that signal in required_signals. The public POST /api/v1/observations write links to the episode_ids you pass explicitly and does not evaluate rules; device paths (the cellular webhook, wearable syncs, the DSP pipeline) link and evaluate.

The condition grammar

The expression language is deliberately small:

  • Comparisons: >=, <=, >, <, ==, !=
  • Logic: AND, OR, NOT, parentheses
  • Arithmetic: +, -, *, /
  • Null checks: state.start_ts != null
  • Variables: signal names (hr, hrv_rmssd, spo2, systolic), state.<key>, baseline.<key>
  • Thresholds: {thresholds.systolic_high} or $systolic_high, substituted from condition_params before parsing

On an observation_create trigger the reading is bound as follows: LOINC 8867-4 becomes hr, 80404-7 becomes hrv_rmssd, 59408-5 becomes spo2, any other coded scalar becomes observation_value, and each numeric component of a panel observation is bound under its own name (a blood-pressure panel exposes systolic and diastolic).

POST /api/v1/policies/{policy_id}/test evaluates the condition against signals, state and baselines you supply, with the rule's own condition_params as thresholds, and returns condition_matched without executing anything.

Actions

Each action is an object with a type and the fields below. Attention actions require an episode in context; without one they record success: false with "error": "No episode context" and the rule moves on.

typeFieldsWhat it does
contribute_attentionpoints (1-100), signals: [string], reason, half_life_hours (default 2.0)Appends a contribution to the episode's attention ledger, sourced to this rule's slug, then recomputes attention. This is the normal way a rule raises attention.
set_attention_floorvalue (0-100), reasonSets the episode's floor. Attention cannot fall below the floor until a person lowers it by acknowledgment.
clear_contributionsource_name, reasonClears every active contribution on the episode whose source name matches (for example the slug of the rule that raised it), then recomputes. Use it for the end condition of the thing that raised attention.
set_attentionvalue, reasonLegacy direct set. Still accepted; prefer contribute_attention.
update_episode_statusstatus, reasonChanges the episode status.
create_eventevent_type, payloadEmits a timeline event sourced to the rule, which can in turn fire event_create rules and export triggers.
enqueue_agentagent_type (an agent slug or type), priorityRuns one of the program's agents against the episode.
send_notificationchannel, template, paramsRenders the template and sends it through the communication gate. A held or withheld outcome is recorded and nothing is sent.
create_observationloinc_code or biosignal_id, value_expression, interpretationWrites a derived observation whose value is the expression's result.
start_symptom_sessionprotocol_refOpens an SMS symptom interview against the program's symptom_protocol, whose id must equal protocol_ref. Validated before opening; a missing or malformed protocol is a recorded failed action, never a session.

Every evaluation that passes the trigger filter is recorded in the rule's execution history, matched or not: trigger_type, trigger_ref, condition_result, actions_executed, actions_failed, evaluation_duration_ms, executed_at. A rule in cooldown, or a reading outside the rule's trigger filter, is skipped before evaluation and leaves no execution row.

Rules the program materializes

Two kinds of rule are derived from a program's spec on every save, so the authored document and the running rule cannot drift:

  • Each agent with an on_schedule trigger gets a schedule rule whose action is enqueue_agent for that agent.
  • A symptom_protocol.trigger gets an observation_create rule scoped to the program, filtered to the trigger's biosignal_slugs, with the trigger's expression, thresholds as condition_params, cooldown_seconds, max_sessions_per_episode as the execution cap, max_reading_age_minutes as the data-age guard, and a single start_symptom_session action naming the protocol's id.

Materialized rules appear in GET /api/v1/policies like any other and are re-synced when the program is saved.

Attention

Attention is computed, never stored as an opinion. The calculator reads the episode's active contributions, decays each one, sums the ones still above a minimum, multiplies for converging signals, caps at 100, and applies the floor:

current(c)  = c.points × 0.5 ^ (hours_since(c.created_at) / c.half_life_hours)
base_sum    = Σ current(c)  for active c with current(c) ≥ min_contribution_threshold
attention   = max(floor, min(100, base_sum × correlation_multiplier))

correlation_multiplier is chosen by the largest signals list on any single active contribution: one signal 1.0, two signals 1.25, three or more 1.5.

Defaults, and where a program overrides them

All of these live in spec.attention_config; a program without the block uses the defaults.

SettingDefaultPath
Default half-life2.0 hoursdecay.default_half_life_hours
Minimum active contribution1.0decay.min_contribution_threshold
Multipliers1.0, 1.25, 1.5correlation_multipliers.single_signal, .two_signals, .three_plus_signals
Routine0 to 20thresholds.routine_max
Elevated21 to 40thresholds.elevated_max
Concerning41 to 60thresholds.concerning_max
Urgent61 to 80thresholds.urgent_max
Critical81 to 100above urgent_max
Acknowledgment required above60floor.require_acknowledgment_above
Auto-clear floor below0 (disabled)floor.auto_clear_below
Classification labels to pointsemptyclassification_attention_map (image and model classifications)

The floor and acknowledgment

A floor is set by a rule (set_attention_floor) and is the minimum attention until a person acknowledges. POST /api/v1/episodes/{id}/attention/acknowledge takes new_floor (must be less than or equal to the current floor), reason, and optional action_taken and notes; it writes an acknowledgment record with the previous floor and the attention at that moment, then lowers the floor and recomputes. A floor cannot be raised through acknowledgment.

Who else writes the ledger

  • The symptom agent adds a contribution when a closed interview surfaces something a person should see; reviewing that agent run clears it.
  • A care hand-off routed by a protocol rule adds a contribution sourced care_handoff; resolving the hand-off clears it.
  • Every recompute that changes the level also emits an attention_level observation on the episode, so attention over time is queryable like any other signal.

GET /api/v1/episodes/{id}/attention returns level, level_label, floor, base_sum, correlation_multiplier, active_contributions, top_reason, the per-contribution breakdown, and config_source (profile:<slug> or system_default). GET /api/v1/episodes?attention_gte=61 lists the episodes at or above a level.

On this page