Skip to content

feat: DeLM-inspired shared failure context, verification gate, and gist layer #705

Description

@lwgray

What Is Marcus, Briefly

Marcus is a multi-agent task coordination system. It breaks a software project into subtasks, places them on a shared kanban board, and dispatches independent AI agents to claim and complete those tasks. Agents communicate only through the board — never directly with each other.


Problem (User-Facing)

Right now, when an agent on a Marcus project goes down a wrong path — tries the wrong file, pursues the wrong fix, picks the wrong library — that failure stays private. The next agent assigned to a related task starts from scratch and may spend its entire budget rediscovering the same dead end.

This is a real cost problem. Consider a project with 10 agents working in parallel. If three of them each spend 30 minutes and 5,000 tokens pursuing a bad approach the first agent already ruled out, the project wastes 90 minutes and ~15,000 tokens (at $3.75/M tokens ≈ $0.06) on repeated exploration that could have been avoided with one shared note.

More subtly: when an agent discovers a hard constraint — "approach X is globally invalid because of Y" — that constraint currently lives only on that agent's task card. A later agent can unknowingly override or soften it, causing downstream failures that are hard to trace.


Motivation: What Stanford's DeLM Research Shows

A June 2026 Stanford paper, Decentralized Language Models (DeLM, arXiv:2606.10662), tested a multi-agent system built on exactly the design Marcus uses: a shared board, a pull-based task queue, and no central orchestrator. Their headline finding:

Agents that share compact, verified intermediate findings — including failures and constraints — solve tasks 10.5 percentage points more accurately at ~50% lower cost than agents working in independent parallel attempts.

The cost saving comes from three concrete mechanisms:

  1. Failure sharing. When one agent writes "I tried approach X, it failed because Y," later agents skip that dead end entirely.
  2. Constraint binding. When a verified constraint lives in shared state, no later agent can accidentally soften or override it on its way through a central controller.
  3. Compact sharing. Discoveries are shared as short, evidence-backed summaries — not full conversation logs — so agents absorb useful progress cheaply.

Marcus already has log_decision (src/marcus_mcp/tools/context.py:22) and log_artifact (src/marcus_mcp/tools/attachment.py:32) MCP tools that agents call during task execution. These cover what to build and design artifacts. What is missing is the complementary channel for what not to do, a verification gate to ensure what gets shared is trustworthy, and a compact summary layer so the shared context stays readable as projects grow.

MCP tool: a function Marcus exposes to agents via the Model Context Protocol — agents call these like API endpoints during their work.


Proposed Features

Four improvements to Marcus's shared-context layer, ranked by expected impact.


Feature 1 — log_failure MCP Tool ★ Highest Priority

What. A new MCP tool agents call when they discover an approach does not work. The failure is written to shared context and made visible to all agents on the same project.

Why. This is the single mechanism responsible for most of DeLM's cost savings. Failed paths are currently private. log_failure turns every dead end into shared knowledge.

Proposed interface:

async def log_failure(
    agent_id: str,
    task_id: str,
    what_was_tried: str,   # "Modified AbstractPythonCodePrinter to add trailing comma"
    why_it_failed: str,    # "Change did not affect lambdify — bypassed by _recursive_to_string"
    constraint: str,       # "Do not modify the printer layer; fix is in lambdify.py:964"
    state: Any
) -> Dict[str, Any]

Where it goes. src/marcus_mcp/tools/context.py — alongside the existing log_decision tool at line 22.

What it writes.

  • A Failure entry to state.context.failures (in-memory, analogous to state.context.decisions)
  • A ❌ DEAD END comment posted to the kanban card for task_id
  • A persisted record to data/project_history/{project_id}/failures.json

How later agents see it. get_task_context should include failures from upstream tasks and the foundation task in its response, the same way it already includes decisions and artifacts.

Foundation task: a special Marcus task that runs before agents spawn; its decisions, artifacts, and now failures are shared project-wide.

Concrete example (adapted from a DeLM paper trace):

An agent is working on a SymPy bug. It tries fixing the Python printer layer and calls:

