Files
multica/server/internal/daemon/execenv/runtime_config_kind.go
5f1f297d57 MUL-6966 chore(brief): stop teaching agents to use issue metadata (#8102)
* MUL-6966 chore(brief): stop teaching agents to use issue metadata

Phase 1 of retiring the per-issue metadata KV bag: remove every piece of
official guidance that recruits agents into writing it, while leaving the
UI, API, CLI and stored data fully working.

The concept was added when the platform had no custom properties and no
custom statuses. Both exist now, so metadata's stated purpose ("custom
key-value state a future run re-reads") no longer names anything the
product does not model better — what is left is an unstructured bag the
brief actively invites agents to fill, with no defined consumer and no
way for a user to notice it drifting out of sync with status, PR links
and comments.

Removed from the runtime brief:
  - the `## Issue Metadata` section and its dispatch
  - the three `multica issue metadata …` bullets in Available Commands
  - the metadata clauses on workflow steps 1 and 5

Removed from the multica-platform skill:
  - `## Metadata: durable custom state` in references/issues.md
  - the metadata framing of custom properties, replaced by a
    where-state-belongs rule (property / status / comment)
  - the routing-table and legacy-redirect mentions

Kept deliberately: the one negative `pr_url` clause. `multica issue get`
still returns whatever an older run left in the bag, so the warning not
to infer PR state from it is still load-bearing.

Every presence assertion becomes an absence assertion — no test coverage
is dropped. TestInjectRuntimeConfigIssueMetadataSectionScope is inverted
into TestBriefCarriesNoMetadataGuidance, which checks all five task kinds.

Brief size: 15443 → 14264 bytes on a comment-triggered issue run (~315
tokens off every run's always-loaded context).

Phase 2 — removing the UI, CLI and API surface — waits until the
remaining consumers are known to have migrated.

Co-authored-by: multica-agent <[email protected]>

* MUL-6966 chore(skill): drop the pr_url name from the stale-PR-state guard

Review feedback: the surviving `pr_url` mention was not worth its cost.

The guard it sat in is "query the link table, do not infer PR state from
branch names, GitHub search, memory, or something stale" — and that rule
is complete without naming a key. Spelling the key out is itself a way of
teaching the vocabulary: an agent that reads "do not trust the `pr_url`
metadata key" has just learned there is one. Phase 1 is about not putting
that vocabulary in front of agents at all.

The stale-data case is unchanged and still covered: `issue get` returns
whatever an older run left on the issue, and the clause now warns about
that generically.

references/issues.md now contains neither "metadata" nor "pr_url", and
the test asserts exactly that — a blanket vocabulary ban replaces the
five string-by-string checks it had after the previous commit.

Co-authored-by: multica-agent <[email protected]>

* MUL-6966 chore(skill): tell the legacy redirect the truth about metadata

Review nit: the stub promised "Nothing was dropped in the move — the
contracts were reorganized, not shortened." The metadata guidance has now
been dropped, and the daemons that reach this stub are exactly the ones
whose frozen brief still sends them here for it. The one place the claim
is read is the one place it is false.

It now names the exception and states the rule that replaces it —
property for state a person filters by, status for the stage, the result
comment for everything else — so an old daemon's stale pointer lands on
an answer instead of a gap.

This is the one file where saying the word is free: the stub is served
only to pre-merge daemons, whose brief already teaches metadata in full.
Mirroring the note into references/issues.md would put the vocabulary in
front of every new agent forever to serve a window that closes.

Pinned by TestLegacyRedirectsFollowTheDaemonsBrief.

Co-authored-by: multica-agent <[email protected]>

---------

Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
2026-09-07 15:32:35 +08:00

73 lines
3.0 KiB
Go

package execenv
// taskKind labels the dispatch path that the runtime brief should
// follow for a given TaskContextForEnv. Used by
// `buildMetaSkillContentSlim` (MUL-3560 brief; the `runtime_brief_slim`
// flag that once gated it against a legacy verbose brief was retired in
// MUL-4297, so this is now the only brief).
//
// Four kinds, mutually exclusive in practice. classifyTask documents the
// tiebreak rule that applies if a future caller accidentally violates the
// mutex.
type taskKind int
const (
// kindIssue: this run operates on a real Multica issue. It deliberately
// does NOT distinguish comment-triggered from assignment-triggered runs.
//
// Those were two kinds until MUL-5377. Splitting them made the rendered
// brief — which Claude Code loads into messages[0], ahead of the entire
// conversation — differ between the first (on-assign) run and every
// later (comment) run on the same resumed session, which invalidated the
// prompt cache for the whole history on every resume. Which trigger
// fired THIS turn is per-turn state and now travels in the per-turn user
// message (daemon.BuildPrompt), which is appended after the cached
// prefix. See runtime_config_sections.go:writeWorkflowIssue.
kindIssue taskKind = iota
// kindAutopilotRunOnly: an autopilot fired in run-only mode (no
// issue created or attached).
kindAutopilotRunOnly
// kindQuickCreate: one-shot "create an issue from a natural-language
// prompt" task.
kindQuickCreate
// kindChat: interactive chat session, no issue.
kindChat
)
// classifyTask maps a TaskContextForEnv to the single taskKind the slim
// brief should be assembled for. Precedence (documented for the tiebreak
// case, although the daemon never sets two specific-kind flags at once):
// chat → quick-create → autopilot run-only → issue.
//
// Deliberately does not read ctx.TriggerCommentID: the classification must
// not vary across runs of the same resumed session, or the brief's bytes
// change and the prompt cache is lost from messages[0] onward (MUL-5377).
func classifyTask(ctx TaskContextForEnv) taskKind {
switch {
case ctx.ChatSessionID != "":
return kindChat
case ctx.QuickCreatePrompt != "":
return kindQuickCreate
case ctx.AutopilotRunID != "":
return kindAutopilotRunOnly
default:
return kindIssue
}
}
// hasIssueContext returns true for the kinds that operate on a real Multica
// issue and therefore can read / write issue-scoped state. The slim
// dispatcher gates one section on this predicate:
//
// - Sub-issue Creation
//
// It is meaningless on the issue-less kinds (chat / quick-create /
// autopilot run-only) and would either render an empty body or steer the
// agent into a guaranteed-failed CLI call. Note this is a kind-based
// predicate, not a check on ctx.IssueID — kindIssue always carries an issue
// id by construction (the daemon refuses to dispatch it otherwise), and the
// other three kinds never do.
func (k taskKind) hasIssueContext() bool {
return k == kindIssue
}