Appearance
Nubosloop
The Nubosloop is the per-task runtime inside np:execute-phase. Every task runs through it: Executor builds, mechanical checks run, the Critic reviews, the routing engine decides the next step, and the loop iterates until zero findings (commit) or loop.maxRounds (stuck, escalate).
ADR-0010 ratifies the design (last amended 2026-05-05, Single-Critic Revision plus Cost Layer L5/L6). The runtime lives in lib/nubosloop.cjs.
The 6 steps
┌──────────────────────────────────────────────────┐
│ Pre-flight: matchExistingLearning (cache hit?) │
└──────────────────────────────────────────────────┘
│
┌─────────┴──────────┐
│ hit │ miss
▼ ▼
Render [CACHED] Researcher-Schwarm (k=3)
to RESEARCH.md merge: Mehrheit/Union/Schnittmenge
│ │
└─────────┬──────────┘
▼
Executor (round 1)
or Build-Fixer (round 2+)
│
▼
Mechanical Checks
(verify, phpstan, pint, tsc,
tool-use audit)
│
┌───────┴────────┐
│ red │ green
│ ▼
│ np-critic (sonnet)
│ — three-axis audit
│ — writes full JSON to <report_path>
│ — emits {verdict, blockers_count, …} envelope
│ │
│ mergeCriticOutputs (file → array)
│ │
│ routeFindings → next destination
│ │
│ ┌──────────┼─────────────────┐
│ │ │ │
│ ▼ ▼ ▼
└─→ Executor Researcher-Schwarm askuser/plan-checker/stuck
│
round < maxRounds: loop
round = maxRounds: stuck → escalate
no findings: commit + auto-log-learningStep 1 — Pre-flight
lib/knowledge-adapter.cjs::match against .nubos-pilot/knowledge/learnings.json. Threshold default 0.9 Jaccard similarity, minOccurrence default 3.
When memory.enabled = true (ADR-0014), the same adapter additionally queries .nubos-pilot/memory/ and merges the BM25 hits with vector hits via α·BM25 + (1−α)·vector (default α = 0.6). The combined score replaces BM25-only; threshold semantics are unchanged. See Vector-Memory.
A hit short-circuits the swarm: the cached pattern is rendered as RESEARCH.md with provenance [CACHED], and Step 2 is skipped. Token cost is roughly zero.
Step 2 — Researcher-Schwarm (on demand)
Spawn swarm.research.k=3 (default) np-researcher agents in parallel. Each receives the same Ticket / CONTEXT / Stack input plus a seed_delta field that nudges its prompt without disclosing the swarm.
lib/researcher-swarm.cjs::mergeConsensus merges the outputs:
- Decisions: Mehrheit (
⌈k/2⌉agreements) → consensus, elseFLAGGED. - Risks: Union, deduplicated by semantic fingerprint, severity = max.
- Patterns: Schnittmenge (≥ 2 spawns) → accepted; solo → demoted to
[ASSUMED]. - Open Questions / Sources: Union, credibility = max.
The merged output enters the Executor's prompt with a <consensus_meta> block. Layer-C requires exactly swarm.research.k np-researcher audits per round (k-of-k gate).
Step 3 — Executor or Build-Fixer
Round 1: np-executor (sonnet) writes code in scope (files_modified from the task plan).
Round 2+: np-build-fixer (sonnet) takes the prior Critic findings + verify output and patches in scope.
Step 4 — Mechanical Checks
The orchestrator runs the checks; lib/nubosloop.cjs only RECEIVES the results via CLI input:
- Task verify — orchestrator runs the task's
verifycommand from frontmatter, then signals vialoop-run-round --phase post-executor --verify-exit-code <int>. - Stack-specific linters —
phpstan,pint(PHP);tsc,eslint,prettier(TS);node:test(JS); etc. - Tool-use audit (Rule 9) — for each spawn, the orchestrator forwards its tool-use log via
loop-audit-tool-use --agent <name> --tool-use-log <json>.auditToolUsechecks for at least one ofSEARCH_TOOLS(search-knowledge,match-existing-learning, …) and persists a violation entry stamped with the current round.
The audit log is converted to findings at the next post-critics phase: auditFindingsForRound(taskId, round, cwd) reads the round's audit entries (with carry-forward for unrouted prior-round violations) and emits one rule-9-violation finding per violating spawn (severity=fail, route=executor). The result joins the merged Critic output before routing.
Red verify or any rule-9-violation routes the next round to the executor with the failure output appended.
Step 5 — Critic (Single-Critic Revision, ADR-0010 §2026-05-05)
One np-critic (sonnet) spawns and audits all three orthogonal axes — style, tests, acceptance — in a single structured findings JSON. The agent loads three audit-surface modules during its read step; module files are not spawnable agents:
| Module file | Tier metadata | Axis |
|---|---|---|
np-critic-style | haiku | naming, conventions, dead code, dangling imports |
np-critic-tests | sonnet | coverage, edge cases, assertion quality |
np-critic-acceptance | sonnet | success_criteria satisfied with evidence |
Verdict-Only Contract (Cost Layer L5). The critic writes its full findings JSON to a <report_path> the orchestrator hands it (typically ${TMPDIR}/nubos-pilot/critic-reports/critic-<task>-r<round>.json) and emits a small envelope as its final message:
json
{ "critic": "critic", "task_id": "M001-S001-T0001", "round": 1,
"verdict": "passed | issues_found", "blockers_count": 0,
"report_path": "...", "run_id": "..." }The full findings/criteria payload never crosses into parent context. loop-run-round --phase post-critics --critic-outputs-path <file> reads the on-disk JSON directly and feeds it through mergeCriticOutputs. This cuts per-round token cost on the critic axis by roughly 95% without losing information. Routing semantics (five-field contract, fingerprint dedup, auto-promotion of criteria) are unchanged.
mergeCriticOutputs then folds in auditFindingsForRound for the round (Rule 9 audit injection — see Findings Routing § Rule 9 audit injection) and routeFindings dispatches per the routing table.
Step 6 — Route or Commit
lib/nubosloop.cjs::routeFindings maps findings to next destinations. See Findings Routing.
- Zero findings → atomic commit + auto-
log-learning+ Messaging sweep. loop.maxRoundsreached →stuck, four-option askuser dialog (+5 Runden/replan/stuck/manual fix), STATE.md marker.next_action=plan-checker→ three-option askuser dialog (replan / stuck / manual-fix); see ADR-0010 Failure Mode.- Otherwise → spawn the routed destination, increment round (when route ∈ {executor, researcher, askuser}), return to Step 3.
Layer-B precondition extension (ADR-0015). _runCommit refuses with loop-commit-precondition-missing (details.missing = "pending-replies-cleared") while any expects_reply: true message in the current task is unarchived. The Critic and Executor / Build-Fixer use the addressed-messaging surface (Named-Agent-Messaging) for per-finding dialogue. An unanswered question keeps the loop alive instead of letting the commit through. --force-commit-phase bypasses the gate, same shape as the existing Layer-A/B overrides.
Phase-completion hook (commit-phase, always-on). After Layer-A/B/C pass and autoLogLearning runs, _runCommit performs a Messaging sweep (ADR-0015): messaging.sweepTaskOnCommit(taskId) moves every message with the matching phase from inbox/ and archive/ into archive/by-task/<taskId>/ and emits a task-swept event in manifest.jsonl. Future tasks see clean inboxes; the per-task audit-trail stays accessible.
The Vector-Memory index is not written at commit — it is a derived cache rebuilt lazily at Pre-flight from the learnings store (ADR-0014; see Vector-Memory).
The _runCommit response:
json
{
"phase": "commit",
"next_action": "commit-task",
"learning_logged": <result | null>,
"learning_skip_reason": <reason | null>,
"messages_swept": <count>,
"forced": <bool>
}Configuration
.nubos-pilot/config.json:
json
{
"loop": { "maxRounds": 3 },
"swarm": {
"research": { "k": 3, "threshold": 0.9, "minOccurrence": 3 },
"critic": { "style_tier": "haiku", "tests_tier": "sonnet", "acceptance_tier": "sonnet" },
"knowledge_adapter": "local"
},
"spawn": {
"headless": {
"enabled": false,
"agents": ["np-critic", "np-researcher"],
"timeout_ms": 600000,
"fallback_on_error": true
}
},
"auto_log_learning": true
}loop.maxRounds defaults to 3 and is clamped to the range [1, 100] by resolveLoopOpts in lib/nubosloop.cjs.
swarm.critic ships as a per-axis tier triple — style_tier (default haiku), tests_tier (default sonnet), acceptance_tier (default sonnet), verified in lib/config-defaults.cjs. The np-critic agent itself resolves from its frontmatter tier (sonnet); resolve-model accepts an optional swarm.critic.tier override for the agent and reads the three *_tier keys as overrides for the corresponding audit-surface modules.
spawn.headless (Cost Layer L6, opt-in) routes critic and researcher spawns through a claude -p subprocess for true parent-context detach. See ADR-0010 §L6 and Configuration → Spawn.
knowledge_adapter selects how cache lookups are routed; "local" (BM25 over .nubos-pilot/knowledge/learnings.json) is the only adapter shipped.
Stuck escalation
Hitting loop.maxRounds is a first-class state, not a silent downgrade. The doctrine treats it as "this task may be mis-planned, discuss with the user" rather than "give up". The orchestrator's stuck handler calls askuser with four options:
- Weitermachen (+5 Runden) — Loop-Cap +5, persisted as
nubosloop.max_rounds_overrideso/np:resume-worksurvives a crash with the operator's decision. - Task neu planen — flag plan-bug, run plan-checker, restart task.
- Task als stuck markieren — STATE.md marker, abort wave.
- Manuell fixen, dann resumen — pause, operator edits, re-invoke.
np:dashboard surfaces the marker prominently with a red badge.
Auto-log-learning
On commit, lib/nubosloop.cjs::autoLogLearning persists the Researcher-Schwarm consensus + the Executor's final diff to .nubos-pilot/knowledge/learnings.json. Future similar tasks bypass the swarm at Step 1.
_runCommit skips the auto-log when (a) cache_hit: true (the cached pattern is already in the store), (b) --learning-pattern is a sentinel placeholder (<...>), or (c) --learning-pattern is empty.
Set auto_log_learning: false in config to disable.
Worked trace
A task with one round-1 style violation that fixes in round 2:
| Round | Action | Outcome |
|---|---|---|
| 1 | Executor writes draft | Verify green |
| 1 | np-critic writes report file, emits envelope {verdict: issues_found, blockers_count: 1} | 1 finding: style: todo-marker @ src/foo.php:42 |
| 1 | Routing | executor (style → executor) |
| 2 | Build-Fixer removes TODO | Verify green |
| 2 | np-critic envelope {verdict: passed, blockers_count: 0} | 0 findings |
| 2 | Routing | commit |
| — | Atomic commit | task(M001-S001-T0001): … |
| — | auto-log-learning | "remove TODO marker before commit" → learnings.json |
A task that gets stuck:
| Round | Action | Outcome |
|---|---|---|
| 1, 2, 3 | Executor → Critic loop | Same finding persists across all three rounds |
| 3 (cap) | evaluateLoop → stuck | STATE.md marker, dashboard badge, four-option askuser |
Driving the loop from the CLI
loop-run-round is the agent-native state-machine driver. Every non-LLM transition lives in this verb; LLM spawns (researcher, executor, critic) stay external. A non-LLM runtime drives one round with a handful of shell-outs:
bash
# 1. Pre-flight cache lookup (advances round counter on first call)
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase preflight --query "$TASK_QUERY"
# 2. Spawn researcher-swarm (if no cache hit) — extern.
# Stamp k audit entries:
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" --agent np-researcher --tool-use-log '[…]' # × k
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-researcher
# 3. Spawn executor — extern.
# Signal verify result + executor audit:
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-executor \
--verify-exit-code "$VERIFY_EXIT" --verify-output-path "$VERIFY_LOG"
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" \
--agent np-executor --tool-use-log "$TOOL_USE_JSON"
# 4. Spawn critic — extern.
# Critic writes full JSON to $CRITIC_REPORT_PATH; final-message envelope is small.
# Stamp critic audit:
node .nubos-pilot/bin/np-tools.cjs loop-audit-tool-use "$TASK_ID" --agent np-critic --tool-use-log '[]'
# 5. Feed critic outputs through routing — Verdict-Only path:
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase post-critics \
--critic-outputs-path "$CRITIC_REPORT_PATH"
# Legacy inline fallback (still accepted):
# --critic-outputs "$CRITIC_JSON"
# Both at once → loop-run-round-post-critics-conflicting-outputs.
# 6. Commit OR stuck OR back to step 3 (depending on next_action)
node .nubos-pilot/bin/np-tools.cjs loop-run-round "$TASK_ID" --phase commit \
--learning-pattern "$CONSENSUS_PATTERN" --learning-outcome verifiedFor low-level state inspection / ad-hoc updates, the granular primitives are still available: loop-state-read, loop-state-record, loop-evaluate, loop-preflight, loop-stuck, loop-metrics. See CLI Commands.
Related
- Researcher-Schwarm — Step 2.
- Findings Routing — Step 6.
- Vector-Memory — opt-in semantic layer that augments Step 1 (hybrid pre-flight).
- Named-Agent-Messaging — addressed Critic ↔ Executor dialogue and the Layer-B
pendingRepliespredicate that gates commit. - Completeness Doctrine — the rules the loop enforces.
- ADR-0010 — full architectural decision record (Single-Critic Revision + Cost Layer L5/L6).
- ADR-0014 — Vector-Memory amendments to Step 1 + commit-phase.
- ADR-0015 — Named-Agent-Messaging surface + Layer-B precondition + sweep hook.