log_failure(
    what_was_tried="Modified AbstractPythonCodePrinter._print_tuple to add trailing comma",
    why_it_failed="Change has no effect on lambdify output — lambdify.py:964 uses _recursive_to_string which bypasses the printer",
    constraint="Do not modify the printer layer; the fix belongs in sympy/utilities/lambdify.py:964"
)

The next agent, assigned a related task, reads this failure via get_task_context. It skips the printer entirely and goes straight to lambdify.py:964. One full round of redundant exploration is eliminated.


Feature 2 — Admission-Time Verification Gate ★ High Priority

What. Before a log_decision or log_failure entry is admitted to shared context, a lightweight LLM check verifies the claim is grounded in the agent's actual task output. Unsupported or hallucinated claims are rejected, and the agent is asked to revise before the entry is broadcast.

Why. The shared context is the communication substrate for all agents. A false claim admitted to shared context propagates to every downstream agent as if it were fact. DeLM's ablation shows that removing verification drops accuracy by 4.9 percentage points (60.1% → 55.2%), even when everything else is the same.

Concrete failure mode the gate prevents (from the DeLM paper):

An agent writes:

log_decision("Pennzoil punitive damages were reduced from USD 3B to USD 1B — see source S5")

But source S5 only says "conditional affirmance with remittitur" — no dollar amounts appear anywhere. Without a gate, this hallucinated number enters shared context as grounded fact. Every subsequent agent treats it as true.

With the gate, a verifier rejects it:

"WRONG: the dollar amounts are not supported by S5. Regenerate the decision without unsupported specifics."

Where it goes. src/marcus_mcp/tools/context.py — add a _verify_before_admit(entry, supporting_context) step inside both log_decision and log_failure before the entry is written to state.context.

Implementation note. The verifier can be a cheap, fast model (e.g., claude-haiku-4-5-20251001). DeLM's ablation found that summarizer/verifier model quality has almost no effect on final accuracy — a cheap model matches expensive ones on this task (§4.3.1 of the paper).


Feature 3 — Compact Gist Layer for Artifact Context ★ Medium Priority

What. Currently, when an agent calls get_task_context, it receives full artifact documents — complete spec files, full design docs. DeLM shows that giving agents a compact ~100-token summary (a "gist") by default, with the option to request the full document on demand, is both cheaper and more accurate than delivering full content upfront.

Gist: a compact (~100 token) plain-English summary of what an artifact contains and why it matters, generated at write time by log_artifact.

Why. A 10-task project might accumulate 5 design artifacts, each 2 KB. Every get_task_context call delivers ~10 KB of artifact content regardless of how relevant it is to the requesting agent's task. This inflates prompt sizes, increases cost, and can push task-specific instructions out of the agent's context window. DeLM's gist length ablation (§4.3.1) found that 100 tokens per artifact is the sweet spot — accuracy plateaus beyond that.

Proposed change to get_task_context response:

{
  "dependency_artifacts": [
    {
      "gist": "REST API spec for user auth: POST /login, POST /refresh, DELETE /logout. JWT-based, rate-limited at 100 req/min. See full spec for error codes.",
      "filename": "api_spec.json",
      "location": "docs/api/api_spec.json",
      "artifact_type": "api",
      "full_content_available": true
    }
  ]
}

A new get_artifact_content(filename) tool lets an agent retrieve the full document when it needs precise details.

Where it goes.

  • src/marcus_mcp/tools/context.py — modify get_task_context to return gists instead of full content by default
  • src/marcus_mcp/tools/attachment.py — generate and store a gist when log_artifact is called (line 32)
  • New tool in src/marcus_mcp/tools/attachment.pyget_artifact_content(filename)

Feature 4 — Selective Unfolding for Decisions and Failures ★ Lower Priority

What. Extend the gist layer to decisions and failures, not just artifacts. Agents read compact summaries of all upstream decisions and failures by default. They call unfold_context(entry_id) to retrieve the full reasoning behind a specific decision or the full trace behind a specific failure.

Why. On longer projects, get_task_context can return many kilobytes of decision and failure text. The unfolding mechanism gives agents a lightweight index of what happened upstream, with opt-in drill-down only for entries that are actually relevant to their current subtask.

