mirror of
https://github.com/multica-ai/multica.git
synced 2026-07-05 21:39:54 +02:00
The MUL-3560 slim runtime brief — kind-driven dispatcher, per-section
gating, prose compression for ~7k chars saved on the typical
comment-triggered task — now ships behind the `runtime_brief_slim`
feature flag wired via the framework-level service from MUL-3615.
Default: OFF in every environment (production stays on the legacy
brief that has shipped for ~2 years). Staging opts in via the YAML
rule set; ops can override per-process with `FF_RUNTIME_BRIEF_SLIM=true`.
Production is held back until staging has burned in long enough that
we are confident the slim brief does not regress agent behaviour.
Architecture (one toggle point, two code paths, both fully tested):
buildMetaSkillContent (runtime_config.go)
│
└─ useSlimBrief() → false (default)
│ → fall through to the legacy verbose body that ships on
│ main today — byte-for-byte unchanged, no migration risk
│
└─ useSlimBrief() → true
→ buildMetaSkillContentSlim (runtime_config_sections.go)
→ classifyTask → 5-way kind switch → per-section writers
BuildCommentReplyInstructions takes the same gate, so the per-turn
comment prompt and the runtime brief stay in sync on which template
they emit.
What's in this PR:
- runtime_config_flag.go (new): package-scope `runtimeFlags` atomic
pointer + `SetFeatureFlags` setter + `useSlimBrief` toggle point.
Nil-safe: a daemon that forgets to wire the service falls back to
legacy, no panic.
- runtime_config_kind.go (new): `taskKind` enum + `classifyTask` +
`hasIssueContext` predicate. Used only by the slim path.
- runtime_config_sections.go (new): the slim brief itself —
`buildMetaSkillContentSlim` + per-section `writeXxx` helpers
+ `writeAvailableCommandsQuickCreate` minimal variant +
`writeBackgroundTaskSafetySlim` compressed safety section. The
Section × Kind matrix is documented inline on
`buildMetaSkillContentSlim` and the test below checks the
dispatcher does not diverge from the spec.
- reply_instructions.go: `BuildCommentReplyInstructions` gains a
short slim-or-legacy prelude; new `buildCommentReplyInstructionsSlim`
is the compressed cookbook (defers the shell-hazard rationale to
`## Comment Formatting`).
- runtime_config.go: `buildMetaSkillContent` gains a 2-line
dispatcher at the top; the legacy body is otherwise untouched.
- runtime_config_kind_test.go (new): canaries for both paths.
- TestClassifyTask: 5 kinds + 3 tiebreak cases.
- TestTaskKindHasIssueContext: predicate semantics.
- TestSlimFlagOffUsesLegacy: nil flag service → legacy path
(renders "Get full issue details.", a legacy-only substring).
- TestSlimFlagOnUsesSlim: flag on → slim path (renders "full
issue.", a slim-only one-liner) AND must NOT render legacy
"Get full issue details.".
- TestBuildMetaSkillContentSlimKindMatrix: locks the per-kind
section set; heading match is line-anchored so inline references
don't trip absence assertions.
- TestSlimQuickCreateAvailableCommands: locks the minimal-variant
content for quick-create (issue create present, every other
Core command absent).
- TestSlimBriefIsSubstantiallyShorter: ≥ 30% reduction guard so
a future change can't accidentally re-bloat the slim path back
to legacy levels.
- cmd/server/main.go: now calls `execenv.SetFeatureFlags(flags)`
immediately after constructing the feature flag service.
Measured impact (slim vs legacy, claude provider, realistic fixture
with 2 repos + 2 skills + member initiator):
legacy = 19567 chars
slim = 11868 chars Δ = -7699 (-39.3%)
Verification:
- go vet ./internal/daemon/... ./cmd/server/... ok
- go test ./internal/daemon/... ok
- go test ./pkg/featureflag/... ok
- TestSlimBriefIsSubstantiallyShorter logs the 39.3% ratio
- TestSlimFlagOffUsesLegacy + TestSlimFlagOnUsesSlim pass both
directions, so the dispatcher is locked in code.
The pre-existing `internal/handler` test failures
(TestLeaveWorkspace_RevokesOwnRuntimes,
TestDeleteMember_CancelsTasksFromAgentReassignment,
TestDeleteMember_NoRuntimes_DeletesMember) reproduce on plain
`origin/main` with the same `relation "channel_user_binding" does
not exist` SQL error — they are a missing-migration bug from the
recent channels foundation PR (ce28d0aa0), not anything this PR
touched.
Rollout plan:
1. Merge this PR. Production daemons keep emitting the legacy brief
(flag default false).
2. Add a YAML rule to staging's
`MULTICA_FEATURE_FLAGS_FILE`:
runtime_brief_slim:
default: true
Staging daemons start emitting the slim brief on next restart.
3. Watch `agent prompt prepared` logs + agent behaviour for 7 days.
4. If staging is clean, flip the prod YAML to `default: true`.
Legacy code path stays in the binary as a kill-switch
(`FF_RUNTIME_BRIEF_SLIM=false` to revert without a deploy).
5. After ~30 days clean in prod, follow up with a PR that deletes
the legacy body and the flag — same pattern as docs/feature-flags.md
recommends ("plan the death of the flag at birth").
Co-authored-by: Eve <eve@multica-ai.local>
Co-authored-by: multica-agent <github@multica.ai>
74 lines
3.2 KiB
Go
74 lines
3.2 KiB
Go
package execenv
|
|
|
|
import (
|
|
"context"
|
|
"sync/atomic"
|
|
|
|
"github.com/multica-ai/multica/server/pkg/featureflag"
|
|
)
|
|
|
|
// runtimeBriefSlimFlag is the feature-flag key that switches the runtime
|
|
// brief from the legacy verbose form (the canonical pre-MUL-3560 prompt that
|
|
// has shipped to production for ~2 years) to the post-MUL-3560 slim form
|
|
// (kind-driven dispatcher + per-section compression).
|
|
//
|
|
// Default OFF in every environment. Staging YAML opts in via:
|
|
//
|
|
// runtime_brief_slim:
|
|
// default: true
|
|
//
|
|
// Ops can also flip it per process with `FF_RUNTIME_BRIEF_SLIM=true`
|
|
// (EnvProvider) without a redeploy. Production stays on the legacy brief
|
|
// until staging has burned in long enough that we are confident the slim
|
|
// brief does not regress agent behaviour.
|
|
//
|
|
// Naming follows the docs/feature-flags.md convention `{team}_{area}_{behavior}`:
|
|
// `runtime` is the area (the agent runtime brief), `brief_slim` is the
|
|
// behavior toggle. `Off` is the legacy / safe state per the standard
|
|
// convention; flip to `On` once an evaluation has shown the slim brief is
|
|
// no worse.
|
|
const runtimeBriefSlimFlag = "runtime_brief_slim"
|
|
|
|
// runtimeFlags is the package-scope feature flag service used by
|
|
// buildMetaSkillContent / BuildCommentReplyInstructions to pick between the
|
|
// legacy and slim brief paths. Stored behind an atomic.Pointer so the daemon
|
|
// can wire the service exactly once at startup (and tests can swap it under
|
|
// a t.Cleanup without races against parallel test goroutines).
|
|
//
|
|
// A nil service is valid: featureflag.Service is nil-safe and returns the
|
|
// caller's default (false → legacy) when no provider is wired. That is what
|
|
// keeps every existing call site working even on a daemon that never bothered
|
|
// to call SetFeatureFlags.
|
|
var runtimeFlags atomic.Pointer[featureflag.Service]
|
|
|
|
// SetFeatureFlags wires the daemon's feature flag service into execenv. The
|
|
// daemon should call this once at startup right after constructing the
|
|
// service in cmd/server/main.go. Passing nil clears the wiring (every flag
|
|
// then falls back to its default), which is useful for tests.
|
|
func SetFeatureFlags(svc *featureflag.Service) {
|
|
runtimeFlags.Store(svc)
|
|
}
|
|
|
|
// briefFlags is the unexported reader used by build-time toggle points. It
|
|
// returns the currently wired service, which may be nil — Service.IsEnabled
|
|
// handles that case (returns the caller's default).
|
|
func briefFlags() *featureflag.Service {
|
|
return runtimeFlags.Load()
|
|
}
|
|
|
|
// useSlimBrief is the canonical toggle point for "should this run render the
|
|
// slim brief or the legacy brief". Always evaluated against a fresh
|
|
// background context (the brief is generated outside any HTTP request, so
|
|
// there is no per-request EvalContext to attach; per-workspace targeting
|
|
// must go through Rule.Allow / Rule.Deny on `workspace_id` or similar
|
|
// once we plumb workspace id through TaskContextForEnv — out of scope for
|
|
// the initial rollout).
|
|
//
|
|
// Default is `false` everywhere except where a Provider explicitly returns
|
|
// true. Production has no provider rule → false → legacy. Staging YAML sets
|
|
// `runtime_brief_slim.default: true` → true → slim. Ops can override per
|
|
// process with `FF_RUNTIME_BRIEF_SLIM=true`.
|
|
func useSlimBrief() bool {
|
|
return briefFlags().IsEnabled(context.Background(), runtimeBriefSlimFlag, false)
|
|
}
|