Open Agent Rules
OAR is an open standard; this page describes Painted Wolf Code’s implementation. The specification, schemas, and conformance corpus live at openagentrules.org. How this host evaluates rules is open-agent-rules.md in the product repository.
Open Agent Rules (OAR) is a portable standard for agent guardrails: a rule is one YAML document — a lifecycle anchor, a selector, a typed condition over observed facts, and a declared effect. Painted Wolf Code accepts oar: '1.0' and fails closed on other versions. Host-specific binding and selector schemas live in the product repository.
A complete rule
Rules ship in a pack’s policy/ directory (unit id policy/<CODE>), one file per rule:
oar: '1.0'
id: TRIAGE_LITE_WRITE_FORBIDDEN # SCREAMING_SNAKE, equals the file stem
kind: policy # schema | policy | invariant | detector
anchor: tool.pre_invoke # a lifecycle anchor id
selector: # clauses AND; each names a declared fact
tool: [write, edit]
requires:
profiles: [session, tool]
when: session_posture == "spec"
effect: block # block | warn | nudge | allow | transform
enforcement: enforce
on_error: fail_closed
copy:
title: Triage Lite is read-only
what: A write tool was called during triage.
cause: The triage-lite workflow reports findings; it does not change files.
fix: Record the finding in your write-up, then leave triage before editing.
instead: Branch on Code TRIAGE_LITE_WRITE_FORBIDDEN and report instead of writing
| Field | Required | Notes |
|---|---|---|
oar | yes | Version gate at load — '1.0' |
id | yes | Stable; doubles as the Code: agents branch on |
kind | yes | One of the four tiers below |
anchor | yes | A lifecycle anchor id from the host’s catalog |
effect | yes | What happens when the rule fires — see Effects |
selector | no | Clauses AND; omitting a clause is permissive |
requires | with facts | Declares the fact profiles the rule reads |
when / flow | no | Absent means the rule fires whenever anchor + selector match |
enforcement | no | Default enforce; monitor evaluates and reports but never acts |
on_error | no | Default fail_closed |
Unknown fields are rejected by name. Human copy lives in copy: (title / what / cause / why / fix / instead).
Four kinds
kind fixes evaluation order—schema → policy → invariant → detector, then id. anchor: decides when the rule runs. The first block short-circuits; warnings and nudges accumulate.
| Kind | What it expresses |
|---|---|
schema | Argument and shape validity |
policy | Context-dependent allow/deny — profile, scope, posture |
invariant | Pre/post conditions over turn and session state |
detector | A condition delegated to a registered external validator |
Where rules fire
anchor: names a lifecycle moment. Guardrails and prompt injects select on the same catalog. Scope a rule with the selector, not by minting new anchors: tool.pre_invoke + selector.tool: [read, grep].
| Trigger class | When |
|---|---|
pre_invoke | Before a tool handler runs |
tool_handler | Inside a native tool’s own checks |
post_tool | After a successful invoke |
post_turn | A coordinator prose turn with no tool call |
finalize | Worker/child completion |
input / output / tool_result | Model-IO content anchors |
The full catalog — every legal anchor: value — is generated from the host:
Show the full lifecycle anchor catalog (62)
tool_rejection
| Anchor | Fires when | Surface |
|---|---|---|
gate.blockedGate blocked | phase_gate_unmet blocks advance; coordinator kick carries failed_leaves. | coordinator inform |
coordinator.citation.groundingCoordinator citation grounding | Closeout JSON rejected for untraced cited_evidence or cited_urls. | coordinator inform |
worker.citation.groundingWorker citation grounding | Worker completion JSON fails evidence grounding. | worker inform |
workflow_event
| Anchor | Fires when | Surface |
|---|---|---|
compose.doneCompose done | Successful compose upsert; coordinator should start the workflow run. | coordinator inform |
phase.advancedPhase advanced | Host auto-advances workflow phase (fallback when no on_enter.reenter_leg). | phase inform |
review_loop.continueReview loop continue | Non-terminal review_loop verdict below iteration_cap — host re-prompt. | phase inform |
review_loop.decideReview loop decide | review_loop reached iteration_cap — coordinator must render terminal verdict. | phase inform |
feedback.receivedFeedback received | Pending user feedback resolves (user submit). | coordinator inform |
phase.exit_requiredWorkflow phase exit required | Turn ended while the active workflow phase requires an exit action. | coordinator inform |
inject.transitionWorkflow transition inject | Transition guidance inject on coordinator turns. | coordinator inform |
manifest_on_reenter
| Anchor | Fires when | Surface |
|---|---|---|
leg.finishedLeg finished | Worker leg reaches terminal state (re-enter / digest). | coordinator inform |
worker.task.finishedWorker task finished | Implement-mode task() worker reaches reactable terminal. | coordinator inform |
phase_on_enter
| Anchor | Fires when | Surface |
|---|---|---|
phase.enteredPhase entered | Cross-phase entry fires on_enter injects (fanout-plan/execute, topology, debate, findings, feedback-pending). | phase inform |
feedback.pendingFeedback pending | Phase on_enter declares request_user_feedback. | coordinator inform |
host_api
| Anchor | Fires when | Surface |
|---|---|---|
worker.task.startedWorker task started | Implement-mode task() spawns a worker child. | worker inform |
worker.leg.startedWorker leg started | Delegation leg spawns a worker child. | worker inform |
coordinator.closeoutCoordinator closeout | Investigate/synthesis final turn forced to the final report (no tools). | coordinator inform |
project.roots.changedProject roots changed | Project crosses from one folder to two or more (POST roots). | coordinator inform |
project.root.detach_cancelledRoot detach cancelled work | DELETE root?force=true stopped workers, overlays, or busy sessions. | coordinator inform |
worker.closeoutWorker closeout | Worker child turn ends with prose-only survey (no tools). | worker inform |
worker.iterations.lowWorker iterations low | Worker child warned once when few tool iterations remain. | worker inform |
worker.cancel.closeoutWorker cancel closeout | Gracefully cancelled worker leg ends with prose-only survey. | worker inform |
worker.summary.trimWorker summary trim | Worker survey exceeds parent character budget. | worker inform |
progress.staleProgress checklist stale after interrupt | User abort left open checklist steps; queue a reconcile nudge. | coordinator inform |
authz.seal_failedAuthorization seal failed | Run-start authorization context could not be recorded; the turn did not start. | coordinator inform |
turn.closeoutTurn closeout | Host limit forces a final prose-only turn on a non-closeout coordinator surface. | coordinator inform |
turn.iterations.lowTurn iterations low | Coordinator warned once when few tool iterations remain this turn. | coordinator inform |
turn.spend.runway_lowTurn spend runway low | Session first crosses the spend-ceiling warning point (one-shot). | coordinator inform |
turn.spend.soft_stopTurn spend soft stop | Running task crossed its spend ceiling; one bounded tool-capable landing round. | coordinator inform |
outbound.secret.withheldOutbound secret withheld | Host withheld an outbound request carrying a detected secret; turn continues. | coordinator inform |
wait_trigger
| Anchor | Fires when | Surface |
|---|---|---|
wait.timer.firedWait timer fired | Coordinator wait() timer expires (until includes timer). | coordinator inform |
scan.finishedScan finished | Security scan reaches terminal while coordinator waits on scan_done. | coordinator inform |
process.finishedProcess finished | Command/verify process reaches terminal while coordinator waits on process_done. | coordinator inform |
worker.budget.lowWorker budget low | Worker tool runway ≤ wake buffer while job still running. | coordinator inform |
loop.wakeLoop wake | Host loop-wake turn; suppress kick when LLM runs ([host:loop-wake] sentinel). Non-content Anchor — no inform Binding. | coordinator inform |
overlay_state
| Anchor | Fires when | Surface |
|---|---|---|
overlay.promote.completeOverlay promote complete | All write overlays integrated (zero pending). | coordinator inform |
observed_history
| Anchor | Fires when | Surface |
|---|---|---|
edit.follow_up.repeatEdit follow-up repeat | Two consecutive user-visible turns each answered with coordinator inline file edits. | coordinator inform |
progress.missingProgress checklist missing | Turn ran progress-gated tools with no checklist authored; queue an authoring nudge. | coordinator inform |
survey.summarizeSurvey stacked without summarize | Stacked read/grep/find since the user-intent boundary with no summarize this round. | coordinator inform |
inject.ledgerLedger receipts inject | BuildLedgerReceipts / RenderLedgerBlock system inject. | coordinator inform |
inject.synthesis_evidenceSynthesis evidence inject | Synthesis-surface evidence digest inject. | coordinator inform |
assembly_inject
| Anchor | Fires when | Surface |
|---|---|---|
board.changedBoard reinjected | Pack board orientation reinjected; omit leg-finished kick on same turn. | coordinator inform |
inject.active_workflowActive workflow inject | Pre-history active-workflow.md / run-context inject (InjectRenderer path). | coordinator inform |
inject.worker_boardWorker board inject | Worker child pack-board block at run start. | worker inform |
inject.worker_legWorker leg inject | L3 worker-leg.md inject for delegation legs. | worker inform |
inject.agents_mdAgents.md inject | Project AGENTS.md / agents-md standard inject prepend. | coordinator inform |
inject.command_jobsActive command jobs inject | Host-authoritative ledger of live command and verify command jobs. | agent inform |
inject.implement_spawnImplement spawn inject | Implement-surface spawn roster inject. | coordinator inform |
inject.blueprintBlueprint inject | Workflow blueprint frontmatter inject. | coordinator inform |
inject.worker_task_assignmentWorker task assignment inject | Worker assignment envelope inject. | worker inform |
inject.worker_task_preambleWorker task preamble inject | Worker task preamble (scope/paths) inject. | worker inform |
inject.scan_guidanceScan guidance ephemeral inject | Ephemeral scan guidance summaries inject. | coordinator inform |
pre_invoke
| Anchor | Fires when | Surface |
|---|---|---|
tool.pre_invokeTool pre-invoke | Before a tool handler runs; scope with selector.tools. | coordinator block transform |
coordinator.pre_invokeCoordinator pre-invoke | Coordinator-scoped pre-invoke. Prefer tool.pre_invoke + selector.profiles:[coordinator] when equivalent. | coordinator block |
session.pre_invokeSession pre-invoke policy | Session/manifest policy checks. | coordinator block |
tool_handler
| Anchor | Fires when | Surface |
|---|---|---|
tool.handlerTool handler | Inside native/structured tool handler (write-scope jail, inline checks). | coordinator block |
post_tool
| Anchor | Fires when | Surface |
|---|---|---|
tool.post_invokeTool post-invoke | After a completed invoke (warn/banner). | coordinator block |
post_turn
| Anchor | Fires when | Surface |
|---|---|---|
coordinator.post_turnCoordinator post-turn | Coordinator no-tool prose turn. Specialized provisional ids (ungrounded/synthesis/investigate/host) map here; distinguish with when:. | coordinator block inform |
finalize
| Anchor | Fires when | Surface |
|---|---|---|
worker.finalizeWorker finalize | Child/worker completion summary evaluation. | worker block inform |
input
| Anchor | Fires when | Surface |
|---|---|---|
content.inputContent input | User + retrieved content before the model sees it (content-safety; core model.input). | coordinator block transform |
output
| Anchor | Fires when | Surface |
|---|---|---|
content.outputContent output | Model response before shown or acted on (content-safety; core model.output). | coordinator block transform |
tool_result
| Anchor | Fires when | Surface |
|---|---|---|
content.tool_resultContent tool result | Tool/RAG content before the model consumes it (content-safety; core model.tool_result). | coordinator block transform |
The Surface column names who the anchor fires for —
coordinator, worker, or the workflow phase — followed
by the effect planes the anchor declares. An anchor on the inform plane is
one a binding can render prompt content at; block is where a rule rejects;
transform is where content may be rewritten in place. Most anchors carry a
single plane — pick the one that matches the effect you are authoring.
Effects
effect | Behavior on this host |
|---|---|
block | Reject the tool call or turn with a structured Code: reject |
nudge | Queue a user-role note into the agent’s next prompt |
warn | Append a banner to the tool output and allow it |
allow | Explicit pass — device/stock policy only |
transform | Rewrite content at a model-IO anchor |
Project-contributed policy/ units may use only block, warn, or nudge. Branch on Code: — never on message text.
Conditions are CEL over facts
when: is a closed, typed CEL environment. Identifiers are registered facts — a typo’d fact name fails at load:
when: 'session_posture == "spec" && lycaon.tool_is_state'
when: mcp_schema_matched && mcp_field_bool("is_ready")
Standard-profile facts are bare names; host-specific facts are namespaced lycaon.*. The host produces facts; the rule composes them; the engine renders effects.
The fact catalogue
This is the full closed set, generated from the same engine table that builds the live CEL environment:
Show the full fact and function catalog
Structural facts
Cheap host, session, and tool state — set on every evaluation.
| Fact | Type | Tier | Observation |
|---|---|---|---|
anchor | string | core | The anchor id of the occurrence being evaluated; every rule at that occurrence sees the same value. |
permission_profile | string | standardtool | The permission profile in force for this call. |
principal | string | standardsession | The identity on whose behalf the call is made. |
principal_roles | list<string> | standardsession | Roles the host attributes to the principal. |
content_length | int | standardcontent-safety | Length of the content at this anchor, in Unicode code points. |
content_roles | list<string> | standardcontent-provenance | Transport-independent role for each content segment, in segment order. |
content_origins | list<string> | standardcontent-provenance | Host-attributed origin for each content segment, in segment order. |
content_authorities | list<string> | standardcontent-provenance | Instruction authority the host attributes to each content segment, in segment order. |
content_trust_tiers | list<string> | standardcontent-provenance | Host trust classification for each content segment, in segment order; trust does not itself grant instruction authority. |
content_sources | list<string> | standardcontent-provenance | Host-defined source id for each content segment, in segment order, or empty when none exists. |
content_segment_count | int | standardcontent-provenance | Number of structured content segments at this occurrence, matching the length of every provenance list. |
content_contains_untrusted | bool | standardcontent-provenance | True when at least one content segment is classified as untrusted. |
tool | string | standardtool | The tool name for the current call. |
tool_args | map<string, dyn> | standardtool | The current call’s arguments, as a map. |
last_assistant | string | hostlycaon.last_assistant | The last assistant message text. |
turn_tools | list<string> | hostlycaon.turn_tools | Tool names invoked so far this turn. |
session_posture | string | standardsession | The active session posture id. |
surface | string | hostlycaon.surface | The active coordinator capability surface. |
profile | string | hostlycaon.profile | The calling agent’s tool profile id. |
phase | string | hostlycaon.phase | The active workflow phase id. |
workers_idle | bool | hostlycaon.workers_idle | True when no delegated workers are running. |
active_worker_count | int | hostlycaon.active_worker_count | Number of delegated workers currently running. |
pending_overlay_promote | bool | hostlycaon.pending_overlay_promote | True when a promote-eligible write overlay awaits landing. |
overlay_state | string | hostlycaon.overlay_state | The current write overlay’s lifecycle state. |
surface_may_finish | bool | hostlycaon.surface_may_finish | True when the active surface allows finishing the run. |
is_host_cycle_turn | bool | hostlycaon.is_host_cycle_turn | True when this turn was initiated by the host loop, not the user. |
batch_phase | string | hostlycaon.batch_phase | The coordinator batch phase for this cycle. |
batch_closed | bool | hostlycaon.batch_closed | True when the current batch is already closed. |
progress_open_items | int | hostlycaon.progress_open_items | Count of open rows on the progress checklist. |
progress_has_open_steps | bool | hostlycaon.progress_has_open_steps | True when the root progress checklist has open - [ ] rows. |
has_completion_report | bool | hostlycaon.has_completion_report | True when the last assistant text parses as a coordinator completion report. |
task_envelope_echo | bool | hostlycaon.task_envelope_echo | True when the last assistant text echoes a worker task envelope. |
closeout_surface | bool | hostlycaon.closeout_surface | True when the active surface delivers typed JSON closeout. |
progress_reconcile_needed | bool | hostlycaon.progress_reconcile_needed | True when a synthesis progress update is not reconcile-only. |
verify_required | bool | hostlycaon.verify_required | True when verify is required before close for this session. |
verifier_pass | bool | hostlycaon.verifier_pass | True when host verify() has already passed. |
premature_verify_close | bool | hostlycaon.premature_verify_close | True when a progress update would close verify rows before host verify(). |
synthesis_wrapup_tool_forbidden | bool | hostlycaon.synthesis_wrapup_tool_forbidden | True when the tool is forbidden on the synthesis wrap-up surface. |
progress_closure_armed | bool | hostlycaon.progress_closure_armed | True when the progress-closure latch is armed for this root. |
progress_gated_tool | bool | hostlycaon.progress_gated_tool | True when the tool is progress-gated. |
progress_missing | bool | hostlycaon.progress_missing | True when no usable progress checklist is present. |
review_loop_active | bool | hostlycaon.review_loop_active | True when the review loop is active for the session. |
overlay_integrate_pending | bool | hostlycaon.overlay_integrate_pending | True when write overlays await promote or reject. |
path_is_worker_branch | bool | hostlycaon.path_is_worker_branch | True when the tool path resolves under a worker sandbox branch. |
has_partial_worker_jobs | bool | hostlycaon.has_partial_worker_jobs | True when history still holds partial worker job envelopes. |
verify_has_command | bool | hostlycaon.verify_has_command | True when the verify() call already carries a command. |
verify_declared | bool | hostlycaon.verify_declared | True when the project declares a verify command. |
progress_closed_beyond_baseline | bool | hostlycaon.progress_closed_beyond_baseline | True when closed progress rows exceed the armed baseline. |
pending_user_input | bool | hostlycaon.pending_user_input | True when a user question is pending an answer. |
stub_valid | bool | hostlycaon.stub_valid | True when the bound plan stub satisfies the required headings. |
tool_allowed_for_profile | bool | hostlycaon.tool_allowed_for_profile | True when the profile’s capability ceiling allows this tool. |
habit_redirect_match | string | hostlycaon.habit_redirect_match | The native-tool redirect id matched for a bash habit, or empty. |
write_roots | list<string> | hostlycaon.write_roots | The active write-scope root paths. |
pattern_parse_ok | bool | hostlycaon.pattern_parse_ok | True when the call’s pattern argument parsed. |
arg_validation_errors | list<string> | standardtool | Argument validation error tokens for the current call. |
action_host_resources | list<string> | hostlycaon.action_host_resources | Host resource ids this action was matched against — machine-observed catalog state, never inferred from command or tool prose. |
action_host_resource_denials | list<string> | hostlycaon.action_host_resource_denials | Host resource ids the action was refused for. |
mode_bits | string | hostlycaon.mode_bits | The mode string observed on a chmod-style call. |
tool_args_fingerprint | string | standardtool | Stable fingerprint of the call’s arguments, for repeat detection. |
posture_unresolved | bool | hostlycaon.posture_unresolved | True when the session posture is not yet resolved. |
high_risk_tool | bool | hostlycaon.high_risk_tool | True when the tool is classed high-risk. |
tool_is_state | bool | hostlycaon.tool_is_state | True when the tool is a workflow state tool. |
tool_is_delegation | bool | hostlycaon.tool_is_delegation | True when the tool is a delegation tool. |
tool_is_task | bool | hostlycaon.tool_is_task | True when the tool is task(). |
tool_is_handoff | bool | hostlycaon.tool_is_handoff | True when the tool is a handoff tool. |
pack_runner_task | bool | hostlycaon.pack_runner_task | True when the task targets a pack-runner agent. |
agent_is_plan_writer | bool | hostlycaon.agent_is_plan_writer | True when the task agent is the plan writer. |
disallowed_agent | bool | hostlycaon.disallowed_agent | True when the requested agent is not allowed on this surface or posture. |
plan_awaiting_approval | bool | hostlycaon.plan_awaiting_approval | True when the plan is waiting on human approval. |
worker_attempted_mutation | bool | hostlycaon.worker_attempted_mutation | True when an implementer child invoked a file-mutation tool. |
batch_ready_ignoring_progress | bool | hostlycaon.batch_ready_ignoring_progress | True when wrap-up gates hold except open progress steps. |
synthesis_delay_count | int | hostlycaon.synthesis_delay_count | Per-cycle count of open-progress closeout delays. |
review_verdict_gate_open | bool | hostlycaon.review_verdict_gate_open | True when the review verdict gate is still open. |
verdict_delay_count | int | hostlycaon.verdict_delay_count | Per-cycle count of review-verdict delays. |
scope_mode | string | hostlycaon.scope_mode | The task() scope mode — read or write. |
scope_paths_empty | bool | hostlycaon.scope_paths_empty | True when a write scope has no paths. |
scope_too_broad | bool | hostlycaon.scope_too_broad | True when a write scope is a sole repo-wide glob under parallel writes. |
profile_mutation_capable | bool | hostlycaon.profile_mutation_capable | True when the task agent’s tool profile can edit files, run commands, or create commits. |
base_overlay_id | string | hostlycaon.base_overlay_id | The stacked base overlay id from task scope, or empty. |
base_overlay_resolves | bool | hostlycaon.base_overlay_resolves | True when base_overlay_id names a live write overlay. |
base_overlay_pending | bool | hostlycaon.base_overlay_pending | True when the base overlay is still pending and stackable. |
active_read_count | int | hostlycaon.active_read_count | Number of read workers currently running. |
active_write_count | int | hostlycaon.active_write_count | Number of write workers currently running. |
max_workers | int | hostlycaon.max_workers | The configured worker cap. |
max_read_workers | int | hostlycaon.max_read_workers | The configured read-worker cap. |
max_write_workers | int | hostlycaon.max_write_workers | The configured write-worker cap. |
citation_unverifiable | bool | hostlycaon.citation_unverifiable | True when a typed citation is unverifiable against the ledger. |
scout_survey_evidence_present | bool | hostlycaon.scout_survey_evidence_present | True when a read scout ran a successful survey tool. |
surface_claim_ungrounded | bool | hostlycaon.surface_claim_ungrounded | True when a UI-complete claim lacks surface snapshot evidence. |
page_measure_ungrounded | bool | hostlycaon.page_measure_ungrounded | True when a layout claim lacks page geometry evidence. |
agent_is_scout | bool | hostlycaon.agent_is_scout | True when the worker agent is a read scout. |
agent_is_implementer | bool | hostlycaon.agent_is_implementer | True when the worker agent is an implementer. |
profile_surveys_project_tree | bool | hostlycaon.profile_surveys_project_tree | True when the task agent’s tool profile can inventory or search the project tree. |
repo_known_empty | bool | hostlycaon.repo_known_empty | True when the host measured zero indexable files in the project. |
last_audit_ungrounded | bool | hostlycaon.last_audit_ungrounded | True when the last assistant citation audit failed to trace. |
grounding_escalated | bool | hostlycaon.grounding_escalated | True when the grounding circuit breaker has escalated. |
command_not_argv | bool | hostlycaon.command_not_argv | True when a command or terminal request does not provide a structured argument vector. |
is_directory | bool | standardfilesystem | The path resolved to a directory. |
not_found | bool | standardfilesystem | The path or artifact is missing. |
path_denied | bool | standardfilesystem | Path policy denied the target. |
bulk_denied | bool | hostlycaon.bulk_denied | A bulk operation was denied. |
binary_denied | bool | hostlycaon.binary_denied | A binary-file operation was denied. |
mode_denied | bool | hostlycaon.mode_denied | The requested mode was denied. |
path_escape | bool | hostlycaon.path_escape | The path escapes the permitted root. |
beyond_eof | bool | hostlycaon.beyond_eof | The requested range lies beyond end of file. |
not_running | bool | hostlycaon.not_running | The referenced process is not running. |
unsupported | bool | hostlycaon.unsupported | The requested operation is unsupported for this target. |
resource_limit | bool | hostlycaon.resource_limit | A resource limit was hit. |
conflict | bool | hostlycaon.conflict | The operation conflicts with current state. |
path_required | bool | hostlycaon.path_required | A required path argument is missing. |
id_required | bool | hostlycaon.id_required | A required id argument is missing. |
policy_denied | bool | standardtool | A profile or scope policy denied the call. |
unknown_target | bool | hostlycaon.unknown_target | The named target does not exist. |
missing | bool | hostlycaon.missing | A required argument or artifact is missing. |
forbidden | bool | hostlycaon.forbidden | The operation is forbidden in this context. |
selector_empty | bool | hostlycaon.selector_empty | The selector matched nothing. |
selector_ambiguous | bool | hostlycaon.selector_ambiguous | The selector matched more than one target. |
reject_observation | string | hostlycaon.reject_observation | Snake_case one-off observation token for unique reject codes. |
pii_entities | list<map<string, dyn>> | standardcontent-safety | PII entity spans from a registered detector — the rule’s when: owns the threshold. Empty when no detector ran. |
secret_matches | list<map<string, dyn>> | standardcontent-safety | Secret and credential spans from a registered detector — the rule’s when: owns the threshold. Empty when no detector ran. |
recent_tool_names | list<string> | hostlycaon.recent_tool_names | Recent tool names in order, for flow: sequence matching. |
mcp_server_id | string | standardmcp | MCP catalog server id for this call; empty when the call is not MCP. |
mcp_tool_name | string | standardmcp | Unqualified MCP tool name for this call. |
mcp_qualified_tool | string | standardmcp | Host tool name mcp_{server}_{tool}; equals tool for MCP calls. |
mcp_server_configured | bool | standardmcp | True when the call’s server id exists in the merged MCP catalog. |
mcp_server_enabled | bool | standardmcp | True when the call’s server is enabled. |
mcp_call_ok | bool | standardmcp | True post-call when the MCP call succeeded; always false pre-call. |
mcp_error_code | string | standardmcp | Machine error code bridged from the MCP error, or empty — never free text. |
mcp_schema_matched | bool | standardmcp | True when at least one pack binding validated the result JSON. |
Derived facts
Computed over history and the evidence ledger by lazy providers — assembled only when a selected rule references them.
| Fact | Type | Tier | Observation |
|---|---|---|---|
unobserved_cited_paths | list<string> | hostlycaon.unobserved_cited_paths | Cited file paths never observed by a tool this session. |
unobserved_cited_urls | list<string> | hostlycaon.unobserved_cited_urls | Cited URLs never fetched this session. |
unobserved_cited_handles | list<string> | hostlycaon.unobserved_cited_handles | Cited evidence handles that don’t trace to the ledger. |
citation_fields_present | bool | hostlycaon.citation_fields_present | True when required typed citation fields are present. |
claims_completion | bool | hostlycaon.claims_completion | True when the last assistant text claims completion. |
has_matching_ledger_job | bool | hostlycaon.has_matching_ledger_job | True when a ledger job matches the claimed work. |
ledger_criteria_met | bool | hostlycaon.ledger_criteria_met | True when the ledger records the completion criteria as met. |
worker_summary_present | bool | hostlycaon.worker_summary_present | True when the worker’s finish summary is present. |
worker_artifact_present | bool | hostlycaon.worker_artifact_present | True when the worker produced a required artifact. |
files_touched | list<string> | hostlycaon.files_touched | Paths mutated during the run or leg. |
summary_length | int | hostlycaon.summary_length | Character length of the worker summary. |
Engine counters
Engine-owned per-session state, exposed read-only; rules mutate them via on_fire, never in CEL.
| Fact | Type | Tier | Observation |
|---|---|---|---|
fire_count | int | core | This rule’s declared counter increments this session, less resets. |
repeat_count | int | hostlycaon.repeat_count | Consecutive identical tool-call repeats. |
fruitless_search_run | int | hostlycaon.fruitless_search_run | Consecutive search calls that returned nothing. |
breaker_count | int | core | Grounding circuit-breaker escalation count. |
same_code_reject_run | int | hostlycaon.same_code_reject_run | Consecutive identical rejects sharing one Code:. |
code_reject_total | int | hostlycaon.code_reject_total | Times one tool was rejected with one Code: this session, across every args shape — unlike the run, it survives a reworded call and an interleaved success. |
Detector facts
Scores and spans from registered detectors — async, cached, probabilistic. The rule's when: owns every threshold.
| Fact | Type | Tier | Observation |
|---|---|---|---|
prompt_injection_score | double | standardcontent-safety | Injection score from a registered detector (0–1) — the rule’s when: owns the threshold. |
jailbreak_score | double | standardcontent-safety | Jailbreak score from a registered detector (0–1) — the rule’s when: owns the threshold. |
Parameterized observation functions
| Function | Signature | Tier | Observation |
|---|---|---|---|
fire_count_of | (string) -> int | core | Number of times the named rule’s fire counter has incremented in this session, less resets. |
breaker_count_of | (string) -> int | core | Number of times the named rule’s circuit-breaker counter has incremented in this session, less resets. |
path_outside_scope | (string) -> bool | standardfilesystem | True when the named tool’s path arguments resolve outside its active scope. |
source_includes | (string) -> bool | hostlycaon.source_includes | True when the request sources include the named provider id. |
tool_arg_string | (string) -> string | standardtool | The named tool argument as a string; empty when absent or not a string. |
tool_arg_int | (string) -> int | standardtool | The named tool argument as an integer; zero when absent or not an integer. |
tool_arg_bool | (string) -> bool | standardtool | The named tool argument as a boolean; false when absent or not a boolean. |
mcp_server_configured_for | (string) -> bool | standardmcp | True when the MCP catalog contains the named server. |
mcp_server_enabled_for | (string) -> bool | standardmcp | True when the named server is configured and enabled. |
mcp_has_field | (string) -> bool | standardmcp | True when a schema binding projected the named key on the last match. |
mcp_field_bool | (string) -> bool | standardmcp | The projected boolean field; false when missing or mistyped. |
mcp_field_string | (string) -> string | standardmcp | The projected string field; empty when missing. |
mcp_field_int | (string) -> int | standardmcp | The projected integer field; zero when missing. |
host_resource_status_for | (string) -> string | hostlycaon.host_resource_status_for | Observed status of the named host resource; empty when the catalog has no such entry. |
host_resource_policy_for | (string) -> string | hostlycaon.host_resource_policy_for | Configured policy for the named host resource; empty when the catalog has no such entry. |
Tier — core and standard facts are part of
the Open Agent Rules vocabulary and
behave the same on any conforming engine. host facts are Painted Wolf’s
own observations; they are published under the namespace shown beside them, and a rule that
uses one is not portable. Reach for them freely — they are what most Painted Wolf rules are
made of.
Stateful rules, on_fire: side-effects, and detector seams are in open-agent-rules.md.
Conformance
The published corpus checks engine conformance. Project fixtures test your own rules with the same evaluator:
{
"name": "triage-lite-write-blocked",
"rule": { "...": "an OAR document validating against oar.schema.json" },
"input": { "anchor": "tool.pre_invoke",
"facts": { "tool": "write", "session_posture": "spec" } },
"expected": { "decision": "block", "code": "TRIAGE_LITE_WRITE_FORBIDDEN" }
}
pw rules test # every rule this project contributes
pw rules test policy/MY_RULE.yaml # one rule and its fixtures
pw rules test --json
Green means the document validates, its when: compiles against the fact catalogue, and every fixture’s expected decision is the decision the engine reaches.