intentframe.org
Specification

Agent Intent Frame 0.1

This document gives the frame types, the field definitions, and the conformance requirements. This document does not specify a transport. You can send a frame with HTTP, with a message queue, or with a function call.

This document uses ASD-STE100 Simplified Technical English. For the reasons for these requirements, refer to Why. That page uses usual English.

Version0.1
StatusDraft
EncodingJSON
LicenseCC BY 4.0

1Conformance

Bold words have the meanings from RFC 2119. Must shows a requirement. Should shows a recommendation. May shows an option. Text that is not bold gives help only. It is not a requirement.

A frame is a JSON object. A sender makes frames. A receiver accepts frames for a capability that it supplies. A runtime is the code between the sender and the receiver. The runtime applies the constraints, calls the model, and validates the result.

The sender and the receiver may be the same process. This does not change the requirements.

Central requirement

A runtime must not use a model to apply a field in constraints. The runtime must apply each constraint with its own code. The model does not have to agree. If a runtime only puts a constraint into the prompt, that runtime does not conform. Most of the other rules come from this one rule.

2Frame types

There are three frame types. One exchange has one request frame. The receiver then sends one result frame or one refusal frame. It does not send more than one.

TypeDirectionMeaning
requestsender → receiverA unit of work with its terms and its definition of done.
resultreceiver → senderWork produced and validated against the frame's criteria.
refusalreceiver → senderThe work was not produced. Carries the layer at which it stopped and a typed violation list.
flowchart LR
    RQ["request<br/>sender → receiver"] --> RT{"Admissible?"}
    RT -- yes --> RS["result<br/>receiver → sender"]
    RT -- no --> RF["refusal<br/>receiver → sender"]
    class RQ context
    class RT check
    class RF refusal
    class RS ok
§2 · Every exchangeExactly one reply for each request. A result always means every acceptance check passed; anything else is a refusal.

A receiver must not send a result frame if a check did not pass. If the receiver cannot satisfy the criteria in the permitted number of attempts, it must send a refusal frame. So a result frame always shows that all the criteria passed.

3Request frame

The table gives the top-level fields of a request frame. The consumer column shows the layer that reads each field. This column is a requirement in one direction only. The runtime must not put a field into the prompt if the column does not show prompt.

FieldConsumerDefinition
versionrequiredruntimeFrame schema version, e.g. "0.1".
intent_typerequireddispatchOne of the enum in §4. Selects runtime configuration.
capabilityrequiredorchestratorDotted capability name, e.g. research.summarize. Addresses a capability, never an agent instance.
goalrequiredpromptObject with objective (required), business_reason, priority.
contextrequiredmixedConversation identity, background, and artifact references. See below.
inputsoptionalpromptFree-form object of task parameters. The capability manifest declares which keys it requires.
constraintsoptionalpolicyEnforceable limits. See §5.
reasoningoptionalpromptanalysis_framework (ordered steps), confidence_threshold, require_evidence.
expected_outputrequiredmixedtype and sections reach the prompt; acceptance_criteria goes to the validator. See §6.
qualityoptionalorchestratorreview_required, reviewers — capability names to route the result through.
failure_policyoptionalruntimeretry_attempts, on_failure — actions taken when criteria cannot be met.
handoffoptionalorchestratorpreferred_next_capability, possible_followups.
metadatarequiredtelemetrytrace_id (required), schema_id, tags.

3.1 Context and artifacts

The context.artifacts field is an array of references. It does not contain the documents. Each entry has a type and an id. The runtime gets the document from an artifact store before it makes the prompt.

A sender should not put a document into a frame. The runtime writes each frame to a log. It also keeps each frame for a replay. So the log and the replay contain the document too. A reference keeps the frame small. It also keeps private data out of the log.

4Intent types

The intent_type field is a closed enum. A runtime must get its settings from this field only. It must not read the prompt to find the type of the request. If you add a value, you must put your own prefix in front of it. An example is x-acme.reconcile. A prefix prevents a clash with a future standard value.

