{"slug": "non-llm-operations-in-pap-the-glue-agent-primitives", "title": "Non-LLM operations in PAP: the glue agent primitives", "summary": "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.", "body_md": "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`\n\nfield. 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.\n\nThese 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.\n\n## What a glue agent looks like\n\nEvery agent in PAP, whether it calls an LLM, hits an HTTP endpoint, or runs a pure function, implements the same two-piece interface: `AgentMeta`\n\ndeclares what the agent is, and `AgentExecutor::execute`\n\nis what it does.\n\nFor a glue agent, `AgentMeta`\n\ntells the whole story:\n\n```\nAgentMeta {\n    name: \"Type Router\",\n    version: \"0.1.0\",\n    provider: \"PAP\",\n    action: \"schema:ChooseAction\",\n    object_types: &[\"schema:Thing\"],\n    requires_disclosure: &[],\n    returns: &[\"schema:ChooseAction\"],\n    configurable_properties: vec![],\n    network_required: false,\n}\n```\n\nTwo fields stand out. `requires_disclosure: &[]`\n\nmeans 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`\n\nmeans 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: &[]`\n\ngets a mandate with an empty disclosure scope by construction. There's nothing to leak because there's nothing granted.\n\nThe execution side is equally plain. Here's the complete implementation of `TypeRouterExecutor`\n\n:\n\n``` php\nfn execute(&self, query: &str) -> Result<Value, TransportError> {\n    let input: Value = serde_json::from_str(query)?;\n\n    // Form 1: explicit routing table + data\n    if let (Some(routes), Some(data)) = (\n        input.get(\"routes\"),\n        input.get(\"data\")\n    ) {\n        let input_type = data\n            .get(\"@type\")\n            .and_then(|v| v.as_str())\n            .unwrap_or(\"schema:Thing\");\n\n        let selected = routes\n            .get(input_type)\n            .and_then(|v| v.as_str())\n            .map(str::to_owned);\n\n        return Ok(json!({\n            \"@type\": \"schema:ChooseAction\",\n            \"selectedAgent\": selected,\n            \"matched\": selected.is_some(),\n            \"data\": data,\n        }));\n    }\n\n    // Form 2: bare object — report its type, no routing table\n    let input_type = input\n        .get(\"@type\")\n        .and_then(|v| v.as_str())\n        .unwrap_or(\"schema:Thing\");\n\n    Ok(json!({\n        \"@type\": \"schema:ChooseAction\",\n        \"inputType\": input_type,\n        \"selectedAgent\": null,\n        \"matched\": false,\n        \"data\": input,\n    }))\n}\n```\n\nNo async. No network call. No prompt construction. The function reads a `@type`\n\nfield, looks it up in a table, and returns a typed `schema:ChooseAction`\n\nthe pipeline can act on. The entire reasoning budget for this step is a hash map lookup.\n\nThis 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.\n\n## The four patterns\n\nPAP'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.\n\n**Transform — schema:ConvertAction**\n\nA 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.\n\nPAP ships three:\n\n| Agent | Input | Output |\n|---|---|---|\n`JsonToMarkdownExecutor` | any `schema:Thing` | `schema:Text` |\n`ItemListToCsvExecutor` | `schema:ItemList` | `schema:Dataset` |\n`FlattenArrayExecutor` | `schema:DataFeed` or bare array | `schema:ItemList` |\n\nThe CSV converter is a good example of why this matters in practice. An upstream agent returns a `schema:ItemList`\n\nof 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`\n\nin, `Dataset`\n\nout, headers inferred from the first element's keys, missing fields emitted as empty strings. Deterministic, testable, zero inference cost.\n\n**Reduce — schema:CombineAction**\n\nA reduce agent merges multiple typed inputs into one. PAP's `TripAssemblerExecutor`\n\ntakes an array of reservations (`schema:FlightReservation`\n\n, `schema:LodgingReservation`\n\n, `schema:EventReservation`\n\n) and assembles them into a single `schema:Trip`\n\n, sorted by date, with overlapping segments detected and flagged.\n\nThis 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`\n\nhas a `price`\n\nfield. A `schema:AggregateOffer`\n\nhas a `lowPrice`\n\n, `highPrice`\n\n, and `offerCount`\n\n. The merge is a fold over known fields, not an interpretation of unstructured text.\n\n**Route — schema:ChooseAction**\n\nA route agent inspects a typed object and returns a dispatch directive: which downstream agent should handle it, based on the object's `@type`\n\nor field values. The routing table is part of the input:\n\n```\n{\n  \"routes\": {\n    \"schema:FlightReservation\": \"Trip Assembler\",\n    \"schema:LodgingReservation\": \"Trip Assembler\",\n    \"schema:NewsArticle\": \"Article Summarizer\"\n  },\n  \"data\": { \"@type\": \"schema:FlightReservation\", \"reservationId\": \"AA123\" }\n}\n```\n\nOutput:\n\n```\n{\n  \"@type\": \"schema:ChooseAction\",\n  \"selectedAgent\": \"Trip Assembler\",\n  \"inputType\": \"schema:FlightReservation\",\n  \"matched\": true,\n  \"data\": { \"...\" }\n}\n```\n\nThe pipeline acts on `selectedAgent`\n\n. If `matched`\n\nis 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.\n\n**Probe — schema:CheckAction**\n\nA probe agent validates a typed object before it proceeds. PAP ships two: `SchemaValidatorExecutor`\n\nchecks structural integrity (`@type`\n\npresent, at least one non-type field), and `FieldPresenceExecutor`\n\nchecks that specific required fields exist and are non-null.\n\n```\n{\n  \"fields\": [\"price\", \"availability\", \"validThrough\"],\n  \"data\": { \"@type\": \"schema:Offer\", \"price\": 299, \"availability\": \"InStock\" }\n}\n```\n\nOutput:\n\n```\n{\n  \"@type\": \"schema:CheckAction\",\n  \"present\": [\"price\", \"availability\"],\n  \"missing\": [\"validThrough\"],\n  \"all_present\": false\n}\n```\n\nA probe that returns `all_present: false`\n\nis 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`\n\ninto existence is strictly worse than a probe that fails closed.\n\n## Zero-disclosure by construction\n\nThe 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.\n\nWhen the orchestrator evaluates an agent for a pipeline step, it reads `requires_disclosure`\n\nfrom `AgentMeta`\n\nand 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\"]`\n\n. 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.\n\nA glue agent that declares `requires_disclosure: &[]`\n\ngets 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.\n\nThis means the zero-disclosure property doesn't depend on trusting the glue agent implementation. Even if the `TypeRouterExecutor`\n\nwere 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.\n\nThat'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.\n\n## How registries advertise glue agents\n\nGlue agents aren't special-cased in the registry. They advertise the same `AgentAdvertisement`\n\nas every other agent, signed by the provider's DID, including `action`\n\n, `object_types`\n\n, and `returns`\n\n, and they appear in the same federation index that syncs to connected clients.\n\nWhat 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`\n\nand `object_types`\n\nfields 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`\n\nand `schema:ConvertAction`\n\nqueries. The probe agents surface on `schema:CheckAction`\n\n.\n\nThis 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`\n\nthat 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.\n\nThe 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`\n\nthat merges trip reservations and one that merges product offers, because the `object_types`\n\nfield carries that specificity. The vocabulary does the disambiguation work that would otherwise require another model call.\n\nThe 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.\n\n## What this adds up to\n\nAn 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.\n\nThe 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`\n\nand returns a well-formed `schema:Thing`\n\n. 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.\n\nThe inference calls in the pipeline are exactly the ones that need inference. Everything else is a function.", "url": "https://wpnews.pro/news/non-llm-operations-in-pap-the-glue-agent-primitives", "canonical_source": "https://baursoftware.com/blog/non-llm-operations-in-pap-the-glue-agent-primitives", "published_at": "2026-07-06 12:00:00+00:00", "updated_at": "2026-07-24 02:08:31.018000+00:00", "lang": "en", "topics": ["ai-agents", "developer-tools"], "entities": ["PAP", "Type Router", "Trip Assembler", "AgentMeta", "AgentExecutor", "schema.org", "JsonToMarkdownExecutor", "TypeRouterExecutor"], "alternates": {"html": "https://wpnews.pro/news/non-llm-operations-in-pap-the-glue-agent-primitives", "markdown": "https://wpnews.pro/news/non-llm-operations-in-pap-the-glue-agent-primitives.md", "text": "https://wpnews.pro/news/non-llm-operations-in-pap-the-glue-agent-primitives.txt", "jsonld": "https://wpnews.pro/news/non-llm-operations-in-pap-the-glue-agent-primitives.jsonld"}}