mirror of
https://gh-proxy.org/https://github.com/klinsmaya/multica.git
synced 2026-11-10 02:16:45 +08:00
main
93
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a008d893dd |
MUL-8292: refactor(brief): move Available Commands detail behind --help (#9596)
* MUL-8292: refactor(brief): move Available Commands detail behind --help pointers Each bullet keeps its signature and the behaviour needed at the call site; wakeup condition catalogue, PR snapshot mechanics, actor filters and the repeated file rules now live only in --help and the multica-platform references. Issue brief 17,300 -> 16,372 bytes (-928); chat and autopilot briefs drop by the same amount. New TestAvailableCommandsKeepsCompressedContracts pins the kept contracts and guards the moved detail from drifting back. Co-authored-by: multica-agent <[email protected]> * MUL-8292: fix(brief): keep Available Commands rules self-contained in every kind Review follow-up on #9596: - comment list / issue create / comment add pointed at Workflow step 2 and Comment Formatting, which chat and autopilot briefs do not have; the bounded-read recipe and the workdir file rule are inline again. - repo checkout restores both keep conditions and the clean-checkout case. - wakeup scopes last_error to missing PR links/snapshots; a pending check or unmet condition records nothing. Contract test now runs every kind x provider, and a new test fails when the shared section references a heading or workflow step the kind lacks. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
90a88519df |
fix(daemon): defer issue status rules to project workflow (MUL-8145) (#9415)
* fix(daemon): defer issue status rules to the project workflow (MUL-8145) When the claim carries a project workflow, the runtime brief no longer tells the agent to set in_progress first and in_review on delivery. Those statuses may be outside the workflow (the server rejects them), and entering a handoff step reassigns the issue. The brief now points at the per-turn [Project workflow] block, and the workflow brief carries the entry/exit checks in workflow terms. Briefs without a workflow are byte-identical to before. Co-authored-by: multica-agent <[email protected]> * fix(squad): word the owning leader's status moves by the project workflow (MUL-8145) The squad protocol (Agent Identity, which outranks the runtime brief) still told an owning leader to move the parent to in_progress and then in_review, which a workflow may not list and which can hand the parent to another step's handler. When the claim carries a project workflow, responsibility 6 now keeps the ownership grant but moves the parent only through the [Project workflow] commands. The claim resolves the workflow before the briefing to choose the wording. Guest leaders and issues without a workflow are unchanged. Co-authored-by: multica-agent <[email protected]> * refactor(daemon): list no status keys in the brief for a workflow issue (MUL-8145) With a project workflow, [Project workflow] already lists the issue's statuses and the commands that move it, and the built-in keys may not be accepted there. The status command line now keeps only the command and points at that block; other issues learn valid statuses from the error. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): stop forbidding status moves nothing asks for (MUL-8145) Under a project workflow the current runtime brief no longer states the built-in in_progress / in_review moves, so the lines that forbade them (in [Project workflow], the runtime brief and the squad protocol) only named statuses the agent had no reason to use. Drop them, and the exit check the runtime brief already states. Older daemons still state the built-in rules, so the daemon now advertises workflow-status-rules-v1; without it the claim keeps the override lines in [Project workflow] and the squad protocol. The multica-platform skill notes that [Project workflow] supersedes its built-in status arc. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): shorten the step's moves in the workflow brief (MUL-8145) Replace the handoff preamble with a plain "Your moves:" heading, name who each move hands off to on its own line, and drop the "this workflow has no blocked status" explanations from Can't proceed. Co-authored-by: multica-agent <[email protected]> * refactor(workflow): state each step outcome as the status to set (MUL-8145) The moves block read like raw CLI ('Step done → multica issue status MUL-7 x'). Each outcome now says which status to set and that the server assigns the next handler; the CLI form is given once. Co-authored-by: multica-agent <[email protected]> * Revert "refactor(workflow): state each step outcome as the status to set (MUL-8145)" This reverts commit 2d82d2d342a99afe88cc40c48516b7ea8e860d1e. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e720fe2219 |
MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) (#9406)
* MUL-8135: feat(wakeups): wake on linked PR merge conflicts (--until-pr conflicts) Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): conflict wakes per PR identity, rotate the watch sweep - A linked-PR condition only fires for a PR key the rule has not seen; a PR leaving the satisfying set no longer re-wakes the agent for the others. - Conflict keys use the PR id, so same-numbered fork PRs on one head stay distinct. - The conflict-watch sweep rotates an address cursor so failing fetches cannot starve the rest. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> * MUL-8135: fix(wakeups): count repeated linked-PR keys when deduping Checks and merged keys carry only the PR number, so same-numbered PRs from two repositories share a key. Compare occurrence counts so the second one satisfying still wakes the agent. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
700d25eef5 |
MUL-7420: render the workflow brief once on the server, for the assignee or an observer (#9293)
- issueworkflow.Brief is the only renderer: the claim carries it into every run's per-turn prompt; the editor preview and timeline show the same text. - Workflow handoffs stop writing the brief into handoff_note, so a person's note no longer drops the brief and the handoff framing fits again. - Agents that are not the issue's assignee get an observer brief without the step's commands. - Clearer rule override and narrower wakeup guidance; user text is stripped of control characters. - multica-platform skill documents project workflows. Co-authored-by: Cursor <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
da1671ca20 |
MUL-7420: Project workflows — columns and handoffs on the workflow (#8814)
* feat(workflows): workspace workflows with step handoffs (MUL-7420)
A workflow is a named, workspace-level selection of statuses from the
shared status catalog, in board order, with an optional handoff on each
step. Projects opt in through project.workflow_id; a project without one
keeps the whole catalog (the implicit Default workflow), so existing
workspaces see no change.
- issue_workflow table + project.workflow_id, CRUD API under
/api/issue-workflows, POST /api/projects/{id}/workflow with a dry-run
status mapping plan. Only issues on statuses the target workflow does
not list are remapped, and remapping never hands issues off.
- Issue writes keep the status inside the project's workflow. Entering a
step with a handler through a status change assigns the issue to that
handler and starts its run with the step's brief as the handoff note;
an explicit assignee in the same write wins. Creating an issue starts
at the workflow's starting step.
- Task claims carry the issue's project workflow, and the brief renders
a Project Workflow section so agents move along this project's
statuses instead of the built-in ones.
- Statuses a workflow lists cannot be archived.
- Adoption is gated by the project_workflows_v1 flag; existing
workflows keep working and projects can always switch back to Default.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): workflow settings, project picker and workflow-aware boards (MUL-7420)
- Settings → Workflows (behind project_workflows_v1): the Default
workflow plus named workflows, each shown as its step chain with
handlers and the projects using it; an editor to pick statuses from the
library, order them, mark the starting step, and set each step's
handoff, instructions and next/back statuses.
- Project Workflow property: switching dry-runs first and asks where
issues on unlisted statuses go; nothing else moves and nothing is
handed off.
- A project board's columns are its workflow's steps, and each column
head names who entering it hands the issue to.
- Status pickers offer only the issue's workflow steps and say who each
hands off to; boards refuse drops onto statuses the dragged card's
workflow does not list.
- New issues in a workflow project start on its starting step, with a
hint naming who they will be handed to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): name built-in steps once in the brief; roomier picker hints (MUL-7420)
Built-in statuses have no stored name, so the handoff brief printed
`todo` todo. Print the key alone when the name adds nothing.
The status picker widens for workflow projects and shows the handoff as
an arrow and name, so step names no longer truncate to one letter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): handoff previews and a single timeline entry per handoff (MUL-7420)
- GET /api/issues/{id}/workflow-handoff?status= says what moving an issue
would do: who it goes to, which runs of its current agent are still
active, and the brief the handler receives. Backs the confirmation.
- POST /api/issue-workflows/preview-brief renders a step's brief from an
unsaved workflow, for the editor's preview.
- Mapping plans list the statuses that stay, so the switch dialog can say
which issues are untouched.
- A handoff is recorded on the status_changed activity (workflow, handler,
whether a run started, the brief) instead of a separate assignee row.
- The brief now reads like the one shown in the design: the workflow, the
current step, its instructions, the statuses, and the commands to hand
off.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): rebuild the workflow UI to the reviewed design (MUL-7420)
The first cut used generic controls where the design had purpose-built
ones. This brings every surface up to the mockup:
- Settings → Workflows is one page: the workflows (step chain with
handler avatars, handoff count, projects using it; Default first) and,
below, the shared status library with each status's workflow usage.
- The workflow editor is a full page instead of a dialog: draggable step
cards summarising each handoff, and a panel for the selected step —
swap its status, pick the handler with a segmented control and a
searchable target, handoff instructions, next/back statuses, a preview
of the brief the handler receives, duplicate and delete.
- The project Workflow picker lists each workflow's statuses, handoffs and
projects, links to settings, and always confirms the switch with what
moves and what stays.
- Moving an issue into a handoff step (status picker, table, board drop)
asks first: who it goes from and to, a default-on option to stop the
previous agent's run, and the brief.
- Status pickers name the workflow and project, show each step's handler
with an avatar or that the assignee stays, and explain themselves.
- Boards: the handler chip in project column heads, hatched columns a
dragged card cannot enter with the reason, and a drop hint naming who
the card will go to.
- The timeline records a handoff under its status change, with the brief
the run received; the issue sidebar shows the workflow it follows.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): word a queued previous run as queued in the handoff confirm
The confirm dialog said the previous agent was "still running (for N
minutes)" even when its task had only been queued. Queued runs now read
as queued, and the checkbox cancels them instead of stopping them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): refuse a run's status change once the issue left its step
A run decides where the issue goes next from the step it was queued for.
When someone else moved the issue while the run worked, applying that
decision undid their move: in the live Grok run, a PM parked NW-1 as
blocked and the still-running agent moved it straight back to code
review.
Tasks now record the issue's status when they are queued
(agent_task_queue.workflow_step, written by the task INSERTs; automatic
retries keep their parent's step), and a run's own status changes move
it along. In a workflow project, an agent status change from a run whose
step no longer matches the issue is refused with 409
workflow_step_moved, naming where the issue is now and who moved it, so
the agent stops instead of retrying.
A handoff away from a squad now also stops the runs made on the squad's
behalf, and stopped runs record who stopped them.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep every status entry point inside the issue's workflow
The issue context menu listed every catalog status and applied handoff
steps without the confirmation the status picker and board show, so a
right-click could pick a status the workflow refuses or start an agent
unasked. It now offers the workflow's steps and opens the handoff
confirmation through a shared modal.
One-click status actions no longer assume built-in statuses a workflow
may not list: "mark done" lands on the workflow's done step (hidden when
it has none), un-marking a duplicate returns to the workflow's start,
and "mark duplicate" is offered only when the workflow lists cancelled.
The batch toolbar offers a single project's workflow statuses when the
selection sits in one project.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep issue wakeups consistent with workflow steps
Three places where project workflows and issue wakeups met:
- A wakeup run is queued when it fires, so it took the issue's status at
that moment as its workflow step. A wakeup an agent registered then
bypassed the stale-step refusal: parked by a PM, the issue could be
moved on by the agent's "check CI, then hand to review" wakeup. It now
keeps the registering run's step; a member's wakeup still starts from
the issue's status.
- Retries carried handoff_note only for wakeup runs, so a retried
workflow handoff lost its brief. Rows with a workflow step keep it.
- Switching or editing a workflow remaps statuses in bulk without the
close rule, so an issue remapped into a done status kept its wakeups
and pending wakeup runs. The remap now ends them like any status write.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): don't also wake the agent a write hands the issue to
A workflow handoff or an assignment already starts the receiving agent's
run. If that agent also subscribed an event wakeup to the issue's status
or assignee, the same write queued a second run for one fact, and
workflows made such writes frequent.
The write now names the agent it hands the issue to in the
transaction-local multica.handed_to_agent (the new agent assignee, a
squad's leader, or a step's handler even when it already owned the
issue), and wakeup capture skips that agent's subscriptions for the
write's issue.* events. Other agents' wakeups, and task, comment and
reaction events, are captured as before.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): tell agents not to watch workflow issues with wakeups
The handoff brief and the runtime's project workflow section now say
that each step's handler gets a run when the issue enters the step, so
wakeups on a workflow issue are for waiting on time or on something
outside it, not on its status, assignee or runs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): refresh project usage after project changes
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 split columns from per-project handoffs
Workflows now only define a board's columns between a fixed head
(backlog, todo) and tail (blocked, done, cancelled). Handoffs move to
project.workflow_handoffs, so projects can share columns and still hand
off to different people. Default becomes a real, editable row that every
project without a named workflow follows. Adds project handoff, add-column
(project fork or shared) and project brief preview endpoints; same-agent
adjacent columns re-run the agent.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(core): MUL-7420 project handoffs and Default workflow client model
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 column-header handoffs and project workflow menu
Board column headers set the project's handoff for that column; the
project toolbar gets a workflow menu (full flow, switch, templates) and an
add-column action. Views outside a project show Default's columns, with
other statuses reachable through a status filter. Adds the issue flow card,
create-project workflow choice, a columns-only workflow editor and a
lighter status picker.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 wrap create-project workflow label in a menu group
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): MUL-7420 keep workflow structure in settings; no squad handoffs
Projects no longer add columns or create workflows: the add-column
endpoint is removed and creating a workflow is admin-only. Handoffs go
to one agent, a member or the project lead; squads route work themselves,
so a handoff to one is rejected, as is the issue-creator handler.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(core): MUL-7420 drop the project add-column client
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 simpler handoff picker and workflow editor form
The handoff form is one searchable "Hand to" picker (no handoff, the
project lead, an agent or a member); the next status defaults to the next
column and next/back overrides sit under "More options". The workflow
editor gets labelled name and description fields and shows the fixed
statuses as one start and one end row. Templates move from the project
menu to Settings → Workflows → New, and the project board loses its
add-column action.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): MUL-7420 keep handoffs on the workflow, make backlog and blocked optional
Handoffs move back onto workflow steps: a workflow is the whole pipeline,
and a project only picks one. The project lead stays a relative handler, so
projects can still share a workflow. Handoff edits are admin-only, through
the workflow write or PUT /api/issue-workflows/{id}/handoffs/{status}; the
brief preview moves to the workflow too. Drops the unreleased
project.workflow_handoffs migration.
Todo, done and cancelled are required; backlog and blocked are optional and
keep their fixed places. Without blocked, the brief points agents at the
step's back status.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Cursor <[email protected]>
* feat(views): MUL-7420 edit handoffs on the workflow, optional backlog and blocked
The workflow editor sets each column's handoff in its side panel, and
Backlog and Blocked switch on and off. The board's column popover and the
flow panel edit the workflow's handoff, say how many projects it reaches,
and are read-only for non-admins. Drops the per-project handoff client
code.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): MUL-7420 keep platform rules on writes that hand off nobody
Migration 579 skips an issue.* event for the agent a write hands the issue
to. Its filter compared agent_id with the transaction's handed_to_agent
setting using IS DISTINCT FROM, which is false when both are NULL. Main's
platform rules (child_done) have no agent_id, so every issue.* event that
handed the issue to nobody was dropped for them: a parent leaving backlog
never woke its agent for sub-issues that closed meanwhile
(TestChildDoneHeldWhileParentInBacklog).
Rules without an agent are never the handoff target, so they always
capture. Extends TestHandoffDoesNotAlsoWakeTheHandler with a platform rule
on a handoff and on a plain status change; the latter fails on the old
filter.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 three-way handoff choice; quieter move confirm
The handoff form picks one of three directions, as in the mockup: no
handoff, hand to an agent (starts a run), or hand to a member, project lead
included (assignee only). What the step does, where it goes next and the
back status only reach an agent's brief, so they show and save for agent
handoffs alone.
The board's move confirmation keeps the agent's brief collapsed. Create
project shows the workflow picker even when Default is the only workflow,
with a note on where to make more. The hidden-columns panel says a board's
columns come from its workflow, with a link to manage workflows for admins.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(workflows): confirm handoff on project move, per-project handoff copy, two-pane editor (MUL-7420)
- A single issue moved into a workflow project from issue detail confirms
the handoff of the column it lands on; the write opts in with
enter_workflow_step. Batch, API and CLI moves stay administrative.
- The handoff preview takes project_id to preview such a move.
- Saving a shared workflow's handoff from a board can keep the change to
that project: the server copies the workflow for it and switches the
project in one transaction (copy_for_project_id).
- The column popover says which workflow it edits and who else uses it.
- The workflow editor is a step list with a detail panel for the
selected step.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* style: gofmt workflow tests
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 show next/back always; edit workflow name and description in place
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): MUL-7420 trim copy on the workflows settings page
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix: MUL-7420 keep pre-workflow behavior for workspaces that never adopt workflows
The run step guard now applies only to workflows that hand off, and the
standalone Statuses settings page keeps its original header.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): MUL-7420 use a solid placeholder tone on the workflow header
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(workflows): keep the runtime brief cache-stable; fix project board and batch status gaps (MUL-7420)
- Move the project workflow out of the runtime brief into the per-turn
prompt so CLAUDE.md stays byte-identical across steps and issues.
- An explicit status filter overrides the project workflow's columns; the
hidden-columns panel lists statuses outside the workflow.
- Batch status offers only statuses every selected workflow allows; a
partial batch rolls back, refetches and warns.
- setProjectWorkflow returns void instead of an unparsed body.
Co-authored-by: Cursor <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
Co-authored-by: Lambda <[email protected]>
Co-authored-by: Cursor <[email protected]>
|
||
|
|
231dedb552 |
MUL-8034: chore: clean historical comments across the codebase (#9274)
* chore: clean historical comments (MUL-8034) Co-authored-by: multica-agent <[email protected]> * docs: preserve pending Composio expiry contract Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
756cdd915c |
MUL-7794 fix(wakeups): surface recoverable PR condition diagnostics [ZIC-302] (#9066)
* fix(wakeups): surface recoverable PR condition diagnostics Co-authored-by: multica-agent <[email protected]> * fix(wakeups): preserve condition diagnostic ownership Co-authored-by: multica-agent <[email protected]> * test(wakeups): isolate receipt cleanup limit Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> Co-authored-by: Yushen <[email protected]> |
||
|
|
b4ca5b4a23 |
refactor(skills): retire legacy issue skill redirect (#9046)
Co-authored-by: Multica Eve <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
43b0571f99 |
MUL-7821: feat(cli): mark duplicates with issue status/update --duplicate-of (#8979)
* feat(cli): mark duplicates from issue status and issue update (MUL-7821) Add --duplicate-of to `multica issue status` and `multica issue update`. It resolves an issue key or UUID and sends duplicate_of_issue_id, which the server already accepts (MUL-7349). The CLI rejects a status other than cancelled, and description or attachment changes, before any request or upload. It fails when the response has no duplicate_of field, so a server older than v0.5.2 cannot silently drop the mark. Closes #8974 Co-authored-by: multica-agent <[email protected]> * docs: teach agents to mark duplicates with --duplicate-of (MUL-7821) Add the command to the agent runtime brief, the multica-platform issues reference, CLI_AND_DAEMON.md and the CLI docs tables, so agents record the mark instead of cancelling a duplicate and explaining it in a comment. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
5ed09342af |
MUL-7680: issue wakeup v2 — conditions, runaway protection, check-ins and visibility (#8807)
* feat(wakeups): add rule deadlines and expose the child-done system rule (MUL-7680)
Wakeups can now end: an absolute deadline (expires_at) or a relative wait
(expires_in_seconds) that restarts when the rule is re-enabled. When the
deadline passes first, the scheduler ends the rule; event rules with
on_timeout=wake run the target once with a wakeup.timeout fact. A timed-out
rule keeps disabled_at NULL so its timeout run stays claimable.
The implicit "wake the parent's assignee when a stage of sub-issues
finishes" behavior is now described by GET /api/issues/{id}/system-wakeups
and can be turned off or given a supplementary instruction per issue via
PUT /api/issues/{id}/system-wakeups/child_done. Rule reads fail open to the
previous behavior.
List responses add the creator (member or agent) and the new expiry fields.
The CLI gains --expires-in, --expires-at and --on-timeout.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(issues): let members create wakeups and show the child-done system rule (MUL-7680)
The issue sidebar's Wakeups section is always available on open issues and
gains a New wakeup popover. Members pick a condition (a time, a recurring
check with an end date, someone's reply, an agent's run ending, or raw
events), the agent to wake, the instruction, how often it fires, how long
to wait and what happens on timeout. It posts the same configuration agents
create with the CLI.
The parent's child-done wake appears as a System rule with its stage,
remaining sub-issues and target, a per-issue toggle and a supplementary
instruction. Rows show when a rule ends, timed-out rules read as such, and
details name who created the rule. Rule titles wrap to two lines instead of
truncating.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(wakeups): document deadlines, member-created rules and the system rule (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(wakeups): platform conditions, runaway protection and check-ins (MUL-7680)
Conditions: a wakeup can now wait for a fact the platform checks itself
(a status, assignee, label or property value; sub-issues or one stage
finishing; a linked PR's checks finishing or merging; another issue's
status). Related events only prompt an early evaluation; the rule wakes
its agent when the predicate becomes true, and a repeating rule fires
again only after it turned false or its facts changed. Finished PR checks
from before registration are ignored.
Runaway protection: repeating event rules stop after max_fires runs
(default 20), a rule pauses when its causal chain passes through it a
third time without a person in between, or when it starts 12 runs in an
hour. The reason is stored and shown.
Also: silent check-ins for every/cron checks (the run then posts no
fallback comment), wake now, delete, a per-rule run history, paused-rule
lists, timeline entries for created/triggered/timed-out/paused/check-in,
and source, paused scope, 7-day runs and child-done system rows in the
workspace list. The CLI gains --until-* conditions, --max-fires and the
trigger/delete/checkin/runs commands; the brief and wakeup prompt state
the check-in exception.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(issues): record child-done transitions in the status write (MUL-7680)
The child-done system rule ran after the status write committed and gave
up on any error, so a crash, deploy or transient failure lost the
parent's wake. A trigger now records each child's move into a closed
status in the writing transaction, for every writer. The request that
wrote it processes the rows right away and a scheduler sweep retries
anything left over; claims make the two paths process a transition
once.
The rule also follows a workspace default (settings key
system_wakeup_child_done) until an issue sets its own, the per-issue
update accepts partial bodies, and each trigger adds a timeline entry
that names its system comment.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): wakeup conditions, history, timeline and workspace list (MUL-7680)
Issue sidebar: members can create the platform-checked conditions (a field
reaching a value, sub-issues or a stage finishing, a linked PR's CI or merge,
another issue's status) and cap how often a repeating wait fires. Rule
details show the fire count, why the platform paused a rule, the latest runs
with silent-check notes, and offer wake now and delete.
The issue header says what the issue is waiting for ("Emacs 在等 Jiayuan 回复
+2") and opens the Wakeups section. Board and list cards use the same short
sentence, or flag a paused rule. Wakeup runs read as "由唤醒触发 · <condition>"
in the execution log, transcript and on the comments they post.
The timeline shows created, triggered, timed-out, paused and check-in entries
with the rule they came from; consecutive check-ins merge, and the child-done
entry stands in for its system comment, which it can reveal.
Automation → Issue wakeups adds a source column and filter, 7-day runs, a
paused scope with a banner, the child-done system rule on each waiting parent,
and "New wakeup" with an issue picker. Settings → Issue statuses sets the
system rule's workspace default. Copy in all five languages.
The server records the watched issue's identifier in other-issue conditions
and the rule's creator in timeline snapshots, and returns board summaries with
their condition.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* docs(wakeups): document conditions, runaway protection and check-ins (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(wakeups): list system rules without a revision and localize statuses (MUL-7680)
System rule rows returned revision 0, which clients reject as a rule
revision; they now return null, and a list row with an invalid revision no
longer fails the whole page. Built-in statuses in conditions and the status
picker read in the viewer's language. Adds a browser test for a condition
created from the form, the header summary, the scheduler waking the agent
and the workspace list.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): say why a rule paused without repeating "paused" (MUL-7680)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(i18n): use the product's task terms in wakeup copy (MUL-7680)
ja, ko and fr wakeup strings said イシュー / 이슈 / ticket; the product term
for an issue in those languages is タスク / 태스크 / tâche (and sub-tasks
accordingly), per the conventions page. French strings are rewritten for
the feminine noun.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* test(views): pin the shortcut platform in the wakeup form test (MUL-7680)
The send shortcut is primary+Enter, which is Ctrl on Linux CI runners, so
pressing Cmd+Enter only submitted on macOS.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(views): simplify the workspace wakeup list
Put the filters on one row: scope as a segmented control on the left,
source/trigger/agent and a search-as-you-type box on the right. "New
wakeup" moves to the page header, which drops the separate Search button.
Rows are single-line: the identifier sits before the title, frequency and
timezone move into the trigger tooltip, and selection, prompt edit and
the last run's transcript show on row hover or focus. The issue column
takes the remaining width and truncates, so the table fits the page.
Also fix creating from the list: the issue picker closes itself right
after reporting the selection, and that close cancelled the flow before
the form could open.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* refactor(server): run the child_done rule as a system issue wakeup
The parent-assignee wake on closed sub-issue stages was its own code path:
a system comment, a mention-style run, an override table and endpoints.
It is now an ordinary issue_wakeup row with system_rule set, sharing the
condition, receipts, runaway protection, timeline entries and run model of
people's rules.
- One children_done evaluator: a stage wakes the assignee when it closes
while a later stage waits; the wrap-up waits for every sub-issue,
unstaged ones included. People's sub-issue conditions read the same set.
- issue_child_event records closing, reopening, re-parenting and restaging
for every writer; the request processes it right after commit and the
sweep retries. User sub-issue rules become immediate too.
- The target is resolved when it fires: an agent run, a squad leader run,
an inbox notification for a member, or only the timeline entry.
- A parked (backlog) parent catches up when it leaves backlog; a waiting
run of the same agent is joined instead of queuing a second one; the
hourly limit pauses a runaway rule. No system comment is posted.
- The run gets the issue's instruction, else the workspace's, else the
built-in one, plus each stage's counts and the next stage.
- Workspace defaults move to GET/PUT /api/system-wakeups (owners and
admins) and apply to every rule nobody customized; turning a rule on
records what already holds so old facts do not fire.
- Existing open parents get their rule from a one-time backfill.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): workspace wakeup settings and sub-issue notifications
- Settings → Wakeups holds the sub-issue rule's workspace default and
default instruction; the toggle leaves Issue statuses.
- The issue's system rule shows who it reaches (a member is notified),
a platform pause, and the instruction it will use.
- Timeline entries say whether the assignee was woken, notified or joined
a waiting run. The folding of the old system comment is gone.
- Inbox renders the new children_done notification on web, desktop and
mobile.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): stop wakeups from repeating their agent's runs
Runs are serialized per issue and agent, so a wakeup never ran beside
another run of its agent, but it queued behind one and repeated it. Each
rule now checks two things before it starts a run:
- Acknowledged: every input came from the agent itself. Its own comments
and issue changes never wake it, and a condition its own unfinished run
on the issue satisfied does not wake it when the platform or the agent
set the rule up. This fixes #8849: a coordinator closing its own stage is
not woken again while that run is going. A person's condition rule still
runs afterwards, since the running agent lacks its instruction.
- Merged: when a run of the agent is already waiting to start on the issue
(assignment, comment, another rule), the rule's instruction and facts
join that run (context.wakeup_joined, sent to the daemon as
wakeup_joined and rendered for every prompt kind) instead of queuing
another. Turning the rule off or replacing it withdraws what it joined.
Sub-issue changes now record the run that made them, and hints and
condition.met inputs carry those runs so a satisfied condition knows
whether the agent caused it. A rule that names the agent itself as the
actor keeps waking it on its own changes.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* feat(views): show merged and acknowledged wakeups, hint assignee comment rules
- Timeline entries say when a wakeup joined the agent's waiting run or was
the agent's own doing, for people's rules and the sub-issue rule.
- The create form tells a member that a reply or comment rule for the
issue's agent assignee joins the run a comment already starts.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): hand waiting wakeups to a run when it is claimed
A rule that fired while its agent had a run waiting used to write its
instruction into that run right away and consume its inputs. That let a
member's rule run under another member's identity, kept deleted rules'
instructions in the run, lost the inputs when the run was cancelled or
claimed by an older daemon, and skipped the fire cap and loop check.
Now the rule only waits for a run that runs as the same person its own
run would, keeping its inputs. When a daemon advertising
joined-wakeups-v1 claims that run, JoinWaitingWakeups rechecks each rule
(on, same agent and person, creator still allowed, not the agent's own
input, no loop), consumes its inputs, counts the fire toward max_fires,
adds its chain and records the merge. A re-claim drops entries of rules
turned off or changed since. Anything else leaves the rule its inputs to
start its own run.
A child_done rule created by the backfill or by processing a change now
treats sub-issues with unprocessed close events as still open, so the
backfill no longer swallows a close waiting for the sweep.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
* fix(server): consume joined wakeup inputs only once the run starts
A claim used to consume the inputs of the rules that joined the run. If
that claim did not go through, the run went back to the queue carrying
inputs the rules no longer had: turning the sub-issue rule off on the
issue or for the workspace (which only changes enabled) did not remove
them from the next claim, and cancelling the run lost a once rule that
had already ended.
A claim now reserves the inputs for the run (receipt task_id, still
unprocessed). The rule's next dispatch settles them: a run that started
consumes them as a merged firing (once ends, max_fires counts and can
pause, timeline entry); a run that ended without starting gives them
back; one that has not started keeps them. Turning a rule off or
changing it discards its pending inputs, reserved ones included, and a
later claim of the same run rechecks every entry and drops the ones
without reserved inputs or no longer allowed to reach the run.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Claude Opus 5.5 <[email protected]>
Co-authored-by: multica-agent <[email protected]>
|
||
|
|
7dac88a6a8 |
MUL-7649: issue deliverables and dynamic blocks — sidebar section, overview grid, viewer info panel, html/mermaid block frame (#8779)
* feat(attachments): issue deliverables model and sequence block ids (MUL-7649) collectDeliverableFiles groups every comment upload into deliverables, merging same-name, same-type re-uploads into versions (newest version first). Description attachments are inputs and never enter the model. Sequence items now carry the id of the block they came from, so a viewer can say which comment (or the description) a file was posted in. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(viewer): host details, overview and info panel slots (MUL-7649) PreviewSequenceProvider takes an optional describeItem (title accessory, info panel, locate action) and onOpenOverview. The viewer renders only the slots it is handed: a locate button, a grid button (G) and an info toggle (I) whose panel sits beside the stage. Letter keys stand down in fields, and any key pressed inside an open menu stays with the menu. A zoom canvas still at fit now follows a viewport resize (the info panel opening), while a zoom the reader chose is only clamped, as before. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(issues): deliverables section, overview grid and info panel (MUL-7649) The sidebar's "Deliverables" section shows what the issue delivered as a whole: its pull requests and the files its comments uploaded, re-uploads merged into one entry marked v2. It lists the three newest images and four newest files, plus "View all N deliverables". The former Pull requests section becomes the code group, keeping its MUL-7429 actions (link a PR by URL, the auto-complete menu) beside the group label. While the workspace shows PRs, the section stays up even with nothing delivered, since that is where a PR gets linked by hand. The overview (from "view all" or G in the viewer) shows code first, then files grouped by posting comment, filterable by kind, each group one click from its comment. Its count always equals the sidebar's. In the viewer, deliverables get a version switcher, and every file gets an info panel (source excerpt, sibling files, uploader, time, size) and "Show in comments", which unfolds a resolved thread if needed and reuses the timeline's jump-and-flash highlight. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(comments): standalone attachments as cards and an image row (MUL-7649) The files under a comment (and a chat message) no longer stack as one full-width row each. Other files become cards in a grid: type glyph, name, "TYPE · size", download / remove on hover, and the whole card opens the file. Several images sit in one row at a shared height; a lone image keeps its full size. HTML keeps its embedded preview (MUL-2330). On the issue page a re-uploaded file's card carries its version (v1, v2) from the deliverables model, through a small context. Standalone attachments are grouped images, then HTML, then files, and the preview sequence walks them in that same order, so paging follows the screen. The file-type glyph moves to editor/utils/file-icon so comment cards and the deliverables surfaces share one mapping. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * refactor(issues): pull requests keep their own section (MUL-7649) Since MUL-7429 the PR block is issue workflow — linking a PR by hand, the auto-complete switch, "won't auto-complete: missing Closes" — rather than output, so it goes back to main's own Pull requests section, unchanged, directly above Deliverables. Deliverables is now the delivered files only: hidden until a comment delivers one, and the sidebar number, "View all N" and the overview's "All N" all count files. The overview drops its code group. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(comments): lay several images out as justified rows (MUL-7649) Fixed-height tiles wrapped as soon as the column narrowed — three screenshots became two plus an orphan, with a ragged right edge. Rows are now justified from each image's real aspect ratio: every row shares one height and a full row runs edge to edge. A row takes as many images as fit above a minimum height, never grows past a maximum, and a last row that does not fill keeps the height of the row above it. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(rich-content): dynamic block frame for html and mermaid fences (MUL-7649) A fenced ```html or ```mermaid block is part of the message body, so it now renders in one shared frame instead of two unrelated widgets: - Always-visible title bar: kind icon, the fence's title="..." (or the kind name), a kind chip, Preview | Source, fullscreen and copy. Previously the controls only appeared on hover and floated over the content. - The body takes its content's height (at least 120px) and collapses past 480px behind a fade and "Show all". An HTML block used to be a fixed 480px. - Loading keeps the height this block had earlier in the session; a script error or a Mermaid parse error is explained inside the frame with "View source" and "Copy error". - HTML gets the app's theme tokens (--foreground, --chart-1 ...); HTML that uses one also gets the app's color-scheme, so it follows dark mode. HTML that uses none keeps its own look. The sandboxed document reports its height and first uncaught error through a one-line postMessage bridge; the page checks the message source and clamps every value, and stops a document whose height follows the frame. rehype-raw drops the fence meta, so the title is read in the closed-fence parse. The Mermaid sandbox now also declares the app's color-scheme and font: in dark mode it was painted on an opaque white canvas, and its labels were drawn in a serif font that did not match the layout Mermaid measured. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(attachments): HTML files show as cards, not inline previews (MUL-7649) Reverses the MUL-2330 pin. An uploaded HTML file is a deliverable to open, not part of the text: like every other non-image file it shows as a card (a row inline in the body, a grid card under it) that opens in the viewer, and its contents are no longer fetched to embed a 480px iframe. HTML meant to be read in place is written as a ```html block, which renders as a dynamic block. - Attachment drops its html branch; HtmlAttachmentPreview is deleted and HtmlPreviewBody keeps only the inline source the viewer uses. - orderStandaloneAttachments groups images, then files (HTML included), so AttachmentList and the viewer sequence still walk the same order. - The MUL-2330 regression pins now assert the card and that no text fetch happens. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * docs: charts go in html/mermaid blocks, files show as cards (MUL-7649) Agents that uploaded an HTML chart expecting it to render in the comment now get a file card instead. Tell them where each kind of content goes: - multica-platform skill (issues reference, routed from the table): a fenced ```html / ```mermaid block renders in place with a title bar and content height; an attached file, HTML included, is a card that opens the viewer. Covers title="...", the sandbox, the theme variables and their opt-in color scheme, and sizing to content rather than the viewport. - `multica issue comment add --attachment` help says the same in one line. - Comments docs (all five languages) describe both. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * feat(daemon): runtime brief says where charts and diagrams go (MUL-7649) The chart/file rule lived only in the multica-platform skill's issues reference, which an agent opens on demand, and the skill's description did not mention comment formatting, so an agent that just wanted a chart in its result had no reason to read it. Agents that uploaded report.html expecting an inline chart now get a card. - The brief's Output section carries one line, beside the file-delivery line, on the surfaces the web renders (issue comments and web/mobile chat): put charts and diagrams in the text as a fenced html or mermaid block, name it with title="...", and an attached file, HTML included, shows as a card. Theming and sizing stay in the reference. - Channel chats, autopilot run results and quick-create stdout do not render these blocks and do not get the line; the delivery tests pin both sides. - The skill description names "charts and files in comments" so the reference is picked for that task. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): locate a file in a collapsed thread (MUL-7649) "Show in comments" for a file posted in a reply only unfolded resolved threads and gave up after 30 frames, so a thread the reader had collapsed stayed shut and the reply never mounted. A reply now goes through the quick-jump rail's jumpToReply, which already undoes every kind of folding and waits for the reply to land. A root comment gets the same treatment for its own thread (the reader's collapse, or a resolved bar), then the jump. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(viewer): the info panel's "Show in comments" closes the viewer (MUL-7649) The top bar's locate went through the viewer, which closes itself first; the same button in the info panel called the host's callback directly, so the page scrolled underneath a viewer that still covered it. Controls handed to describeItem gain close(), and the issue page builds one close-then-locate action for both entry points. Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> * fix(issues): the deliverables overview is a real modal dialog (MUL-7649) The overview was a portaled layer with role="dialog" and nothing else: focus stayed on the opener, Tab walked into the page underneath, and closing left focus wherever it was. It now renders through the shared Dialog, restyled to cover the window, so the primitive moves focus in, keeps it inside, returns it to the opener, and handles Escape. `G` back to the viewer's file stays. The dialog is named by its title ("MUL-123 deliverables"). Found in review by Emacs. Co-Authored-By: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Claude Opus 5.5 <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
e909e9c89d |
MUL-7587 fix(daemon): show on-behalf-of identity in agent context (#8688)
* fix(daemon): expose original requester in agent context * fix(daemon): render run originator as on-behalf-of identity * docs(daemon): update stale Task Initiator comments to On Behalf Of Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
5140b99d86 |
MUL-7577: remove no-start guidance from runtime and platform skill (#8680)
* fix(prompts): remove no-start guidance from runtime briefs Co-authored-by: multica-agent <[email protected]> * fix(skills): remove no-start guidance from platform instructions Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
8c9f865e3e |
MUL-7480: Revert comment steering feature (#8648)
* Revert "MUL-7480: Link delivered steers to final replies (#8623)" This reverts commit |
||
|
|
624648cc14 |
fix(daemon): offer a Git Bash variant of the Windows reply cookbook (MUL-7554) (#8634)
The Windows brief only knew PowerShell: the per-turn reply cookbook and the Comment Formatting cleanup line assumed every Windows agent pastes into PowerShell. The daemon knows the host OS, not the shell behind the agent's command tool. Claude Code runs Git Bash for its Bash tool, and a user who turns off its PowerShell tool (CLAUDE_CODE_USE_POWERSHELL_TOOL=0, GitHub #8627) gets a cookbook whose 'if ($LASTEXITCODE ...)' line is a bash syntax error that fires AFTER the post succeeded, so the agent sees a failure and may re-post. Render one variant per shell on Windows (PowerShell with the $LASTEXITCODE gate, Git Bash with '&&') and tell the agent to pick the one its command tool runs. The shell-execution test now selects the block by shell and adds a windows/git-bash case that runs the bash variant under sh. Co-authored-by: multica-agent <[email protected]> |
||
|
|
c375f83c2e |
MUL-7418: add issue event and time wakeups (#8467)
* feat: add issue event and time wakeups (MUL-7418) Co-authored-by: multica-agent <[email protected]> * feat(wakeups): capture issue collaboration event lifecycle Co-authored-by: multica-agent <[email protected]> * fix(wakeups): preserve reply source when reopening threads Co-authored-by: multica-agent <[email protected]> * feat(wakeups): summarize sidebar and issue activity cues Co-authored-by: multica-agent <[email protected]> * fix(wakeups): restore disabled configurations with a two-way toggle Co-authored-by: multica-agent <[email protected]> * feat(wakeups): manage workspace wakeups in Autopilot Co-authored-by: multica-agent <[email protected]> * chore(wakeups): reserve migrations after current main Co-authored-by: multica-agent <[email protected]> * test(wakeups): align inventory fixtures with shared contracts Co-authored-by: multica-agent <[email protected]> * fix(wakeups): distinguish trigger conditions and execution results Co-authored-by: multica-agent <[email protected]> * fix(wakeups): suppress registration feedback and drain bounded evidence Co-authored-by: multica-agent <[email protected]> * fix(wakeups): align sidebar actions with titles Co-authored-by: multica-agent <[email protected]> * fix(wakeups): bound database work and unify shared issue visibility Co-authored-by: multica-agent <[email protected]> * feat(wakeups): filter mutation events by member or agent actor Co-authored-by: multica-agent <[email protected]> * refactor(wakeups): merge structured evidence and narrow receipt locks Co-authored-by: multica-agent <[email protected]> * feat(wakeups): let people edit wakeup instructions Co-authored-by: multica-agent <[email protected]> * fix(wakeups): align inventory controls and compact prompt editing Co-authored-by: multica-agent <[email protected]> * fix(mobile): handle deferred agent run status Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8ce795b007 |
MUL-7480: Steer active agent turns from comments (#8605)
* feat: steer active agent turns from comments (MUL-7480) Co-authored-by: multica-agent <[email protected]> * fix: make comment steering hint driven Co-authored-by: multica-agent <[email protected]> * fix: harden active comment steering races Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Forge-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
9018df3efb |
feat(projects): set a repository's checkout ref from the UI (MUL-7504) (#8592)
* feat(projects): set a repository's checkout ref from the UI (MUL-7504) A project that tracks one delivery line could already pin the branch its tasks start from — `github_repo.resource_ref.ref` has been stored, sent to the daemon and honored at checkout since #4467. The UI only ever displayed it. Anyone working through the web, desktop or mobile app had to drop to `multica project resource update --ref` to set something the interface already showed as a property of the resource, and until they did, every task silently started from the remote default branch. This adds the missing input, on the three paths a repository is configured: - Create-project modal: a "Branch, tag, or commit" field per selected repo, shown once a URL is entered so the common default-branch case still reads as one field. - Resource panel: the same field in the attach form, plus a dialog on each attached repo — the row previously offered only delete. - Mobile: the field in the attach sheet, and the ref rendered in the list, which never showed it at all. Three details the shape of the existing code forced: - The ref renders on its own line rather than appended to the repo label. A custom name took that slot, so naming a repo hid the one signal that its tasks do not start from the default branch — and mobile is the only client that collects a name. - Edits spread the stored ref before overwriting. The server replaces resource_ref wholesale, so a payload carrying only `ref` is rejected for a missing URL. - Pasting a `.../tree/<branch>` URL now splits into the two fields. Mobile's URL check accepted that whole string and stored it as the clone URL, so the gap was already producing targets that cannot be cloned. Server-side, `ref` was stored with no validation beyond a trim, so a typo survived to checkout and surfaced as a daemon 500 naming its local repo cache, minutes later and to whoever ran the task rather than whoever typed it. validateGitRef applies the shape rules from `git check-ref-format` plus a length cap. Existence on the remote is deliberately still not checked: the server cannot see the repository, and an offline daemon or a private repo must not block saving configuration. Free text rather than a branch picker, by design. Nothing in the product can list a repository's branches today, and a picker could not express a tag or a commit SHA anyway. A "list remote branches" endpoint remains the follow-up #8572 proposes; it should stay an input aid, never the only way. Co-authored-by: multica-agent <[email protected]> * feat(projects): make the pinned branch the delivery target too (MUL-7504) Pinning a repository's starting point only solved half the problem. Nothing in Multica creates pull requests — `gh pr create` is the agent's own call, and without `--base` it targets the repository's default branch. So a project pinned to `release/2026-09` had its agents start in the right place and then open pull requests into `main`, carrying every commit the release line had that `main` lacked. Product review asked for the two halves to move together, so the brief now states both. The Repositories section names each repo's starting point and, when anything is pinned, says to deliver back to it. The Project Context bullet stops implying `--ref` is how you reach the configured revision — the daemon already applied it, and `--ref` is the override. The delivery rule is stated conditionally, because a pin is not necessarily a branch. The field accepts anything git resolves, and neither the server nor the daemon can tell a branch from a tag or a commit without asking the remote — which the product deliberately does not do. A tag has nothing to merge back into, so the agent is told to confirm rather than guess a base. That same ambiguity shapes how the UI narrows to branches. The field now says "Starting branch" and explains that tasks both start and deliver there, but the promise cannot be enforced: `v1.2.3` is a legal branch name and `main` is a legal tag. Only a full-length object id is unambiguous, and only that is declined, pointing at `multica repo checkout --ref` where a one-off revision belongs. The stored grammar is unchanged, so the CLI and API still accept tags and commits — the daemon has always resolved all three, and this is a UI-level product choice, not a data restriction. Two fixes from the same review: - An unpinned repository now reads "Default branch" instead of showing nothing. Clearing a branch used to make the line vanish, which looks identical to the setting never having existed — there was no way to confirm from the panel that a repo was deliberately on its default, or that the row had a branch setting at all. - The edit dialog says the change applies to newly started work. The ref is read when a task is claimed, so work already underway keeps the branch it started on, and the copy now says so rather than leaving it to be discovered. Co-authored-by: multica-agent <[email protected]> * fix(projects): close three gaps in the starting-branch flow (MUL-7504) All three found in review of the two commits before this one. **A resumed task could be told to deliver somewhere new.** The starting point in the brief is the project's CURRENT setting, read when the run was claimed. A task that started from `release/a`, left uncommitted work and resumed after the project moved to `release/b` kept its old checkout — the work is still branched off `release/a` — while the brief now named `release/b` and told it to deliver there. Following that would push the old line's work into the new one, and would break what the edit dialog promises: work already underway keeps the branch it started on. The brief cannot settle this alone, because whether a checkout gets reused is only known once `repo checkout` runs. It defers to the checkout instead, which already reports `Kept` and names the branch it is on. **Create Project accepted a branch it had already rejected.** The submit gate only checked the add-a-repo field, so a branch edited on an already-selected repo reached the payload with nothing but an inline error to show for it — and the server accepts commit ids by design, so nothing downstream caught it. Both the button and handleSubmit now check every selected repo; the button alone was never enough, since TitleEditor's onSubmit calls handleSubmit directly. **A second pasted browse URL was stored whole.** Splitting `/tree/<branch>` was gated on the branch field being empty, so pasting another repository's browse URL after one was already filled in saved the entire `/tree/...` string as the clone URL — a target that cannot be cloned. Normalising the URL is now unconditional in all three entry points. Whether to overwrite the BRANCH is the separate question, and the pasted pair wins: the branch field only appears once a URL is present, so a value sitting in it came from the previous URL rather than from something typed ahead of time. One defect the review surfaced indirectly: GithubRefField took an optional `id` and the per-repo instance in the create-project modal passed none, which detached the label from its input — leaving the field unnamed for a screen reader, and unreachable by getByLabelText, which is why the gap had no test. The id is now generated when omitted. Co-authored-by: multica-agent <[email protected]> * fix(execenv): stop naming the checkout as the resumed delivery target (MUL-7504) The resume warning added in the previous commit told the agent to deliver kept work "to the branch it actually started from, which the checkout names". The second clause is wrong. A kept checkout reports the branch the worktree is ON — resolved by `git symbolic-ref`, so the task's own `agent/<name>/<task>` branch. That is the HEAD of a pull request, never its base, and nothing in WorktreeResult carries the ref it was cut from. An agent following that sentence would pass its own branch as `--base` and open a pull request whose base equals its head. The warning now says what the reported branch actually is, and points at the sources that do settle the question: the base of the work's existing pull request, or the target the task states, and ask when neither does. It also moves out of the `if pinned` branch. A project cleared back to its default branch renders no starting point at all, yet a task resumed after that change still holds a checkout cut from the old one — gating the warning on a pin dropped it exactly where the mismatch is invisible. The one sentence that only makes sense alongside a listed starting point ("do not retarget it to a starting point listed above") stays conditional, so the cleared variant does not point at a line that is not there. Regressions cover both resume shapes — pinned A to B, and A cleared to the default branch — plus the specific wrong phrasing, so it cannot come back. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
d7009df8cc |
Revert "MUL-7300 fix(issues): preserve Quick Create original input (#8519)" (#8546)
This reverts commit
|
||
|
|
a6472044c3 |
MUL-7300 fix(issues): preserve Quick Create original input (#8519)
* fix(issues): preserve quick-create original input Co-authored-by: multica-agent <[email protected]> * fix(issues): validate quick-create origins Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
f39debf8d1 |
MUL-7344: skip the issue read and the comment scan when the server already answered them (#8377)
* feat(daemon): read the comment delta with one --since, not a roots scan (MUL-7344) A resumed follow-up turn whose claim carried a server-computed issue-wide comment delta was still told to run the workflow's two-step scan: a `--roots-only --summary` pass to discover which threads moved, then a `--thread ... --since ...` expansion per thread. That rediscovers, in two or more calls, an answer the server already has. `--since <anchor>` without `--thread` IS that answer. ListCommentsSinceForIssue returns every comment on the issue created after the anchor, in every thread and at every nesting level, with no folding, on the same `created_at > since` predicate CountNewCommentsSince counted with. It is a strict superset of what the roots scan could surface: roots-only `last_activity_at` is MAX(created_at) over a thread, so any comment able to move a thread's activity is already in the `--since` result. So BuildNewCommentsHint now hands over exactly one wide read and says it answers step 2's scan — the same rule BuildResumedCommentsHint already applies to a server-computed EMPTY delta. The optional second command stays a THREAD read on `--tail 30` for the reply itself; it is never the wide read, and it never combines `--thread` with `--since`, which drops the thread root. Brief step 2 and the multica-platform skill's bounded-reads rule both gain the matching static sentence, so an agent is not left choosing between the hint and two surfaces that contradict it. Cold start, dropped resume and unknown-delta paths are untouched, byte for byte: only an affirmative server report may stand in for the scan. Co-authored-by: multica-agent <[email protected]> * feat(claim): compare the issue against the last run so a resumed turn can skip the read (MUL-7344) A follow-up turn that resumes its provider session already holds the issue it read minutes ago, and was still told, unconditionally, to run `multica issue get` before doing anything. On an issue that did not move, that read returns bytes the session already has. The claim now answers the question instead. Each claim records the comparable state of the issue it built its payload from — status, assignee, priority, and sha256 of title and description — in a new `agent_task_queue.issue_snapshot` JSONB column, and compares against the snapshot the SAME agent's previous run on the SAME issue recorded. The response carries `issue_state_delta_known`, `issue_changed_fields`, and the current `issue_status` / assignee. Why a per-claim snapshot and not a `content_revision` on `issue`: a revision column has to be bumped by every writer that touches one of those five fields, and the first one missed reports "unchanged" forever with no symptom. A snapshot taken at claim time has exactly one writer. It also rides the anchor the comment delta already uses (agent_id, issue_id, started_at DESC), so both deltas on a claim mean "since your last run" in the same sense. The daemon renders one of three things, gated on a REAL resume: - unchanged → reported as workflow step 1's answer; the read survives only as "if resumed memory is not enough". - changed → the changed field NAMES plus the read, so the agent knows what to look for instead of diffing blind. - otherwise → the pre-existing instruction, byte for byte. `issue_state_delta_known` carries the same contract as `new_comments_delta_known` (MUL-6984), and for the same reason: a cold start, a missing prior snapshot, a read error, a foreign shape version and an old server all produce an empty changed-field list, identical to a genuine "nothing changed". Only the flag separates them, and only the affirmative one may stand in for the read. The compared set is named in full in the prompt rather than summarised as "the issue": labels, parent, due date, stage, project and metadata are NOT compared, and an agent told "unchanged" has to be able to see that. The snapshot write is NOT gated on the task being comment-backed — an assignment run that recorded nothing would leave the next run with no baseline. It shares the receipt's CAS, so a stale handler cannot write over a newer reclaim or after execution starts, and a snapshot that fails to encode is simply not recorded: the next run reports "not compared" and reads the issue. Co-authored-by: multica-agent <[email protected]> * fix(claim): renumber the migration past main and fold in review nits (MUL-7344) CI caught a real collision: main landed 471_comment_deleted_at through 474 while this branch was open, so 471_agent_task_issue_snapshot shared a numeric prefix and TestMigrationNumericPrefixesAreUnique failed. Renumbered to 475 after merging main; no other migration references it. Review nits from the PR review, all non-blocking, all cheap to take now: 1. Brief step 2's closing sentence told the agent that a resumed run's scan surfaces moved threads via `last_activity_at`. That is now true only on the path where the delta is UNKNOWN — when the server hands over a `--since` delta there is no scan to read it from. Rewritten to attach the `last_activity_at` guidance to the scan itself, wherever the scan happens. Done in this PR rather than deferred because this PR already moves brief bytes, so it costs no extra prompt-cache invalidation. 2. The two per-claim anchor reads are now one. GetLastTaskStartedAtForIssueAndAgent and GetLastTaskIssueSnapshotForIssueAndAgent selected the same row with the same WHERE and ORDER, differing only in projection; they are replaced by GetLastRunAnchorForIssueAndAgent returning started_at and issue_snapshot together. A comment-triggered claim is back to ONE anchor read, as before this branch. The saved round trip is the smaller half. The real gain is that "since your last run" is now a single resolved fact on a claim instead of two independently-resolved ones that could disagree. The shared read also shares a failure mode, and both consumers already degrade the same safe way: comment scan required, issue comparison unknown. 3. The hint's `%d new comment(s)` excludes the injected trigger and the agent's own comments; the `--since` read it hands over has no such filter, so the row count is legitimately higher. Narrowing the read to match would mean a second count-shaped query for no gain, so the text now states the mismatch and its direction outright instead of leaving the agent to reconcile two numbers. New test pins the merge: one claim, one prior run, both deltas resolved from that single anchor row. Co-authored-by: multica-agent <[email protected]> * fix(migrations): renumber the issue-snapshot migration to 478 (MUL-7344) main landed 475_issue_status_key_not_reserved through 477 while the previous renumber was in flight, so 475_agent_task_issue_snapshot collided in turn. Merged main again and moved to 478. TestMigrationNumericPrefixesAreUnique passes, and the full ledger applies cleanly from scratch through 478. Co-authored-by: multica-agent <[email protected]> * fix(claim): measure both deltas from the run whose session is resumed (MUL-7344) Review found a real correctness bug, not a performance one: the claim could tell a resumed agent "the issue is unchanged" while the session it handed back predated the change. Both deltas were anchored on the most recently STARTED run for (agent, issue). The session the daemon resumes is chosen by a different rule, and the two are not the same row: - GetLastTaskSession skips poisoned and retired sessions, so a newer run that died on an unprocessable conversation is passed over for an OLDER healthy one. - A manual rerun resumes the exact source task the operator clicked. So: an old session remembers description A, the user edits it to B, a newer run records B and then dies unresumable. The next follow-up resumes the OLD session but compares B against B, reports "unchanged", and the agent keeps working from a context that still says A. PriorSessionResumeUnavailable does not catch this — it flags a missing Codex rollout, which is only one of the ways the resumed session can be older than the newest run. The fix binds both deltas to the run actually being resumed. GetLastTaskSession now returns that row's started_at and issue_snapshot alongside its session, the rerun path takes them from its source task, and the delta computation moves after resume resolution so it can only ever read the adopted row. Where no session is adopted — no prior session, a session on another runtime, a forced-fresh retry, a rerun whose source failure poisoned the conversation — no delta is reported and the daemon performs the reads it always has. That makes the standalone anchor query unnecessary: the anchor is now a projection on a row the claim already fetched, so a claim reads no extra row at all (GetLastRunAnchorForIssueAndAgent is removed). The precondition is now visible in the fixtures: a prior run has to be resumable for a delta to exist, so the comment-delta tests seed a session_id. The regression test covers both paths and was verified to fail against the previous anchor — both sub-cases report an empty changed-field list, which is exactly the wrong "unchanged". Also renumbers the migration 478 → 491; main landed 478_issue_status_category_expand through 490 while the branch was open. Co-authored-by: multica-agent <[email protected]> * fix(claim): a never-started anchor row may date neither delta (MUL-7344) Final review found the last corner of the resumed-run bug. Binding the anchor to the resumed SESSION was necessary but not sufficient: GetLastTaskSession returns that session's latest TERMINAL row, and that row is not always a run that ran. A retry child can inherit the session, be claimed — which writes its snapshot — and then fail before the agent ever starts: runtime offline during prepare, cancelled while dispatched. Its snapshot describes the issue as of ITS claim, while the session's memory stops at the last run that actually executed. If the description was edited in between, the current issue matches that snapshot and the claim reports "unchanged" to an agent whose context predates the edit. The started_at gate now covers both deltas rather than only the comment one. The comment delta was already immune because it read StartedAt directly; the issue delta read only the snapshot, so it had no such guard. Hoisting the check makes the row's requirement explicit — a row that never ran dates nothing — and the inner duplicate is dropped so no reader has to wonder what each guards. Regression test reproduces the exact fixture and was verified to fail without the gate: known=true with an empty changed-field list, i.e. the false "unchanged". Also from review: - Adopt the no-adopted-session table (missing session, session on another runtime, force-fresh retry, poisoned rerun source). Each seeds a usable snapshot AND an unread comment, so a regression has something to report and the assertion cannot pass on an empty fixture. - The resumed-run regression test asserted a comment anchor that never executed: both sub-cases created only the trigger comment, which the count excludes, so NewCommentsSince was empty and the branch was dead. A comment between the two runs now makes it run and pins known/count/since. - Rewrite the migration header, which still described the old "(agent_id, issue_id, started_at DESC)" anchor and an index rationale for a column that is never a lookup key. Co-authored-by: multica-agent <[email protected]> * refactor(claim): compare title, description and status only (MUL-7344) The snapshot compared five fields. The comparison answers exactly one question — must the agent run `issue get` again? — so a field earns a place only if changing it alters what the agent does AND its current value is not already in the per-turn message. Assignee fails the second test: the claim ships the current assignee outright, and "is this mine now" is answered by that value, not by whether it moved. Priority fails the first: it is reachable only by reading the issue, but it does not change what the agent does. Both are dropped. Status stays even though it is also shipped as a current value, so comparing it is strictly redundant for "what is it now". It earns its place as a signal rather than a value: "changed: status" is the clearest evidence that somebody intervened between the runs — a push back from in_review to todo means the delivery was rejected — and it costs nothing to carry. Narrowing the set means a narrower claim, so the prompt now says "compared title, description and status". An issue whose only change is assignee or priority is reported as unchanged, which is correct and now pinned by a test that also asserts the current assignee still ships — without that second assertion the fixture's UPDATE could silently not apply and the test would pass on nothing. Snapshot version goes to 2, because the rule is that the version tracks the compared SET. No deployment has written a v1 row, but the gate is what makes narrowing safe in general: a v1 row read by v2 code is "not compared", which degrades to the unconditional read. Test fixtures now build snapshots at issueSnapshotVersion rather than a literal 1, so a future bump cannot quietly turn them into not-compared tests while they keep passing. The foreign-version case stays explicit at +1. Also renumbers the migration 491 -> 495; main landed 491 through 494 while the branch was open. Fifth renumber. Co-authored-by: multica-agent <[email protected]> * refactor(claim): keep the snapshot at v1 (MUL-7344) The previous commit bumped issueSnapshotVersion to 2 when it narrowed the compared set. That was the version rule applied past its purpose. The rule — "bump when the compared SET changes" — exists to stop a stored snapshot from being compared against a differently-shaped one. It protects DEPLOYED rows. This column is introduced by this same unmerged PR: the migration exists only on this branch, main has neither the column nor the writer, and no environment has ever stored a row with the wider set. There is nothing for the bump to protect, and shipping v2 as the first version the column has ever held would leave a reader hunting for a v1 that never existed. Back to 1. The `v` field and its unknown-version gate stay exactly as they were — those are what make a real narrowing safe later. The comment now says which version this is and why the review-time narrowing did not earn a bump, so the rule still has a stated precedent without a burned number. No behaviour change: the compared set is still title, description and status, and fixtures build at issueSnapshotVersion rather than a literal, so they followed the constant back down. Co-authored-by: multica-agent <[email protected]> * fix(claim): only a completed run may date a delta (MUL-7344) Final review found a window one step later than the never-started case. A snapshot is written at CLAIM time. The daemon then writes started_at (TaskService.StartTask) and only afterwards launches the provider. So a run can carry both a snapshot and a started_at while its prompt never reached the session: the daemon exits during prepare, the runtime drops, orphan recovery marks the row failed. Anchoring there compares the current issue against a snapshot describing an edit that session never saw — reporting "unchanged" across a real change, and dating the comment delta late enough to hide every comment older than that row's start. Reproduced on the real CreateRetryTask -> claim -> StartTask -> RecoverOrphanedTasksForRuntime -> follow-up claim path. Nothing on the row separates "failed after the provider ran" from "failed before it ran". session_id is inherited by CreateRetryTask at insert, so it does not mean the session was fed; started_at precedes the launch, so it does not mean the turn began. The only terminal status that PROVES delivery is 'completed' — a run cannot finish a turn it never started. So failed and cancelled sources now date neither delta. Their SESSION is still resumed exactly as before, and GetLastTaskSession still accepts them on purpose; those runs simply perform the context reads they always did. That is the cheap side of the trade: a follow-up after a failure or a stop is already recovering from something, while the case the optimization exists for — an ordinary follow-up after a healthy run — is untouched. Both conditions now live in newResumedRunAnchor rather than at the use site, so "which run may date a delta" is answered once, and GetLastTaskSession carries the status needed to answer it. Regression test is the reviewer's repro, verified to fail without the rule: known=true with an empty changed list AND a computed zero comment count, i.e. both a false "unchanged" and a false "no new comments". Also: - Renumber the migration 495 -> 499; main landed 495 through 498. Sixth renumber. - The migration's content_revision rationale named the old five-field set while arguing about hypothetical issue-row writers; it now refers to "any compared field", which is what the argument actually depends on. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
080b118f07 |
fix(daemon): isolate cached worktree Git identities (MUL-7423) (#8470)
* fix(daemon): isolate Git identities in cached worktrees (MUL-7423) Co-authored-by: multica-agent <[email protected]> * fix(repocache): preserve config lock ownership on handoff Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: multica-agent <[email protected]> |
||
|
|
d665f0ac60 |
MUL-7379 docs: retranslate the issue status model and correct category-wide behavior claims (#8438)
Documentation half of MUL-7379, after #8435 fixed the code. The stale pages did not merely use outdated wording — they promised behavior the platform stopped providing, in the three languages nobody re-read. zh/ja/ko issues.mdx were left on the retired seven-category inheritance model when MUL-7240 rewrote the English page. Each still said a new status "inherits that category's behavior in full", with a seven-row table promising that an in_review-category status finishes the autopilot run and auto-archives past failure notices, an in_progress-category status rolls back to todo on failure, and a backlog-category status parks — plus the worked example naming `Code Review`. An admin following the Chinese page would create that status and their autopilot runs would silently never finish. All three now mirror the English source, including the installed-client wire-enum paragraph they lacked entirely; ja and ko also gained the agent status-writing paragraph only en and zh carried. Status keys and category names stay lowercase English per the i18n glossary, which already listed the four values correctly. assigning-issues (all four locales) said only the `backlog` CATEGORY parks and a custom status in it behaves identically. WillEnqueueRun parks on the literal key, so a reader following that page parks work on their own unstarted status and gets exactly the run the page promises they will not. The same sentence named a `cancelled` category, which is now `closed`. inbox (all four locales) attributed failure-notice archiving to an `in_review` category that does not exist; it now states the handoff rule #8435 implemented, including why blocked is excluded. The Korean settings subtitle said five lifecycle categories directly above the four groups. CLI_AND_DAEMON.md presented the seven built-ins as the complete enum while validateIssueStatus is deliberately shape-only. One behavioral change, in its own commit with its test: the agent brief rendered each lifecycle group as "- `unstarted`: `backlog`, `todo` (built-in)". That leading backticked token used to always be a valid status key, so sharing the formatting was harmless; `unstarted`, `started` and `closed` are now category-only names that Resolve refuses, so the one line teaching agents how to write status invited `multica issue status <id> started` — a 400. The category label is now plain text and only settable keys keep backticks. Review caught two overclaims, both corrected: filtering resolves on the concrete status like boards, lists and sorting do, not on the category; and `done` is both a lifecycle category and a built-in key, so it is not among the names Resolve refuses. |
||
|
|
b62cce56da |
MUL-7359 perf(runtime): avoid echoing final comment bodies (#8390)
* perf(runtime): avoid echoing final comment bodies * refactor(daemon): keep the file-first guardrail as the Comment Formatting lede The receipt-mode rule opened the section, pushing the file-first guardrail (MUL-2904 / #4182 / MUL-4252) out of the lede. The guardrail is the section's correctness red line — a shell-mangled body is a wrong comment — while the receipt mode only decides how much of an already-correct comment is echoed back. Move the rule after the guardrail in both OS variants; it is now a single const so the two branches cannot drift. Co-authored-by: multica-agent <[email protected]> * docs(skills): drop the duplicated final-comment-receipt section issues.md states its own charter as "Product contracts the runtime brief does not fully encode", and SKILL.md gives the brief ownership of "when to comment". The brief's ## Comment Formatting now carries this rule in full, so the copy here duplicates a brief-owned instruction against the single-owner convention (MUL-6984 #7971, MUL-5825 #6517). It was also unreachable by the documented routing: the section was added to the file's own table of contents, but SKILL.md's Routing row for issues.md was not updated, so no task would route here for this topic. Co-authored-by: multica-agent <[email protected]> * fix(daemon): gate final-comment cleanup on the post succeeding Agents hand the reply cookbook to one shell call. With cleanup as its own statement, a failed post is followed by a cleanup that SUCCEEDS, so the call exits 0 and ./reply.md is deleted. Under `--output table` a failed and a successful post both write nothing to stdout, which leaves the exit status as the only machine-checkable signal — masking it lets an agent end its turn believing an unposted final result was delivered, and drops the body it would need to retry with. The brief already told agents to check the posting command's exit status; the rendered snippet made that impossible. POSIX chains with `&&`. PowerShell 5.1 has no `&&` (it landed in 7), so the Windows variant propagates $LASTEXITCODE instead. Both keep ./reply.md on disk when the post fails. The guard EXECUTES the rendered snippet against a stub CLI: this is a shell-composition property, and the handler-level test can only prove the command returns an error, not that the pasted block preserves it. Verified to fail on the previous two-statement form. The pwsh case runs wherever PowerShell is installed, including Linux runners, and skips otherwise. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
d77a1c5ea7 |
fix(prompts): remove status wire details from operational guidance (#8401)
Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
aac85ab263 |
fix(daemon): preserve issue status catalog wire compatibility (#8378)
Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
7dafc0cd62 |
MUL-7240: Unify issue lifecycle into four stored categories (#8261)
* feat: simplify issue status categories Co-authored-by: multica-agent <[email protected]> * feat: adopt four stored issue lifecycle categories Co-authored-by: multica-agent <[email protected]> * fix: preserve visible work when upgrading saved category preferences Co-authored-by: multica-agent <[email protected]> * docs: align lifecycle comments with stored and wire contracts Co-authored-by: multica-agent <[email protected]> * test: align mobile grouping expectations with four categories Co-authored-by: multica-agent <[email protected]> * fix(issues): group user views by concrete status keys Co-authored-by: multica-agent <[email protected]> * fix(settings): simplify status editor copy and category selector Co-authored-by: multica-agent <[email protected]> * feat(statuses): allow ordering built-ins and unify row actions Co-authored-by: multica-agent <[email protected]> * feat(statuses): configure custom status icons independently of category Co-authored-by: multica-agent <[email protected]> * fix(statuses): dismiss built-in status notice Co-authored-by: multica-agent <[email protected]> * fix(issue-status): require moving issues before archive and polish controls Co-authored-by: multica-agent <[email protected]> * fix(ci): renumber issue status migrations after main updates Co-authored-by: multica-agent <[email protected]> * fix(statuses): isolate archive inspection and align catalog behavior Co-authored-by: multica-agent <[email protected]> * fix(statuses): respect injectable catalog for table grouping Co-authored-by: multica-agent <[email protected]> * refactor(statuses): simplify built-in skill and runtime guidance Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Lambda <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
2441638c05 |
MUL-7284: fix(daemon): keep an existing checkout's work when repo checkout runs again (#8309)
* fix(daemon): keep an existing checkout's work when repo checkout runs again Re-running `multica repo checkout` on a path that already held a checkout ran git reset --hard and git clean -fd and moved it to a new branch from the default branch; isolated checkouts also deleted earlier agent/* branches. Uncommitted changes, untracked files, and unpushed commits were lost with output that still read as a normal checkout. An existing checkout that holds work, or is already on this task's branch, is now kept exactly as it is and only its remote refs are fetched. The result reports that it was kept, with its branch and uncommitted/unpushed counts, and the CLI says so. --fresh restores the old start-over behavior. Isolated-mode pruning keeps agent/* branches with unpushed commits, and a linked worktree holding work is no longer removed to migrate it. MUL-7284 Co-authored-by: multica-agent <[email protected]> * fix(daemon): carry a migrated linked worktree's unpushed branch into the isolated checkout --fresh migration of a linked worktree to an isolated checkout removed the worktree and seeded a new clone whose copied branches are all deleted, so a branch holding unpushed commits stayed only in the shared cache: out of the agent's reach and dropped by the next GC. Carry that one branch into the new checkout (no other cache branch), imported before the task branch so a same-named task branch moves to a timestamped name. Align the agent-facing --fresh wording: it discards uncommitted and untracked files and starts a new branch; commits stay on the old branch, but push any still needed first, since a linked worktree's old branch lives in the shared cache that GC prunes. MUL-7284 Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
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]>
|
||
|
|
2efe1b2a43 |
MUL-6984 fix(daemon): let workflow step 2 alone decide the comment scan (#7972)
* MUL-6984 fix(daemon): let workflow step 2 alone decide the comment scan
The brief's step 2 called the catch-up scan mandatory while the per-turn
hints in the same context made it conditional ("Need cross-thread
background?", "Only if you need context from the other threads", "If your
reply depends on thread context"). Step 2 now owns the modality; the hints
carry facts and this turn's exact commands and hand the scan over as the
wide read. Hint dispatch keys on PriorSessionID before the delta so a run
whose resume the daemon dropped takes the fresh-session path instead of the
resumed delta hint. The assignment pointer drops the parenthetical that
implied only assignment turns treat the read as mandatory.
Measured on 537 comment-triggered runs: 1 in 10 scans opened a thread the
prompt had not named; 0 of 36 non-scanning runs did; scan median 434 B.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): only waive the comment scan on a delta the server actually computed (MUL-6984)
Addresses review on #7972.
[P1] NewCommentCount == 0 was read as "nothing else was said on this issue",
and used to tell the agent its scan is already answered. The producer in
buildClaimedTaskResponse is best-effort: it writes the count fields only when
the anchor lookup and the count query both succeed AND the count is positive.
A failed anchor read, a failed count query, a cold start with no prior run, a
true zero, and an old server that never sends the fields therefore all arrive
as the same zero — and only the true zero looked at the issue. On a resumed
run a single failed count query was enough to delete the mandatory scan
exactly when the run could least afford it.
The claim now carries new_comments_delta_known, set on the success path
whatever the count. The count fields still stay suppressed at zero (there is
no delta hint to render from a zero), so the flag is the only thing that
separates a checked zero from an unchecked one. Absent on old servers, which
reads as "not authoritative" and keeps the scan — the safe direction in both
mixed-version pairings. The resumed path with no authoritative delta now
renders BuildResumedUnknownDeltaCommentsHint, which keeps the session facts
and hands the scan over as a command instead of claiming it is answered.
Workflow step 2 is tightened to match: only an explicit server report waives
the scan, and silence is not that report.
[P2] The warm path was selected on PriorSessionID != "" alone. MUL-5305 lets
the server withhold a more recent Codex session whose rollout is missing and
return an OLDER fallback session instead: PriorSessionResumeUnavailable is
true while PriorSessionID is non-empty. That older session may resume cleanly,
but it is not the turn this run continues from, so the run was taking the warm
path — and on an empty delta could skip both the full trigger-thread read and
the scan — on the strength of context it does not have. It now requires
!PriorSessionResumeUnavailable and reconstructs from the record instead, which
is what the continuity notice already tells the user happened.
Tests cover the states the existing suite could not see: an older fallback
session with every other warm-path precondition satisfied, resumed-with-delta-
unavailable versus resumed-with-authoritative-zero as siblings, and a
server-side computed zero carrying the flag. Each was verified against the
un-fixed code, where it fails. TestBuildPromptResumedNoDeltaDoesNotForceThread-
Read now sets the flag: its subject is the waiver branch, which is exactly the
branch that requires an authoritative answer.
Co-authored-by: multica-agent <[email protected]>
* docs(handler): correct the new-comment delta's scope on the claim wire (MUL-6984)
Review should-fix on #7972. NewCommentCount's doc comment still described it as
"trigger-thread comments since last run", while the query that produces it
(CountNewCommentsSince: "ACROSS THE WHOLE ISSUE, every thread, not just the
triggering one"), the daemon-side mirror field, and the NewCommentsDeltaKnown
comment directly beneath it all say issue-wide. One struct carrying two
mutually exclusive readings of the same field is worse than either.
Also records on NewCommentsSince that it is suppressed together with the count
at zero, so the next reader does not mistake a non-empty anchor for the signal
that the server looked — NewCommentsDeltaKnown is that signal.
Comment text only.
Co-authored-by: multica-agent <[email protected]>
* fix(daemon): stop calling an older-fallback resume a fresh session (MUL-6984)
Under MUL-5305 the server hands back an OLDER session with the continuity
gap flagged, and the daemon resumes it (ResumeSessionID is not gated on the
flag). The cold hint opened with "This run starts a fresh session on this
issue" and the readable continuity notices said "you are on a fresh one";
both were false in that state, and a test pinned the first.
The cold hint now states nothing about the session — none of the four runs
it serves (first run, dropped resume, explicit fresh, older fallback) makes
a different decision on it, and all four reconstruct from the issue record
with the same two reads. The three readable notices say the run "does not
continue" the lost session and that the memory gone is "from the turns
that did not come back", true in both the fresh and the fallback case.
TestBuildPromptOlderFallbackSessionRequiresReconstruction (renamed) asserts
the reconstruction commands and the notice, and bans "fresh session" and
"fresh one"; the resumed-path negative anchors now key on a phrase only
the cold hint carries.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: Bohan-J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
|
||
|
|
0242f37154 |
MUL-6986 refactor(skills): merge built-in skills into one platform skill and drop source maps (#7968)
* MUL-6986 refactor(skills): merge built-in skills into one platform skill
Built-in skills are read on the user's machine, which has no checkout of
this repo. Half the shipped payload by bytes was a source-location index:
eight `references/*-source-map.md` files (103KB, 1084 lines) whose content
was `internal/handler/github.go:687` style citations, plus "verification
commands" like `go test ./internal/service` and
`grep -n 'func derivePRState' internal/handler/github.go`. Four SKILL.md
bodies also carried source paths and Go identifiers, and every one of them
pointed the reading agent at its source map ("When in doubt, read that
file"). One source map even leaked an internal branch name.
That was not an oversight: CLAUDE.md required updating a source map in the
same PR as any behavior change, and this test file asserted all seven
existed. The reasoning against it was already written at the top of this
file — anything inside a skill directory is shipped to agent machines, so
evals must stay in _test.go — it just was never applied to the maps.
The nine skills were also split along backend modules, not along how the
work arrives. An agent has one entry point ("I am doing something on the
Multica platform"), so nine descriptions competed in the always-loaded
listing with overlapping boundaries, a cross-domain task activated three
skills, and Quick start / Core model / CLI / Side effects were restated in
most of them.
- Delete all eight source maps.
- Merge the eight platform skills into `multica-platform`: an 81-line
routing SKILL.md (positioning, the eight shared invariants that were
duplicated across the old bodies, and a table naming one reference per
domain) plus `references/{issues,mentions,agents,squads,autopilots,
projects,runtimes,skill-import}.md`. This is the layout the skill spec
documents for `references/` — a large skill split by domain, loaded on
demand — rather than an evidence layer for source.
- Strip every source path, Go identifier, test name, migration ordinal and
tracker citation from the payload; the behavior contracts themselves are
unchanged, restated in user-observable terms.
- Scope `multica-onboarding` to Mika. It is one agent's procedure, and
every agent was paying for its description and its files.
- Add TestBuiltinSkillPayloadCarriesNoSourceReferences so this cannot
regress, and re-home the ~100 contract anchors onto the reference that
now owns each one. Anchors match with whitespace collapsed, so a
paragraph reflow no longer reads as a deleted contract.
- Point the brief's two skill references and CLAUDE.md at the new layout,
and add one recall line to the brief's Skills section: eight
descriptions became one, so an agent has one name to guess instead of
eight. It is emitted only when a listed skill resolves to exactly
`multica-platform`, so a workspace skill taking that slug silences the
pointer rather than aiming it at a stranger's skill.
Payload per ordinary agent goes from ~211KB across nine skills to ~100KB
in one; the always-loaded description budget from 1,616 chars to 229.
Co-authored-by: multica-agent <[email protected]>
* MUL-6986 fix(skills): resolve the brief's platform pointer from the built-in
Review found the brief's recall line claimed a fail-closed behavior it did not
have. The claim was that a workspace skill taking the `multica-platform` slug
would silence the pointer; in fact the workspace skill is listed first, wins the
bare slug, and the pointer named it — handing the agent a stranger's skill as
the source of Multica's platform contracts, while the built-in sat at
`multica-platform-multica` and became unreachable.
Documenting that would have left a recall hole, and recall is the one thing this
merge may not cost: eight descriptions became one, so the agent's path to a
platform contract has to stay intact.
Fixed at the source instead. Built-in skills now declare `Source` at load time
(the bundle builder already derived it for the refs path, but the older inline
claim path appended them raw), the daemon carries `Source` into the brief's view
of a skill instead of dropping it, and the pointer resolves the slug from the
built-in's own entry. A collision now points at the built-in wherever it landed.
No bundle hash changes: the bundle builder normalizes `Source` to this value
before hashing.
Also adds TestPlatformSkillDescriptionNamesEveryDomain, which fails if a
reference ships without its trigger word in the description — the merge's recall
guarantee as a test rather than an assertion.
Review nits:
- TOCs for projects.md and runtimes.md, both over the 100-line bar.
- Inlined the two reference-to-reference links in mentions.md, so the routing
file's "they do not depend on each other" is true.
- Example issue keys use MUL-123, not MUL-2759 — a real internal issue.
- Fixtures naming the deleted skills now name live ones.
- Recorded where the description boundary sits: task nouns are trigger wording,
body section headings are the inventory the cap rules out.
Co-authored-by: multica-agent <[email protected]>
* MUL-6986 fix(skills): keep the brief's skill pointers resolvable across daemon skew
The runtime brief is assembled by the DAEMON, from a binary the user installs on
their own schedule. Deploying a backend does not rewrite it, and backend
upgrades do not force a daemon upgrade, so renaming the built-ins left two brief
lines — the metadata write discipline and the sub-issue playbook — naming
`multica-working-on-issues`, a skill this server no longer ships. The window is
open-ended, and a pointer to a skill that is not installed is worse than no
pointer: the agent hunts for it and may skip the contract when it misses.
Both skew directions happen, and each needs the fix on the side that is still
deployable:
- Old daemon, new backend. That daemon's brief text is frozen, so the fix has to
come from the server: a daemon that does not advertise `platform-skill-v1`
additionally receives a redirect stub under the old name, carrying nothing but
the new location. A current daemon receives nothing extra, so the stub retires
itself per install as daemons update — no cleanup release to remember, and no
cost paid by daemons that never needed it. Gated on capability, never on a
version string, matching how every other daemon skew is handled here.
- New daemon, old backend. The server has no idea the merge happened, so this
side has to cope. The brief now resolves both pointers from the skills the
task actually received: the merged skill if present, else the pre-merge one,
else no pointer at all. The `--status todo` / `--stage <N>` / secrets rules
stay unconditional either way — only the "read more here" clause is
conditional.
Resolution matches on built-in source as well as slug, so a workspace skill that
sanitizes to the same name cannot be advertised as the source of Multica's
platform contracts.
The redirect stub is deliberately a signpost with no contracts of its own: a
stub that grew content would rot against references/issues.md precisely on the
installs that cannot be updated from here, and a test caps its size and forbids
supporting files. It also rides through the template, strict-frontmatter and
source-leak suites like any other shipped payload.
Co-authored-by: multica-agent <[email protected]>
* MUL-6986 fix(skills): route by domain, not by count, and test the redirect wiring
Review must-fix: the routing text turned "load references on demand" into "load
exactly one". `SKILL.md` said to open "the ONE reference that matches your
task", the brief said "open the one reference its routing table names", and a
test pinned the singular.
That is wrong for the tasks the merge was argued on. Creating a squad, assigning
it an issue, then writing a mention needs squads.md, issues.md AND mentions.md;
wording that implies one would have the agent act on contracts it never read —
exactly the recall loss this work is not allowed to cause. Routing is now by
domain: open what the task touches, usually one and sometimes a few, and never
all eight out of uncertainty. The tests pin the domain framing and reject the
singular forms.
Also adds the wiring test the review found missing. The service test passed the
decision in as a bool and the brief test hand-built a skill set, so both ends
were covered while the middle — X-Client-Capabilities on the claim, through the
handler, into the delivered payload — was not. A break there would have left
every existing test green while an old daemon silently lost its redirect.
Covered across both claim branches, since each assembles the built-in set
separately, and verified to fail when the wire is cut. The daemon's capability
header test now pins platform-skill-v1 alongside the worktree gate.
Non-blocking review cleanups:
- The invariant claimed every domain has `list` and `get`; there is no
`runtime get` and a mention is not a gettable entity. It now points at the
read-only commands the opened reference actually names.
- Two comments still said the `multica-` prefix means a built-in slug can never
collide with a workspace skill. This PR ships the fix for that exact
collision, so they now say what is true: the prefix namespaces, nothing
reserves it server-side, and the brief resolves pointers from built-in source
rather than trusting the bare name.
- Recorded the allowed-tools tradeoff: merging eight skills merged their tool
declarations, so the router declares Bash(git *) / Bash(gh *) for domains that
do not need them. Dropping them would regress the issue workflow that runs
`gh pr`; today the daemon's global permission mode means this grants nothing
the agent did not already have.
Co-authored-by: multica-agent <[email protected]>
* MUL-6986 docs: drop the CLAUDE.md payload rule that a test already enforces
CLAUDE.md is loaded into every task in this repo and asks to stay short:
rules there should be hard to infer from code or easy to get wrong. This one
was neither — TestBuiltinSkillPayloadCarriesNoSourceReferences fails CI on
every class it named, and its failure message already carries the rule and the
reason at the moment someone violates it, which is both cheaper and impossible
to drift from.
The line above it stays: nothing enforces "update the reference in the same
PR" when a CLI flag changes.
Co-authored-by: multica-agent <[email protected]>
* MUL-6986 refactor(skills): accept the skill-name collision instead of handling it
Product decision: a workspace skill whose name sanitizes to exactly
`multica-platform` is rare enough not to design around. Half-handling it was
costing more than the risk — the brief resolved collisions dynamically while the
redirect stub could not (the stub is server-authored text, and the final slug is
only assigned when the daemon writes it to disk), so every review round found
another place that "did not handle it". Either handle it everywhere or nowhere;
this takes nowhere, and states the assumption where the pointers are.
Removed:
- builtinSlug matches the exact name again, instead of built-in source plus a
slug prefix.
- SkillContextForEnv.Source, the line carrying it through convertSkillsForEnv,
and the Source stamp on loaded built-ins. All three existed only for the
collision check, and dropping the stamp also removes a wire-facing change:
the bundle builder derives "builtin" from an empty id/source exactly as it
did before.
- The three collision test cases.
- The "the prefix does not guarantee uniqueness, so resolve from source"
narrative, replaced by the assumption it stands in for: `multica-` is the
platform namespace, pointers name built-ins by bare name, and nothing
reserves the prefix server-side yet. When that becomes real the fix is to
reject the prefix at skill create/import, not to make every pointer
defensive.
Kept, because it is version skew rather than collision and its window has no
upper bound: the `platform-skill-v1` capability, the redirect stub for daemons
that predate the merge, the brief's new-then-legacy-then-none resolution, and
their tests.
Co-authored-by: multica-agent <[email protected]>
---------
Co-authored-by: J <[email protected]>
Co-authored-by: multica-agent <[email protected]>
|
||
|
|
49098deb6f |
MUL-6984 refactor(prompt): give each duplicated instruction one owner (#7971)
* refactor(prompt): give each duplicated instruction one owner (MUL-6984) Four cases where the same data or rule was rendered two or three times into one context window, following the sibling-block removal in #7937. Each is resolved by picking the surface that owns it and pointing the others there — no behavior change beyond what an agent stops reading twice. The rule used to pick the owner is the one MUL-5377 already set for issue tasks: the runtime brief holds what is true of this KIND of task (stable, and it is the prompt-cache prefix), the per-turn user message holds what is true of this RUN, and the workdir sidecar holds nothing. Autopilot, quick-create and squad-leader paths had never been cleaned up against it. Autopilot run data (run id, autopilot id, title, source, trigger payload, instructions) was rendered in the brief, the per-turn message, and the sidecar. Now per-turn only; the brief keeps the run-only framing and the shared issue-command guard. This also settles a contract violation: those are per-run values, and the brief promises not to carry any. MUL-5696 already caught the two copies drifting on the guard sentence and fixed that one line with a shared constant. The payload is now capped at 8 KB with an explicit truncation notice — it is written by whatever system fired the trigger, so nothing here bounds it, and a clipped JSON object read as a whole one is worse than a short one. The squad leader no_action rule was written four times: the Squad Operating Protocol, the per-turn prompt, brief workflow step 4, and brief ## Output. The four had already drifted — only some carried the MUL-6622 escape hatch that keeps a FAILED `squad activity` call from ending the turn in silence. The protocol is now the single statement (it is the only document sent exclusively to leaders); the two brief sites and the per-turn reply imperative carry the EXCEPTION to their own imperative and point back at it. Quick-create's five run/output rules were in the brief's Workflow, the brief's ## Output, and the per-turn "Output format" block; the --description-file /tmp guidance was in the brief's Available Commands and per-turn. The brief owns both now — its own comment says these guardrails must hold "even if the user message is missing" — and per-turn renders only the field values the modal picked. .agent_context/issue_context.md is deleted outright. Nothing read it: the rendered CLAUDE.md never mentioned .agent_context, no per-turn message pointed at it, and no provider discovers it. Its writer's own comment said "the runtime brief already carries every fact this file would". With it gone the directory is created only when the default provider needs .agent_context/skills, so a local_directory task no longer leaves an empty managed directory in the user's checkout. Compatibility: prompt text only. No wire field, CLI flag, or DB column changes, so no server/daemon version coupling. Tests follow the text to its new owner rather than being deleted: the protocol's no_action rule is now pinned in internal/handler, the quick-create rules in the brief's own suite, and the payload cap has its own coverage including the rune-boundary cut. Co-authored-by: multica-agent <[email protected]> * fix(prompt): drop the autopilot payload cap, pin the moved invariant (MUL-6984) Addresses review on #7971. The 8 KB trigger-payload cap is removed. Its rationale was wrong twice over: webhook ingress already bounds a body at 256 KiB (handler.maxWebhookBodyBytes), so the payload was never unbounded from Multica's side; and because de-duplication made the per-turn message the payload's ONLY rendering, truncating there dropped task input with no way back — GET /api/autopilots/{id}/runs/{runId} returns the full payload but no CLI command exposes it, and `autopilot runs` lists runs without payloads. A legitimate 9 KB webhook would have silently lost fields the autopilot instructions might name, and the "JSON is incomplete" notice only prevents misreading, it does not recover anything. The cap belongs with an agent-facing read-back command and a threshold taken from real payload sizes, not as a side effect of removing duplicates. Rendering it once instead of twice is still a strict improvement over the pre-PR behavior. TestBriefByteIdenticalAcrossRunsForEveryKind could not see the invariant this PR actually moves: its autopilot row pins run-1 / ap-1 and every variant mutates only the generic resume / initiator / connected-app fields, so putting the title, source, payload or description back in the brief left it green. TestAutopilotBriefByteIdenticalAcrossRunScopedFields adds a second and third run that vary all of them — including a different autopilot id — asserts the brief is byte-identical, and asserts the brief contains none of those values. That second half matters: byte-identity alone also holds if the values reach no surface at all. Verified against a deliberately reintroduced field, where it fails as intended. TestBuildPromptAutopilotRunOnly gains the source and payload assertions so the per-turn message is pinned as their sole owner. Contract comments that still described the deleted sidecar are corrected: agent.MinHandoffCLIVersion (the gate always measured the opening prompt — no provider read the sidecar), the handoff_note field docs on both sides of the claim wire, the claim handler's note, TaskContextForEnv.HandoffNote, and errPathPreExists's namespace list. The Reuse path keeps its issue_context.md mentions, now labelled as cleanup of manifests written by older builds rather than of anything this code still writes. Co-authored-by: multica-agent <[email protected]> * refactor(squad): keep the protocol's ownership note out of the prompt (MUL-6984) Review nit on #7971. The Squad Operating Protocol ended with a paragraph saying it is the single statement of the no_action rule and that the rule changes here — true, and worth writing down, but addressed to whoever maintains the text. That const is sent to the model on every leader turn, so the leader paid ~40 tokens per turn for a sentence it cannot act on. Moved to the const's doc comment, next to the other constraints a maintainer reads before editing it. The skill side already records the same ownership in multica-squads/references/squad-source-map.md. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
879d0de916 |
refactor(agent tasks): drop the Active sibling runs prompt block (MUL-6984) (#7937)
Every issue turn carried a claim-time snapshot of the same agent's other in-flight tasks, scoped only by (agent, workspace) and capped at five. The scope had no relatedness filter, so the list answered "what else is this agent running right now", not "what else touches the code you are about to change". Measured over one day of runs on this workspace: the block was injected 78 times across 30 runs, listing 96 distinct (current issue, sibling) pairs, and not one pair shared a PR, a branch, or an issue family. `run-messages` was invoked zero times off it. Meanwhile the day's one genuine overlap — a PR review pushing to server/internal/metrics/pricing.go and a concurrent change to the model catalog next to it — was never paired, because overlapping in time is not the same as overlapping in code. The duplicate-run defect this shipped with (#6947) is fixed by shouldSuppressActiveSelfAssignment, which stops a second run on the same (issue, agent) at the server. Once that holds, the block can only ever list a DIFFERENT issue, so it was structurally unable to help with what it was added for. #7922 then gave the coordination question a correct answer — `issue runs --active` / `--siblings`, cross-agent, family-scoped, and read at the moment it is asked — which is the same information as a pull instead of a per-turn push. Removed end to end rather than hidden behind a flag: the prompt block, the claim payload field on both sides of the wire, and ListActiveSiblingIssueTasks. The field was `omitempty` and the daemon already rendered nothing for an empty list, so old and new server/daemon pairs stay compatible in both directions; nothing outside server/ ever read active_sibling_runs. Kept: the self-assignment suppression and its tests, `--no-start`, `issue run-messages`, and `issue runs --active/--siblings`. Co-authored-by: J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
f93855470f |
MUL-6874 fix(desktop): recover unexpectedly stopped daemon (#7808)
* MUL-6874 fix(desktop): recover unexpectedly stopped daemon Co-authored-by: multica-agent <[email protected]> * MUL-6874 fix(desktop): bound daemon recovery retries Co-authored-by: multica-agent <[email protected]> * MUL-6874 fix(desktop): queue one-shot daemon operations Co-authored-by: multica-agent <[email protected]> * MUL-6874 fix(desktop): reject nested daemon operations Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8c563b49be |
MUL-6555 feat(issues): preserve source context for sub-issues (#7461)
* feat(issues): preserve source context for sub-issues * fix(issues): address source context review findings * fix(migrations): guard source context rollback preflight |
||
|
|
2284420966 |
MUL-6622: fix(server): authorize squad activity by task provenance (#7489)
* MUL-6622: fix(server): authorize squad activity by task provenance `multica squad activity` rejected every issue that was not itself assigned to a squad, so a leader woken by an `@squad` mention on an agent-owned issue or running a leader task bound to a child issue could not record its evaluation. Because the injected `no_action` rule makes that call the substitute for a comment, the decision left no trace at all. The claim path already keys the squad briefing and the mandatory-recording instruction off the task row (`is_leader_task` + `squad_id`), not off the issue's assignee. `RecordSquadLeaderEvaluation` now uses the same source: - load the `X-Task-ID` row first and require it to be a leader task with a stamped squad; resolve the squad from `task.squad_id`; - drop the `issue.assignee_type == "squad"` precondition; - authorize against `task.agent_id` rather than the live `squad.leader_id`, and record `activity_log.actor_id` as `task.agent_id`. A mid-run leader change no longer discards a real evaluation, and no longer misattributes it in a way that silently desyncs the `no_action` comment suppression lookup (which matches on `task.agent_id`); - name the task's issue in the cross-issue rejection, since a stage-barrier wake runs on the parent and reaching for the child id is the common slip. Leader agents running a NON-leader task are now rejected: they are not running as the leader, and the runtime only mandates the call for leader tasks. This is the one behavior narrowing, covered by a test. The injected instructions also gain a failure fallback. The comment prohibition is already conditional on the activity existing (comment.go:1851), so a leader whose recording call errored may comment — it just was not told to. It now is, closing the silent-drop path for any remaining failure cause. Fixes #7487 Co-authored-by: multica-agent <[email protected]> * MUL-6622: fix(server): close review findings on squad activity authorization Addresses the four items from PR review. 1. Tenant isolation. `GetAgentTask` is a global lookup by id and the cross-issue rejection quotes the task's issue id, so an unrelated task probed through a readable issue echoed back a foreign workspace's issue id. The task is now loaded with `GetAgentTaskInWorkspace`, and check order is explicit: tenant scope, then "caller owns this task", and only then any message naming a task-derived id. 2. Claim-time role divergence. `is_leader_task` on the row is enqueue-time intent: when the leader is swapped before the claim, the claim path clears `resp.IsLeaderTask` and runs the task as an ordinary agent turn while the row keeps the flag set. Trusting the row alone let such a downgraded run write a leader verdict and suppress its own comment, so the live `squad.leader_id == caller` gate is restored on top of the task ownership gate. `activity_log.actor_id` stays `task.agent_id`, which is what the no_action suppression lookup matches on. Removing the live gate later requires persisting the role the claim actually delivered. 3. Failure fallback vs. one comment per turn. The new "comment if the recording call fails" instruction contradicted the exactly-one-comment rule on the `action` path, where a delegation comment already exists. All four injection sites now scope the fallback to turns that have not commented yet and cap it at one comment. Pinned by TestSquadOperatingProtocolRecordsAgainstTheTurnsIssue and TestBuildPromptSquadLeaderNoActionFailureFallback. 4. Shared test fixtures. The new handler tests are rebuilt on `internal/testutil` (`dbfx.Issue` / `dbfx.Task` / `dbfx.Squad`, `testutil.Call(...).Want(...)`) instead of open-coded `INSERT ... RETURNING` and recorder quartets, per CLAUDE.md. The shared `runningSquadLeaderTaskFixture` moved over too. New negative coverage: foreign-workspace task and foreign-agent rejections both assert the response body does not contain the other issue's id, and a leader change before recording is now asserted to be refused. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8f9c766e40 |
MUL-6460: feat(brief): inject the workspace status catalog into the agent brief (#7306)
Agents could not see custom issue statuses (MUL-6243): the brief's status command line hardcoded the seven built-in keys, so a workspace's Human Review / Rework statuses were invisible even when instructions asked for them, and the CLI offers no catalog read an agent could fall back on. Ship the catalog on the claim payload, the same path WorkspaceContext already rides: buildClaimedTaskResponse reads the workspace's ACTIVE CUSTOM statuses (built-ins stay off the wire — their keys, names and semantics are locked) and the daemon copies them into TaskContextForEnv. The brief's status bullet then renders the workspace's statuses grouped by category, with each custom status's name and description — category anchors the inherited platform behavior, name is what users call the status in instructions, and description is the admin's disambiguator when a category holds more than one status. A bridge bullet in the issue workflow connects the existing category-level status rules (which stay untouched) to the concrete key choice. With no custom statuses — every old-server pairing included — both renders are byte-identical to before, keeping the prompt-cache prefix stable for existing deployments (MUL-5377). The catalog is durable workspace configuration like WorkspaceContext, not per-turn state, so it belongs in the brief rather than the per-turn prompt. The claim read is not gated on the custom_issue_statuses flag: the flag only guards creation, and a catalog written before a flag flip must stay visible as long as issues can sit on it. Name/description are user-authored and pass through sanitizeNameForBriefMarkdown; a key failing the code-token guard drops its entry. A 30-entry cap with an explicit omitted count keeps a scripted catalog from flooding the brief while disclosing the truncation. Also update the built-in skills that document status semantics (multica-working-on-issues, multica-squads) and their source maps: statuses beyond the built-ins inherit their category's behavior in full, with the two literal-key exceptions (failed-task rollback writes todo, merged close-intent PR writes done) called out. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
55c7cf0575 |
fix(daemon): add the reference fact to the Mentions brief — @-as-attribution is not a mention need (MUL-6528) (#7379)
MUL-6417 replaced the prescriptive "Default: NO mention" rule with facts that invalidate each false prior from human @-culture, but only covered two of the three: notify-for-visibility and courtesy/thank-you. The third prior — the @-form used merely to write someone's name — was left standing, and an agent attributing a product decision to "@Steve Jobs" in prose enqueued a run for the agent it was crediting. Add the missing fact in the section's established style: crediting or citing someone is prose about them, not work for them; the link form dispatches whoever it names, so a reference stays plain text. Pin it in TestInjectRuntimeConfigMentionLoopHardening alongside the other facts. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
3268d01172 |
fix(daemon): move the in_progress start write into workflow step 3 (MUL-6417 follow-up 2) (#7295)
* fix(daemon): move the in_progress start write into workflow step 3; define ancillary by output (MUL-6417 follow-up 2) Field evidence from MUL-6460: a run that verifiably received the #7283 brief (its workdir CLAUDE.md carries the two-moment rule) walked steps 1→2→3 for over an hour and never issued a single status write. Two compounding causes, both in the prompt: - Placement. The start-moment rule lived in the status block BELOW the numbered steps. At the moment its condition triggers the model is executing the list, and a rule outside the list does not fire — the pre-MUL-6417 opening write was reliable precisely because it was an explicit step. The conditional write now opens step 3 itself, at the read→work boundary. - The research example. "A todo issue you were asked to research stays todo" was written for consult pull-ins, but the MUL-6460 turn's work WAS research toward its own issue's ask — the example pattern-matched the exact case it was never meant to cover. Ancillary is now defined by output (the turn produced none of the issue's own deliverable), and research/design explicitly count as work when they are the ask. Guard tests pin the step-3 placement and the research-counts-as-work clause, and ban the retired example. Skill, source maps, and user docs updated to match. Co-authored-by: multica-agent <[email protected]> * refactor(daemon): state the status rule as continuous, anchored at the moments that fire (MUL-6417) Bohan's review of #7295: 'at the end of your work, write the state it reached' still read as restricting writes to fixed checkpoints. The lead now states the invariant — keep status true at every point in the turn, write whenever the work changes it, mid-turn included (a blocker is recorded when hit) — and the end of the turn becomes a verification ('do not exit with a stale value') instead of the write moment. The anchors stay. A free-floating 'write when necessary' with no attachment points is exactly the shape that produced zero writes in the MUL-6460 incident: permission without a trigger moment does not fire. Step 3 keeps the start write; the exit check keeps the guarantee that a turn never ends with a lying board. Co-authored-by: multica-agent <[email protected]> * fix(daemon): drop activity-word skip-lists; ancillary is decided by output alone (MUL-6417) Elon's #7295 review: 'review' still sat in step 3's skip clause and the no-write bullet's example list, replaying the research incident's exact failure class on an issue whose ask IS reviewing a PR. Activity words carry no deliverable information in either direction, so both lists are gone: step 3 names forms of work (code, research, a design, a plan, or a review when reviewing is the ask), the no-write case is stated purely by output (the turn produced none of THIS issue's deliverable), and the rule now says so explicitly — the kind of activity never decides, only whether the output is part of what the issue asks for. Guards pin the review-as-work clause and the never-decides sentence, and ban both retired skip-lists. Skill, source map, and docs synced. Measured brief delta vs main (claude, production-length UUID): ordinary 13,362 → 13,971; leader 14,467 → 15,076 (+609 each, cached prefix); per-turn messages unchanged. Co-authored-by: multica-agent <[email protected]> * refactor(daemon): adopt J's review — one criterion, anchors in steps, category-scoped skip (MUL-6417) J's review of #7295, all four points taken: - The positive form-list that replaced the banned skip-list was the same failure shape read as exhaustive (a triage or reproduce-the-bug turn finds itself in no list). Both directions of activity-word lists are gone; the never-decides sentence moves INTO step 3 — the position that fires — instead of the status block, the position this PR itself proved dead. - The exit-side check is anchored inside step 5 ('Before exiting, ...'), making the PR's own placement argument self-consistent. - The step-3 skip is scoped to the in_progress CATEGORY, so a custom status like Planning already counts as recorded once MUL-6460 puts the status catalog in front of agents — no rewording needed then. - Tense: 'if any part of what this turn will produce ...' — step 3 is a prediction; mispredictions self-heal under the continuous rule. Plus J's meta-point on guards: presence-pins cannot tell where a sentence lives, and both field incidents were correct sentences in dead positions — the anchors are now pinned positionally (must appear inside the step-3 / step-5 lines), and the retired positive list joins the banned set. Kept the questions/discussion/acknowledgements clause (J's conservative option): acknowledgements are not covered by 'answered a question' and the sentence is the MUL-6300-era invariant other tests pin. Measured (claude, production-length UUID): ordinary brief 13,685, leader 14,790 — +243 vs main, down from +609 at the previous commit. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
4ff37edf91 |
fix(daemon): record in_progress when work on the issue's ask starts, not only at turn end (MUL-6417 follow-up) (#7283)
Post-merge report from Bohan: a todo issue assigned to an agent sat in todo for the whole first work turn — half an hour of active work the board attributed to nobody. The end-of-turn-only rule conflated when the agent can JUDGE with when the FACT changes: a turn that advances the issue's own ask makes "being worked" true the moment it begins. The activity indicator shows the run, but columns, filters, and sorting read status — the indicator alone is not the surface people watch. The status rule now has two write moments, same fact anchor: - start: the moment the turn is known to advance the issue's own ask → in_progress (only if it differs — rework on an in_review issue flips it back the same way); - end: the state the work reached (in_review / in_progress-continuing / blocked). An ancillary turn (research, answers, review, discussion) still writes nothing at either moment, so the research-stays-todo case and the concurrent-convergence property from the original MUL-6417 change are unchanged. This is the pre-MUL-6417 timing with the fact anchor's conditionality — not a revert to the unconditional lifecycle flip. Docs, skill reference, and source maps updated to the two-moment rule; guard tests pin the start moment and ban the end-only phrasing. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
4f56785b02 |
refactor(daemon): merge Reply/Ownership turn modes into one fact-driven workflow (MUL-6417) (#7245)
* refactor(daemon): merge Reply/Ownership turn modes into one fact-driven workflow (MUL-6417)
The Reply/Ownership split dated from PR #205 and had already been merged
in substance by MUL-5377 (one brief section + mode router) and MUL-6300
(reply turns own the same status arc). What remained was a per-turn
marker plus two mode blocks restating information the per-turn message
already carries as data. This finishes the merge:
- Status is judged once, at the END of the turn, from what the work
actually changed about the issue — not from the trigger type, not from
the run lifecycle, and not gated on being the assignee. No opening
in_progress write: the board's in-flight signal is the issue activity
indicator, and a todo issue the agent was only asked to research now
correctly stays todo (the old unconditional arc got that wrong twice).
Lifecycle writes oscillate under concurrent runs; fact writes converge,
which is what the retired assignee gate existed to protect.
- Delivery routes on data: a triggering comment in the per-turn message
(reply in that thread with THIS turn's --parent, one reply per thread)
or none (post a new top-level comment).
- The blocked rule and the squad-leader dispatch case become end states
of the same judgment instead of mode-block special cases; the leader
keeps one bullet (dispatching members is not delivery). The reply-mode
mention pointer is gone — the discipline lives in ## Mentions.
- The Turn mode markers are removed from the per-turn prompts, deleting
the marker/router-drift failure class (
|
||
|
|
212ecfb8dd |
MUL-6300: fix(daemon): let reply turns own the status arc for their own issue (#7083)
* fix(daemon): let reply turns own the status arc for their own issue (MUL-6300) A comment-triggered run was forbidden from touching issue status: "Do NOT change the issue status unless the comment explicitly asks for it", written in PR #205 (2026-03-31) on the day on_comment became the default trigger. It was aimed at conversation — questions, thanks, agent-to-agent chatter should not churn the board — but it also covered the most common real trigger, a comment asking for rework on a delivered issue. The agent reworked the issue with its status frozen, and a human had to move the card by hand (MUL-5165). Reply mode now owns the same arc Ownership mode does — in_progress when the work starts, the status the work is actually in when the turn completes — but only for turns that carry work on an issue assigned to this agent. The two properties the old prohibition really protected are kept and pinned by tests: - a purely conversational turn never writes status; - a run that is a guest on someone else's issue (@mention pull-in) never writes status, so an agent pulled in to answer a question cannot push another assignee's in-flight issue to in_review. Squad leaders keep routing on the Squad Operating Protocol's "Own the parent issue status" grant, which the server injects only when the issue is assigned to that squad — so the guest-leader path (MUL-3724) stays closed, and the grant no longer has to be phrased as an exception to a prohibition. Also refreshes the two built-in skills that documented the old rule. Supersedes #5781, whose one-line exception was written against brief text that MUL-5377 has since rewritten. Co-authored-by: multica-agent <[email protected]> * fix(daemon): compress reply-mode status rule, cover unassigned issues (MUL-6300) Review round on #7083. The ordinary reply-mode status bullet drops from 103 to 70 words and the squad-leader bullet from 110 to 92, with the conversational-turn sentence now identical in both branches. The no-write invariant broadens from 'assigned to someone else via @mention' to 'not assigned to you': an @mention can also land an agent on an UNASSIGNED issue (triage), which the old blanket prohibition covered but the first draft of the positive rule left undefined. Guard-test pins and the fallback pin in reply_instructions_test.go are synced to the new wording; the arc-trigger substring is unchanged so its two pins survive as-is. SKILL.md carries the same broadened phrasing. Co-authored-by: multica-agent <[email protected]> * docs(daemon): purge stale no-status-unless-asked semantics from ledger surfaces (MUL-6300) Elon's review round on #7083: four surfaces still explained current behavior with the pre-MUL-6300 rule. The squads source map (agent- readable) now says the status-ownership grant, not the system comment's ask, authorizes the leader's wrap-up write; prompt.go's turn-mode comment, squad_briefing.go's grant rationale (which also still pointed at the long-gone writeWorkflowComment), and the child-done stage test comment now describe the conditional arc. Past-tense history quotes (contract-test design note, MUL-6300 replacement note) stay verbatim. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8b1acfd19e |
fix(execenv): fold redundant metadata-list read into the issue-get step (#7016) (#7042)
Issue-triggered runs paid two reads for the same data: the runtime brief mandated 'issue get' (whose JSON always carries the metadata bag) and then a separate 'issue metadata list'. Fold the read guidance into step 1, renumber shared steps 1-6 to 1-5, and update the brief tests to pin the new wording and ban the retired standalone read step. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
cc2aea3878 |
MUL-6104: fix(daemon): tell the brief's reader not to merge stderr into parsed output (#6870)
Both brief builders now state that `--output json` writes JSON to stdout while confirmations and warnings go to stderr, and that merging them (`2>&1`) into anything that parses the output makes a successful write look failed and invites a duplicate retry. Adds a regression test anchored on the rule's wording — premise, prohibition and consequence — asserted exactly once per rendered brief, so a reversed or duplicated rule fails the test. 234 bytes per rendered brief; a run renders one of the two builders. |
||
|
|
19155e41f9 |
refactor(prompts): compress duplicate --no-start guidance from #6956 (MUL-6168) (#6971)
PR #6956 stated the same rule — use --no-start when the write only records ownership/status of work already underway — five times across the always- injected brief, the sibling-runs block, and the working-on-issues skill. State it once per surface and keep the flag in the command signatures. Prompt-text only; no behavior change. Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
b5a48eca13 |
fix(agent tasks): prevent duplicate self-assignment work (MUL-6168) (#6956)
* fix(agent tasks): avoid duplicate self-assignment runs Co-authored-by: multica-agent <[email protected]> * fix(agent tasks): address duplicate-run review Co-authored-by: multica-agent <[email protected]> * fix(agent tasks): resolve sibling-run nits Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Sol-Boy <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
8d292ef7ce |
MUL-5963: feat(server): let web chat read back history via multica chat history (#6680)
* feat(server): let web chat read back history via `multica chat history` When an agent's provider session resume fails on a web chat / Feishu task, the daemon injected SessionContinuityNoticeUnrecoverable, making the agent reply "this is a new session, no memory". But the conversation was never lost — it is persisted in chat_message, and the agent just had no command to read it back (only Slack had a history reader). Make `multica chat history` serve the stored chat_message transcript for any non-Slack session (web chat, Feishu), reusing the existing task-token authorization and the shared response shape. Route web chat / Feishu continuity notices to a new variant that tells the agent to read the transcript back instead of scripting a "new session" disclosure, and teach the Feishu channel prompt the read-back command. - handler/chat_history.go: GetChatChannelHistory falls back to chatMessageHistory (ListChatMessages, oldest-first, default recent 30) on ErrNoSlackSession. - execenv: add SessionContinuityNoticeChatTranscript; keep Unrecoverable for channels Multica stores no transcript for (e.g. WeCom). - prompt.go: route web chat / Feishu to the transcript notice; Feishu channel awareness teaches `multica chat history`. - tests: update surface/notice matrix, Feishu prompt policy, add handler transcript fallback tests. Co-Authored-By: Claude Fable 5 <[email protected]> * test(server): fix transcript read-back test to insert a real assistant row The insertChatVisibilityMessage helper hardcodes role='user', so the "assistant reply" row in TestGetChatHistory_NoSlackBindingReadsStoredTranscript was actually stored as a user message and the handler (correctly) returned role "user". Add an insertChatMessageRole helper and use it for the assistant row so the role-mapping assertion is meaningful. Co-Authored-By: Claude Fable 5 <[email protected]> * refactor(server): treat WeCom as a readable transcript surface for session continuity WeCom is fully wired on main: it persists the conversation to chat_message and stamps ChatChannelType="wecom", so `multica chat history` reads it back just like Feishu's. Stop routing it to the Unrecoverable fallback; give it the transcript notice and the `multica chat history` history command, symmetric with Feishu (no thread reader, no upload). Co-Authored-By: Claude Fable 5 <[email protected]> * fix(server): make transcript read-back reachable and honor the paging contract Addresses review on the web-chat history read-back feature. - GetChatChannelHistory no longer dead-ends on "no channel integration" when SlackHistory is nil: a no-Slack deployment (the exact target of this feature) now serves the stored chat_message transcript directly, and Slack-backed sessions still go through the Slack reader. - chatMessageHistory swaps ListChatMessages for ListChatMessagesPage, honoring the ?before cursor (encoded as created_at|id) and clamping ?limit (default 30, max 50), and advertises NextCursor on a full page so an agent can page back without re-reading the recent window. Author now uses the channel vocabulary (Bot / User) instead of the raw role string. - Adds a single SurfacePersistsTranscript predicate (web chat, Feishu, WeCom, DingTalk) as the one source of truth for "which surfaces are readable", shared by the continuity-notice router, the chat-prompt history copy, and the surface list — closing the DingTalk gap where the notice said "read it back" but the prompt said "no history reader for dingtalk". Tests: nil-reader-with-rows regression, transcript paging without duplicates or gaps, Author/TS pins, and DingTalk cases in the surface/notice and prompt tables. Co-Authored-By: Claude Fable 5 <[email protected]> * test(server): fix transcript paging test assertion The paging test wrongly asserted messages are strictly oldest-first ACROSS pages, but paging to older messages means each later page is older than the previous one — the sequence is not monotonic. The handler was correct; the assertion was wrong. Now it checks the real invariants: each page is oldest-first, and the union of all pages is exactly the transcript with no duplicates or gaps. Co-Authored-By: Claude Fable 5 <[email protected]> * fix(server): stop telling transcript surfaces their history is not in Multica The channel-awareness block opened with "This conversation and its history live in <platform>, NOT in Multica" for every channel type, and the very next line told a Feishu/WeCom/DingTalk agent that Multica stores the conversation and hands it `multica chat history` to read it back. An agent that believes the first sentence has no reason to run the command in the second. Only the useful half — do not go hunting through issues and comments — is unconditional now. Where the conversation actually lives moved into each branch: Slack keeps the original wording (true there), transcript surfaces say the conversation happens on the platform and Multica stores a transcript of it, and the defensive fallback keeps both claims for a surface that really has no reader. Co-authored-by: multica-agent <[email protected]> * fix(server): name the channel a served transcript came from HistoryPage.ChannelType is documented as empty ONLY for a session bound to no channel, but the transcript branch never set it. A WeCom/Feishu/DingTalk session that HAD messages therefore reported channel_type "" — telling the agent it was in a web-only chat — while the same session's empty read already named the platform. One endpoint, two answers, decided by whether the session happened to have messages. Reuses the existing sessionChannelType helper, so the transcript path inherits its refusal to guess: a binding read that fails for any reason other than "no such row" is reported rather than silently answered as web-only. That failure now returns the same status as the empty-read path does for the same cause, instead of the 502 reserved for a failed channel read. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: qiushihao279-cloud <[email protected]> Co-authored-by: Claude Fable 5 <[email protected]> Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
90eadd940a |
MUL-5905: feat(wecom): send the files an agent produced into the chat (#6604)
* feat(wecom): send the files an agent produced into the chat An agent that produced a file and answered in WeCom delivered only the sentence. `multica attachment upload` had already run, the object was in storage, and CompleteTask had bound the row to the assistant message — but nothing downstream of that bind knew how to reach a WeCom conversation, so the file stopped there. The user read "here is the forecast" and got no forecast, with no error anywhere to explain it. Worse when the agent said nothing at all: an empty completion with a file bound to it returned early, so the artifact was thrown away silently. This adds the last hop. media_upload.go carries a file up the long connection — WeCom has no REST endpoint for this, so it goes as aibot_upload_media_init / _chunk / _finish over the same socket, and all three read their verdict through the request() layer #6590 generalized. outbound_media.go is the caller: after the answer is out, it reads the bound attachment out of object storage, decides what to call it, and pushes it in as its own message. Three decisions worth naming. The answer goes first and the file never blocks it. An upload is megabytes and round trips and it can fail; delivery runs after the words are out, on its own goroutine and a budget of its own, and its worst outcome is one extra line saying a file did not make it. A lost ack and a refusal are opposite failures. A chunk whose verdict never came is offered once more — the protocol makes re-sending idempotent, and losing one slice of a hundred must not cost the file. A chunk the server refused is not, because the refusal is its answer and will be its answer again. The push of the finished media follows the same rule in reverse: a timeout is reported as-is rather than retried, because the same media_id sent twice puts the file on the user's screen twice with nothing to undo. WeCom validates bytes against the msgtype, so what to call a file is a decision. A kind past its ceiling is demoted to a plain file rather than sent and refused — a card the user can open beats a photo the server rejected. WithAttachments is what turns this on, and router.go passes it only when the deployment configured object storage. Without it the adapter behaves exactly as it did before. * fix(daemon): stop telling a WeCom agent its conversation is text-only The per-turn chat prompt and the runtime brief both decided file delivery by asking "is there a channel at all". Any non-empty ChatChannelType got the text-only paragraph: `attachment upload` does not apply here, describe the file in words, never write its local path. That was true of every IM channel when it was written, and it stopped being true for WeCom, whose adapter now reads the bound attachment out of object storage and sends it into the room. So an agent on WeCom was instructed not to use the one command that would have delivered its file. The capability shipped and the agent was told it did not exist. The delivery question is per-adapter, not "is there a channel", and ChannelCarriesFiles is where that answer now lives. Both call sites branch three ways instead of two. The new copy is deliberately built from main's existing web-chat branch rather than written fresh. Both open on the same clause — "To include a file or image you produced in your reply, run `multica attachment upload <local-path>`" — and diverge exactly where the platforms diverge: web chat says the file "appears as an attachment card below it" and offers a markdown snippet to place it inline; WeCom says Multica "sends it into the WeCom conversation as a separate message right after your text" and that "there is no way to place it inline". That last clause is the one worth having. An agent told only that files work here writes "see the chart below" and then nothing appears below, because the long connection has no msg_item and a picture cannot be embedded in the reply. Slack and Lark keep their existing text-only wording untouched. Two comments are rewritten rather than carried over, because they asserted the binary the same commit falsifies — "keys off 'is there a channel at all'" in prompt.go, and "any non-empty channel type means the reply leaves Multica for an external IM platform, where `attachment upload` has nothing to bind to" in the brief. A comment that contradicts the code beneath it is worse than none. ChannelCarriesFiles names every channel type this package knows rather than leaving Slack and Lark to the default, so a fourth adapter surfaces here as a decision to make. An unrecognized type answers false: the agent is told to describe its file in words, and the worst case is a deliverable that could have been sent was not. False the other way would have it promise a delivery nothing performs. * fix(wecom): stop claiming more certainty than the system has Three review blockers, one shape: a promise made in one place that another place cannot keep. Capability came from the channel type. The daemon answered "this conversation takes files" from `chat_channel_type == "wecom"`, but the last hop only exists where the server wired object storage — so a WeCom deployment without storage had its agents told to send files nothing would carry, and a new daemon on an older server inferred a capability that build has no code for. The server now answers it per turn on the claim (`chat_channel_delivers_files`), declared by the same `if store != nil` branch that gives the adapter the storage, and absent/false everywhere else. Upload limits came from the framing. 50 MB is what 100 chunks of 512 KB can describe; WeCom's own OpenClaw plugin puts the hard file ceiling at 20 MiB, so that is now the cap, enforced against the attachment's recorded size before any byte is read. Concurrency was a fixed 4 regardless of size; WeCom's own SDK steps it down — all at once up to 4 chunks, 3 up to 10, 2 past that, because their backend answers a burst of chunks on a large upload with a system error. Failure handling had two words for three outcomes. A send whose ack never came was reported as a definite failure although the file may well be in the chat, and the local failures — a full pending queue, a slot wait that expired, a lookup that errored — were reported to nobody at all. There are now three states: delivered says nothing, definitely_failed keeps the definite wording, and unknown gets wording that holds either way and is never resent, since a duplicate file cannot be taken back. The attachment lookup moved ahead of the rationing so a shed delivery knows there was a file to apologise for, and a turn with nothing attached still says nothing. * fix(wecom): bound the attachment lookup, and stop calling an attempted write a failure Two gates, not one better place for one gate. The pending counter is claimed after the lookup so a shed can name a real file, which is also why it cannot bound the lookup: at the point it would have to be claimed, whether the turn carries a file is exactly what is not yet known. admitAttachmentDelivery is claimed before the spawn and bounds the goroutines and the queries they run. Its cap is twice the pending cap, so a backlog meets the honest post-lookup shed first and only reaches the silent one when the system is already well past it. A refusal there is logged and not spoken — telling a user their file was dropped on a turn that may have carried none is the false alarm the post-lookup gate exists to prevent. Separately, a WebSocket write error stopped being read as proof of non-delivery. wsSender.write already distinguished the stages for tracing; it now carries that out in the error. Past WriteMessage the frame may be at the peer — a half-closed connection reports "broken pipe" to the writer for bytes the reader has — so those classify as unknown and are never resent. What fails before the write is provably local and stays definite. Tests drive the real path rather than the counters: one blocks the lookup, submits past the cap, and counts the goroutines and queries that actually happen; one hands the frame to a connection that records it and then fails. Both fail without the change. fakeObjectStore takes a lock, because it is no longer only ever driven inline. * docs(wecom): cite the size limits to WeCom's docs, not a third-party constant The upload ceilings were sourced to Tencent's OpenClaw plugin. That file carries the right numbers but cites nothing itself, so it corroborates rather than establishes them. WeCom documents all four on aibot_upload_media_init's body.total_size row, and repeats them on the classic media upload API. Point at those. * fix(wecom): drop the two media helpers #6605 brought in ahead of this branch This branch carried its own cleanShort filename and extension helpers because nothing else in the package had them when it was written. #6605 has since merged, and it brings both — so the package now declares each twice and does not compile against current main. GitHub still reported the branch mergeable and CI was green, because both were measured against a main that predated that merge by six hours. Upstream's copies are the ones to keep: its cleanMediaFilename also strips control characters out of a name, and its mediaExtension drops a charset parameter before matching. Neither of those was in the copies here. * fix(daemon): keep the file-delivery verdict out of the cached brief chat_channel_delivers_files arrives on every claim and answers a deployment question: does this server carry a file the last hop into the room. Upgrading a server so it starts sending the field, or turning object storage on or off, flips it under a chat session that is already running — and the brief branched on it, so one resumed session rendered two different briefs. The brief is the prompt-cache prefix (MUL-5377), which makes that a miss on every turn after the flip. The two rows in the delivery test asserting different briefs for the two verdicts were the exposure written down: they are what one session sees in sequence. The brief's channel-backed chat copy now names the platform and says the answer is stated per turn. It holds on either verdict and contradicts neither. The verdict has a home already — the per-turn chat prompt, whose branch was correct and is untouched — and that message is emitted on every turn, so nothing is lost by the brief declining to answer. Slack and Lark take the same copy. Scoping it to WeCom would put a channel-type decision back into the brief, which is what ChannelCarriesFiles exists to prevent, and would hardcode a denial a server-side change can falsify. Coverage moves rather than goes. TestBriefByteIdenticalAcrossRunsForEveryKind gains a WeCom kind and a variant that flips the field; both fail without this change, and the existing Slack kind fails on the variant too. The delivery test's two WeCom rows assert the inverse property now — one brief for both verdicts — and the upload/describe split they used to hold is pinned by TestBuildChatPromptTwoLayerChannelPolicy on the per-turn prompt, which has covered both branches all along. * docs(wecom): correct what the admitted cap implies, and note that delivery cannot answer back The comment on the two counters said reaching the admitted cap means the pending cap is full and as many turns again are still in their lookup. It does not. Admission is held for a goroutine's whole life, including turns whose lookup finds no file and which therefore never claim a pending slot — TestDeliverAttachments_AdmissionBoundsTheLookupStage fills admission with pending at zero. The two-counter split and the ordering it buys are unchanged: a backlog of file-carrying turns still meets the honest post-lookup shed first. The package doc gains the fact that an agent cannot learn its delivery was refused. `multica attachment upload` returns once the object is in storage and bound to the reply, while the send into the room runs on EventChatDone, after the run has ended, so everything the adapter knows about a failed delivery goes to the person in the chat and to nobody else. |
||
|
|
545d890dae |
feat(cli): add --compact to issue comment list, adopt it in every agent read command (MUL-5442) (#6546)
* feat(cli): add --compact to issue comment list, adopt it in every agent read command (MUL-5442) The #5999 follow-up its closing note asked for, now that the workspace measurements exist: the two bounded reads the agent workflow prescribes are dominated by reader-noise metadata, and every byte of it enters the conversation history at full price once and is replayed at cache-read price by every later call of the session. --compact (JSON output only, opt-in) drops per comment: the issue_id echoed from the request path, source_task_id, updated_at when identical to created_at, null-valued fields, and empty arrays. Content, identity, and thread-summary fields pass through untouched; a real edit timestamp or non-empty array is never dropped. Measured on MUL-5442 (production data): --roots-only --summary scan 6,117B -> 4,738B (-23%); --thread --tail 30 deep read 47,359B -> 39,349B (-17%). Adoption: all nine runnable comment-list command templates in the per-turn channel (prompt.go, reply_instructions.go) now carry --compact, and the brief's Available Commands line and workflow step 3 document it (+~40B one-time, byte-stable). Command pins updated accordingly; new unit test pins the drop/keep contract and the help-contract test pins the flag's rendered usage. Known local-env failure: TestResolveWorkspaceID_AgentContextSkipsConfig fails identically on clean origin/main in this agent runtime (agent context env interference) — unrelated. Co-authored-by: multica-agent <[email protected]> * test(cli): wire-level --compact coverage and zero-value keep pins (MUL-5442) Review nits on #6546: TestRunIssueCommentListCompactWiring drives runIssueCommentList against an httptest server both ways — default JSON keeps every field, --compact drops the reader-noise set while zero-value scalars (reply_count: 0, content_truncated: false, folded_count: 0) survive, pinning that null pruning never generalizes to zero pruning. Same zero-value pins added to the pure-function test. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |
||
|
|
73ab498d1d |
MUL-5811: fix(squad) derive leader role from wire fields, not instructions text (#6519)
* fix(squad): derive leader role from wire fields, not instructions text (MUL-5811) The daemon decided whether a task ran in the squad-leader role by grepping the agent's Instructions for the "## Squad Operating Protocol" heading that the server-injected briefing starts with. Role detection therefore depended on user-writable Markdown: any ordinary agent whose own instructions happened to contain that heading was promoted to leader and handed the leader rules — mandatory `multica squad activity` calls, a licence to exit silently on no_action, and the squad-maintenance CLI section in its brief. The correct signals were already on the wire the whole time. The claim handler decides injection from explicit fields (`task.is_leader_task` + `squad_id` for issue-bound runs, the quick-create `squad_id` for the picker path) and forwards both to the daemon. Read those instead: - `taskIsSquadLeader` returns `IsLeaderTask || SquadID != ""`; brief assembly now shares that helper instead of repeating the substring check. - The claim handler clears `is_leader_task` on the response whenever its defensive gate withholds the briefing (NULL squad_id, squad hard-deleted, leader swapped after enqueue). The flag now means "briefing injected", so a leader task with no roster and no protocol degrades to an ordinary agent turn instead of booting into a leader role it cannot perform. Every legal path is byte-identical: the server sets these fields on exactly the claims it injects into, so real leader runs and ordinary runs produce the same brief and prompt as before. The only behavior change is the misdetection case, which is the bug. Also corrects two comments claiming IsSquadLeader is agent configuration rather than a per-task role, and notes the flag contract in the squad source map. Co-authored-by: multica-agent <[email protected]> * fix(squad): gate leader-role field reads behind a server capability (MUL-5811) Review catch (#6519): reading is_leader_task / squad_id unconditionally breaks the new-daemon → old-server direction the repo deliberately supports (claimTasksLegacy, MUL-4257). is_leader_task only reached the claim response in #4951, while briefing injection predates it — so against an older server a real issue-bound leader task arrives with the full briefing and both fields at zero, and field-only detection would silently demote that leader to a plain worker, dropping the entire operating protocol. Add `leader_role_resolved`, a claim-only wire capability the server sets on every claim response (leader or not). Its absence is what identifies a server too old to have answered the role question: - capability present → the explicit fields decide, and an ordinary agent whose instructions contain the briefing heading is no longer promoted to leader; - capability absent → keep the legacy briefing-marker inference, which is exactly today's behavior against those servers. That branch goes away once a minimum server version is enforced. The field is claim-only and never rendered into a prompt, so it costs no prompt tokens and changes no brief or prompt bytes on any path. Tests: the role table now covers both server generations, including legacy real-leader (heading, no capability), current ordinary-agent-with-heading, and current withheld-briefing; an end-to-end legacy prompt test pins that an old server's leader keeps its rules; and every claim test now asserts the server actually advertises the capability — losing it would send all daemons back to text inference. Also rewords two comments per review: the brief byte-stability tradeoff is owner-accepted and recorded in MUL-5811, not an open tracking item. Co-authored-by: multica-agent <[email protected]> * docs(squad): state the pre-capability server shapes precisely (MUL-5811) Review nit: the comments said every server without leader_role_resolved "never sent is_leader_task", which is only true before #4951. Servers between #4951 and this change do send the flag — they just never promised it implies an injected briefing, which is the actual reason the fallback covers them too. Names both shapes at each site (daemon types, handler response, helper doc, role-table doc, squad source map) so a future cleanup of the fallback reads the version window correctly. Comments only; no behavior change. Co-authored-by: multica-agent <[email protected]> --------- Co-authored-by: Bohan-J <[email protected]> Co-authored-by: multica-agent <[email protected]> |