Skip to content

Commit e029167

Browse files
feat: define Jidoka continuity boundary (#93)
1 parent fa262e5 commit e029167

13 files changed

Lines changed: 1687 additions & 4 deletions

README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ end
158158
```
159159

160160
The SQLite adapter stores canonical rooms, participants, messages, threads,
161+
Jidoka continuity links, bridge bindings, routing policies, bridge configs,
162+
and ingress subscriptions.
161163
bridge bindings, routing policies, bridge configs, ingress subscriptions, and
162164
safe Jidoka agent directory projections.
163165
The SQLite adapter stores canonical rooms, participants, principals, scoped
@@ -242,6 +244,42 @@ helpers enforce the same instance and room scope before they invoke the
242244
projection. This keeps a failed optional index from changing canonical message
243245
commit behavior. Small deployments can omit a projection.
244246

247+
### Jidoka Continuity References
248+
249+
Jidoka is the first-party agent authoring and execution surface. Jidoka owns
250+
sessions, memory, snapshots, prompt assembly, resume work, and handoffs. Jido
251+
Messaging stores only a strict reference from a messaging thread and agent
252+
participant to that Jidoka state.
253+
254+
```elixir
255+
{:ok, link} =
256+
MyApp.Messaging.put_thread_continuity(%{
257+
room_id: room.id,
258+
thread_id: thread.id,
259+
principal_id: agent_participant.id,
260+
continuity_ref: %{
261+
integration_id: "jidoka-primary",
262+
jidoka_agent_ref: %{system: :jidoka, id: "support-agent"},
263+
session_id: "session-123",
264+
request_id: "request-456"
265+
},
266+
source_revision: 1,
267+
source_updated_at: DateTime.utc_now()
268+
})
269+
270+
{:ok, scope} = MyApp.Messaging.history_scope([room.id])
271+
272+
{:ok, context} =
273+
MyApp.Messaging.jidoka_continuity_context(thread.id, scope, limit: 50)
274+
```
275+
276+
The context contains the opaque link and canonical messages from only the
277+
linked thread. Room scope is checked before messages are read. A Jidoka-owned
278+
integration can use the context with Jidoka public APIs. Jido Messaging does
279+
not call Jidoka and does not depend on Jidoka or `jido_harness`.
280+
281+
See [Jidoka Continuity Integration Boundary](docs/jidoka-continuity-boundary.md)
282+
for lifecycle states, revision rules, replacement rules, and ownership.
245283
### Scoped Jidoka Agent Discovery
246284

247285
A Jidoka-owned adapter can publish a strict display projection without making

docs/jidoka-continuity-boundary.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# Jidoka Continuity Integration Boundary
2+
3+
Status: implemented contract for issue
4+
[#83](https://github.com/agentjido/jido_messaging/issues/83).
5+
6+
## Decision
7+
8+
Jidoka is the first-party agent authoring and execution surface. Jidoka owns
9+
agent sessions, memory, snapshots, request state, resume work, and handoff
10+
state. Jido Messaging owns rooms, threads, participants, canonical messages,
11+
authorization scope, and the small correlation record that connects a thread
12+
to Jidoka.
13+
14+
The dependency direction is one way:
15+
16+
1. A Jidoka-owned integration writes and resolves Jido Messaging correlation
17+
records.
18+
2. The integration requests a scoped canonical transcript from Jido
19+
Messaging.
20+
3. The integration passes the opaque reference and messages to Jidoka public
21+
APIs.
22+
23+
Jido Messaging does not call Jidoka. It has no Jidoka or `jido_harness`
24+
dependency.
25+
26+
This boundary follows the first-party Jidoka guidance for
27+
[architecture boundaries](https://github.com/agentjido/jidoka/blob/main/guides/architecture-boundaries.md),
28+
[sessions and stores](https://github.com/agentjido/jidoka/blob/main/guides/sessions-and-stores.md),
29+
[snapshots and resume](https://github.com/agentjido/jidoka/blob/main/guides/snapshots-and-resume.md),
30+
[memory](https://github.com/agentjido/jidoka/blob/main/guides/memory.md), and
31+
[handoffs](https://github.com/agentjido/jidoka/blob/main/guides/handoffs.md).
32+
33+
## Ownership
34+
35+
| Concern | Owner | Jido Messaging record |
36+
| --- | --- | --- |
37+
| Room and thread | Jido Messaging | Canonical room and thread IDs |
38+
| Agent principal | Jido Messaging | Canonical agent participant ID |
39+
| Canonical transcript | Jido Messaging | Message records in one authorized thread |
40+
| Session and request history | Jidoka | Opaque session and request IDs only |
41+
| Memory and prompt assembly | Jidoka | None |
42+
| Snapshot data and resume | Jidoka | Opaque snapshot ID only |
43+
| Agent handoff state | Jidoka | Opaque transition reference only |
44+
45+
## Contract
46+
47+
`JidokaContinuityRef` contains:
48+
49+
- one integration ID;
50+
- a fixed `%{system: :jidoka, id: ...}` agent reference;
51+
- one Jidoka session ID;
52+
- optional request, turn, snapshot, and expiry references.
53+
54+
`ThreadContinuityLink` adds the messaging room, thread, and agent principal.
55+
It also adds a sequential source revision, source time, availability status,
56+
safe reason code, and optional transition reference. The link ID is stable for
57+
the room and thread.
58+
59+
Both constructors use an allow list. They reject extra fields. Session data,
60+
memory entries, prompts, serialized snapshots, provider clients, credentials,
61+
and arbitrary metadata are not valid input.
62+
63+
## Write and replacement rules
64+
65+
- The first source revision is `1`.
66+
- A byte-equivalent source revision is idempotent.
67+
- Each changed write increments the revision by one.
68+
- Conflicting, stale, and skipped revisions return different errors.
69+
- A principal, Jidoka agent, or session replacement needs a non-secret opaque
70+
`transition_ref`.
71+
- One live `{integration_id, session_id}` can belong to only one messaging
72+
thread in one messaging instance.
73+
- `:expired`, `:deleted`, and `:cleared` are terminal for the same session.
74+
A later active link must identify a replacement session or agent and include
75+
a transition reference.
76+
- Terminal links remove request, turn, and snapshot references. They keep the
77+
stable session reference as a durable correlation and deletion marker.
78+
79+
The ETS and SQLite adapters enforce the same rules. SQLite persists the link
80+
across a messaging restart. Room or principal deletion removes its orphaned
81+
link.
82+
83+
## Read and authorization rules
84+
85+
Every link read and transcript request needs an instance-bound
86+
`HistoryScope`. The link room must be in the supplied room list. Jido
87+
Messaging checks that scope before it reads messages. A cursor from another
88+
room or thread cannot expand the result.
89+
90+
`jidoka_continuity_context/3` returns a `JidokaContinuityContext` with the link
91+
and canonical messages for the linked thread. It does not return a prompt or
92+
memory projection. The caller must authorize the rooms before it builds the
93+
scope. Jido Messaging does not make this policy decision.
94+
95+
The resolver also requires an active messaging thread and a live link:
96+
97+
| Stored or effective state | Result |
98+
| --- | --- |
99+
| Missing link | `{:error, :not_found}` |
100+
| Active | Opaque `JidokaContinuityRef` |
101+
| Unavailable | `{:error, {:continuity_unavailable, reason}}` |
102+
| Expired | `{:error, {:continuity_expired, reason}}` |
103+
| Deleted | `{:error, {:continuity_deleted, reason}}` |
104+
| Cleared | `{:error, {:continuity_cleared, reason}}` |
105+
| Closed or archived messaging thread | `{:error, {:continuity_thread_not_active, status}}` |
106+
107+
An `expires_at` value has read-time effect. A Jidoka-owned integration should
108+
also write an explicit terminal state when it confirms deletion or expiry.
109+
110+
## Handoff and recovery
111+
112+
The `transition_ref` is a correlation value, not a handoff record. Jidoka
113+
creates and interprets it. Jido Messaging only uses its presence to approve an
114+
identity replacement. It does not copy Jidoka ownership, private context, or
115+
handoff state.
116+
117+
After a Jido Messaging restart, the integration can resolve the same session
118+
ID and request a scoped canonical transcript. Jidoka then decides whether to
119+
load, recover, fork, or reject its session. A missing Jidoka session must be
120+
reported back with `set_thread_continuity_status/4`; Jido Messaging does not
121+
attempt recovery.
122+
123+
## Compatibility and follow-up work
124+
125+
The core contract uses the existing agent participant and `HistoryScope`
126+
types, so it does not need a Jidoka package. Future Jidoka-owned integration
127+
work can align the opaque agent reference with the canonical principal and
128+
endpoint contracts from issues
129+
[#77](https://github.com/agentjido/jido_messaging/issues/77) and
130+
[#78](https://github.com/agentjido/jido_messaging/issues/78). Authorization
131+
grants and activity projections remain separate concerns.
132+
133+
Applications must not put private Jidoka values in message, thread, or bridge
134+
metadata as a workaround. The strict continuity types are the only supported
135+
messaging-side continuity record.

lib/jido_messaging.ex

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -624,6 +624,63 @@ defmodule Jido.Messaging do
624624
Jido.Messaging.HistoryScope.new(__MODULE__, room_ids, metadata)
625625
end
626626

627+
@doc "Create or update a messaging reference to Jidoka-owned continuity"
628+
def put_thread_continuity(attrs) do
629+
Jido.Messaging.put_thread_continuity(__jido_messaging__(:runtime), attrs)
630+
end
631+
632+
@doc "Get a thread continuity link within an authorized history scope"
633+
def get_thread_continuity(thread_id, scope) do
634+
Jido.Messaging.get_thread_continuity(
635+
__MODULE__,
636+
__jido_messaging__(:runtime),
637+
thread_id,
638+
scope
639+
)
640+
end
641+
642+
@doc "Resolve a usable opaque Jidoka continuity reference"
643+
def resolve_thread_continuity(thread_id, scope) do
644+
Jido.Messaging.resolve_thread_continuity(
645+
__MODULE__,
646+
__jido_messaging__(:runtime),
647+
thread_id,
648+
scope
649+
)
650+
end
651+
652+
@doc "Change the availability state of a thread continuity link"
653+
def set_thread_continuity_status(thread_id, status, expected_revision, opts \\ []) do
654+
Jido.Messaging.set_thread_continuity_status(
655+
__jido_messaging__(:runtime),
656+
thread_id,
657+
status,
658+
expected_revision,
659+
opts
660+
)
661+
end
662+
663+
@doc "Clear a thread continuity link and keep a durable marker"
664+
def clear_thread_continuity(thread_id, expected_revision, opts \\ []) do
665+
Jido.Messaging.clear_thread_continuity(
666+
__jido_messaging__(:runtime),
667+
thread_id,
668+
expected_revision,
669+
opts
670+
)
671+
end
672+
673+
@doc "Return a scoped canonical transcript and its opaque Jidoka link"
674+
def jidoka_continuity_context(thread_id, scope, opts \\ []) do
675+
Jido.Messaging.jidoka_continuity_context(
676+
__MODULE__,
677+
__jido_messaging__(:runtime),
678+
thread_id,
679+
scope,
680+
opts
681+
)
682+
end
683+
627684
@doc "Return messages sent by one canonical participant in the allowed history scope"
628685
def participant_transcript(participant_id, scope, opts \\ []) do
629686
Jido.Messaging.participant_transcript(
@@ -1736,6 +1793,36 @@ defmodule Jido.Messaging do
17361793
end
17371794
end
17381795

1796+
@doc "Create or update a messaging reference to Jidoka-owned continuity."
1797+
def put_thread_continuity(runtime, attrs) do
1798+
Jido.Messaging.Continuity.put(runtime, attrs)
1799+
end
1800+
1801+
@doc "Get a thread continuity link within an authorized history scope."
1802+
def get_thread_continuity(instance_module, runtime, thread_id, scope) do
1803+
Jido.Messaging.Continuity.get(instance_module, runtime, thread_id, scope)
1804+
end
1805+
1806+
@doc "Resolve a usable opaque Jidoka continuity reference."
1807+
def resolve_thread_continuity(instance_module, runtime, thread_id, scope) do
1808+
Jido.Messaging.Continuity.resolve(instance_module, runtime, thread_id, scope)
1809+
end
1810+
1811+
@doc "Change the availability state of a thread continuity link."
1812+
def set_thread_continuity_status(runtime, thread_id, status, expected_revision, opts \\ []) do
1813+
Jido.Messaging.Continuity.set_status(runtime, thread_id, status, expected_revision, opts)
1814+
end
1815+
1816+
@doc "Clear a thread continuity link and keep a durable marker."
1817+
def clear_thread_continuity(runtime, thread_id, expected_revision, opts \\ []) do
1818+
Jido.Messaging.Continuity.clear(runtime, thread_id, expected_revision, opts)
1819+
end
1820+
1821+
@doc "Return a scoped canonical transcript and its opaque Jidoka link."
1822+
def jidoka_continuity_context(instance_module, runtime, thread_id, scope, opts \\ []) do
1823+
Jido.Messaging.Continuity.context(instance_module, runtime, thread_id, scope, opts)
1824+
end
1825+
17391826
@doc """
17401827
Return participant-scoped canonical history within an explicit room scope.
17411828

0 commit comments

Comments
 (0)