atlas-workflow 0.8.2 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -3
- package/VERSION +1 -1
- package/build/bump-version.mjs +113 -0
- package/build/cli/atlas-init.mjs +84 -7
- package/hosts/opencode/.opencode/atlas/VERSION +1 -1
- package/hosts/opencode/.opencode/atlas/orchestrator/README.md +9 -2
- package/hosts/opencode/.opencode/atlas/orchestrator/references/host-adapters.md +13 -12
- package/hosts/opencode/.opencode/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/README.md +3 -2
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/package.json +1 -1
- package/hosts/opencode/.opencode/atlas/packages/mcp-server/server.js +337 -10
- package/hosts/opencode/.opencode/skills/atlas-direct-execute/SKILL.md +31 -0
- package/hosts/opencode/.opencode/skills/atlas-plan-execute/SKILL.md +32 -0
- package/hosts/opencode/.opencode/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
- package/hosts/pi/.pi/agents/atlas-direct-execute.md +31 -0
- package/hosts/pi/.pi/agents/atlas-plan-execute.md +32 -0
- package/hosts/pi/atlas/VERSION +1 -1
- package/hosts/pi/atlas/orchestrator/README.md +9 -2
- package/hosts/pi/atlas/orchestrator/references/host-adapters.md +13 -12
- package/hosts/pi/atlas/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
- package/hosts/pi/atlas/packages/mcp-server/README.md +3 -2
- package/hosts/pi/atlas/packages/mcp-server/package.json +1 -1
- package/hosts/pi/atlas/packages/mcp-server/server.js +337 -10
- package/hosts/pi/skills/atlas-direct-execute/SKILL.md +31 -0
- package/hosts/pi/skills/atlas-plan-execute/SKILL.md +32 -0
- package/hosts/pi/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
- package/package.json +1 -1
- package/plugins/atlas-workflow-orchestrator/.codex-plugin/plugin.json +1 -1
- package/plugins/atlas-workflow-orchestrator/VERSION +1 -1
- package/plugins/atlas-workflow-orchestrator/agents/atlas-direct-execute.md +1 -1
- package/plugins/atlas-workflow-orchestrator/agents/atlas-plan-execute.md +1 -1
- package/plugins/atlas-workflow-orchestrator/orchestrator/README.md +9 -2
- package/plugins/atlas-workflow-orchestrator/orchestrator/references/host-adapters.md +13 -12
- package/plugins/atlas-workflow-orchestrator/orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/README.md +3 -2
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/package.json +1 -1
- package/plugins/atlas-workflow-orchestrator/packages/mcp-server/server.js +337 -10
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-direct-execute/SKILL.md +31 -0
- package/plugins/atlas-workflow-orchestrator/packages/skills/atlas-plan-execute/SKILL.md +32 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-direct-execute/SKILL.md +31 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-plan-execute/SKILL.md +32 -0
- package/plugins/atlas-workflow-orchestrator/skills/atlas-workflow-orchestrator/SKILL.md +25 -53
|
@@ -78,6 +78,17 @@ const VALIDATOR_MAX_ATTEMPTS = 2;
|
|
|
78
78
|
// bloqueia com causa explícita em vez de re-despachar indefinidamente.
|
|
79
79
|
const VALIDATOR_CHALLENGE_MAX_FAILURES = 2;
|
|
80
80
|
const VALIDATOR_PASSED_STATUSES = new Set(['passed', 'passed_with_observations']);
|
|
81
|
+
const EXECUTOR_BOOTSTRAP_TIMEOUT_MS = 120_000;
|
|
82
|
+
const EXECUTOR_PROGRESS_TIMEOUT_MS = 300_000;
|
|
83
|
+
const EXECUTOR_CHECKPOINT_EVENTS = new Set([
|
|
84
|
+
'executor_started',
|
|
85
|
+
'skill_loaded',
|
|
86
|
+
'plan_loaded',
|
|
87
|
+
'handoff_accepted',
|
|
88
|
+
'task_started',
|
|
89
|
+
'first_write',
|
|
90
|
+
'state_path_created',
|
|
91
|
+
]);
|
|
81
92
|
|
|
82
93
|
function validatorRunId(runId, attempt, timestamp) {
|
|
83
94
|
return `${runId}:validator:${attempt}:${timestamp}`;
|
|
@@ -255,6 +266,25 @@ const HOST_ADAPTERS = {
|
|
|
255
266
|
// Fail-closed — só passam se o caller reportar disponibilidade real (não otimismo do perfil).
|
|
256
267
|
prereq_policy: 'must_report',
|
|
257
268
|
},
|
|
269
|
+
antigravity: {
|
|
270
|
+
label: 'Antigravity',
|
|
271
|
+
subagent_dispatch: {
|
|
272
|
+
mechanism: 'define_subagent(name, system_prompt) + invoke_subagent(Subagents)',
|
|
273
|
+
example: 'define_subagent(name: "atlas-task-validator", system_prompt: "<SKILL_MD>") e invoke_subagent(Subagents: [{TypeName: "atlas-task-validator", Role: "Validator", Prompt: "<state_path>"}])',
|
|
274
|
+
registration: 'Mapeamento de skills e agents via define_subagent dinâmico',
|
|
275
|
+
},
|
|
276
|
+
validator_dispatch: {
|
|
277
|
+
dispatcher: 'orchestrator',
|
|
278
|
+
join: {
|
|
279
|
+
sync: 'self_evident',
|
|
280
|
+
confidence: 'high',
|
|
281
|
+
mechanism: 'invoke_subagent bloqueante por design do host',
|
|
282
|
+
},
|
|
283
|
+
},
|
|
284
|
+
todo_tool: null,
|
|
285
|
+
hooks: { supported: false, mechanism: null },
|
|
286
|
+
capabilities_flags: { subagent_available: true, mcp_available: true, todo_available: false },
|
|
287
|
+
},
|
|
258
288
|
generic: {
|
|
259
289
|
label: 'Host genérico',
|
|
260
290
|
subagent_dispatch: {
|
|
@@ -512,7 +542,15 @@ function parseWorkflowConfig() {
|
|
|
512
542
|
|
|
513
543
|
function consumerRoot(args = {}) {
|
|
514
544
|
const explicitRoot = optionalString(args, 'project_root');
|
|
515
|
-
|
|
545
|
+
if (explicitRoot && explicitRoot.trim() !== '') {
|
|
546
|
+
return path.resolve(explicitRoot);
|
|
547
|
+
}
|
|
548
|
+
const cwd = process.cwd();
|
|
549
|
+
if (cwd === '/' || cwd === '/var/folders') {
|
|
550
|
+
const home = process.env.HOME || process.env.USERPROFILE;
|
|
551
|
+
if (home) return path.resolve(home);
|
|
552
|
+
}
|
|
553
|
+
return path.resolve(cwd);
|
|
516
554
|
}
|
|
517
555
|
|
|
518
556
|
function runRoot(args = {}) {
|
|
@@ -584,6 +622,11 @@ function nowIso() {
|
|
|
584
622
|
return new Date().toISOString();
|
|
585
623
|
}
|
|
586
624
|
|
|
625
|
+
function isoPlusMs(iso, ms) {
|
|
626
|
+
const base = Date.parse(iso);
|
|
627
|
+
return new Date((Number.isFinite(base) ? base : Date.now()) + ms).toISOString();
|
|
628
|
+
}
|
|
629
|
+
|
|
587
630
|
function redact(value) {
|
|
588
631
|
if (Array.isArray(value)) return value.map(redact);
|
|
589
632
|
if (!value || typeof value !== 'object') return value;
|
|
@@ -597,8 +640,12 @@ function redact(value) {
|
|
|
597
640
|
}
|
|
598
641
|
|
|
599
642
|
function logCall(entry, args = {}) {
|
|
600
|
-
|
|
601
|
-
|
|
643
|
+
try {
|
|
644
|
+
const line = JSON.stringify({ timestamp: nowIso(), ...entry }) + '\n';
|
|
645
|
+
fs.appendFileSync(path.join(ensureRunDir(args), 'mcp.log'), line, { mode: 0o600 });
|
|
646
|
+
} catch (error) {
|
|
647
|
+
// Ignora silenciosamente falhas de gravação de log (ex: diretório somente-leitura)
|
|
648
|
+
}
|
|
602
649
|
}
|
|
603
650
|
|
|
604
651
|
function rpcError(code, message, data) {
|
|
@@ -882,6 +929,7 @@ function patchDispatchResult(runId, result, args = {}) {
|
|
|
882
929
|
timestamp: result.timestamp,
|
|
883
930
|
phase: result.phase ?? null,
|
|
884
931
|
action: result.action ?? null,
|
|
932
|
+
event: result.event ?? null,
|
|
885
933
|
status: result.status,
|
|
886
934
|
next_action: result.next_action ?? null,
|
|
887
935
|
error: result.error ?? null,
|
|
@@ -1693,6 +1741,27 @@ function expectedNextPhase(routing, dispatch) {
|
|
|
1693
1741
|
return 'prd_interview';
|
|
1694
1742
|
}
|
|
1695
1743
|
|
|
1744
|
+
function initialExecutorLiveness(timestamp) {
|
|
1745
|
+
return {
|
|
1746
|
+
status: 'spawned',
|
|
1747
|
+
bootstrap_timeout_ms: EXECUTOR_BOOTSTRAP_TIMEOUT_MS,
|
|
1748
|
+
progress_timeout_ms: EXECUTOR_PROGRESS_TIMEOUT_MS,
|
|
1749
|
+
bootstrap_deadline_at: isoPlusMs(timestamp, EXECUTOR_BOOTSTRAP_TIMEOUT_MS),
|
|
1750
|
+
next_progress_deadline_at: null,
|
|
1751
|
+
required_first_checkpoint: 'executor_started',
|
|
1752
|
+
last_checkpoint: null,
|
|
1753
|
+
last_progress_at: null,
|
|
1754
|
+
checkpoints: [],
|
|
1755
|
+
};
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
function checkpointStatus(event) {
|
|
1759
|
+
if (event === 'state_path_created') return 'handoff_ready';
|
|
1760
|
+
if (event === 'task_started' || event === 'first_write') return 'executing';
|
|
1761
|
+
if (event === 'plan_loaded' || event === 'handoff_accepted') return 'ready';
|
|
1762
|
+
return 'booting';
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1696
1765
|
function startDispatch(args, context) {
|
|
1697
1766
|
const phase = requiredString(args, 'phase');
|
|
1698
1767
|
if (Object.prototype.hasOwnProperty.call(args, LEGACY_ROUTE_KEY)) {
|
|
@@ -1752,7 +1821,11 @@ function startDispatch(args, context) {
|
|
|
1752
1821
|
current_phase: phase,
|
|
1753
1822
|
expected_phase: expected,
|
|
1754
1823
|
dispatch: {
|
|
1755
|
-
active: {
|
|
1824
|
+
active: {
|
|
1825
|
+
phase,
|
|
1826
|
+
started_at: timestamp,
|
|
1827
|
+
...(phase === 'plan_execute' ? { liveness: initialExecutorLiveness(timestamp) } : {}),
|
|
1828
|
+
},
|
|
1756
1829
|
previous_phase: context.dispatch.previous_phase ?? null,
|
|
1757
1830
|
next_phase: null,
|
|
1758
1831
|
next_action: `complete_${phase}`,
|
|
@@ -1761,6 +1834,220 @@ function startDispatch(args, context) {
|
|
|
1761
1834
|
};
|
|
1762
1835
|
}
|
|
1763
1836
|
|
|
1837
|
+
function checkpointDispatch(args, context) {
|
|
1838
|
+
const phase = requiredString(args, 'phase');
|
|
1839
|
+
const event = requiredString(args, 'event');
|
|
1840
|
+
const timestamp = nowIso();
|
|
1841
|
+
const active = context.dispatch.active;
|
|
1842
|
+
|
|
1843
|
+
if (phase !== 'plan_execute') {
|
|
1844
|
+
return {
|
|
1845
|
+
gate: 'G12',
|
|
1846
|
+
action: 'checkpoint',
|
|
1847
|
+
phase,
|
|
1848
|
+
event,
|
|
1849
|
+
status: 'blocked',
|
|
1850
|
+
timestamp,
|
|
1851
|
+
error: 'Checkpoint de liveness só se aplica a plan_execute',
|
|
1852
|
+
current_phase: active?.phase ?? null,
|
|
1853
|
+
expected_phase: 'plan_execute',
|
|
1854
|
+
next_action: 'corrigir_fase_do_checkpoint',
|
|
1855
|
+
};
|
|
1856
|
+
}
|
|
1857
|
+
if (!active || active.phase !== phase) {
|
|
1858
|
+
return {
|
|
1859
|
+
gate: 'G12',
|
|
1860
|
+
action: 'checkpoint',
|
|
1861
|
+
phase,
|
|
1862
|
+
event,
|
|
1863
|
+
status: 'blocked',
|
|
1864
|
+
timestamp,
|
|
1865
|
+
error: `Checkpoint fora de ordem: fase ativa ${active?.phase ?? 'nenhuma'}, recebido ${phase}`,
|
|
1866
|
+
current_phase: active?.phase ?? null,
|
|
1867
|
+
expected_phase: active?.phase ?? expectedNextPhase(context.routing, context.dispatch),
|
|
1868
|
+
next_action: active ? `checkpoint_${active.phase}` : `dispatch_${expectedNextPhase(context.routing, context.dispatch)}`,
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1871
|
+
if (!EXECUTOR_CHECKPOINT_EVENTS.has(event)) {
|
|
1872
|
+
return {
|
|
1873
|
+
gate: 'G12',
|
|
1874
|
+
action: 'checkpoint',
|
|
1875
|
+
phase,
|
|
1876
|
+
event,
|
|
1877
|
+
status: 'blocked',
|
|
1878
|
+
timestamp,
|
|
1879
|
+
error: `Checkpoint desconhecido: ${event}`,
|
|
1880
|
+
current_phase: phase,
|
|
1881
|
+
expected_phase: phase,
|
|
1882
|
+
next_action: 'emitir_checkpoint_executor_valido',
|
|
1883
|
+
};
|
|
1884
|
+
}
|
|
1885
|
+
|
|
1886
|
+
const planPath = optionalString(args, 'plan_path');
|
|
1887
|
+
const statePathValue = optionalString(args, 'state_path');
|
|
1888
|
+
const detail = optionalString(args, 'detail');
|
|
1889
|
+
if (event === 'state_path_created') {
|
|
1890
|
+
if (!statePathValue || statePathValue.trim() === '') {
|
|
1891
|
+
return {
|
|
1892
|
+
gate: 'G12',
|
|
1893
|
+
action: 'checkpoint',
|
|
1894
|
+
phase,
|
|
1895
|
+
event,
|
|
1896
|
+
status: 'blocked',
|
|
1897
|
+
timestamp,
|
|
1898
|
+
error: 'state_path obrigatório para checkpoint state_path_created',
|
|
1899
|
+
current_phase: phase,
|
|
1900
|
+
expected_phase: phase,
|
|
1901
|
+
next_action: 'emitir_state_path_created_com_state_path',
|
|
1902
|
+
};
|
|
1903
|
+
}
|
|
1904
|
+
try {
|
|
1905
|
+
JSON.parse(fs.readFileSync(resolveConsumerPath(statePathValue, args), 'utf8'));
|
|
1906
|
+
} catch (error) {
|
|
1907
|
+
return {
|
|
1908
|
+
gate: 'G12',
|
|
1909
|
+
action: 'checkpoint',
|
|
1910
|
+
phase,
|
|
1911
|
+
event,
|
|
1912
|
+
status: 'blocked',
|
|
1913
|
+
timestamp,
|
|
1914
|
+
error: `state_path inválido ou ilegível para checkpoint state_path_created: ${error.message}`,
|
|
1915
|
+
current_phase: phase,
|
|
1916
|
+
expected_phase: phase,
|
|
1917
|
+
next_action: 'corrigir_state_path_antes_do_handoff',
|
|
1918
|
+
};
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
const liveness = active.liveness && typeof active.liveness === 'object'
|
|
1922
|
+
? active.liveness
|
|
1923
|
+
: initialExecutorLiveness(active.started_at ?? timestamp);
|
|
1924
|
+
const checkpoint = {
|
|
1925
|
+
event,
|
|
1926
|
+
timestamp,
|
|
1927
|
+
...(planPath ? { plan_path: planPath } : {}),
|
|
1928
|
+
...(statePathValue ? { state_path: statePathValue } : {}),
|
|
1929
|
+
...(detail ? { detail } : {}),
|
|
1930
|
+
};
|
|
1931
|
+
const nextLiveness = {
|
|
1932
|
+
...liveness,
|
|
1933
|
+
status: checkpointStatus(event),
|
|
1934
|
+
last_checkpoint: event,
|
|
1935
|
+
last_progress_at: timestamp,
|
|
1936
|
+
next_progress_deadline_at: isoPlusMs(timestamp, EXECUTOR_PROGRESS_TIMEOUT_MS),
|
|
1937
|
+
checkpoints: [
|
|
1938
|
+
...(Array.isArray(liveness.checkpoints) ? liveness.checkpoints : []),
|
|
1939
|
+
checkpoint,
|
|
1940
|
+
],
|
|
1941
|
+
};
|
|
1942
|
+
|
|
1943
|
+
return {
|
|
1944
|
+
gate: 'G12',
|
|
1945
|
+
action: 'checkpoint',
|
|
1946
|
+
phase,
|
|
1947
|
+
event,
|
|
1948
|
+
status: 'passed',
|
|
1949
|
+
timestamp,
|
|
1950
|
+
executor_liveness: nextLiveness.status,
|
|
1951
|
+
current_phase: phase,
|
|
1952
|
+
expected_phase: phase,
|
|
1953
|
+
dispatch: {
|
|
1954
|
+
active: {
|
|
1955
|
+
...active,
|
|
1956
|
+
liveness: nextLiveness,
|
|
1957
|
+
},
|
|
1958
|
+
executor_liveness: nextLiveness,
|
|
1959
|
+
next_action: `complete_${phase}`,
|
|
1960
|
+
},
|
|
1961
|
+
next_action: `continue_${phase}`,
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
function statusDispatch(args, context) {
|
|
1966
|
+
const phase = requiredString(args, 'phase');
|
|
1967
|
+
const timestamp = nowIso();
|
|
1968
|
+
const active = context.dispatch.active;
|
|
1969
|
+
|
|
1970
|
+
if (!active || active.phase !== phase) {
|
|
1971
|
+
return {
|
|
1972
|
+
gate: 'G12',
|
|
1973
|
+
action: 'status',
|
|
1974
|
+
phase,
|
|
1975
|
+
status: 'blocked',
|
|
1976
|
+
timestamp,
|
|
1977
|
+
error: `Status fora de ordem: fase ativa ${active?.phase ?? 'nenhuma'}, recebido ${phase}`,
|
|
1978
|
+
current_phase: active?.phase ?? null,
|
|
1979
|
+
expected_phase: active?.phase ?? expectedNextPhase(context.routing, context.dispatch),
|
|
1980
|
+
next_action: active ? `status_${active.phase}` : `dispatch_${expectedNextPhase(context.routing, context.dispatch)}`,
|
|
1981
|
+
};
|
|
1982
|
+
}
|
|
1983
|
+
|
|
1984
|
+
const liveness = active.liveness && typeof active.liveness === 'object'
|
|
1985
|
+
? active.liveness
|
|
1986
|
+
: (phase === 'plan_execute' ? initialExecutorLiveness(active.started_at ?? timestamp) : null);
|
|
1987
|
+
const checkpoints = Array.isArray(liveness?.checkpoints) ? liveness.checkpoints : [];
|
|
1988
|
+
const deadline = Date.parse(liveness?.bootstrap_deadline_at ?? '');
|
|
1989
|
+
const now = Date.parse(timestamp);
|
|
1990
|
+
const bootstrapExpired = phase === 'plan_execute'
|
|
1991
|
+
&& checkpoints.length === 0
|
|
1992
|
+
&& Number.isFinite(deadline)
|
|
1993
|
+
&& Number.isFinite(now)
|
|
1994
|
+
&& now > deadline;
|
|
1995
|
+
const progressDeadline = Date.parse(liveness?.next_progress_deadline_at ?? '');
|
|
1996
|
+
const progressExpired = phase === 'plan_execute'
|
|
1997
|
+
&& checkpoints.length > 0
|
|
1998
|
+
&& Number.isFinite(progressDeadline)
|
|
1999
|
+
&& Number.isFinite(now)
|
|
2000
|
+
&& now > progressDeadline;
|
|
2001
|
+
|
|
2002
|
+
if (bootstrapExpired || progressExpired) {
|
|
2003
|
+
const cause = bootstrapExpired ? 'executor_bootstrap_timeout' : 'executor_progress_timeout';
|
|
2004
|
+
const stalledLiveness = {
|
|
2005
|
+
...liveness,
|
|
2006
|
+
status: 'stalled',
|
|
2007
|
+
stalled_at: timestamp,
|
|
2008
|
+
cause,
|
|
2009
|
+
};
|
|
2010
|
+
return {
|
|
2011
|
+
gate: 'G12',
|
|
2012
|
+
action: 'status',
|
|
2013
|
+
phase,
|
|
2014
|
+
status: 'blocked',
|
|
2015
|
+
timestamp,
|
|
2016
|
+
cause,
|
|
2017
|
+
error: bootstrapExpired
|
|
2018
|
+
? `Executor sem checkpoint até ${liveness.bootstrap_deadline_at}`
|
|
2019
|
+
: `Executor sem progresso desde ${liveness.last_progress_at}`,
|
|
2020
|
+
current_phase: phase,
|
|
2021
|
+
expected_phase: phase,
|
|
2022
|
+
dispatch: {
|
|
2023
|
+
active: null,
|
|
2024
|
+
previous_phase: phase,
|
|
2025
|
+
next_phase: phase,
|
|
2026
|
+
next_action: `retry_${phase}`,
|
|
2027
|
+
executor_liveness: stalledLiveness,
|
|
2028
|
+
},
|
|
2029
|
+
next_action: `retry_${phase}`,
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
return {
|
|
2034
|
+
gate: 'G12',
|
|
2035
|
+
action: 'status',
|
|
2036
|
+
phase,
|
|
2037
|
+
status: 'passed',
|
|
2038
|
+
timestamp,
|
|
2039
|
+
executor_liveness: liveness?.status ?? 'not_tracked',
|
|
2040
|
+
current_phase: phase,
|
|
2041
|
+
expected_phase: phase,
|
|
2042
|
+
dispatch: {
|
|
2043
|
+
active: liveness ? { ...active, liveness } : active,
|
|
2044
|
+
executor_liveness: liveness,
|
|
2045
|
+
next_action: `complete_${phase}`,
|
|
2046
|
+
},
|
|
2047
|
+
next_action: `complete_${phase}`,
|
|
2048
|
+
};
|
|
2049
|
+
}
|
|
2050
|
+
|
|
1764
2051
|
function completeDispatch(args, context) {
|
|
1765
2052
|
const phase = requiredString(args, 'phase');
|
|
1766
2053
|
const timestamp = nowIso();
|
|
@@ -1896,13 +2183,15 @@ function lockDispatch(args = {}) {
|
|
|
1896
2183
|
throw rpcError(-32602, `unknown_property: ${LEGACY_ROUTE_KEY}`);
|
|
1897
2184
|
}
|
|
1898
2185
|
const action = args.action ?? 'start';
|
|
1899
|
-
if (!['start', 'complete', 'abort'].includes(action)) {
|
|
2186
|
+
if (!['start', 'checkpoint', 'status', 'complete', 'abort'].includes(action)) {
|
|
1900
2187
|
throw rpcError(-32602, `Ação inválida para atlas_lock_dispatch: ${action}`);
|
|
1901
2188
|
}
|
|
1902
2189
|
|
|
1903
2190
|
const context = getDispatchState(runId, args);
|
|
1904
2191
|
const result =
|
|
1905
2192
|
action === 'start' ? startDispatch(args, context) :
|
|
2193
|
+
action === 'checkpoint' ? checkpointDispatch(args, context) :
|
|
2194
|
+
action === 'status' ? statusDispatch(args, context) :
|
|
1906
2195
|
action === 'complete' ? completeDispatch(args, context) :
|
|
1907
2196
|
abortDispatch(args, context);
|
|
1908
2197
|
|
|
@@ -1987,6 +2276,33 @@ function validatorStart(args, context) {
|
|
|
1987
2276
|
};
|
|
1988
2277
|
}
|
|
1989
2278
|
|
|
2279
|
+
const liveness = context.dispatch.active.liveness && typeof context.dispatch.active.liveness === 'object'
|
|
2280
|
+
? context.dispatch.active.liveness
|
|
2281
|
+
: null;
|
|
2282
|
+
const checkpoints = Array.isArray(liveness?.checkpoints) ? liveness.checkpoints : [];
|
|
2283
|
+
const lastCheckpoint = checkpoints.at(-1);
|
|
2284
|
+
if (
|
|
2285
|
+
liveness?.status !== 'handoff_ready'
|
|
2286
|
+
|| liveness.last_checkpoint !== 'state_path_created'
|
|
2287
|
+
|| lastCheckpoint?.event !== 'state_path_created'
|
|
2288
|
+
|| lastCheckpoint?.state_path !== statePathValue
|
|
2289
|
+
) {
|
|
2290
|
+
return {
|
|
2291
|
+
gate: 'G12',
|
|
2292
|
+
action: 'start',
|
|
2293
|
+
status: 'blocked',
|
|
2294
|
+
timestamp,
|
|
2295
|
+
error: 'Validator bloqueado: executor não comprovou state_path_created para este state_path',
|
|
2296
|
+
current_phase: 'plan_execute',
|
|
2297
|
+
executor_liveness: liveness?.status ?? 'not_tracked',
|
|
2298
|
+
expected_checkpoint: 'state_path_created',
|
|
2299
|
+
state_path: statePathValue,
|
|
2300
|
+
last_checkpoint: liveness?.last_checkpoint ?? null,
|
|
2301
|
+
last_state_path: lastCheckpoint?.state_path ?? null,
|
|
2302
|
+
next_action: 'aguardar_state_path_created_antes_do_validator',
|
|
2303
|
+
};
|
|
2304
|
+
}
|
|
2305
|
+
|
|
1990
2306
|
if (cycle.active) {
|
|
1991
2307
|
return {
|
|
1992
2308
|
gate: 'G4',
|
|
@@ -2733,11 +3049,14 @@ function assertAfterPlan(args = {}) {
|
|
|
2733
3049
|
}
|
|
2734
3050
|
|
|
2735
3051
|
function toolResult(value) {
|
|
3052
|
+
// JSON compacto (sem indentação): o consumidor é o LLM orquestrador, que parseia
|
|
3053
|
+
// igual com ou sem whitespace. Pretty-print só gastava tokens em toda resposta MCP
|
|
3054
|
+
// (~10-13 por run). Mesmos campos/valores — zero impacto em determinismo/contrato.
|
|
2736
3055
|
return {
|
|
2737
3056
|
content: [
|
|
2738
3057
|
{
|
|
2739
3058
|
type: 'text',
|
|
2740
|
-
text: JSON.stringify(value
|
|
3059
|
+
text: JSON.stringify(value),
|
|
2741
3060
|
},
|
|
2742
3061
|
],
|
|
2743
3062
|
};
|
|
@@ -2876,7 +3195,7 @@ function toolsList() {
|
|
|
2876
3195
|
},
|
|
2877
3196
|
{
|
|
2878
3197
|
name: 'atlas_lock_dispatch',
|
|
2879
|
-
description: 'Gates G7/G8: controla fase ativa, transições de dispatch, validator antes de review e concorrência 1.',
|
|
3198
|
+
description: 'Gates G7/G8/G12: controla fase ativa, checkpoints de liveness do executor, transições de dispatch, validator antes de review e concorrência 1.',
|
|
2880
3199
|
inputSchema: {
|
|
2881
3200
|
type: 'object',
|
|
2882
3201
|
additionalProperties: false,
|
|
@@ -2884,8 +3203,16 @@ function toolsList() {
|
|
|
2884
3203
|
properties: {
|
|
2885
3204
|
run_id: { type: 'string', minLength: 1 },
|
|
2886
3205
|
project_root: { type: 'string', minLength: 1 },
|
|
2887
|
-
action: { type: 'string', enum: ['start', 'complete', 'abort'], default: 'start' },
|
|
3206
|
+
action: { type: 'string', enum: ['start', 'checkpoint', 'status', 'complete', 'abort'], default: 'start' },
|
|
2888
3207
|
phase: { type: 'string', enum: ['plan_handoff', 'plan_execute', 'slice_review'] },
|
|
3208
|
+
event: {
|
|
3209
|
+
type: 'string',
|
|
3210
|
+
enum: [...EXECUTOR_CHECKPOINT_EVENTS],
|
|
3211
|
+
description: 'Checkpoint G12 emitido pelo executor durante plan_execute.',
|
|
3212
|
+
},
|
|
3213
|
+
plan_path: { type: 'string' },
|
|
3214
|
+
state_path: { type: 'string' },
|
|
3215
|
+
detail: { type: 'string' },
|
|
2889
3216
|
validator_status: { type: 'string' },
|
|
2890
3217
|
},
|
|
2891
3218
|
},
|
|
@@ -3027,9 +3354,9 @@ function startStdioLoop() {
|
|
|
3027
3354
|
send({
|
|
3028
3355
|
id: message.id,
|
|
3029
3356
|
error: {
|
|
3030
|
-
code: error.code
|
|
3357
|
+
code: Number.isInteger(error.code) ? error.code : -32603,
|
|
3031
3358
|
message: error.message,
|
|
3032
|
-
data: error.data,
|
|
3359
|
+
data: error.data || { original_code: error.code },
|
|
3033
3360
|
},
|
|
3034
3361
|
});
|
|
3035
3362
|
}
|
|
@@ -11,6 +11,29 @@ Execute directly from a PRD/spec/task while preserving execution quality: explic
|
|
|
11
11
|
|
|
12
12
|
This is not planless execution. Replace the visible markdown plan with a compact operational contract held in the current turn and passed to validation.
|
|
13
13
|
|
|
14
|
+
## Executor liveness checkpoints
|
|
15
|
+
|
|
16
|
+
Depois de carregar esta skill e antes de qualquer discovery longo, emita um checkpoint MCP:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
atlas_lock_dispatch({
|
|
20
|
+
"action": "checkpoint",
|
|
21
|
+
"phase": "plan_execute",
|
|
22
|
+
"event": "executor_started"
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Em seguida, emita checkpoints materiais conforme avança:
|
|
27
|
+
|
|
28
|
+
- `skill_loaded` — skill carregada e contrato reconhecido.
|
|
29
|
+
- `plan_loaded` — PRD/spec/task de entrada lido.
|
|
30
|
+
- `handoff_accepted` — boundary, obligations, `state_path` alvo e contrato direto aceitos.
|
|
31
|
+
- `task_started` — primeira task começou.
|
|
32
|
+
- `first_write` — primeira mutação de workspace feita.
|
|
33
|
+
- `state_path_created` — state file escrito antes de devolver `validator_handoff_required`.
|
|
34
|
+
|
|
35
|
+
Se não conseguir emitir checkpoint por MCP, retorne `blocked`: liveness não é comprovável. Sem `state_path_created` com o mesmo `state_path`, `atlas_lock_validator(start)` bloqueia em G12 e o orquestrador não pode despachar o validador frio.
|
|
36
|
+
|
|
14
37
|
## Use Criteria
|
|
15
38
|
|
|
16
39
|
Use when all are true:
|
|
@@ -40,6 +63,8 @@ Ask at most 1-3 blocking questions only when a reasonable assumption could chang
|
|
|
40
63
|
|
|
41
64
|
### 1. Load inputs
|
|
42
65
|
|
|
66
|
+
First, emit `executor_started`, then `skill_loaded`, before doing any long scan.
|
|
67
|
+
|
|
43
68
|
Read the user-provided PRD/spec/task and any directly referenced files needed to resolve scope. If the input names repo artifacts, verify those artifacts exist before editing.
|
|
44
69
|
|
|
45
70
|
Extract only execution-relevant items:
|
|
@@ -57,6 +82,8 @@ Extract only execution-relevant items:
|
|
|
57
82
|
|
|
58
83
|
If the PRD references another PRD or code contract as dependency, inspect enough to confirm the dependency shape and required bridge. Do not satisfy a dependency by creating parallel synthetic contracts unless the PRD explicitly allows it.
|
|
59
84
|
|
|
85
|
+
After the input is loaded, emit `plan_loaded`. After validating the execution boundary, obligations, and `state_path` target, emit `handoff_accepted`.
|
|
86
|
+
|
|
60
87
|
### 2. Build Compact Execution Contract
|
|
61
88
|
|
|
62
89
|
Before editing, write a compact contract in the working response or internal task state. Size follows complexity: terse for simple tasks, denser only where needed to preserve scope, invariants, and validator quality.
|
|
@@ -122,6 +149,8 @@ For each task, keep a tiny task contract:
|
|
|
122
149
|
|
|
123
150
|
Do not widen scope for opportunistic cleanup.
|
|
124
151
|
|
|
152
|
+
Before the first concrete task, emit `task_started`. After the first workspace mutation, emit `first_write`.
|
|
153
|
+
|
|
125
154
|
### 4. Gate each task
|
|
126
155
|
|
|
127
156
|
Run focused checks appropriate to the diff:
|
|
@@ -148,6 +177,8 @@ For direct execution, the state file is still the only validator input. Use the
|
|
|
148
177
|
|
|
149
178
|
The state file is the only validator input. Validation is always **sibling**, on every host: this executor **never** dispatches `atlas-task-validator` itself and never validates its own work in the same context. After tasks and local gates pass and the state file is written, this executor **stops mutation** and returns `validator_handoff_required` with the `state_path`. The orchestrator then dispatches `atlas-task-validator` as the next isolated sibling phase, locks it via `atlas_lock_validator`, and — if the verdict is `fail` — dispatches `atlas-findings-repair` (not this executor) before the **2nd and last** validator.
|
|
150
179
|
|
|
180
|
+
After writing the state file and before returning, emit `state_path_created` with the same `state_path`.
|
|
181
|
+
|
|
151
182
|
Do not paste the compact contract, diff, obligation ledger, local checks, or closure analysis packet into the state file's handoff. Those belong in the state file and referenced artifacts.
|
|
152
183
|
|
|
153
184
|
**Finish all local work before the handoff — then stop idle.** Finish every local gate (lint, analyze, tests, `git diff --check`, diff-stat) and write the state file **before** returning the handoff. After returning `validator_handoff_required`, do nothing: no diff hygiene checks, no extra reads, no opportunistic edits, no parallel work. The orchestrator now owns the slice; any mutation here would change what the sibling validator reads and breaks determinism (same failure class as the orchestrator's G9).
|
|
@@ -22,6 +22,29 @@ Operate as a bounded state machine:
|
|
|
22
22
|
|
|
23
23
|
Use `atlas_run_state` as the primary source of run state. Do not read or write run ledger files directly. If the MCP is unavailable, report the gate as unprovable and abort instead of continuing with a silent file fallback.
|
|
24
24
|
|
|
25
|
+
## Executor liveness checkpoints
|
|
26
|
+
|
|
27
|
+
Depois de carregar esta skill e antes de qualquer discovery longo, emita um checkpoint MCP:
|
|
28
|
+
|
|
29
|
+
```json
|
|
30
|
+
atlas_lock_dispatch({
|
|
31
|
+
"action": "checkpoint",
|
|
32
|
+
"phase": "plan_execute",
|
|
33
|
+
"event": "executor_started"
|
|
34
|
+
})
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
Em seguida, emita checkpoints materiais conforme avança:
|
|
38
|
+
|
|
39
|
+
- `skill_loaded` — skill carregada e contrato reconhecido.
|
|
40
|
+
- `plan_loaded` — plano/PRD de entrada lido.
|
|
41
|
+
- `handoff_accepted` — `plan_path`, `state_path` alvo, boundary e tasks aceitos.
|
|
42
|
+
- `task_started` — primeira task começou.
|
|
43
|
+
- `first_write` — primeira mutação de workspace feita.
|
|
44
|
+
- `state_path_created` — state file escrito antes de devolver `validator_handoff_required`.
|
|
45
|
+
|
|
46
|
+
Se não conseguir emitir checkpoint por MCP, retorne `blocked`: liveness não é comprovável. Não fique em discovery/preflight interno sem checkpoint. O orquestrador trata ausência de checkpoint como `stalled` via Gate G12.
|
|
47
|
+
|
|
25
48
|
## Plan path resolution
|
|
26
49
|
|
|
27
50
|
Resolve plan paths in this order:
|
|
@@ -55,6 +78,8 @@ Esta skill aceita entrada pelo modo `execute` do orquestrador: um `PLAN_*.md` pr
|
|
|
55
78
|
## Required Workflow
|
|
56
79
|
|
|
57
80
|
### 1. Load the plan as an execution contract
|
|
81
|
+
First, emit `executor_started`, then `skill_loaded`, before doing any long scan.
|
|
82
|
+
|
|
58
83
|
Read the `atlas-plan-handoff` artifact. Extract at minimum:
|
|
59
84
|
* **Execution metadata**: Prefix, mode, and validator options.
|
|
60
85
|
* **Executive translation and PRD links** (from Section 1 — include path to PRD; cite `PRD §3` D* IDs, do not paste the full D* table).
|
|
@@ -72,12 +97,16 @@ If optional Section 9 (open questions / real blockers — **not** PRD §7 Apênd
|
|
|
72
97
|
|
|
73
98
|
When Section 8 checklist is thin, read **PRD §4–6** from the PRD path in the plan header for business acceptance.
|
|
74
99
|
|
|
100
|
+
After the plan is loaded, emit `plan_loaded`. After validating the execution boundary and `state_path` target, emit `handoff_accepted`.
|
|
101
|
+
|
|
75
102
|
### 2. Create a task-scoped execution contract
|
|
76
103
|
Before editing code, write a short task contract for the current task only (objective, files, invariants, local checks, and repair budget).
|
|
77
104
|
|
|
78
105
|
### 3. Implement in the smallest coherent slice
|
|
79
106
|
Do not implement the entire feature before validating anything. Prefer one task at a time. Follow closed decisions from the plan.
|
|
80
107
|
|
|
108
|
+
Before the first concrete task, emit `task_started`. After the first workspace mutation, emit `first_write`.
|
|
109
|
+
|
|
81
110
|
### 4. Run a focused quality gate after each task slice
|
|
82
111
|
Run only the checks that are relevant to the current diff and task risks (linter, analyze of the affected package, or tests).
|
|
83
112
|
|
|
@@ -113,6 +142,9 @@ Create `.atlas/state/<run_id>/<slice>.json` following `packages/templates/STATE_
|
|
|
113
142
|
|
|
114
143
|
Validation is always **sibling**, on every host. The validator is registered as a real subagent on every host, but this executor **never** dispatches it and never validates its own work. After tasks and local gates pass and the state file is written, this executor **stops mutation** and returns `validator_handoff_required` with the `state_path`. The orchestrator dispatches `atlas-task-validator` as the next isolated sibling phase, locks it via `atlas_lock_validator`, and — if the verdict is `fail` — dispatches `atlas-findings-repair` (not this executor) before the **2nd and last** validator.
|
|
115
144
|
|
|
145
|
+
After writing the state file and before returning, emit `state_path_created` with the same `state_path`.
|
|
146
|
+
Without this exact checkpoint, `atlas_lock_validator(start)` blocks in G12 and the orchestrator cannot dispatch the cold validator.
|
|
147
|
+
|
|
116
148
|
The only handoff input is `state_path`. Do not paste the contract, diff, or task list inline. The validator reads everything it needs from the state file and the plan it points to. (`atlas_capabilities` is the runtime source of truth for the dispatch mechanism the orchestrator uses — see `references/host-adapters.md`.)
|
|
117
149
|
|
|
118
150
|
**Finish all local work before the handoff — then stop idle.** Finish every local gate (lint, analyze, tests, `git diff --check`, diff-stat) and write the state file **before** returning the handoff. After returning `validator_handoff_required`, the executor must not mutate anything: the orchestrator now owns the slice, and any mutation here would change what the sibling validator reads and breaks determinism (same failure class as the orchestrator's G9).
|
|
@@ -11,6 +11,29 @@ Execute directly from a PRD/spec/task while preserving execution quality: explic
|
|
|
11
11
|
|
|
12
12
|
This is not planless execution. Replace the visible markdown plan with a compact operational contract held in the current turn and passed to validation.
|
|
13
13
|
|
|
14
|
+
## Executor liveness checkpoints
|
|
15
|
+
|
|
16
|
+
Depois de carregar esta skill e antes de qualquer discovery longo, emita um checkpoint MCP:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
atlas_lock_dispatch({
|
|
20
|
+
"action": "checkpoint",
|
|
21
|
+
"phase": "plan_execute",
|
|
22
|
+
"event": "executor_started"
|
|
23
|
+
})
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Em seguida, emita checkpoints materiais conforme avança:
|
|
27
|
+
|
|
28
|
+
- `skill_loaded` — skill carregada e contrato reconhecido.
|
|
29
|
+
- `plan_loaded` — PRD/spec/task de entrada lido.
|
|
30
|
+
- `handoff_accepted` — boundary, obligations, `state_path` alvo e contrato direto aceitos.
|
|
31
|
+
- `task_started` — primeira task começou.
|
|
32
|
+
- `first_write` — primeira mutação de workspace feita.
|
|
33
|
+
- `state_path_created` — state file escrito antes de devolver `validator_handoff_required`.
|
|
34
|
+
|
|
35
|
+
Se não conseguir emitir checkpoint por MCP, retorne `blocked`: liveness não é comprovável. Sem `state_path_created` com o mesmo `state_path`, `atlas_lock_validator(start)` bloqueia em G12 e o orquestrador não pode despachar o validador frio.
|
|
36
|
+
|
|
14
37
|
## Use Criteria
|
|
15
38
|
|
|
16
39
|
Use when all are true:
|
|
@@ -40,6 +63,8 @@ Ask at most 1-3 blocking questions only when a reasonable assumption could chang
|
|
|
40
63
|
|
|
41
64
|
### 1. Load inputs
|
|
42
65
|
|
|
66
|
+
First, emit `executor_started`, then `skill_loaded`, before doing any long scan.
|
|
67
|
+
|
|
43
68
|
Read the user-provided PRD/spec/task and any directly referenced files needed to resolve scope. If the input names repo artifacts, verify those artifacts exist before editing.
|
|
44
69
|
|
|
45
70
|
Extract only execution-relevant items:
|
|
@@ -57,6 +82,8 @@ Extract only execution-relevant items:
|
|
|
57
82
|
|
|
58
83
|
If the PRD references another PRD or code contract as dependency, inspect enough to confirm the dependency shape and required bridge. Do not satisfy a dependency by creating parallel synthetic contracts unless the PRD explicitly allows it.
|
|
59
84
|
|
|
85
|
+
After the input is loaded, emit `plan_loaded`. After validating the execution boundary, obligations, and `state_path` target, emit `handoff_accepted`.
|
|
86
|
+
|
|
60
87
|
### 2. Build Compact Execution Contract
|
|
61
88
|
|
|
62
89
|
Before editing, write a compact contract in the working response or internal task state. Size follows complexity: terse for simple tasks, denser only where needed to preserve scope, invariants, and validator quality.
|
|
@@ -122,6 +149,8 @@ For each task, keep a tiny task contract:
|
|
|
122
149
|
|
|
123
150
|
Do not widen scope for opportunistic cleanup.
|
|
124
151
|
|
|
152
|
+
Before the first concrete task, emit `task_started`. After the first workspace mutation, emit `first_write`.
|
|
153
|
+
|
|
125
154
|
### 4. Gate each task
|
|
126
155
|
|
|
127
156
|
Run focused checks appropriate to the diff:
|
|
@@ -148,6 +177,8 @@ For direct execution, the state file is still the only validator input. Use the
|
|
|
148
177
|
|
|
149
178
|
The state file is the only validator input. Validation is always **sibling**, on every host: this executor **never** dispatches `atlas-task-validator` itself and never validates its own work in the same context. After tasks and local gates pass and the state file is written, this executor **stops mutation** and returns `validator_handoff_required` with the `state_path`. The orchestrator then dispatches `atlas-task-validator` as the next isolated sibling phase, locks it via `atlas_lock_validator`, and — if the verdict is `fail` — dispatches `atlas-findings-repair` (not this executor) before the **2nd and last** validator.
|
|
150
179
|
|
|
180
|
+
After writing the state file and before returning, emit `state_path_created` with the same `state_path`.
|
|
181
|
+
|
|
151
182
|
Do not paste the compact contract, diff, obligation ledger, local checks, or closure analysis packet into the state file's handoff. Those belong in the state file and referenced artifacts.
|
|
152
183
|
|
|
153
184
|
**Finish all local work before the handoff — then stop idle.** Finish every local gate (lint, analyze, tests, `git diff --check`, diff-stat) and write the state file **before** returning the handoff. After returning `validator_handoff_required`, do nothing: no diff hygiene checks, no extra reads, no opportunistic edits, no parallel work. The orchestrator now owns the slice; any mutation here would change what the sibling validator reads and breaks determinism (same failure class as the orchestrator's G9).
|