ValueMeaningTypical runtime posture
requestProduce work.Capable model, sampling temperature, read tools permitted.
verifyCheck work against a stated contract.Independent model, temperature pinned to 0, no tools, strict validation.
critiqueImprove work without replacing it.Independent model, read-only tools.
informDeliver information; no result expected.May carry side-effecting tools if the capability is privileged.
flowchart TD
    F{"intent_type"} -- request --> A["Produce work<br/>capable model · sampling temperature<br/>read tools permitted"]
    F -- verify --> B["Check work against a contract<br/>independent model · temperature 0<br/>no tools · strict validation"]
    F -- critique --> C["Improve work without replacing it<br/>independent model · read-only tools"]
    F -- inform --> E["Deliver information, no result expected<br/>may carry side-effecting tools<br/>if the capability is privileged"]
    class F route
    class A model
    class B,C check
    class E orchestrate
§4 · Dispatch from one fieldA runtime must get this configuration from intent_type only. It does not read the prompt.

A receiver must refuse a frame if it does not accept the intent_type of that frame. It gives a refusal code of its own.

5Constraints

Each field in constraints is a maximum value that the sender asks for. The sender does not control the value. A runtime must decrease each value to its own policy limit. If a frame asks for more than the policy limit, the runtime must refuse the frame.

Do not decrease the value without a message. The sender must learn that its terms are not acceptable.

FieldTypeEnforced by
deadline_secondsintegerScheduler; frame is abandoned when exceeded.
cost.maximum_usdnumberRuntime accounting, checked cumulatively across retries.
llm.maximum_tokensintegerPassed as an API parameter. Must not appear as prompt text.
llm.temperaturenumberUpper bound; the dispatch profile for the intent type wins if lower.
tooling.allowedstring[]Tool broker. Requesting a tool does not grant it.
tooling.disallowedstring[]Tool broker. Overlap with allowed is a contradiction and must be refused.
security.classificationenumPolicy engine. One of Public, Internal, Confidential, Restricted.
security.allow_piibooleanOutput scanner, not the model.
security.allow_external_uploadbooleanPolicy engine, checked against classification.

6Acceptance criteria

Acceptance criteria must be checks that a computer can do. They must not be sentences. A computer cannot check "contains a clear recommendation". A computer can check must_include_sections(["Recommended Action"]). A criterion is an object.

A criterion has an id, a check that gives the kind, and an args object. It can also have an advertise_to_model flag. The default value of the flag is true.

{
  "id": "c3",
  "check": "must_exclude_patterns",
  "args": { "patterns": ["email", "phone"], "label": "personal contact details" },
  "advertise_to_model": false
}

A runtime must supply these kinds of check:

checkargsPasses when
max_tokenslimitOutput length is at or below the limit.
must_include_sectionssectionsEvery named section is present.
must_include_phrasesphrasesEvery phrase appears.
must_exclude_patternspatterns, labelNo named pattern class matches.
json_keyskeysOutput parses as a JSON object containing every key.
min_confidencethresholdThe result's self-assessed confidence meets the threshold.

If advertise_to_model is false, the prompt builder does not put the criterion into the prompt. The validator applies the criterion to the result. There are two conditions for this flag. A sender should use the flag in both conditions:

  • The model cannot apply the check to itself. Usually the source document contains the data that the check forbids. So the model must agree to obey. This is not a control.
  • The model can satisfy the check with its tone. The min_confidence check is the usual example. If you tell a model the limit, the model writes with more confidence. It does not give more evidence.

A frame with an empty acceptance_criteria array does not give a definition of a good result. A runtime should refuse this frame with the code UNENFORCEABLE_CONTRACT. Do not accept work that you cannot evaluate.

7Result frame

A receiver sends a result frame only if all the acceptance criteria passed.

FieldDefinition
in_response_toThe request frame's metadata.trace_id.
statusAlways "fulfilled".
answerThe produced artifact, of the declared expected_output.type.
self_assessmentconfidence, assumptions, caveats. A receiver must populate this; absence is a contract breach and should be treated as zero confidence.
validationPer-criterion report: id, check kind, pass state. Carried so the caller can audit rather than re-derive.
usageattempts, token counts, cost_usd, latency_ms.
handoffResolved next capability and followups, after reviewers from quality were folded in.

