DEV Community

Cover image for Meta Launched Muse Wonder. Let's Build a Tiny Active Focus Loop in TypeScript.
Bobby Hall Jr
Bobby Hall Jr

Posted on

Meta Launched Muse Wonder. Let's Build a Tiny Active Focus Loop in TypeScript.

Most agents fail in one of two boring ways.

They quit at the first error.

Or they never stop.

One hands you half a task and a polite apology.

The other retries the same broken idea all afternoon.

Neither is focus.

On October 12, 2026, Meta introduced Muse Wonder and Muse Wonder Full, which it calls its most capable models to date for personal and professional agentic tasks. Muse Wonder is available today through the Meta Model API and Muse Code. Muse Wonder Full is available today for Muse Code subscribers.

Two features carry the launch.

Active Memory carries important context across conversations and retrieves the details relevant to the task.

Active Focus is the part I keep thinking about. From Meta's research blog:

Active Focus describes how Muse Wonder continues working toward a challenging goal: trying alternative approaches, working through blockers, and adapting to new information. Along the way, it asks for clarification when needed and checks in before taking important actions.

Meta says it evaluates focus with FocusBench, and sustained execution with SWE-Marathon, which tests work on long-running software projects.

That distinction matters.

Focus is not "try harder."

It is a set of decisions.

Keep going. Switch approaches. Ask. Check in. Stop.

Persistence is easy to fake. Knowing which move comes next is the actual feature.

So let's build a tiny version.

By the end, you'll run one command:

npx tsx focus.ts
Enter fullscreen mode Exit fullscreen mode

And watch a scripted agent switch approaches, ask which lease you meant, check in before deleting branches, report a blocker, and give up honestly.

No API key.

No network.

One honesty note: the goals, files, and branches below are example inputs. The loop is my small model of the behavior Meta describes. It is not how Muse Wonder works inside.

Table of Contents

  1. What We Are Building
  2. Project Setup
  3. Step 1: Model the Goal and the Attempt Log
  4. Step 2: Respect the Constraints
  5. Step 3: Write the Focus Policy
  6. Step 4: Run the Loop
  7. Step 5: Script the Example Goals
  8. Where It Breaks Down
  9. The Bigger Idea

Code: github.com/bobbyhalljr/tiny-active-focus

What We Are Building

Attempt, log, decide: continue, switch, ask, check in, stop

One goal. A few approaches. An attempt log. A policy.

Every step, the agent tries something and records what happened:

  • progress: it moved forward
  • blocked: something outside the approach stopped it
  • failed: the approach itself did not work
  • needs_info: it cannot choose without you
  • done: the goal is met

Then the policy picks the next move.

The model does the work. The loop decides what the work means.

This is a small piece of what I'm building into Roster: AI employees that keep working a lane, and know when to come back to you.

Project Setup

You will need Node.js 18 or newer.

mkdir tiny-active-focus
cd tiny-active-focus

npm init -y
npm install --save-dev typescript tsx @types/node
Enter fullscreen mode Exit fullscreen mode

Save the following blocks, in order, as focus.ts. The full file, with the report() helper and all five example goals, is in the repo.

Step 1: Model the Goal and the Attempt Log

type Outcome = "progress" | "blocked" | "failed" | "needs_info" | "done";

type Step = {
  action: string;
  outcome: Outcome; // scripted result. A real agent would produce this.
  note: string;
  important?: boolean;
};

type Approach = { name: string; uses: string[]; steps: Step[] };

type Goal = { text: string; forbids: string[]; approaches: Approach[] };

type Attempt = { n: number; approach: string; action: string; outcome: Outcome; note: string };

type Budget = {
  maxAttempts: number;
  maxFailsPerApproach: number;
  maxConsecutiveBlocked: number;
};

const BUDGET: Budget = { maxAttempts: 8, maxFailsPerApproach: 2, maxConsecutiveBlocked: 3 };

type State = {
  attempts: Attempt[];
  approach: number;
  step: number;
  fails: number;
  blocked: number;
};
Enter fullscreen mode Exit fullscreen mode

Three numbers carry the policy.

Attempts in total.

Failures before an approach is dead.

Blockers in a row before a retry stops making sense.

Notice blocked and failed are different outcomes.

A failed approach is a bad idea. Switch.

A blocked approach might be a good idea on a bad day. Retry, a little.

Most retry loops treat every error the same. That's how you get ten identical retries against an expired token.

Step 2: Respect the Constraints

function allowedApproaches(goal: Goal): Approach[] {
  return goal.approaches.filter((a) => {
    const bad = a.uses.filter((u) => goal.forbids.includes(u));
    if (bad.length) console.log(`  [SKIP] "${a.name}" uses ${bad.join(", ")}, which the goal forbids`);
    return bad.length === 0;
  });
}
Enter fullscreen mode Exit fullscreen mode

Each approach declares what it touches.

Each goal declares what it forbids.

