Skip to content

feat: natural-language orchestration, DeepSeek provider, coordinator hardening#22

Open
ysfscream wants to merge 3 commits into
id:masterfrom
ysfscream:runtime-api
Open

feat: natural-language orchestration, DeepSeek provider, coordinator hardening#22
ysfscream wants to merge 3 commits into
id:masterfrom
ysfscream:runtime-api

Conversation

@ysfscream

Copy link
Copy Markdown

Summary

Backend changes that power the new dashboard's runtime view. Self-contained on the Python side (no frontend in this PR).

Runtime API

  • Natural-language planner on the skitter agent: plan a new composed app from the live discovery registry, or route a request to an existing app so the normal coordinator session path executes it.
  • DeepSeek LLM provider across config, the setup wizard, the LLM client, and env/docs (default model deepseek-chat).
  • Graph generation now requires every selected agent to be used.

Coordinator hardening

  • Guard the main MQTT message loop so a single bad request can no longer tear down the coordinator.
  • Track agent online/offline status from discovery (a2a-status) and keep offline agents out of LLM planning (the registry's offline filter was previously inert).
  • Cap per-task event result/error payloads to bound MQTT packet size.

Cleanup + tests

  • llm.strip_code_fence and discovery.extract_app_tasks remove duplicated fence-stripping / app-extension parsing.
  • Tighten _resolve_app_ref so a short reference can't resolve to the wrong app.
  • Unit + E2E coverage for the above.

Verification

  • uv run pytest tests/unit tests/test_e2e.py293 passed (EMQX via docker compose).
  • uvx ruff format / uvx ruff check clean.

…hardening

Runtime API:
- natural-language planner via the `skitter` agent: plan a new composed
  app from the live discovery registry, or route a request to an existing
  app so the normal coordinator session path handles execution
- DeepSeek LLM provider (config, setup wizard, llm client, env/docs)
- graph generation now requires every selected agent to be used

Coordinator robustness:
- guard the main MQTT message loop so one bad request can no longer tear
  down the coordinator
- track agent online/offline status from discovery (a2a-status) and keep
  offline agents out of LLM planning
- cap per-task event result/error payloads to bound MQTT packet size

Shared helpers + tests:
- llm.strip_code_fence and discovery.extract_app_tasks remove duplicated
  fence-stripping / app-extension parsing
- tighten _resolve_app_ref to avoid resolving the wrong app
- unit + e2e coverage for the above
Copilot AI review requested due to automatic review settings June 29, 2026 09:44

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Skitter’s coordinator/runtime backend to support natural-language orchestration (planning new workflows from discovered agents or routing to an existing workflow), adds a DeepSeek LLM provider option, and hardens coordinator behavior around discovery liveness and runtime/event publishing.

Changes:

  • Add a natural-language planner to the skitter runtime API that can create new apps or route requests to run an existing app.
  • Add DeepSeek as an LLM provider (config, setup defaults, LLM client behavior, and docs/env examples).
  • Improve coordinator robustness (per-message guard in the MQTT loop, discovery online/offline tracking, and bounded event payload sizes), plus expanded unit/E2E test coverage.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/unit/test_setup.py Adds coverage for DeepSeek default model selection in setup.
tests/unit/test_runtime_api.py Adds/extends tests for natural-language planning, app routing, discovery status handling, and task metadata normalization.
tests/unit/test_llm.py Adds test coverage for DeepSeek base URL + chat API path.
tests/unit/test_graph.py Adds tests for required-agent enforcement and retry behavior in graph generation.
tests/unit/test_discovery.py Adds tests for task-agent card detection helper.
tests/unit/test_coordinator.py Updates prompt ordering expectations and adds offline-agent filtering test for registry listing.
tests/test_e2e.py Updates E2E mocking to match new graph generator signature.
skitter/setup.py Adds DeepSeek as a selectable provider and default model mapping.
skitter/runtime_api.py Implements natural-language planning/routing, adds new result types, and enriches app card task metadata.
skitter/llm.py Adds DeepSeek base URL behavior and shared code-fence stripping helper.
skitter/graph_gen.py Adds required-agent validation support and reuses shared code-fence stripping.
skitter/discovery.py Adds helpers to normalize app tasks/needs and detect task-agent cards.
skitter/coordinator/service.py Adds runtime routing for “run app”, improves event payloads, liveness-aware discovery updates, and guards the MQTT loop.
skitter/coordinator/reply_handler.py Publishes richer/truncated task/session event payloads on completion/failure paths.
skitter/coordinator/registry.py Tracks agent online/offline status and filters offline agents from selection lists.
skitter/config.py Documents DeepSeek as a supported llm.api option.
docs/usage.md Updates usage docs for DeepSeek provider and environment variables.
docs/architecture.md Documents natural-language runtime behavior and updated coordinator capabilities.
CONTRIBUTING.md Updates contributor-facing env var docs for DeepSeek support.
.gitignore Adds frontend-related ignore patterns (node_modules/build artifacts).
.env.example Updates example env to include DeepSeek option and model.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread skitter/runtime_api.py Outdated
Comment thread skitter/graph_gen.py Outdated
Comment thread skitter/coordinator/service.py
Comment thread skitter/runtime_api.py Outdated
Comment thread skitter/llm.py Outdated

log = logging.getLogger("skitter.llm")

DEEPSEEK_BASE_URL = "https://api.deepseek.com"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

I don't think it is necessary to add special treatment for deepseek. Have you tried to use openai-completions and override base_url?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

yes

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right — removed the dedicated deepseek provider. DeepSeek is OpenAI-compatible, so it now works via api: openai-completions + base_url: https://api.deepseek.com, documented in docs/usage.md. Pushed in 49d9668.

🤖 Addressed by Claude Code

…lidation

- remove the DeepSeek LLM provider special-casing (base_url default, routing,
  setup choice, tests). DeepSeek is OpenAI-compatible, so it works via
  `openai-completions` + base_url, now documented as such (review feedback)
- runtime planner: stop echoing the raw exception in the user-facing message
  (it is already logged)
- validate_graph: reject a non-list `needs` with a clear error instead of
  iterating it character by character; add a regression test
generate_graph no longer forces every provided agent into the graph:
validate_graph loses the required_agent_ids check and the parameter, and
the prompt now asks the model to use only the agents relevant to the
instructions. This removes a brittle failure mode where the NL planner
(which defaults to all available agents) could be forced to build a graph
around agents unrelated to the request, failing generation outright.

Also drop a stale apps/* pattern from .gitignore (frontend lives in web/,
which has its own .gitignore; the top-level node_modules/ catch-all stays).
@ysfscream

Copy link
Copy Markdown
Author

A couple of intentional design notes for reviewers (things deliberately kept as-is):

  • Typed QueryResult union (8 subclasses). Each is constructed and maps to a distinct outcome; the four single-use ones (CancelSessionResult, CreateAppResult, DeleteAppResult, RunAppResult) each drive a different coordinator side effect. runtime_api only receives db + registry, so it returns intent and the coordinator (which owns the MQTT client + session state) executes it. Collapsing these into one result with an action field plus optional fields would trade away type safety, so they stay a tagged union.

  • _resolve_app_ref fuzzy fallback. It matches exact id, then exact name, then a single unambiguous name-substring (never the free-text description, never when more than one name matches). That substring step is a deliberate tolerance for near-miss references from the planner, not an accidental loosening.

🤖 Note added by Claude Code

@id

id commented Jun 30, 2026

Copy link
Copy Markdown
Owner

@ysfscream thanks, the coordinator hardening and shared-helper cleanup are good and I'd like to keep those. My one concern is the NL planner running an LLM call inside the coordinator. The coordinator is a singleton handling every app request and agent reply on one serial MQTT loop, so a multi-second planner call there head-of-line-blocks every in-flight workflow. It has to stay lean and LLM-free.

Same feature, different home: make the planner its own A2A agent. It subscribes to its own request topic, reads discovery for the live agent/app list (agent status and task-agent filtering go with it), runs the LLM, then either replies, sends create app {json} to the coordinator's existing runtime API, or sends a plain A2A request to the target app's topic to run it. NL request to skitter becomes just another A2A request to an agent which is naturally parallelized and decoupled. Running an app is already what the coordinator does for any app request, so the in-coordinator run_app routing just disappears. For the dashboard (#21), point the chat console at that agent; "run app" is already a direct request and is unchanged.

Separately, worth thinking about: if an app becomes its own planner and executor (its own templates plus memory, generating and running its graph), you stop needing a coordinator at all.

Suggested split: the hardening part (merge as-is), and the planner reworked as its own agent.

@ysfscream

Copy link
Copy Markdown
Author

Thanks Ivan, this makes sense to me.

I agree the coordinator should stay lean and LLM-free. The planner-as-agent direction fits Skitter much better than putting natural-language orchestration inside the coordinator loop.

I'll rethink the design and implementation around that split: keep the coordinator hardening/shared cleanup separate, and move the NL planner into its own A2A agent.

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.

3 participants