§3.8 second slice: real modes wired (matrix.relevance/downgrade/search,
distillation.score, drift.scorer)
Lands the workflow.Mode adapters for the §3.4 components + the
distillation scorer + drift quantifier. Workflows can now compose
real measurement capabilities; the substrate's parallel
capabilities become composable Lego bricks (per the prior commit's
closing insight).
Modes registered (in observerd's registerBuiltinModes):
Pure-function wrappers (no I/O):
- matrix.relevance → matrix.FilterChunks
- matrix.downgrade → matrix.MaybeDowngrade
- distillation.score → distillation.ScoreRecord
- drift.scorer → drift.ComputeScorerDrift
HTTP-backed:
- matrix.search → POST matrixd /matrix/search
(registered only when matrixd_url is set)
Fixture (kept from §3.8 first slice):
- fixture.echo, fixture.upper
internal/workflow/modes.go:
Each mode follows the same glue pattern: marshal generic input
through a typed struct (free schema validation + clear error
messages), call the underlying capability, return a generic
output map. Roundtrip-via-JSON gives us schema validation
without writing custom field-by-field coercion.
internal/workflow/modes_test.go (10 tests, all PASS):
- matrix.relevance filters adjacency pollution (Connector kept,
catalogd::Registry dropped — same headline as the relevance
smoke, run through the workflow mode)
- matrix.downgrade flips lakehouse→isolation on strong model;
keeps lakehouse on weak (qwen3.5:latest); errors on missing
fields
- distillation.score rates scrum_review attempt_1 as accepted;
rejects empty record
- drift.scorer reports zero drift on matched inputs; errors on
empty inputs slice
- matrix.search HTTP flow round-trips through httptest fake
matrixd; non-OK status surfaces a clear error
scripts/workflow_smoke.sh (5 assertions PASS, was 4):
New assertion #5: real-mode chain
matrix.downgrade (lakehouse + grok-4.1-fast → isolation)
→ distillation.score (scrum_review attempt_1 → accepted)
Proves §3.4 components compose through the workflow runner with
no fixture intermediation. Both nodes ran successfully, runner
recorded provenance, status=succeeded.
Mode listing assertion now expects 7 modes (5 real + 2 fixture)
instead of just the fixtures.
17-smoke regression all green. SPEC §3.8 acceptance gate G3.8.D
("Mode catalog dispatches matrix.search invocation to the matrixd
backend without going through HTTP") still pending — current path
goes through HTTP for matrix.search, which is the cleaner service-
mesh shape but slower than direct in-process. In-process dispatch
when matrixd is co-resident is a future optimization.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+37
-14
@@ -71,7 +71,11 @@ func main() {
|
||||
}
|
||||
|
||||
runner := workflow.NewRunner()
|
||||
registerBuiltinModes(runner, store)
|
||||
// matrixd URL: prefer explicit observerd config field, fall back
|
||||
// to gateway's matrixd_url so a single-toml deploy works without
|
||||
// duplicating the address.
|
||||
matrixdURL := cfg.Gateway.MatrixdURL
|
||||
registerBuiltinModes(runner, store, matrixdURL)
|
||||
|
||||
h := &handlers{store: store, runner: runner}
|
||||
if err := shared.Run("observerd", cfg.Observerd.Bind, h.register, cfg.Auth); err != nil {
|
||||
@@ -187,18 +191,23 @@ func summarizeOutput(output map[string]any) string {
|
||||
return string(bs)
|
||||
}
|
||||
|
||||
// registerBuiltinModes wires the modes the runner knows about. v0
|
||||
// ships with fixture.echo + fixture.upper for testing the runner
|
||||
// mechanics; real-mode integrations (matrix.search, distillation.
|
||||
// score, drift.scorer, llm.chat) land in follow-up commits.
|
||||
// registerBuiltinModes wires the modes the runner knows about. The
|
||||
// pure-function wrappers (matrix.relevance, matrix.downgrade,
|
||||
// distillation.score, drift.scorer) are direct Go calls. matrix.search
|
||||
// is HTTP-backed, pointed at the configured matrixd_url so workflows
|
||||
// can compose retrieval into multi-pass measurement chains.
|
||||
//
|
||||
// Each mode's signature matches workflow.Mode. The store parameter
|
||||
// is reserved for modes that need to record their own ObservedOps
|
||||
// (most don't — the runner records per-node provenance generically).
|
||||
func registerBuiltinModes(r *workflow.Runner, _ *observer.Store) {
|
||||
// Fixture modes (fixture.echo, fixture.upper) stay registered for
|
||||
// the workflow_smoke that proves the runner mechanics independently
|
||||
// of the real modes' availability.
|
||||
//
|
||||
// Real-mode follow-ups still pending:
|
||||
// - playbook.record (HTTP to matrixd)
|
||||
// - playbook.lookup (HTTP to matrixd)
|
||||
// - llm.chat (HTTP to gateway /v1/chat)
|
||||
func registerBuiltinModes(r *workflow.Runner, store *observer.Store, matrixdURL string) {
|
||||
// Fixture modes for runner mechanics smokes.
|
||||
r.RegisterMode("fixture.echo", func(_ workflow.Context, input map[string]any) (map[string]any, error) {
|
||||
// Verbatim copy of input → output. Useful for ref-substitution
|
||||
// chains in smokes.
|
||||
out := make(map[string]any, len(input))
|
||||
for k, v := range input {
|
||||
out[k] = v
|
||||
@@ -206,14 +215,28 @@ func registerBuiltinModes(r *workflow.Runner, _ *observer.Store) {
|
||||
return out, nil
|
||||
})
|
||||
r.RegisterMode("fixture.upper", func(_ workflow.Context, input map[string]any) (map[string]any, error) {
|
||||
// Returns {"upper": strings.ToUpper(input["prompt"])}. Toy
|
||||
// mode for proving DAG ref substitution end-to-end.
|
||||
prompt, _ := input["prompt"].(string)
|
||||
return map[string]any{"upper": strings.ToUpper(prompt)}, nil
|
||||
})
|
||||
|
||||
// Real modes — pure-function wrappers (no I/O).
|
||||
r.RegisterMode("matrix.relevance", workflow.MatrixRelevance)
|
||||
r.RegisterMode("matrix.downgrade", workflow.MatrixDowngrade)
|
||||
r.RegisterMode("distillation.score", workflow.DistillationScore)
|
||||
r.RegisterMode("drift.scorer", workflow.DriftScorer)
|
||||
|
||||
// HTTP-backed modes — only register when their backend URL is set.
|
||||
// matrixd_url defaults to a known address but tests/dev may run
|
||||
// without matrixd.
|
||||
if matrixdURL != "" {
|
||||
hc := &http.Client{Timeout: 30 * time.Second}
|
||||
r.RegisterMode("matrix.search", workflow.MatrixSearch(matrixdURL, hc))
|
||||
}
|
||||
|
||||
_ = store // reserved for future modes that need self-provenance
|
||||
}
|
||||
|
||||
// stub to silence "imported and not used" until a real mode uses it
|
||||
// context still used in decodeJSON via http.Request.Context().
|
||||
var _ = context.Background
|
||||
|
||||
func decodeJSON(w http.ResponseWriter, r *http.Request, v any) bool {
|
||||
|
||||
Reference in New Issue
Block a user