Skip to content

fix(docker): ship demo-image plugins as directory bundles#567

Open
staging-devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1780752485-demo-image-plugin-bundles
Open

fix(docker): ship demo-image plugins as directory bundles#567
staging-devin-ai-integration[bot] wants to merge 1 commit into
mainfrom
devin/1780752485-demo-image-plugin-bundles

Conversation

@staging-devin-ai-integration
Copy link
Copy Markdown
Contributor

@staging-devin-ai-integration staging-devin-ai-integration Bot commented Jun 6, 2026

Summary

  • Dockerfile.demo copied bare plugin .so files into plugins/native/, but the loader only scans directory bundles (plugins/native/<id>/ with a plugin.yml + the library) — so skit serve on the -demo image logged "no plugins found" and any TTS/STT request failed with HTTP 500 "node kind not found". Each plugin builder stage now emits a bundle dir (/build/dist/native/<id>/ with plugin.yml + .so) and the runtime stage copies bundles. Side benefit: the image no longer ships the whisper plugin's full source tree (the old COPY /build/plugins swept it in).
  • The observability sample's entrypoint.sh shim (from docs: local observability stack sample, guide, and skill #552) now passes through bundles shipped by fixed images while still reassembling the layout for the pinned v0.5.0-demo, so the sample works with both old and fixed images. It can be deleted once the stack pins a fixed tag.
  • From the issue's related findings: the speech gateway's hardcoded ggml-base.en-q5_1.bin (not present in the demo image) is now configurable via GATEWAY_STT_MODEL / --stt-model, defaulting to models/ggml-tiny-q5_1.bin — the model the demo image actually ships. The bundled sample pipelines were already rewritten to that model inside the image by an existing sed step.
  • Not addressed here (per issue): re-pointing latest-demo — it lags released versions and predates the plugin.call.* metrics; publishing a new release/demo tag after this merge resolves it naturally.

Review & Validation

Local validation (image built from this branch, docker run -p 4545:4545):

  • skit serve loaded all 11 plugins, each logging Loaded native plugin from directory bundle (aac-encoder, helsinki, kokoro, matcha, piper, pocket-tts, sensevoice, slint, supertonic, vad, whisper); no "no plugins found" / "Bare plugin file" warnings.
  • Kokoro TTS oneshot POST /api/v1/process with the bundled kokoro-tts.yml returned HTTP 200 with a valid WebM/Opus file.
  • Whisper STT oneshot with the bundled speech_to_text.yml (fed the TTS output re-muxed to Ogg/Opus) returned HTTP 200 with the correct transcription: "Hello from the fixed stream kit demo image."
  • Observability shim pass-through: ran the new image with samples/observability/skit/entrypoint.sh + SK_PLUGINS__DIRECTORY=/opt/streamkit/np — all 11 bundles copied through and loaded. The old-image path is unchanged (the pass-through loop only fires for dirs containing both plugin.yml and a .so, which the old image's stray whisper source dir does not).
  • just lint and just test pass.

Fixes #553

Link to Devin session: https://staging.itsdev.in/sessions/c11dd028bf294fe5a7584109304f3fc0
Requested by: @streamer45


Devin Review

Status Commit
🟢 Reviewed 466647e
Open in Devin Review (Staging)

The -demo images copied bare plugin .so files into plugins/native/, but
the loader only scans directory bundles (plugins/native/<id>/ with a
plugin.yml + the library), so 'skit serve' found no plugins and TTS/STT
requests failed with 'node kind not found'. Each plugin builder stage
now emits a bundle directory (plugin.yml + .so) and the runtime stage
copies bundles instead of bare files.

The observability sample's entrypoint shim now passes through bundles
shipped by fixed images while still reassembling the layout for older
ones, and the speech gateway's Whisper model is configurable via
GATEWAY_STT_MODEL/--stt-model with a default matching the model the
demo image actually ships (ggml-tiny-q5_1.bin).

Fixes #553

Signed-off-by: streamkit-devin <devin@streamkit.dev>
@staging-devin-ai-integration
Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

Copy link
Copy Markdown
Contributor Author

@staging-devin-ai-integration staging-devin-ai-integration Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 4 potential issues.

Open in Devin Review (Staging)
Debug

Playground

- kind: plugin::native::whisper
params:
model_path: models/ggml-base.en-q5_1.bin
model_path: %s
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Unquoted fmt.Sprintf substitution into YAML template is safe for file paths but fragile for arbitrary strings

The model_path: %s template at examples/speech-gateway/cmd/gateway/main.go:58 is substituted via fmt.Sprintf at line 137. Because the value is unquoted YAML, a model path containing YAML-special characters (#, : , [, {, etc.) would produce invalid or misinterpreted YAML. This is not a bug because: (1) the old hardcoded value was also unquoted (model_path: models/ggml-base.en-q5_1.bin), so no regression; (2) the value comes from operator configuration (env var / CLI flag), not HTTP request input; (3) realistic model file paths never contain these characters. Wrapping in quotes (model_path: "%s") would make it more robust but would require escaping embedded quotes. The current approach is consistent with how all other YAML values in both pipeline templates are written.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

defaultSkitURL = "http://127.0.0.1:4545"
defaultListenAddr = ":8080"
// Matches the Whisper model bundled in the -demo images.
defaultSTTModel = "models/ggml-tiny-q5_1.bin"
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: Default STT model changed from base.en to tiny — intentional fix, not a regression

The old hardcoded model path was models/ggml-base.en-q5_1.bin, but the demo Dockerfile (line 173) only ships ggml-tiny-q5_1.bin. The old default would silently fail at pipeline execution time on the demo image. The new default models/ggml-tiny-q5_1.bin correctly matches the bundled model. This is a bug fix for users running the gateway against the demo image.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Comment on lines +21 to +33
for dir in "$SRC"/*/; do
[ -d "$dir" ] || continue
id=$(basename "$dir")
if [ -f "$dir/plugin.yml" ] && ls "$dir"/*.so > /dev/null 2>&1; then
cp -r "$SRC/$id" "$DST/$id"
echo "copied plugin bundle: $id"
fi
done

for manifest in /repo-manifests/*/plugin.yml; do
[ -f "$manifest" ] || continue
id=$(basename "$(dirname "$manifest")")
[ -d "$DST/$id" ] && continue
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: entrypoint.sh correctly handles both old and new plugin layouts without conflict

The updated entrypoint.sh first copies any directory bundles from $SRC (lines 21-28), then falls back to assembling bundles from repo manifests for any remaining bare .so files (lines 30-41). The [ -d "$DST/$id" ] && continue guard at line 33 prevents the second loop from overwriting bundles already copied by the first loop. Since $DST is freshly created at line 19, there are no pre-existing directory issues. The cp -r at line 25 creates the destination directory (not copies into it) because $DST/$id doesn't exist yet at that point. For old images with only bare .so files, the first loop finds no directories with plugin.yml, so the second loop handles everything — preserving backward compatibility.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Comment thread Dockerfile.demo
# IMPORTANT: Use --chown=app:app on every COPY to avoid a bulk `chown -R` later
# which would duplicate every file in a new layer (~12GB waste).
COPY --chown=app:app --from=whisper-builder /build/plugins /opt/streamkit/plugins
COPY --chown=app:app --from=whisper-builder /build/dist /opt/streamkit/plugins
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 Info: First COPY uses /build/dist (full tree) while subsequent COPYs use specific subdirectories

The whisper plugin is copied with COPY --from=whisper-builder /build/dist /opt/streamkit/plugins (line 647), which bootstraps the entire plugins/native/ tree structure. All subsequent plugins use targeted paths like COPY --from=kokoro-builder /build/dist/native/kokoro /opt/streamkit/plugins/native/kokoro (line 651). This asymmetry is intentional: the first COPY creates the native/ directory hierarchy, and subsequent COPYs merge individual plugin directories into it. If whisper were ever removed, the first plugin in the list would need to use the full-tree COPY pattern instead to create the directory structure.

Open in Devin Review (Staging)

Was this helpful? React with 👍 or 👎 to provide feedback.

Debug

Playground

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Demo image ships bare plugin .so files; loader expects directory bundles

2 participants