Compare commits

...

1 Commits

Author SHA1 Message Date
Jiayuan Zhang
b254135116 refactor(daemon/execenv): compress per-turn CLAUDE.md prompt sections
Compress instructional sections that the agent re-reads on every turn:

- `## Mentions` collapsed from 15 lines to 6 (anti-loop signals preserved
  in prose; section sub-headers removed).
- `## Important: Always Use the multica CLI` collapsed from 6 lines to 2.
- `## Output` no longer duplicates the "results MUST go via comment add"
  warning — the workflow step is now the single source of truth.
- HEREDOC code-block example removed from the comment-add command help;
  the rule sentence remains.
- Comment-trigger workflow steps 4–6 collapsed; the canonical
  agent-to-agent anti-loop guidance now lives only in the per-turn user
  message (daemon.buildCommentPrompt agent block) with a one-line
  pointer left in CLAUDE.md.

Tests updated to assert the surviving anti-loop signals and to pin the
new pointer-style guidance.

Co-authored-by: multica-agent <github@multica.ai>
2026-05-07 12:11:55 +08:00
2 changed files with 38 additions and 67 deletions

View File

@@ -924,9 +924,9 @@ func TestPrepareWithRepoContextOpencode(t *testing.T) {
// TestInjectRuntimeConfigRequiresExplicitCommentPost ensures the injected
// workflow makes "post a comment with results" an explicit, unmissable step in
// both the assignment- and comment-triggered branches, plus hard-warns in the
// Output section that terminal/log text is not user-visible. Agents were
// silently finishing tasks without ever posting their result to the issue; see
// both the assignment- and comment-triggered branches, including a hard
// warning that terminal/log text is not user-visible. Agents were silently
// finishing tasks without ever posting their result to the issue; see
// MUL-1124. Covering this in a test prevents the guidance from decaying back
// into a nested clause again.
func TestInjectRuntimeConfigRequiresExplicitCommentPost(t *testing.T) {
@@ -956,28 +956,22 @@ func TestInjectRuntimeConfigRequiresExplicitCommentPost(t *testing.T) {
s := string(data)
// The workflow must contain an explicit `multica issue comment add`
// invocation for this issue — not just a prose mention of posting.
// invocation for this issue, the word "mandatory" so the agent can
// see the post-result step is non-optional, and a hard warning
// that terminal/log text is not user-visible. After MUL-1823 the
// terminal-not-delivered warning lives inside the workflow step
// itself (single source of truth), not duplicated in an Output
// section.
mustContain := []string{
"multica issue comment add issue-1",
"mandatory",
"NOT delivered",
}
for _, want := range mustContain {
if !strings.Contains(s, want) {
t.Errorf("%s: CLAUDE.md missing %q\n---\n%s", tc.name, want, s)
}
}
// The Output section must carry a hard warning that terminal/log
// output is not user-visible. This is the second line of defense
// in case the agent skips past the workflow steps.
for _, want := range []string{
"Final results MUST be delivered via `multica issue comment add`",
"does NOT see your terminal output",
} {
if !strings.Contains(s, want) {
t.Errorf("%s: Output warning missing %q", tc.name, want)
}
}
})
}
}
@@ -1002,10 +996,9 @@ func TestInjectRuntimeConfigDirectsMultiLineWritesToStdin(t *testing.T) {
s := string(data)
for _, want := range []string{
"multi-line content",
"MUST pipe via stdin",
"Multi-line content",
"MUST pipe via stdin with a HEREDOC",
"--content-stdin",
"<<'COMMENT'",
"`--description`",
"--description-stdin",
} {
@@ -2045,11 +2038,13 @@ func TestInjectRuntimeConfigMentionLoopHardening(t *testing.T) {
t.Run("mentions-section-lists-loop-protocol", func(t *testing.T) {
t.Parallel()
s := readClaudeMD(t, assignmentCtx)
// After MUL-1823 the Mentions section is condensed: the When-NOT /
// When-IS section headers are gone, but every anti-loop *signal* must
// still survive in prose.
for _, want := range []string{
"side-effecting actions",
"enqueues a new run for that agent",
"When NOT to use a mention link",
"When a mention IS appropriate",
"Default to NO `@mention`",
"end with no mention at all",
"Silence ends conversations",
} {
@@ -2069,16 +2064,18 @@ func TestInjectRuntimeConfigMentionLoopHardening(t *testing.T) {
}
})
t.Run("workflow-carries-silence-as-exit-and-no-signoff-mention", func(t *testing.T) {
t.Run("workflow-points-to-per-turn-anti-loop-guidance", func(t *testing.T) {
t.Parallel()
s := readClaudeMD(t, commentTriggerCtx)
// The anti-loop signal for CLAUDE.md lives in the numbered workflow
// steps (4 + 5), not in a dedicated preamble. Lock in the key phrases
// so the signal can't decay back into pure prose again.
// After MUL-1823 the canonical anti-loop guidance lives in the
// per-turn user message (daemon.buildCommentPrompt agent block).
// CLAUDE.md only carries a one-line pointer to it so the rule has a
// single source of truth. Lock in the pointer so it cannot silently
// decay; the per-turn message itself is covered by daemon prompt
// tests.
for _, want := range []string{
"Decide whether a reply is warranted",
"Silence is a valid and preferred way",
"Never @mention the agent you are replying to as a thank-you or sign-off",
"per-turn user message",
"when to stay silent",
} {
if !strings.Contains(s, want) {
t.Errorf("comment-triggered CLAUDE.md missing %q", want)

View File

@@ -132,18 +132,8 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
b.WriteString("- `multica issue label remove <issue-id> <label-id>` — Detach a label from an issue\n")
b.WriteString("- `multica issue subscriber add <issue-id> [--user <name>|--user-id <uuid>]` — Subscribe a member or agent to issue updates (defaults to the caller when neither flag is set; the two flags are mutually exclusive)\n")
b.WriteString("- `multica issue subscriber remove <issue-id> [--user <name>|--user-id <uuid>]` — Unsubscribe a member or agent\n")
b.WriteString("- `multica issue comment add <issue-id> --content-stdin [--parent <comment-id>] [--attachment <path>]` — Post a comment. Agent-authored comments should always pipe content via stdin, even for short single-line replies. Use `--parent` to reply to a specific comment; `--attachment` may be repeated.\n")
b.WriteString(" - **For comment content, you MUST pipe via stdin; this is mandatory for multi-line content (anything with line breaks, paragraphs, code blocks, backticks, or quotes).** Do not use inline `--content` and do not write `\\n` escapes. Use a HEREDOC instead:\n")
b.WriteString("\n")
b.WriteString(" ```\n")
b.WriteString(" cat <<'COMMENT' | multica issue comment add <issue-id> --content-stdin\n")
b.WriteString(" First paragraph.\n")
b.WriteString("\n")
b.WriteString(" Second paragraph with `code` and \"quotes\".\n")
b.WriteString(" COMMENT\n")
b.WriteString(" ```\n")
b.WriteString("\n")
b.WriteString(" - The same rule applies to `--description` on `multica issue create` and `multica issue update` — use `--description-stdin` and pipe a HEREDOC for any multi-line description; the inline `--description \"...\"` form is for short single-line text only.\n")
b.WriteString("- `multica issue comment add <issue-id> --content-stdin [--parent <comment-id>] [--attachment <path>]` — Post a comment. Multi-line content (paragraphs, code blocks, backticks, quotes) MUST pipe via stdin with a HEREDOC (`cat <<'EOF' | ... --content-stdin`); inline `--content` and literal `\\n` escapes corrupt formatting. Use `--parent` to reply to a specific comment; `--attachment` may be repeated.\n")
b.WriteString(" - The same rule applies to `--description` on `multica issue create` and `multica issue update` — use `--description-stdin` for multi-line text.\n")
b.WriteString("- `multica issue comment delete <comment-id>` — Delete a comment\n")
b.WriteString("- `multica label create --name \"...\" --color \"#hex\"` — Define a new workspace label (use this only when the label you need does not exist yet; reuse existing labels via `multica label list` first)\n")
b.WriteString("- `multica autopilot create --title \"...\" --agent <name> --mode create_issue [--description \"...\"]` — Create an autopilot\n")
@@ -246,14 +236,11 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
// Comment-triggered: focus on reading and replying
b.WriteString("**This task was triggered by a NEW comment.** Your primary job is to respond to THIS specific comment, even if you have handled similar requests before in this session.\n\n")
fmt.Fprintf(&b, "1. Run `multica issue get %s --output json` to understand the issue context\n", ctx.IssueID)
fmt.Fprintf(&b, "2. Run `multica issue comment list %s --output json` to read the conversation\n", ctx.IssueID)
b.WriteString(" - If the output is very large or truncated, use pagination: `--limit 30` to get the latest 30 comments, or `--since <timestamp>` to fetch only recent ones\n")
fmt.Fprintf(&b, "3. Find the triggering comment (ID: `%s`) and understand what is being asked — do NOT confuse it with previous comments\n", ctx.TriggerCommentID)
b.WriteString("4. **Decide whether a reply is warranted.** If you produced actual work this turn (investigated, fixed, answered a real question), post the result via step 6 — that is a normal reply, not a noise comment. If the triggering comment was a pure acknowledgment / thanks / sign-off from another agent AND you produced no work this turn, do NOT post a reply — and do NOT post a comment saying 'No reply needed' or similar. Simply exit with no output. Silence is a valid and preferred way to end agent-to-agent conversations.\n")
b.WriteString("5. If a reply IS warranted: do any requested work first, then **decide whether to include any `@mention` link.** The default is NO mention. Only mention when you are escalating to a human owner who is not yet involved, delegating a concrete new sub-task to another agent for the first time, or the user explicitly asked you to loop someone in. Never @mention the agent you are replying to as a thank-you or sign-off.\n")
b.WriteString("6. **If you reply, post it as a comment — this step is mandatory when you reply.** Text in your terminal or run logs is NOT delivered to the user. ")
fmt.Fprintf(&b, "2. Run `multica issue comment list %s --output json` to read the conversation (use `--limit 30` or `--since <timestamp>` if truncated)\n", ctx.IssueID)
fmt.Fprintf(&b, "3. Find the triggering comment (ID: `%s`) — see the per-turn user message for full anti-loop guidance (when to stay silent, when to skip the @mention)\n", ctx.TriggerCommentID)
b.WriteString("4. If a reply is warranted, posting it as a comment is mandatory — text in your terminal or run logs is NOT delivered. ")
b.WriteString(BuildCommentReplyInstructions(ctx.IssueID, ctx.TriggerCommentID))
b.WriteString("7. Do NOT change the issue status unless the comment explicitly asks for it\n\n")
b.WriteString("5. Do NOT change the issue status unless the comment explicitly asks for it\n\n")
} else {
// Assignment-triggered: defer to agent Skills for workflow specifics.
b.WriteString("You are responsible for managing the issue status throughout your work.\n\n")
@@ -290,20 +277,12 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
}
b.WriteString("## Mentions\n\n")
b.WriteString("Mention links are **side-effecting actions**, not just formatting:\n\n")
b.WriteString("- `[MUL-123](mention://issue/<issue-id>)` — clickable link to an issue (safe, no side effect)\n")
b.WriteString("- `[@Name](mention://member/<user-id>)` — **sends a notification to a human**\n")
b.WriteString("Mention links are **side-effecting actions**, not formatting:\n\n")
b.WriteString("- `[MUL-123](mention://issue/<issue-id>)` — issue link (safe, no side effect)\n")
b.WriteString("- `[@Name](mention://member/<user-id>)` — **notifies a human**\n")
b.WriteString("- `[@Name](mention://agent/<agent-id>)` — **enqueues a new run for that agent**\n\n")
b.WriteString("### When NOT to use a mention link\n\n")
b.WriteString("- Referring to someone in prose (e.g. \"GPT-Boy is right\") — write the plain name, no link.\n")
b.WriteString("- **Replying to another agent that just spoke to you.** By default, do NOT put a `mention://agent/...` link anywhere in your reply. The platform already shows your comment to everyone on the issue; re-mentioning the other agent will make them run again, and if they reply with a mention back, you will be triggered again. That is a loop and it costs the user money.\n")
b.WriteString("- Thanking, acknowledging, wrapping up, or signing off. These are exactly the moments where an accidental `@mention` causes the other agent to reply \"you're welcome\" and restart the loop. If the work is done, **end with no mention at all**.\n\n")
b.WriteString("### When a mention IS appropriate\n\n")
b.WriteString("- Escalating to a human owner who is not yet involved.\n")
b.WriteString("- Delegating a concrete sub-task to another agent for the first time, with a clear request.\n")
b.WriteString("- The user explicitly asked you to loop someone in.\n\n")
b.WriteString("If you are unsure whether a mention is warranted, **don't mention**. Silence ends conversations; `@` restarts them.\n\n")
b.WriteString("Use `multica issue list --output json` to look up issue IDs, and `multica workspace members --output json` for member IDs.\n\n")
b.WriteString("Default to NO `@mention`. Only mention to escalate to a new human owner, delegate a fresh sub-task to another agent, or when the user asked you to loop someone in. Never @mention as a thanks/acknowledgment/sign-off — that restarts loops; **end with no mention at all**. **Silence ends conversations; `@` restarts them.**\n\n")
b.WriteString("Look up IDs via `multica workspace members --output json` and `multica issue list --output json`.\n\n")
b.WriteString("## Attachments\n\n")
b.WriteString("Issues and comments may include file attachments (images, documents, etc.).\n")
@@ -313,11 +292,7 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
b.WriteString("After downloading, you can read the file directly (e.g. view an image, read a document).\n\n")
b.WriteString("## Important: Always Use the `multica` CLI\n\n")
b.WriteString("All interactions with Multica platform resources — including issues, comments, attachments, images, files, and any other platform data — **must** go through the `multica` CLI. ")
b.WriteString("Do NOT use `curl`, `wget`, or any other HTTP client to access Multica URLs or APIs directly. ")
b.WriteString("Multica resource URLs require authenticated access that only the `multica` CLI can provide.\n\n")
b.WriteString("If you need to perform an operation that is not covered by any existing `multica` command, ")
b.WriteString("do NOT attempt to work around it. Instead, post a comment mentioning the workspace owner to request the missing functionality.\n\n")
b.WriteString("All Multica platform access (issues, comments, attachments, files) must go through the `multica` CLI — `curl`/`wget` cannot authenticate. If a needed operation is missing, post a comment to the workspace owner instead of working around it.\n\n")
b.WriteString("## Output\n\n")
switch {
@@ -329,11 +304,10 @@ func buildMetaSkillContent(provider string, ctx TaskContextForEnv) string {
b.WriteString("- Print exactly one final line: `Created MUL-<n>: <title>` after a successful `multica issue create`.\n")
b.WriteString("- On CLI failure, exit with the CLI error as the only output. The platform translates that into a `quick_create_failed` inbox item carrying the original prompt for the user.\n")
default:
b.WriteString("⚠️ **Final results MUST be delivered via `multica issue comment add`.** The user does NOT see your terminal output, assistant chat text, or run logs — only comments on the issue. A task that finishes without a result comment is invisible to the user, even if the work itself was correct.\n\n")
b.WriteString("Keep comments concise and natural — state the outcome, not the process.\n")
b.WriteString("Good: \"Fixed the login redirect. PR: https://...\"\n")
b.WriteString("Bad: \"1. Read the issue 2. Found the bug in auth.go 3. Created branch 4. ...\"\n")
b.WriteString("When referencing an issue in a comment, use the issue mention format `[MUL-123](mention://issue/<issue-id>)` so it renders as a clickable link. (Issue mentions have no side effect; only member/agent mentions do — see the Mentions section above.)\n")
b.WriteString("Reference issues with `[MUL-123](mention://issue/<issue-id>)` (issue mentions have no side effect; only member/agent mentions do).\n")
}
return b.String()