Skip to content

refactor(workspace): generalize worktree session-attribution schema (drop kimaki/opencode-specific field names) #416

@chubes4

Description

@chubes4

Summary

The worktree session-attribution metadata (origin_session capture) hardcodes vendor-specific field names — kimaki_session_id, kimaki_thread_id, kimaki_thread_url, opencode_session_id, opencode_run_id — directly into DMC's schema, value objects, env-var sniffing, smoke tests, and report rendering. This couples DMC, a layer that should be runtime-agnostic, to specific coding-agent runtime brands.

Per the platform's Layer purity rule (now codified in RULES.md under "## Coding"):

A layer must not reference, name, special-case, or know about anything below it. The substrate (e.g. agents-api) doesn't know about transports. A generic transport runtime (e.g. DMC's CLI transport) doesn't know about specific CLI tools (kimaki, cc-connect, telegram). The integration plugin (e.g. wp-coding-agents) is the only place where vendor-specific names live.

DMC's worktree-lifecycle code violates this. The integration layer that should own these names is wp-coding-agents.

Current violation surface

```
$ git grep -nE 'kimaki_session_id|kimaki_thread_id|kimaki_thread_url|opencode_session_id|opencode_run_id'
```

Schema (inc/Abilities/WorkspaceAbilities.php):

  • L1550-L1554: origin_session schema declares the five vendor-specific fields as named properties.

Value object / builder (inc/Workspace/WorktreeContextInjector.php):

  • L404: docblock @return shape hardcodes the field names.
  • L409-L413: summarize_session() reads each field by name from $session.
  • L416: primary_id resolution chains specific brand fields: $kimaki_session_id ?? $opencode_session_id ?? $opencode_run_id ?? $kimaki_thread_id.
  • L420-L424: output array hardcodes brand-named keys.
  • L1139-L1180: env-var sniffer reads KIMAKI_SESSION_ID, KIMAKI_THREAD_ID, KIMAKI_THREAD_URL, OPENCODE_SESSION_ID, OPENCODE_RUN_ID directly into branded keys.

Smoke tests (vendor-name dependent):

  • tests/smoke-worktree-agent-session-lifecycle.php (multiple assertions on branded keys).
  • tests/smoke-worktree-inventory-store.php L128.

Why this is wrong

  1. DMC claims to be runtime-agnostic. The README lists kimaki and opencode as examples of supported runtimes; the plugin description says "Claude Code, OpenCode, kimaki, etc." That positioning is incompatible with hardcoded kimaki_* and opencode_* field names in shared schemas. Either DMC is generic and the brands live in config, or DMC is kimaki+opencode-specific and the prose is wrong.
  2. Adding a new runtime (cc-connect, claude-code-sdk, future tools) requires editing DMC. That's the test the layer-purity rule is built to catch. New vendors should be config or installer-side concerns.
  3. primary_id resolution baking in brand precedence (kimaki_session_id ?? opencode_session_id ?? opencode_run_id ?? kimaki_thread_id) is operationally fragile — it assumes which fields are most authoritative without letting the calling integration declare that itself.
  4. The env-var sniffer reads brand-named env vars directly into brand-named keys, making the runtime → key mapping invisible and undocumented anywhere a new bridge could discover it.

Proposed shape

Replace the typed brand-named map with a generic envelope:

```json
{
"primary_id": "",
"ids": {
"": {
"session_id": "",
"thread_id": "",
"thread_url": "",
"run_id": ""
}
}
}
```

Where:

  • primary_id stays — it's already the unbranded primary identifier, used everywhere downstream. No change to its semantics.
  • ids is keyed by runtime identifier ("kimaki", "opencode", future ones). The runtime ID is a string the integration layer chooses, not something DMC enumerates.
  • Each entry is a flexible string-map. DMC doesn't validate against a closed set of subkeys — integrations declare what they want to capture (session_id, thread_id, etc.).
  • primary_id resolution becomes a single ordered scan of registered runtime IDs, ordered by registration priority. DMC does not bake in a brand precedence.

Env-var sniffer becomes registry-driven

Instead of hardcoding:

```php
$kimaki_session_id = getenv( 'KIMAKI_SESSION_ID' );
// ...
$opencode_run_id = getenv( 'OPENCODE_RUN_ID' );
```

Use a registered map from a new filter (or a WorktreeRuntimeRegistry::register( $runtime_id, $env_var_map ) API):

```php
apply_filters( 'datamachine_code_worktree_runtime_signatures', array() );
// returns e.g.
[
'kimaki' => [ 'session_id' => 'KIMAKI_SESSION_ID', 'thread_id' => 'KIMAKI_THREAD_ID', 'thread_url' => 'KIMAKI_THREAD_URL' ],
'opencode' => [ 'session_id' => 'OPENCODE_SESSION_ID', 'run_id' => 'OPENCODE_RUN_ID' ],
]
```

Then wp-coding-agents (the layer that knows about kimaki and opencode) registers its signatures via that filter. DMC reads the registered map and iterates env vars generically. All brand names move out of DMC into wp-coding-agents.

Acceptance criteria

  • Schema in inc/Abilities/WorkspaceAbilities.php origin_session block contains zero brand names. Likely shape: primary_id (string|null) + ids (object<string, object<string, string|null>>).
  • WorktreeContextInjector::summarize_session() produces output keyed only by primary_id + ids[runtime_id]. No branched lookups by hardcoded brand.
  • WorktreeContextInjector env-var sniffer reads from a registered filter map (e.g. datamachine_code_worktree_runtime_signatures). Default registration in DMC is empty; integrations populate it.
  • wp-coding-agents (separate PR) registers the kimaki + opencode signatures via that filter during setup.sh / runtime install.
  • Smoke tests are updated to register a test runtime via the filter, then assert generic-shape output (no string-literal kimaki_session_id etc. in test assertions).
  • grep -riE 'kimaki|discord|telegram|slack|whatsapp|cc-connect' inc/ tests/ returns zero matches in DMC's source. (Plugin description prose is the only acceptable exception per the layer-purity rule.)
  • Migration: existing rows in workspace inventory storage are upgraded on read — if old branded fields exist (kimaki_session_id, etc.), they're mapped into the new ids envelope under the inferred runtime ID. No data loss.

Out of scope

  • The dispatch architecture (CliChannelTransport, CliChannelRegistry) — already generic, no changes needed.
  • Other ad-hoc kimaki/opencode references outside the worktree-attribution surface, if any exist. (If you find them, file follow-up issues — do not bundle.)
  • The wp-coding-agents PR that registers its runtime signatures. That's a separate sibling PR depending on this one.

Related

  • Layer purity rule (canonical): added to RULES.md on 2026-05-17. Applies retroactively to existing violations including this one.
  • Sibling work (already shipped, transport-agnostic by design): CliChannelTransport / CliChannelRegistry (feat(channels): generic CLI transport runtime for agents/dispatch-message #412, v0.44.0). This issue is the same rule applied to the worktree-attribution surface.
  • Future sibling PR in wp-coding-agents: register kimaki + opencode runtime signatures via the new filter, eliminating the env-var hardcode.

This issue is the operational follow-through on the layer-purity rule. Existing violations are tech debt to be paid down with tracked issues, not grandfathered.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions