xworkmate-app/docs/architecture/openclaw-session-key-state-and-data-flow.md
2026-06-06 13:56:03 +08:00

7.1 KiB

OpenClaw Session Key State And Data Flow

This note is the source-of-truth for the XWorkmate App -> Bridge -> OpenClaw session identity boundary.

Terms

Term Owner Example Meaning
TaskThread.sessionKey XWorkmate App draft:1780658097668838-1 App-local thread identity. It owns UI state, queue state, local workspace, and persisted TaskThread data.
appThreadKey XWorkmate typed integration metadata draft:1780658097668838-1 The typed cross-repo name for the App TaskThread identity. It is currently the same value as TaskThread.sessionKey.
local thread workspace XWorkmate App ~/.xworkmate/threads/draft-1780658097668838-1 Filesystem path derived from the App thread key. Path formatting is not a protocol key.
openclawSessionKey OpenClaw native session layer agent:main:draft:1780658097668838-1 OpenClaw SessionEntry key and chat.send.sessionKey value.
runId OpenClaw task/runtime layer run-openclaw-... The active OpenClaw run/task id. Bridge updates to the actual OpenClaw runId if chat.send returns one different from the initial request id.

Rules:

  • App code may keep sessionKey for local TaskThread and UI session APIs.
  • App session.start only carries appThreadKey in typed metadata; it does not preemptively choose the OpenClaw native session key.
  • Bridge resolves or reuses openclawSessionKey, persists the mapping, and then uses that key for OpenClaw native APIs such as chat.send.
  • Plugin lookup must read appThreadKey -> openclawSessionKey from SessionEntry.pluginExtensions, not from string replace or reverse parsing.
  • xworkmate.tasks.get must be invoked with the persisted association (appThreadKey, openclawSessionKey, runId/taskId), not with a legacy sessionKey alias.

State Graph

stateDiagram-v2
  [*] --> AppDraftCreated
  AppDraftCreated: TaskThread.sessionKey = appThreadKey
  AppDraftCreated: local workspace path is derived for filesystem use

  AppDraftCreated --> Submitted: App sends session.start
  Submitted: params.sessionId/threadId = App thread identity
  Submitted: metadata.xworkmateTaskArtifactContract.appThreadKey
  Submitted: metadata.xworkmateTaskArtifactContract.expectedArtifactDirs

  Submitted --> BridgePrepared: Bridge calls xworkmate.session.prepare
  BridgePrepared: schemaVersion = 1
  BridgePrepared: appThreadKey
  BridgePrepared: openclawSessionKey
  BridgePrepared: expectedArtifactDirs
  BridgePrepared: requestId/externalTaskId

  BridgePrepared --> MappingPersisted: Plugin patches SessionEntry.pluginExtensions
  MappingPersisted: source = bridge_prepare or session_start
  MappingPersisted: conflicts fail closed

  MappingPersisted --> OpenClawRunning: Bridge calls chat.send
  OpenClawRunning: chat.send.sessionKey = openclawSessionKey
  OpenClawRunning: idempotencyKey = requestId

  OpenClawRunning --> OpenClawRunning: agent.wait still running
  OpenClawRunning --> RunIdReprepared: chat.send returns actual runId
  RunIdReprepared: Bridge prepares actual run scope
  RunIdReprepared --> OpenClawRunning

  OpenClawRunning --> Terminal: agent.wait or native task-registry terminal state
  Terminal --> ArtifactSnapshot: collect-and-snapshot
  ArtifactSnapshot --> ArtifactExport: xworkmate.artifacts.export
  ArtifactExport --> AppReady: session.update or xworkmate.tasks.get result
  AppReady --> [*]

  OpenClawRunning --> ReconcileAfterReconnect: App/Bridge reconnect
  ReconcileAfterReconnect --> MappingPersisted: lookup mapping by appThreadKey
  ReconcileAfterReconnect --> Terminal: query native task-registry by openclawSessionKey/runId

Data Flow

