feat: SF stays standalone forever; strengthen Python/Rust detection
ADR-019 framing corrections: - SF is single-machine, single-user, single-repo by design — character, not limitation. Stays a standalone app permanently; does not get absorbed into ACE. - Phase 6 reframed: "pattern transfer" not "orchestration convergence." ACE ports patterns from SF, both apps remain independent. - Phase 2 reframed: SF stays local. Federation is an ACE concern; SF doesn't wire memory-store remote-mode against singularity-memory. Detection strengthened for Python (priority for ace-coder work): - Detect uv / poetry / pdm and prefix verification commands accordingly - Emit ruff check when configured (file or [tool.ruff] in pyproject.toml) - Emit mypy / pyright when configured — skip when no config to avoid false fails - pyprojectHasTool helper for [tool.<name>] section detection Detection strengthened for Rust: - cargo fmt --check (fastest, catches style first) - cargo check (type-only, faster than test) - cargo clippy -- -D warnings (warnings as errors) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
2280893464
commit
e7519e904d
2 changed files with 117 additions and 28 deletions
|
|
@ -18,12 +18,15 @@ Two autonomous agent systems are being developed in parallel:
|
|||
|
||||
- **SF** (`singularity-forge`) — TypeScript orchestrator. Works today. Dispatches
|
||||
Claude Code sessions as ephemeral units (milestone → slice → task). Isolation
|
||||
via git worktrees. Single-repo, single-user.
|
||||
via git worktrees. **Single-machine, single-user, single-repo by design.** That
|
||||
scope is its character, not a limitation. SF stays a standalone app permanently;
|
||||
it does not grow into a platform.
|
||||
|
||||
- **ACE** (`ace-coder`) — Python platform. Partially operational. HTDAG execution
|
||||
backbone, Project Manager ownership, 20 defined agent personas, LiteLLM
|
||||
multi-provider, RBAC, PGMQ task queue, tiered memory. Multi-tenant data model
|
||||
(`tenant_id`) exists; per-task execution isolation does not.
|
||||
(`tenant_id`) exists; per-task execution isolation does not. ACE is where
|
||||
multi-tenant, multi-repo, federated workloads live.
|
||||
|
||||
- **singularity-memory** — Separate Go service (migrating from Python per ADR-014).
|
||||
Postgres + vchord vector store. Federated knowledge layer.
|
||||
|
|
@ -35,15 +38,22 @@ Two autonomous agent systems are being developed in parallel:
|
|||
memory while they help build the system; it is not the production wire for
|
||||
internal services and is expected to shrink once the system is self-hosting.
|
||||
|
||||
Both systems share the same end destination but are approaching it from different
|
||||
directions. SF is production-reliable but architecturally constrained (single-repo,
|
||||
git-worktree isolation). ACE has the right orchestration primitives (HTDAG, PM,
|
||||
RBAC, tenant model) but lacks execution isolation and is not yet production-reliable.
|
||||
The two systems are **not converging into one app.** They occupy different niches:
|
||||
|
||||
The strategy is **incremental convergence**: SF continues to work and delivers value
|
||||
while autonomously helping build out ACE. As ACE becomes reliable, SF's dispatch
|
||||
model transitions to use ACE's execution substrate. They meet at the workspace VM
|
||||
boundary.
|
||||
- SF is the local single-user developer tool — fast, generic, runs on the developer's
|
||||
machine on whatever repo they're working on.
|
||||
- ACE is the multi-tenant platform — federated, multi-repo, scales beyond one user.
|
||||
|
||||
Convergence in this ADR refers to **shared substrate**, not application merging:
|
||||
shared wire schemas (singularity-grpc), shared execution isolation primitive
|
||||
(Firecracker workspaces) when SF chooses to dispatch into one. SF can live entirely
|
||||
on its own without ACE; ACE doesn't depend on SF.
|
||||
|
||||
The strategy is **incremental pattern transfer**: SF continues to work as a
|
||||
standalone single-user tool while autonomously helping build out ACE. ACE ports
|
||||
proven patterns from SF as it matures. SF gains an optional engine adapter for
|
||||
dispatching units into ACE workspaces when multi-tenant or multi-repo work is
|
||||
needed. Neither replaces the other.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -196,16 +206,20 @@ build system.
|
|||
- ACE develops its HTDAG, PM, and worker primitives independently.
|
||||
- Both systems mature on their own tracks.
|
||||
|
||||
### Phase 2 — Federated memory (near-term, ADR-012 Tier 1)
|
||||
- Wire `memory-store.ts` remote-mode → singularity-memory HTTP endpoint (typed
|
||||
TS client generated from the Go API — not MCP).
|
||||
- SF instances on different machines share learnings.
|
||||
- ACE connects to the same singularity-memory endpoint via a typed Python client
|
||||
(also generated, also not MCP). Internal services do not pay the MCP tax.
|
||||
### Phase 2 — Federated memory for ACE (near-term, ADR-012 Tier 1)
|
||||
- ACE connects to singularity-memory via a typed Python client (generated from
|
||||
the Go API — not MCP). Internal services do not pay the MCP tax.
|
||||
- **SF stays local.** SF is single-machine, single-user, local-first by design.
|
||||
`memory-store.ts` continues to work on `.sf/memory/`; no remote mode wired in
|
||||
SF core. When SF runs inside an ACE-managed workspace, the workspace surfaces
|
||||
federated context through the ACE engine adapter as additional KNOWLEDGE
|
||||
injection — SF doesn't know that's where it came from. Federation is an ACE
|
||||
concern, not a SF concern.
|
||||
- The MCP façade on singularity-memory is reserved for external coding tools
|
||||
(Claude Code, Cursor) that need to read/write memory while helping build the
|
||||
system. Temporary scaffold; not a production wire.
|
||||
- **Outcome:** shared knowledge layer operational before execution convergence.
|
||||
- **Outcome:** federated knowledge layer operational for ACE; SF unchanged and
|
||||
unaware of memory federation infrastructure.
|
||||
|
||||
### Phase 3 — Workspace VM opt-in for SF (medium-term)
|
||||
- Build `sf-workspace` shim: thin Rust binary that manages Firecracker VMs.
|
||||
|
|
@ -227,14 +241,24 @@ build system.
|
|||
- The `sf-workspace` shim and ACE's VM dispatch path are the same binary.
|
||||
- **Outcome:** two orchestrators, one execution substrate.
|
||||
|
||||
### Phase 6 — Orchestration convergence (long-term)
|
||||
- SF's state machine (milestone → slice → task) becomes an ACE workflow spec
|
||||
(compiled DAG via ACE's `graph_compiler`), not a hand-coded state machine.
|
||||
- ACE's HTDAG becomes the unified orchestration backbone.
|
||||
- SF's CLI and headless mode remain as user-facing entry points; they drive ACE
|
||||
via the existing JSON-RPC stdio contract (already in `packages/rpc-client/`),
|
||||
not via MCP. MCP at this layer would be redundant — both ends are first-party.
|
||||
- **Outcome:** one system with SF's reliability and ACE's generality.
|
||||
### Phase 6 — Pattern transfer (long-term)
|
||||
**SF remains a separate, standalone app — permanently.** It is not absorbed,
|
||||
re-platformed, or re-implemented inside ACE. The convergence is at the wire and
|
||||
execution-substrate layers (Phases 3–5), not at the application layer.
|
||||
|
||||
What Phase 6 actually means:
|
||||
- ACE ports proven patterns from SF — idempotency primitives, state-derivation
|
||||
discipline, the structured notification model, the watchdog pattern, project
|
||||
preferences as a config layer, scaffold-as-contract. These become ACE's own
|
||||
primitives, written in Python, owned by ACE.
|
||||
- SF stays single-machine, single-user, local-first — its character. SF gets
|
||||
*generally* better as a standalone tool: better project detection, cleaner
|
||||
engine adapter extension point, harder-tested crash recovery.
|
||||
- SF and ACE remain independent runtimes. SF can be dispatched into an ACE
|
||||
workspace (Phase 5) for multi-tenant or multi-repo work, but it is also fully
|
||||
usable on its own with no ACE present.
|
||||
- **Outcome:** two distinct apps that share wire schemas (singularity-grpc) and
|
||||
optionally an execution substrate (Firecracker). Neither replaces the other.
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -774,9 +774,13 @@ function detectVerificationCommands(
|
|||
}
|
||||
|
||||
if (detectedFiles.includes("Cargo.toml")) {
|
||||
// Format check first — fastest, catches style drift before anything else runs.
|
||||
commands.push("cargo fmt --check");
|
||||
// Type-check without running tests (faster than test, catches most regressions).
|
||||
commands.push("cargo check");
|
||||
// Limit test threads so Rust tests don't saturate all CPUs.
|
||||
commands.push("cargo test -- --test-threads=2");
|
||||
commands.push("cargo clippy");
|
||||
commands.push("cargo clippy -- -D warnings");
|
||||
}
|
||||
|
||||
if (detectedFiles.includes("go.mod")) {
|
||||
|
|
@ -812,8 +816,48 @@ function detectVerificationCommands(
|
|||
detectedFiles.includes("setup.py") ||
|
||||
detectedFiles.includes("requirements.txt")
|
||||
) {
|
||||
// Single-process pytest by default; -x stops on first failure (fast feedback).
|
||||
commands.push("pytest -x");
|
||||
// Detect Python package manager. uv > poetry > pdm > raw.
|
||||
// The runner prefix changes which python gets invoked, so it matters that
|
||||
// commands match the project's actual env.
|
||||
const hasUvLock = existsSync(join(basePath, "uv.lock"));
|
||||
const hasPoetryLock = existsSync(join(basePath, "poetry.lock"));
|
||||
const hasPdmLock = existsSync(join(basePath, "pdm.lock"));
|
||||
const pyRunner = hasUvLock
|
||||
? "uv run"
|
||||
: hasPoetryLock
|
||||
? "poetry run"
|
||||
: hasPdmLock
|
||||
? "pdm run"
|
||||
: "";
|
||||
const prefix = pyRunner ? `${pyRunner} ` : "";
|
||||
|
||||
// Lint first — ruff is fast and catches drift before slower checks run.
|
||||
const hasRuff =
|
||||
existsSync(join(basePath, "ruff.toml")) ||
|
||||
existsSync(join(basePath, ".ruff.toml")) ||
|
||||
pyprojectHasTool(basePath, "ruff");
|
||||
if (hasRuff) {
|
||||
commands.push(`${prefix}ruff check`);
|
||||
}
|
||||
|
||||
// Type check — only emit if config exists (mypy or pyright).
|
||||
// Without config these tools error confusingly on first run; better to
|
||||
// skip than to emit a command that always fails.
|
||||
const hasMypy =
|
||||
existsSync(join(basePath, "mypy.ini")) ||
|
||||
existsSync(join(basePath, ".mypy.ini")) ||
|
||||
pyprojectHasTool(basePath, "mypy");
|
||||
const hasPyright =
|
||||
existsSync(join(basePath, "pyrightconfig.json")) ||
|
||||
pyprojectHasTool(basePath, "pyright");
|
||||
if (hasMypy) {
|
||||
commands.push(`${prefix}mypy .`);
|
||||
} else if (hasPyright) {
|
||||
commands.push(`${prefix}pyright`);
|
||||
}
|
||||
|
||||
// Tests — single-process pytest by default; -x stops on first failure.
|
||||
commands.push(`${prefix}pytest -x`);
|
||||
}
|
||||
|
||||
if (detectedFiles.includes("Gemfile")) {
|
||||
|
|
@ -913,6 +957,27 @@ function readMakefileTargets(basePath: string): string[] {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether a Python tool is configured under [tool.<name>] in pyproject.toml.
|
||||
* Used by Python verification command detection so we only emit `mypy` / `pyright` /
|
||||
* `ruff` invocations for projects that actually configure those tools.
|
||||
*
|
||||
* Naive substring scan — avoids pulling in a TOML parser for a check this simple.
|
||||
* Matches the standard `[tool.<name>]` section header at the start of a line.
|
||||
*/
|
||||
function pyprojectHasTool(basePath: string, toolName: string): boolean {
|
||||
try {
|
||||
const raw = readFileSync(join(basePath, "pyproject.toml"), "utf-8");
|
||||
const header = `[tool.${toolName}]`;
|
||||
for (const line of raw.split("\n")) {
|
||||
if (line.trim().startsWith(header)) return true;
|
||||
}
|
||||
return false;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function pushUnique(arr: string[], value: string): void {
|
||||
if (!arr.includes(value)) arr.push(value);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue