19 External Controller Guide
March 29, 2026 ยท View on GitHub
DeepScientist already exposes enough durable state to support an outer orchestration layer without patching core runtime code.
This guide explains the minimal public pattern for external controllers that:
- inspect recent quest state
- decide whether the current run should continue
- enqueue a routed follow-up message through the quest mailbox
- optionally stop the current run through
quest_control - record a durable report explaining why the guard fired
This is intentionally lighter than a plugin framework.
When to use an external controller
Use an external controller when the rule is:
- project-specific
- expensive to hard-code into global prompts or skills
- better treated as outer governance than as core runtime behavior
Examples:
- publishability admission rules before paper-facing writing
- repeated figure-polish loops that monopolize the frontier
- lab-specific stop / branch policies
Public contracts you can rely on
The safest extension surface is the existing durable runtime contract:
- quest mailbox
- queued user-facing messages are stored under
.ds/user_message_queue.json
- queued user-facing messages are stored under
- recent quest state
- runtime state, artifact state, and connector-visible outputs are already durable files
- daemon quest control
POST /api/quests/<quest_id>/control
- connector-visible durable reports
- write your own report under the quest tree so the next turn can cite it
Prefer these contracts over prompt patching, private monkey-patching, or editing installed package files.
Minimal controller loop
An external controller usually follows this sequence:
- Read the latest durable quest state.
- Decide whether a guard condition is active.
- Write a durable report describing:
- what was observed
- why it matters
- the recommended next route
- If intervention is needed:
- optionally stop the current run through
quest_control - enqueue one clear routed mailbox message for the next turn
- optionally stop the current run through
The mailbox message should explain the conclusion, not dump raw logs.
Example control flow
read quest state
-> detect low-yield loop or route violation
-> write durable report
-> stop current run if needed
-> enqueue one mailbox message with the required next route
Example quest_control request
curl -X POST http://127.0.0.1:20999/api/quests/<quest_id>/control \
-H 'Content-Type: application/json' \
-d '{
"action": "stop",
"source": "external-controller"
}'
Example mailbox intervention shape
The exact queue file is runtime-owned, so your controller should preserve the existing schema and only append a normal user-style message payload.
The message content should be short and actionable, for example:
Hard control message from external orchestration layer: stop the current figure loop.
Return to the main line and do one bounded route next:
1. literature scout
2. reference expansion
3. manuscript body revision
Example controllers you can adapt
Example 1: publishability admission guard
This controller is useful when a quest is drifting into paper-facing writing before the evidence line is ready.
Typical inputs:
- the latest verification note
- the current draft / summary state
- recent baseline or utility results
Typical stop condition:
- the claimed paper direction still has weak support
- one or more mandatory evidence items are still missing
Typical intervention:
- Write
reports/publishability_guard.mdexplaining the missing support. - Stop the current write-heavy run through
quest_control. - Enqueue one mailbox message that routes the next turn back to
idea,analysis, or a bounded evidence-repair step.
Example mailbox text:
External controller: do not continue manuscript-facing writing yet.
Reason: the current evidence line does not pass the publishability admission gate.
Next route: return to one bounded evidence-building step before write resumes.
Example 2: figure-loop guard
This controller is useful when the frontier is monopolized by repeated reopen / polish cycles on the same figure.
Typical inputs:
- recent figure artifact history
- repeated reopen events
- the latest review or summary note
Typical stop condition:
- the same figure has been reopened multiple times without improving the main claim
- the next useful action is no longer figure polish, but evidence repair or manuscript revision
Typical intervention:
- Write
reports/figure_loop_guard.mdsummarizing the loop. - Stop the current figure branch if needed.
- Enqueue one mailbox message that names exactly one next route.
Example mailbox text:
External controller: stop the current figure-polish loop.
Reason: repeated reopen cycles are no longer improving the main evidence line.
Next route: return to one bounded manuscript or analysis task.
Example connector customization surface
The control logic should stay connector-agnostic. In practice, adapting the same controller to a different connector usually means changing only the message profile, not the stop logic or durable report contract.
For example:
connector_profiles:
weixin:
summary_style: concise
max_route_options: 2
include_report_path: true
telegram:
summary_style: concise
max_route_options: 3
include_report_path: true
studio:
summary_style: detailed
max_route_options: 4
include_report_excerpt: true
The controller decision stays the same:
- read durable state
- decide whether to stop
- write a durable report
- enqueue one routed mailbox message
What changes per connector is only how much detail you surface to the human operator.
Durable report shape
Keep reports simple and auditable.
A good report usually includes:
generated_atquest_idstatusrecommended_actionblockersevidence_summary
Markdown plus a machine-readable JSON companion is a practical pattern.
What not to rely on
Avoid building controllers that depend on:
- private prompt text offsets
- internal temporary logs that are not documented durable state
- patching installed package files inside
site-packages - undocumented frontend-only state
If a controller needs one of those, the contract is not stable enough yet.
Design recommendations
- keep each controller focused on one question
- prefer additive reports over hidden side effects
- stop only when the next routed action is clear
- keep domain- or lab-specific policy outside core defaults
- treat external controllers as optional governance, not as required runtime plumbing
Good first controllers
If you want to start small, begin with one of these:
- a publishability admission guard for paper-mode quests
- a figure-loop guard that stops repeated reopen cycles
- a route-drift guard that blocks accidental
writetransitions before evidence is ready