Where it goes. src/core/context.py (the Context class managing state.context.decisions and the new state.context.failures) and src/marcus_mcp/tools/context.py.


Implementation Order

  1. log_failure tool — highest impact, most self-contained, ships independently
  2. Verification gate — adds trust to both log_failure and log_decision; ships independently
  3. Gist layer for artifacts — requires coordinated changes to get_task_context and log_artifact
  4. Selective unfolding — builds on Feature 3; lowest urgency

Where to Look in the Code First

File Purpose
src/marcus_mcp/tools/context.py log_decision (line 22), get_task_context — primary file for Features 1, 2, 4
src/marcus_mcp/tools/attachment.py log_artifact (line 32) — primary file for Feature 3
src/core/context.py Context class; in-memory decision storage; cross-task propagation logic (line 487 for dependency filtering)
src/core/project_history.py Persistent JSON/SQLite storage for decisions and artifacts; failures.json would be added here for Feature 1
tests/unit/mcp/ Unit tests for MCP tools — new tests go here
tests/integration/ Integration tests for cross-task context propagation

Glossary

Term Meaning
Agent An AI instance that claims and completes one task from the kanban board
Kanban board The shared task board that is the only communication channel between agents; Marcus manages it
MCP tool A function Marcus exposes to agents via the Model Context Protocol; agents call these like API endpoints
Blackboard architecture Design pattern where independent agents read/write a shared board instead of talking directly
DeLM Decentralized Language Models — Stanford research framework (arXiv:2606.10662) achieving 50% cost reduction via verified shared context
Gist A compact (~100 token) plain-English summary of a finding, artifact, or failure
Admission-time verification Checking a claim against its supporting evidence before it enters shared context
get_task_context MCP tool agents call to receive all shared context relevant to their current task
log_decision Existing MCP tool (src/marcus_mcp/tools/context.py:22) for agents to record architectural decisions
log_artifact Existing MCP tool (src/marcus_mcp/tools/attachment.py:32) for agents to share files (specs, designs, APIs)
Foundation task A special pre-agent task whose outputs (decisions, artifacts, and proposed: failures) are shared project-wide
Selective unfolding Mechanism to request progressively more detail about a gist entry — compact by default, full on demand

How to Verify It Works

After Feature 1 (log_failure):

  1. Start a Marcus project with at least two tasks that share a dependency (one upstream, one downstream).
  2. Simulate the upstream agent calling log_failure with a concrete failure description and constraint.
  3. Call get_task_context for the downstream task.
  4. Expected: the response includes a failures key containing the failure logged in step 2.
  5. Check data/project_history/{project_id}/failures.jsonexpected: a failure entry exists with correct task_id, agent_id, constraint, and timestamp fields.
  6. Check the kanban board card for the upstream task — expected: a ❌ DEAD END comment is visible on the card.

After Feature 2 (verification gate):

  1. Call log_decision with a claim that cannot be verified against the task's context (e.g., a specific number that does not appear in any artifact or task output).
  2. Expected: the tool returns {"success": false, "reason": "claim not grounded in provided evidence"} and nothing is written to state.context.decisions.
  3. Call get_task_context for a downstream task — expected: the unverified decision does NOT appear.

After Feature 3 (gist layer):

  1. Log an artifact via log_artifact with a large document (>500 tokens).
  2. Call get_task_context from a downstream task.
  3. Expected: dependency_artifacts contains a gist field (~100 tokens) and a full_content_available: true flag, but NOT the full document body.
  4. Call the new get_artifact_content(filename) tool.
  5. Expected: full document content is returned.

Related

  • DeLM paper: Decentralized Multi-Agent Systems with Shared Context, Mao & Mirhoseini, Stanford, arXiv:2606.10662 (June 2026)
  • log_decision implementation: src/marcus_mcp/tools/context.py
  • log_artifact implementation: src/marcus_mcp/tools/attachment.py
  • get_task_context implementation: src/marcus_mcp/tools/context.py

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureSystem architecture and designenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions