cd /news/ai-agents/non-llm-operations-in-pap-the-glue-a… · home topics ai-agents article
[ARTICLE · art-71277] src=baursoftware.com ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Non-LLM operations in PAP: the glue agent primitives

PAP ships eight glue agents that perform deterministic structural operations without any LLM inference call, as detailed in the company's documentation. These agents, including the Type Router and Trip Assembler, implement a two-piece interface with AgentMeta and AgentExecutor::execute, and they cover four patterns—Transform, Route, Merge, and Validate—each mapped to a schema.org action type. The design ensures that agents declaring requires_disclosure: &[] and network_required: false operate with no data leakage and no outbound connections, making them first-class primitives in PAP's agent registry.

read9 min views16 publishedJul 6, 2026

PAP ships eight agents that will never make an inference call. Not because they're simple — the Trip Assembler merges flight, hotel, and activity reservations into a single typed itinerary, and the Type Router dispatches to downstream agents by inspecting a schema.org @type

field. They skip the model because they don't need one. The work is structural: typed input goes in, a different typed shape comes out, deterministically, every time.

These are glue agents. They're the operations that live between the reasoning steps in an agent pipeline, and they're a first-class primitive in PAP's agent registry.

What a glue agent looks like #

Every agent in PAP, whether it calls an LLM, hits an HTTP endpoint, or runs a pure function, implements the same two-piece interface: AgentMeta

declares what the agent is, and AgentExecutor::execute

is what it does.

For a glue agent, AgentMeta

tells the whole story:

AgentMeta {
    name: "Type Router",
    version: "0.1.0",
    provider: "PAP",
    action: "schema:ChooseAction",
    object_types: &["schema:Thing"],
    requires_disclosure: &[],
    returns: &["schema:ChooseAction"],
    configurable_properties: vec![],
    network_required: false,
}

Two fields stand out. requires_disclosure: &[]

means this agent requests no principal data. It will never see a credential, a calendar entry, or a location, because it never asked for one. network_required: false

means no outbound connection is made. These aren't flags you set to be polite; they're the fields the orchestrator reads when it builds the mandate. An agent that declares requires_disclosure: &[]

gets a mandate with an empty disclosure scope by construction. There's nothing to leak because there's nothing granted.

The execution side is equally plain. Here's the complete implementation of TypeRouterExecutor

:

fn execute(&self, query: &str) -> Result<Value, TransportError> {
    let input: Value = serde_json::from_str(query)?;

    // Form 1: explicit routing table + data
    if let (Some(routes), Some(data)) = (
        input.get("routes"),
        input.get("data")
    ) {
        let input_type = data
            .get("@type")
            .and_then(|v| v.as_str())
            .unwrap_or("schema:Thing");

        let selected = routes
            .get(input_type)
            .and_then(|v| v.as_str())
            .map(str::to_owned);

        return Ok(json!({
            "@type": "schema:ChooseAction",
            "selectedAgent": selected,
            "matched": selected.is_some(),
            "data": data,
        }));
    }

    // Form 2: bare object — report its type, no routing table
    let input_type = input
        .get("@type")
        .and_then(|v| v.as_str())
        .unwrap_or("schema:Thing");

    Ok(json!({
        "@type": "schema:ChooseAction",
        "inputType": input_type,
        "selectedAgent": null,
        "matched": false,
        "data": input,
    }))
}

No async. No network call. No prompt construction. The function reads a @type

field, looks it up in a table, and returns a typed schema:ChooseAction

the pipeline can act on. The entire reasoning budget for this step is a hash map lookup.

This is what it means for an operation to not need a model: not that it's simple, but that its inputs and outputs have known shapes and the transformation between them is a function, not a judgment.

The four patterns #

PAP's glue agents cover four structural operations. Each maps to a schema.org action type, not by convention, but because these action types already exist in the vocabulary and describe exactly what the operation does.

Transform — schema:ConvertAction

A transform agent takes one typed shape and produces a different typed shape. No interpretation, no inference about meaning, just a deterministic mapping from source schema to target schema.

PAP ships three:

Agent Input Output
JsonToMarkdownExecutor any schema:Thing schema:Text
ItemListToCsvExecutor schema:ItemList schema:Dataset
FlattenArrayExecutor schema:DataFeed or bare array schema:ItemList

The CSV converter is a good example of why this matters in practice. An upstream agent returns a schema:ItemList

of flight options. A downstream agent needs a table. Without a glue agent, that conversion either goes through an LLM prompt ("format this as CSV") or gets hardcoded into the pipeline. With a glue agent, it's a typed contract: ItemList

in, Dataset

out, headers inferred from the first element's keys, missing fields emitted as empty strings. Deterministic, testable, zero inference cost.

Reduce — schema:CombineAction

A reduce agent merges multiple typed inputs into one. PAP's TripAssemblerExecutor

takes an array of reservations (schema:FlightReservation

, schema:LodgingReservation

, schema:EventReservation

) and assembles them into a single schema:Trip

, sorted by date, with overlapping segments detected and flagged.

This is the operation most likely to get routed to a model unnecessarily. "Combine these three results" sounds like it needs judgment. It doesn't, not when the inputs are typed. A schema:Offer

has a price

field. A schema:AggregateOffer

has a lowPrice

, highPrice

, and offerCount

. The merge is a fold over known fields, not an interpretation of unstructured text.

Route — schema:ChooseAction

A route agent inspects a typed object and returns a dispatch directive: which downstream agent should handle it, based on the object's @type

or field values. The routing table is part of the input:

{
  "routes": {
    "schema:FlightReservation": "Trip Assembler",
    "schema:LodgingReservation": "Trip Assembler",
    "schema:NewsArticle": "Article Summarizer"
  },
  "data": { "@type": "schema:FlightReservation", "reservationId": "AA123" }
}

Output:

{
  "@type": "schema:ChooseAction",
  "selectedAgent": "Trip Assembler",
  "inputType": "schema:FlightReservation",
  "matched": true,
  "data": { "..." }
}

The pipeline acts on selectedAgent

. If matched

is false, it falls through to a default. The routing logic is inspectable at any point. It's a JSON table, not a prompt, so you can read it, test it, and change it without touching inference.

Probe — schema:CheckAction

A probe agent validates a typed object before it proceeds. PAP ships two: SchemaValidatorExecutor

checks structural integrity (@type

present, at least one non-type field), and FieldPresenceExecutor

checks that specific required fields exist and are non-null.

{
  "fields": ["price", "availability", "validThrough"],
  "data": { "@type": "schema:Offer", "price": 299, "availability": "InStock" }
}

Output:

{
  "@type": "schema:CheckAction",
  "present": ["price", "availability"],
  "missing": ["validThrough"],
  "all_present": false
}

A probe that returns all_present: false

is a hard stop. The pipeline doesn't proceed to checkout with an offer missing its expiry date. This is the kind of validation that otherwise gets skipped, hardcoded into the consuming agent, or sent to a model to "check if this looks complete." A model that hallucinates validThrough

into existence is strictly worse than a probe that fails closed.

Zero-disclosure by construction #

The phrase "zero-disclosure" in PAP has a specific technical meaning. It's not a policy. It's a structural outcome of how mandates are built.

When the orchestrator evaluates an agent for a pipeline step, it reads requires_disclosure

from AgentMeta

and constructs the mandate's disclosure scope from that list. An LLM agent that needs the user's location and name declares requires_disclosure: &["schema:location", "schema:name"]

. The orchestrator surfaces those fields to the principal for approval before the step runs, and the SD-JWT selective disclosure envelope reveals only those two properties.

A glue agent that declares requires_disclosure: &[]

gets a mandate with an empty disclosure scope. The orchestrator doesn't ask the principal for approval over data fields because there are none to approve. There is no SD-JWT envelope to construct because there is nothing to disclose. The agent receives exactly what was passed in the pipeline step's typed payload and nothing else.

This means the zero-disclosure property doesn't depend on trusting the glue agent implementation. Even if the TypeRouterExecutor

were somehow compromised, it has no path to principal data: no credential was ever included in its mandate, no disclosure scope was ever opened, no ambient context was passed. The isolation is enforced at the protocol layer before the agent code runs.

That's the distinction between policy-based privacy ("this agent promises not to use your data") and structural privacy ("this agent was never given your data"). PAP's glue agents are the latter. Evidence, not faith.

How registries advertise glue agents #

Glue agents aren't special-cased in the registry. They advertise the same AgentAdvertisement

as every other agent, signed by the provider's DID, including action

, object_types

, and returns

, and they appear in the same federation index that syncs to connected clients.

What makes them useful in the intent index is exactly the properties that make them cheap to run. When Papillon receives a user's intent and queries the semantic index to find matching agents, it's building a candidate set from the advertised action

and object_types

fields across every agent in the index: local catalog agents, registry agents from federated peers, and glue agents. The routing and transform agents surface on schema:ChooseAction

and schema:ConvertAction

queries. The probe agents surface on schema:CheckAction

.

This matters because pipelines can be assembled from agents that span multiple registries, with glue agents contributed by any peer that advertises them, without central coordination. A registry that specializes in travel data can advertise a TripAssemblerExecutor

that understands its own reservation format. A client that has already routed to that registry's flight search agent can find the compatible reduce agent in the same index sweep, without knowing ahead of time that it exists.

The semantic routing in Papillon's intent index, built on ordvec's two-stage sign-probe with exact cosine rescore, is sophisticated enough to distinguish between a schema:CombineAction

that merges trip reservations and one that merges product offers, because the object_types

field carries that specificity. The vocabulary does the disambiguation work that would otherwise require another model call.

The result is that glue agents are discoverable the same way capabilities are discoverable: through the registry's advertised schema, not through hardcoded pipeline wiring. A pipeline assembled for one set of agents can pick up a better transform or a stricter probe from a newly-federated registry without any configuration change. The type contract is the interface; the registry is the directory; the intent index is the resolver. No coordinator required.

What this adds up to #

An agent pipeline built with PAP's glue primitives has a property that's hard to get otherwise: every junction between steps is a typed contract you can inspect statically, not a black box you have to trust ran correctly.

The reasoning steps, the ones that actually need a model, are bounded on both sides by typed shapes. The model receives a well-formed schema:Action

and returns a well-formed schema:Thing

. Before that result moves to the next step, a probe checks it. If it needs to change shape for the consuming agent, a transform handles it. If it needs to go to one of several downstream agents, a route dispatches it. None of those intermediate steps cost inference. None of them carry mandate scope beyond the payload they're explicitly handling.

The inference calls in the pipeline are exactly the ones that need inference. Everything else is a function.

── more in #ai-agents 4 stories · sorted by recency
── more on @pap 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/non-llm-operations-i…] indexed:0 read:9min 2026-07-06 ·