Forbidden approaches are dropped before anything runs.

An agent that keeps working toward the goal will eventually find the shortcut.

Loosen the assertion. Skip the flaky test. Ship it.

Persistence without constraints is how you get a green suite with fewer tests in it.

Step 3: Write the Focus Policy

The focus policy as a decision ladder

type Decision =
  | "continue"
  | "try_alternative"
  | "ask_clarify"
  | "check_in"
  | "report_blocked"
  | "give_up"
  | "done";

function decide(
  s: State,
  approaches: Approach[],
  last: Attempt | undefined,
  next: Step | undefined,
  approved: Set<string>,
  b: Budget,
): Decision {
  if (last?.outcome === "done") return "done";
  if (last?.outcome === "needs_info") return "ask_clarify";
  if (s.attempts.length >= b.maxAttempts) return "give_up";

  const stuck =
    (last?.outcome === "failed" && s.fails >= b.maxFailsPerApproach) ||
    (last?.outcome === "blocked" && s.blocked >= b.maxConsecutiveBlocked) ||
    next === undefined;

  if (stuck && s.approach + 1 < approaches.length) return "try_alternative";
  if (stuck) return last?.outcome === "blocked" ? "report_blocked" : "give_up";
  if (next?.important && !approved.has(next.action)) return "check_in";
  return "continue";
}
Enter fullscreen mode Exit fullscreen mode

The order is the design.

  1. Done wins.
  2. Missing information means ask. Do not guess which lease.
  3. Budget spent means stop.
  4. Stuck with an alternative left means switch.
  5. Stuck on a blocker with nothing left means report it.
  6. Stuck on failures with nothing left means give up.
  7. An important next step nobody approved means check in.
  8. Otherwise, keep going.

Here's the part I find most interesting.

Only one of those eight lines is "keep going."

The rest are ways to stop well.

Meta lists asking and checking in inside Active Focus, not as exceptions to it.

Step 4: Run the Loop

type Result = { decision: Decision; attempts: Attempt[]; pending?: Step; reason: string };

function runFocus(goal: Goal, approved = new Set<string>(), b = BUDGET): Result {
  const approaches = allowedApproaches(goal);
  const s: State = { attempts: [], approach: 0, step: 0, fails: 0, blocked: 0 };
  let last: Attempt | undefined;

  while (true) {
    const approach = approaches[s.approach];
    const next = approach?.steps[s.step];
    const d = decide(s, approaches, last, next, approved, b);

    if (d === "try_alternative") {
      console.log(`  [SWITCH] "${approach.name}" -> "${approaches[s.approach + 1].name}"`);
      Object.assign(s, { approach: s.approach + 1, step: 0, fails: 0, blocked: 0 });
      last = undefined;
      continue;
    }
    if (d !== "continue") {
      const reason = d === "give_up" ? `out of approaches or budget. Last: ${last?.note ?? "nothing ran"}` : last?.note ?? "";
      return { decision: d, attempts: s.attempts, pending: next, reason };
    }

    // Mocked agent step. No model call. No network.
    const a: Attempt = { n: s.attempts.length + 1, approach: approach.name, ...next! };
    s.attempts.push(a);
    s.step++;
    if (a.outcome === "failed") s.fails++;
    s.blocked = a.outcome === "blocked" ? s.blocked + 1 : 0;
    console.log(`  #${a.n} [${a.outcome.toUpperCase()}] ${a.action}: ${a.note}`);
    last = a;
  }
}
Enter fullscreen mode Exit fullscreen mode

The step is mocked with a scripted outcome, so the demo is deterministic and offline.

In a real agent, that line is a model call plus a tool call. The policy around it does not change.

The check-in returns the pending action. Not a summary. The exact step it wants to take.

Step 5: Script the Example Goals

Six scripted runs, five different endings

A small report() helper (in the repo) prints each run and maps the decision to a tag: [OK], [CLARIFY], [CHECK_IN], [BLOCKED], or [GIVE_UP].

// ===== EXAMPLE INPUTS (scripted, not real repos or real files) =====
// fixTests, lease, preview, and flaky have the same shape. See the repo.

const branches: Goal = {
  text: "Clean up stale branches",
  forbids: ["force_push"],
  approaches: [
    {
      name: "list, then delete",
      uses: ["git_read", "git_delete"],
      steps: [
        { action: "list branches merged over 90 days ago", outcome: "progress", note: "14 candidates" },
        { action: "delete 14 remote branches", outcome: "done", note: "deleted 14 branches", important: true },
      ],
    },
  ],
};

report("3", branches);
report("3b (approved)", branches, new Set(["delete 14 remote branches"]));
Enter fullscreen mode Exit fullscreen mode

Those are example inputs. No repo is touched. No branch is deleted. The repo runs all six cases.

Run npx tsx focus.ts. Real output from this machine:

tiny-active-focus demo
No API key. No network. Example inputs only.
budget: 8 attempts, 2 fails per approach, 3 blocked in a row