flowchart LR
  subgraph App["xworkmate-app"]
    TT["TaskThread\nsessionKey = draft:..."]
    Meta["Typed metadata\nschemaVersion=1\nappThreadKey\nexpectedArtifactDirs"]
    Assoc["OpenClawTaskAssociation\nappThreadKey\nopenclawSessionKey\nrunId"]
  end

  subgraph Bridge["xworkmate-bridge"]
    Validate["validate request"]
    PrepareParams["prepare params\nschemaVersion=1\nappThreadKey\nopenclawSessionKey\nexpectedArtifactDirs\nrequestId/externalTaskId"]
    NativeChat["OpenClaw native chat.send\nsessionKey = openclawSessionKey\nidempotencyKey"]
    Events["normalize native events\nsession.update to App"]
  end

  subgraph OpenClaw["openclaw.svc.plus"]
    SessionEntry["SessionEntry\nkey = openclawSessionKey"]
    NativeTask["native task-registry\nrunId/taskId/status"]
    Transcript["transcript/runtime events"]
  end

  subgraph Plugin["openclaw-multi-session-plugins"]
    Prepare["xworkmate.session.prepare"]
    Ext["pluginExtensions mapping\nxworkmate.sessionMapping"]
    TasksGet["xworkmate.tasks.get\nnative registry lookup"]
    Artifact["artifact resolver\nexpectedArtifactDirs"]
  end

  TT --> Meta
  Meta --> Validate
  Validate --> PrepareParams
  PrepareParams --> Prepare
  Prepare --> Ext
  Ext --> SessionEntry
  PrepareParams --> NativeChat
  NativeChat --> SessionEntry
  NativeChat --> NativeTask
  NativeTask --> Transcript
  Transcript --> Events
  Events --> Assoc
  Assoc --> TasksGet
  TasksGet --> NativeTask
  TasksGet --> Artifact
  Artifact --> Events

Field Contract

App session.start:

{
  "sessionId": "draft:1780658097668838-1",
  "threadId": "draft:1780658097668838-1",
  "metadata": {
    "xworkmateTaskArtifactContract": {
      "schemaVersion": 1,
      "appThreadKey": "draft:1780658097668838-1",
      "expectedArtifactDirs": [
        "artifacts/",
        "reports/",
        "exports/",
        "assets/",
        "assets/images/",
        "dist/"
      ]
    }
  }
}

Bridge xworkmate.session.prepare:

{
  "schemaVersion": 1,
  "appThreadKey": "draft:1780658097668838-1",
  "openclawSessionKey": "agent:main:draft:1780658097668838-1",
  "runId": "turn-...",
  "requestId": "turn-...",
  "externalTaskId": "turn-...",
  "expectedArtifactDirs": ["artifacts/", "reports/"]
}

Plugin writes:

{
  "schemaVersion": 1,
  "appThreadKey": "draft:1780658097668838-1",
  "openclawSessionKey": "agent:main:draft:1780658097668838-1",
  "expectedArtifactDirs": ["artifacts/", "reports/"],
  "source": "bridge_prepare",
  "createdAt": "2026-06-05T00:00:00.000Z",
  "updatedAt": "2026-06-05T00:00:00.000Z"
}

App xworkmate.tasks.get params:

{
  "appThreadKey": "draft:1780658097668838-1",
  "openclawSessionKey": "agent:main:draft:1780658097668838-1",
  "runId": "run-openclaw-...",
  "includeArtifacts": true
}

Final Consistency

For the debug case:

  • App local key: TaskThread.sessionKey = appThreadKey = draft:1780658097668838-1
  • App local path: ~/.xworkmate/threads/draft-1780658097668838-1
  • OpenClaw URL: ?session=agent%3Amain%3Adraft%3A1780658097668838-1
  • OpenClaw key: openclawSessionKey = agent:main:draft:1780658097668838-1
  • Durable mapping: SessionEntry.pluginExtensions["openclaw-multi-session-plugins"]["xworkmate.sessionMapping"]

This keeps the visible IDs aligned for debugging while avoiding string inference as the lookup source. The deterministic agent:main:<appThreadKey> creation policy is only used before the mapping exists; after xworkmate.session.prepare the durable mapping is authoritative.