8Refusal frame

A refusal frame has a refused_at field. This field gives the layer that stopped the work. The value is important to the caller. A refusal at admission shows that the frame was not acceptable. The sender must change the frame. A refusal at validation shows that the frame was correct.

The capability could not satisfy the criteria. The caller must do a different thing for each value. So the two values are not one general failure code.

refused_atstatusMeaning for the sender
admissionrefusedThe frame violates policy or the capability contract. No model was invoked and nothing was spent. Fix the frame.
validationfailedRetries were exhausted without satisfying the criteria. The frame was valid; the work was not achievable under these terms.
budgetfailedCumulative cost or deadline was exhausted mid-flight.
flowchart LR
    A["Frame arrives"] --> B{"Admit?"}
    B -- fails --> R1["refused_at: admission<br/>status: refused<br/>0 attempts · $0 spent<br/>the sender must change the frame"]
    B -- passes --> C["Model invoked"]
    C --> D{"Passed?"}
    D -- passes --> OK["result<br/>status: fulfilled"]
    D -- "fails after retries" --> R2["refused_at: validation<br/>status: failed<br/>the frame was fine;<br/>the work was not achievable"]
    C -- "cost or deadline exhausted" --> R3["refused_at: budget<br/>status: failed"]
    class A context
    class B enforce
    class C model
    class D check
    class R1,R2,R3 refusal
    class OK ok
§8 · Where a refusal stoppedThe caller must do a different thing for each problem. So refused_at is a separate field. It is not one general failure code.

8.1 Refusal codes

Each violation has a code, a section, and a detail. The section gives the part of the frame that caused the violation. The detail text is for a person. The codes do not change between versions. The detail text can change. A client must not parse the detail text.

A receiver can also refuse a frame for its own reasons. It knows which capabilities it supplies, which intent types it accepts, and which inputs it needs. It uses codes of its own for these conditions. This standard does not give them, because they are local to one receiver.

CodeThe receiver sends this code when
TOKEN_CEILING_EXCEEDEDRequested token budget is above deployment policy.
COST_CEILING_EXCEEDEDRequested spend is above deployment policy.
DEADLINE_TOO_LONGRequested deadline is above deployment policy.
TOOL_ESCALATIONA side-effecting tool was requested by a capability not privileged to use it.
READONLY_INTENT_WITH_WRITE_TOOLSA verify or critique frame requested write tools.
TOOL_LIST_CONTRADICTIONA tool appears in both allowed and disallowed.
EXFILTRATION_RISKExternal upload permitted on Confidential or Restricted material.
REVIEW_REQUIREDClassification demands review and the frame requested none.
UNENFORCEABLE_CONTRACTNo machine-checkable acceptance criteria were supplied.
ARTIFACT_UNRESOLVEDAn artifact reference could not be dereferenced.
ACCEPTANCE_CRITERIA_UNMETRetries exhausted with criteria still failing.
BUDGET_EXHAUSTEDCumulative cost passed the frame's ceiling mid-flight.

9Discovery

Discovery is out of scope. This standard does not tell a caller how to find a receiver. It does not give a document format. It does not reserve a path.

This is the same rule as the rule for transport. AIF gives the terms of one request. It does not give the network around that request.

Use a mechanism that you have already. If you use A2A, declare each capability as an A2A Skill in your Agent Card. Give the Skill the same name as the capability. Then a caller can find the receiver, and the frame gives the terms.

Change from an earlier draft

An earlier draft gave a capability manifest at /.well-known/capabilities.json. This draft does not have that section. The A2A Agent Card does the same work and does it better. A standard must not give a second answer to a question that has a good answer.

10Versioning

A frame gives its version as MAJOR.MINOR. If you add an optional field, the change is minor. These changes are major: you add a required field, or you remove a field. These changes are also major: you decrease the values of an enum, or you give a field a new definition.