1: Make the parseDate tests pass
  [SKIP] "loosen the assertion" uses edit_tests, which the goal forbids
  #1 [FAILED] add fallback in formatInvoice: 2 tests still fail
  #2 [FAILED] add a second fallback: same 2 tests fail
  [SWITCH] "patch the call site" -> "fix timezone handling in parseDate"
  #3 [BLOCKED] run tests: package registry timed out
  #4 [PROGRESS] run tests again: reproduced: UTC offset dropped
  #5 [DONE] normalize input to UTC: 12 of 12 tests pass
[OK] after 5 attempt(s). 12 of 12 tests pass

2: Summarize the renewal terms in my lease
  #1 [PROGRESS] list docs/leases: found 2 lease files
  #2 [NEEDS_INFO] pick the lease: 2025 and 2026 leases both match. Which one?
[CLARIFY] after 2 attempt(s). 2025 and 2026 leases both match. Which one?

3: Clean up stale branches
  #1 [PROGRESS] list branches merged over 90 days ago: 14 candidates
[CHECK_IN] after 1 attempt(s). Approve before: "delete 14 remote branches"

3b (approved): Clean up stale branches
  #1 [PROGRESS] list branches merged over 90 days ago: 14 candidates
  #2 [DONE] delete 14 remote branches: deleted 14 branches
[OK] after 2 attempt(s). deleted 14 branches

4: Publish the preview build
  #1 [BLOCKED] trigger preview deploy: staging token expired
  #2 [BLOCKED] retry after backoff: staging token expired
  #3 [BLOCKED] retry on a fresh runner: staging token expired
[BLOCKED] after 3 attempt(s). staging token expired

5: Make the flaky checkout e2e test pass
  [SKIP] "mark it skipped" uses skip_tests, which the goal forbids
  #1 [FAILED] wait 10s for the iframe: fails 3 of 10 runs
  #2 [FAILED] wait 30s for the iframe: fails 3 of 10 runs
  [SWITCH] "raise the wait timeout" -> "mock the payment iframe"
  #3 [FAILED] stub the iframe postMessage: fails 2 of 10 runs
  #4 [FAILED] stub the payment script: fails 2 of 10 runs
[GIVE_UP] after 4 attempt(s). out of approaches or budget. Last: fails 2 of 10 runs
Enter fullscreen mode Exit fullscreen mode

Six runs. Five different endings.

Run 1 drops the cheating approach, switches after two failures, works through a timeout, and finishes.

Run 3 stops before deleting 14 branches. Run 3b gets the approval and finishes.

Run 4 does not retry forever. Three expired tokens in a row is a human problem.

Run 5 refuses test.skip, tries two honest approaches, and says so when neither works.

Giving up with a clear reason is a result. Pretending is not.

Where It Breaks Down

  1. Outcomes are scripted. The hard part of a real loop is classifying what happened. Failure or blocker? A model has to judge that, and it will sometimes be wrong.

  2. "Important" is a label here. Deciding which actions need a check-in is real policy work. Deletes, payments, and messages to other people are a good start. Ask too often and people stop reading.

  3. The budget is a guess. Long-running tasks need budgets in time and money, not only attempt counts.

  4. Constraints are string tags. A "read" tool that can also write will slip past a tag check.

  5. Approaches are listed up front. A real agent invents new ones as it learns. That is where most of the intelligence lives, and most of the risk.

  6. No memory between runs. Run 3b repeats the listing step. With something like Active Memory, the agent would carry the 14 candidates forward.

The Bigger Idea

Goal + constraints
  ↓
Drop forbidden approaches
  ↓
Attempt → log the outcome
  ↓
Policy
  ├→ continue
  ├→ try an alternative
  ├→ ask for clarification
  ├→ check in before an important step
  ├→ report the blocker
  └→ give up, with a reason
Enter fullscreen mode Exit fullscreen mode

The model provides the attempts.

The constraints provide the boundaries.

The attempt log provides the evidence.

The policy provides the judgment.

The check-in provides the human.

This part is prediction, not history.

I think "how long can it work alone" is becoming the headline question for agent models. Meta naming Active Focus as a feature, and evaluating it directly, is the right direction.

But the length of a run is not the interesting number.

The interesting number is how often it stopped at the right moment.

Ask too late, and the branches are gone.

Ask too early, and you might as well do it yourself.

A focused agent is not one that never stops. It is one that stops for the right reasons.


Try Roster

I'm building Roster around this idea: AI employees with real responsibilities, tools, memory, schedules and computer access. They keep working the lane, and they check in when it matters.

If the same follow-ups, handoffs, and waiting loops keep eating your week, give them to an AI employee.

Try Roster →

Top comments (1)

Collapse
 
botdevsupports profile image
DEV SUPPORT •
You need to verify your account.
Enter fullscreen mode Exit fullscreen mode

tr.ee/dev-to