|
| 1 | +# Jidoka Agent Discovery Projection |
| 2 | + |
| 3 | +Jido Messaging provides a small, safe directory projection for Jidoka agents. |
| 4 | +Jidoka stays the source of agent definitions and capability data. Jido |
| 5 | +Messaging stores only the data that a messaging client needs for discovery. |
| 6 | + |
| 7 | +## Ownership boundary |
| 8 | + |
| 9 | +Jidoka owns: |
| 10 | + |
| 11 | +- `Agent.Spec` and agent versions; |
| 12 | +- instructions, models, tools, controls, and credentials; |
| 13 | +- process hosting, availability inputs, sessions, memory, and execution; |
| 14 | +- the decision about which source fields are safe to publish. |
| 15 | + |
| 16 | +Jido Messaging owns: |
| 17 | + |
| 18 | +- the canonical messaging principal reference; |
| 19 | +- the canonical messaging endpoint reference; |
| 20 | +- the scoped directory query contract; |
| 21 | +- the safe display projection and its source revision; |
| 22 | +- persistent ETS and SQLite projection records. |
| 23 | + |
| 24 | +Jido Messaging does not depend on Jidoka or `jido_harness`. A Jidoka-owned |
| 25 | +integration package depends on Jido Messaging and implements |
| 26 | +`Jido.Messaging.AgentDirectoryProjector`. |
| 27 | + |
| 28 | +## Projection contract |
| 29 | + |
| 30 | +`Jido.Messaging.AgentDirectoryProjection` accepts only these source fields: |
| 31 | + |
| 32 | +- an opaque Jidoka agent reference with `system` and `id`; |
| 33 | +- a canonical messaging principal ID; |
| 34 | +- an optional canonical messaging endpoint reference; |
| 35 | +- a safe name and description; |
| 36 | +- at most 32 safe capability codes; |
| 37 | +- an availability summary and agent version; |
| 38 | +- a small invocation mode and approval summary; |
| 39 | +- an integration-reported verification state; |
| 40 | +- a listing state, source revision, source time, and bounded freshness period. |
| 41 | + |
| 42 | +The Jidoka reference has this shape: |
| 43 | + |
| 44 | +```elixir |
| 45 | +%{"system" => "jidoka", "id" => "support-guide"} |
| 46 | +``` |
| 47 | + |
| 48 | +The endpoint reference has this shape: |
| 49 | + |
| 50 | +```elixir |
| 51 | +%Jido.Messaging.AgentDirectoryEndpointRef{ |
| 52 | + system: "jido_messaging", |
| 53 | + id: "endpoint:support-guide" |
| 54 | +} |
| 55 | +``` |
| 56 | + |
| 57 | +The projection does not accept an agent specification, instructions, prompts, |
| 58 | +models, tools, functions, controls, credentials, tokens, private keys, |
| 59 | +environment data, sessions, memory, or runtime records. The name, description, |
| 60 | +and capability codes are still an integration trust boundary. The Jidoka |
| 61 | +adapter must create redacted display text. It must not copy instruction text |
| 62 | +or tool configuration into these fields. |
| 63 | + |
| 64 | +## Publishing from Jidoka |
| 65 | + |
| 66 | +A Jidoka-owned adapter can map its source data to a projection: |
| 67 | + |
| 68 | +```elixir |
| 69 | +defmodule MyApp.JidokaDirectoryProjector do |
| 70 | + @behaviour Jido.Messaging.AgentDirectoryProjector |
| 71 | + |
| 72 | + @impl true |
| 73 | + def to_directory_projection(agent_spec, context, _opts) do |
| 74 | + {:ok, |
| 75 | + %{ |
| 76 | + jidoka_agent_ref: %{system: :jidoka, id: context.agent_id}, |
| 77 | + principal_id: context.principal_id, |
| 78 | + endpoint_ref: %{system: :jido_messaging, id: context.endpoint_id}, |
| 79 | + name: context.safe_name, |
| 80 | + description: context.safe_description, |
| 81 | + capabilities: context.safe_capability_codes, |
| 82 | + availability: context.availability, |
| 83 | + version: context.version, |
| 84 | + invocation_summary: %{mode: :thread, approval: :may_require}, |
| 85 | + verification_state: :verified, |
| 86 | + listing_state: :listed, |
| 87 | + source_revision: context.revision, |
| 88 | + source_updated_at: context.source_updated_at, |
| 89 | + fresh_for_seconds: 300 |
| 90 | + }} |
| 91 | + end |
| 92 | +end |
| 93 | + |
| 94 | +{:ok, projection} = |
| 95 | + MyApp.Messaging.project_jidoka_agent_from( |
| 96 | + MyApp.JidokaDirectoryProjector, |
| 97 | + jidoka_agent_spec, |
| 98 | + safe_projection_context |
| 99 | + ) |
| 100 | +``` |
| 101 | + |
| 102 | +The adapter source and context are transient. The strict projection |
| 103 | +constructor validates the returned map before persistence. An integration can |
| 104 | +also call `project_jidoka_agent/1` with a map that it has already redacted. |
| 105 | + |
| 106 | +The first source revision is `1`. Later revisions must be sequential. Equal |
| 107 | +content at the same revision is idempotent. Conflicting equal revisions, old |
| 108 | +revisions, and revision gaps fail. This prevents old availability or endpoint |
| 109 | +data from replacing a newer projection. |
| 110 | + |
| 111 | +Use `listing_state: :withdrawn` at a new revision to remove an agent from |
| 112 | +search without loss of the revision marker. |
| 113 | + |
| 114 | +## Scoped discovery |
| 115 | + |
| 116 | +Agent search always needs an `AgentDirectoryScope`. The scope maps endpoint |
| 117 | +IDs to principal IDs: |
| 118 | + |
| 119 | +```elixir |
| 120 | +{:ok, scope} = |
| 121 | + MyApp.Messaging.agent_directory_scope(%{ |
| 122 | + "endpoint:support-guide" => "principal:support-guide" |
| 123 | + }) |
| 124 | + |
| 125 | +{:ok, entries} = |
| 126 | + MyApp.Messaging.search_jidoka_agents( |
| 127 | + %{capability: "support", invokable: true}, |
| 128 | + scope, |
| 129 | + limit: 20 |
| 130 | + ) |
| 131 | +``` |
| 132 | + |
| 133 | +An application must build the map from current active room memberships and a |
| 134 | +current authorization result. It must not build it from user input. After the |
| 135 | +principal grant and durable endpoint work is integrated, the Jidoka adapter |
| 136 | +must use those contracts to construct the scope. The directory does not create |
| 137 | +membership or grant access. |
| 138 | + |
| 139 | +Search returns a projection only when its endpoint and principal pair is in |
| 140 | +the supplied scope. A projection without an endpoint is not returned. This |
| 141 | +lets an integration store a safe Jidoka projection before it has a usable |
| 142 | +messaging binding without presentation of the agent as invokable. |
| 143 | + |
| 144 | +`directory_search(:agent, query, scope: scope)` and |
| 145 | +`directory_lookup(:agent, query, scope: scope)` use the same rules. Existing |
| 146 | +participant and room directory queries do not change. |
| 147 | + |
| 148 | +## Freshness and invocation |
| 149 | + |
| 150 | +Each result reports `freshness` as `:fresh` or `:stale`. `invokable` is true |
| 151 | +only when all of these conditions are true: |
| 152 | + |
| 153 | +- the projection is listed; |
| 154 | +- the scoped endpoint exists in the result; |
| 155 | +- projected availability is `:available`; |
| 156 | +- verification is not rejected; |
| 157 | +- the freshness period has not ended. |
| 158 | + |
| 159 | +`invokable` is a user interface hint. It is not an authorization decision. |
| 160 | +The caller must get a current invocation decision before it sends a request. |
| 161 | +The verification state is also an integration-reported display value. It does |
| 162 | +not prove controller identity, grant access, or establish reputation. |
| 163 | + |
| 164 | +Availability can differ from the current Jidoka runtime. A short freshness |
| 165 | +period and frequent revision updates reduce this risk. Freshness is limited to |
| 166 | +24 hours. Source times more than five minutes in the future fail validation. |
| 167 | + |
| 168 | +## Persistence and compatibility |
| 169 | + |
| 170 | +ETS and SQLite store the same safe projection. SQLite keeps projections across |
| 171 | +a messaging restart. Deletion of a canonical participant also deletes its |
| 172 | +directory projection. |
| 173 | + |
| 174 | +The new persistence callbacks are optional. A custom persistence adapter that |
| 175 | +does not implement them returns `{:error, :unsupported}` from the projection |
| 176 | +API. The PostgreSQL adapter must add these callbacks when the persistence work |
| 177 | +is combined. |
| 178 | + |
| 179 | +The current additive implementation validates the principal through the agent |
| 180 | +participant record. After canonical principals and durable Jidoka endpoints |
| 181 | +are merged, the integration must align this validation with those records. |
| 182 | +When the endpoint callbacks are present, publication also checks that the |
| 183 | +endpoint is active and belongs to the projected principal. |
| 184 | + |
| 185 | +## Security and privacy rules |
| 186 | + |
| 187 | +- Do not put `Agent.Spec`, prompts, tools, controls, or private runtime data in |
| 188 | + the projection. |
| 189 | +- Do not show an endpoint that is not in the current caller scope. |
| 190 | +- Do not treat projected capability, verification, availability, or |
| 191 | + `invokable` as authorization. |
| 192 | +- Do not expose room membership, controller identity, or grant details in a |
| 193 | + directory result. |
| 194 | +- Publish a withdrawal revision when an agent must no longer be discoverable. |
| 195 | +- Keep the freshness period short enough for the Jidoka availability source. |
| 196 | +- Keep source revision state durable so that old events cannot publish stale |
| 197 | + data again. |
0 commit comments