A receiver for major version N must accept all the minor versions of N. If it finds a field that it does not know, it must ignore that field. It must not refuse the frame. If the major version is not known, the receiver must refuse the frame with the code UNSUPPORTED_VERSION. Do not try to use the parts that you know. A frame that you understand only in part is more dangerous than a refusal.

11Conformance levels

LevelRequires
L1 · StructuralEmits and accepts well-formed frames. Validates required fields. Returns typed result and refusal frames.
L2 · EnforcingL1, plus constraints enforced outside the model, typed acceptance criteria evaluated on every result, and refusal before invocation when policy is violated.
L3 · ObservableL2, plus telemetry for each intent type, with the trace_id, and an event log sufficient to reconstruct a run.

At level L2 this standard gives the results in this document. Level L1 only puts the prompt into fields. The runtime does not apply the constraints at level L1.

AMinimal valid frame

{
  "version": "0.1",
  "intent_type": "request",
  "capability": "research.summarize",
  "goal": { "objective": "Summarise incident 4471 for the executive committee." },
  "context": { "conversation_id": "rel-review-q3" },
  "expected_output": {
    "type": "bullet_summary",
    "sections": ["Customer Impact", "Top Risks", "Recommended Action"],
    "acceptance_criteria": [
      { "id": "c1", "check": "max_tokens", "args": { "limit": 300 } },
      { "id": "c2", "check": "must_include_sections",
        "args": { "sections": ["Recommended Action"] } }
    ]
  },
  "metadata": { "trace_id": "trace-8f21" }
}

BFull example

This is the same task with all the optional sections. Only these fields go to the model: goal, context.background, inputs, reasoning.analysis_framework, expected_output.type, expected_output.sections, and the criteria with the advertise flag. Code reads the other fields.

{
  "version": "0.1",
  "intent_type": "request",
  "capability": "research.summarize",
  "goal": {
    "objective": "Summarise incident 4471 for the executive committee.",
    "business_reason": "Quarterly reliability review, board pre-read.",
    "priority": "high"
  },
  "context": {
    "conversation_id": "rel-review-q3",
    "parent_turn": "turn-07",
    "background": "Committee has 90 seconds of attention for each incident.",
    "artifacts": [
      { "type": "incident_report", "id": "incident-4471" },
      { "type": "policy", "id": "postmortem-policy-v3" }
    ]
  },
  "inputs": { "audience": "executive committee", "region": "EU-C" },
  "constraints": {
    "deadline_seconds": 90,
    "cost": { "maximum_usd": 0.08 },
    "llm": { "maximum_tokens": 800, "temperature": 0.2 },
    "tooling": { "allowed": ["knowledge.search"], "disallowed": ["email.send"] },
    "security": { "allow_pii": false, "classification": "Internal" }
  },
  "reasoning": {
    "analysis_framework": [
      "Establish customer impact",
      "Rank contributing factors by recurrence risk",
      "Name one owning team and one funded action"
    ],
    "confidence_threshold": 0.75,
    "require_evidence": true
  },
  "expected_output": {
    "type": "bullet_summary",
    "sections": ["Customer Impact", "Top Risks", "Recommended Action"],
    "acceptance_criteria": [
      { "id": "c1", "check": "max_tokens", "args": { "limit": 300 } },
      { "id": "c2", "check": "must_include_sections",
        "args": { "sections": ["Customer Impact", "Top Risks", "Recommended Action"] } },
      { "id": "c3", "check": "must_exclude_patterns", "advertise_to_model": false,
        "args": { "patterns": ["email", "phone"], "label": "personal contact details" } },
      { "id": "c4", "check": "min_confidence", "advertise_to_model": false,
        "args": { "threshold": 0.75 } }
    ]
  },
  "quality": { "review_required": true, "reviewers": ["qa.verify", "comms.notify"] },
  "failure_policy": { "retry_attempts": 2,
                      "on_failure": ["NotifyPlanner", "EscalateToHuman"] },
  "handoff": { "possible_followups": ["comms.notify"] },
  "metadata": { "schema_id": "aif/0.1", "trace_id": "trace-8f21",
                "tags": ["reliability", "exec"] }
}

Implementation guide → · Python library →