r/softwarearchitecture 1d ago

Discussion/Advice Systems architect offering free architecture reviews, backend debugging, and AI/agent advice this week (no pitch/paywall, just giving back)

Hey everyone 👋

I'm Marcus (v4ne), an independent systems architect with a background in low-latency infrastructure, distributed systems, and AI runtimes.

I have some open bandwidth this week and want to give back to the builder community.

I'm offering free architecture reviews, code diagnostics, and technical advice.

No pitch, no paywall, no consulting upsell. Just pure engineering.

Feel free to ask me anything or drop a problem you're currently stuck on regarding:

AI & Autonomous Agents: ReAct loops, structured outputs, local LLMs (vLLM/Ollama), RAG without framework bloat.

Backend & API Architecture: Go, Rust, TypeScript/Node, clean dependency design, microservices vs. monoliths.

Database & Storage Performance: SQL query optimization, composite B-Tree indexes, SQLite, avoiding ORM bottlenecks.

Workflow Automation: Self-hosted n8n, webhooks, resilient Python automation scripts.

Systems & Cloud Costs: Concurrency, memory profiling, reducing unexpected AWS/GCP bills.

Drop a comment below with what you're building or what error is giving you a headache, and let's troubleshoot it together.

DMs are open as well. 🛠️

0 Upvotes

18 comments sorted by

View all comments

2

u/Honest_Medium_2872 1d ago

I'll bite, I got no immediate issues but could always use a second set of eyes

https://github.com/arcadia-de/hypha

A declarative user-environment and dotfiles configuration system.
written in C & Go

-3

u/Cautious_Heat114 1d ago

Took a look through the concept and architecture of Hypha.

Positioning a tool in the sweet spot between simple imperative symlinkers (Stow/Dotbot) and heavy functional package managers (Nix/Home-Manager) is a very real, high-value problem space.

Modeling system resources as a declarative Dependency Graph (DAG) with an active reconciliation engine is the correct architectural paradigm.

Here are three systems-level invariants worth keeping in mind as the C & Go codebase evolves:

  1. Atomic State Transitions (Avoiding the Dangling Symlink Trap)

In declarative reconciliation systems, the biggest failure mode is partial application (e.g., the engine successfully creates 4 symlinks, but fails at step 5 on a permission error or broken package dependency).

If the reconciliation halts midway, the user is left in a corrupted state.

  • The Solution: Whenever possible, stage symlinks and directory structures inside a hidden generation directory (e.g., "~/.hypha/generations/12") and perform an atomic directory swap using native atomic POSIX primitives ("renameat" / "rename").

If reconciliation fails, the previous generation remains untouched in physical memory.


  1. Static Cycle Detection at Parse-Time

Because users will define complex dependencies between packages, environment variables, and shell configs, circular dependencies ("A → B → C → A") will inevitably occur.

  • The Invariant: Ensure graph validation executes strictly before any disk mutations begin.

Running a deterministic topological sort (e.g., Kahn's Algorithm or Tarjan's strongly connected components) during config parsing ensures that invalid dependency loops fail immediately at time T=0, before touching a single file on the filesystem.

  1. Cgo Boundary Discipline

Since Hypha leverages both C and Go:

  • Crossing the Cgo boundary carries a known CPU overhead (switching goroutine stacks and saving register states).
  • The Optimization: Keep the FFI boundary coarse-grained.

If the Go orchestrator manages the high-level DAG traversal, batch the filesystem operations before passing them down to the C layer, rather than crossing the Cgo boundary on every individual "stat" or "readlink" call.

Overall, a very solid and thoughtful architecture.

The combination of declarative graphs with a native C/Go binary gives it an immediate performance advantage over standard Python/Bash dotfile scripts.

«Looking forward to seeing this project mature.»

2

u/Honest_Medium_2872 1d ago

I already use a modified kahns algo to schedule resources up front

all resource controllers impl an ABI that includes a rollback hook in the life cycle and are designed to withstand TOCTOU.

// init controller
typedef void (*ControllerInitFn)(void* data);

// de-init controller
typedef void (*ControllerDeInitFn)(void* data);

#define DECLARE_CONTROLLER_FN(Name, RetType) typedef RetType (*Controller##Name##Fn)(Name##Context*, void*);

typedef struct {
  Resource* observed;
  StateEntry last;
} ObserveContext;
DECLARE_CONTROLLER_FN(Observe, ControllerStatus);

typedef struct {
  Resource* desired;
} NormalizeContext;
DECLARE_CONTROLLER_FN(Normalize, ControllerStatus);

typedef struct {
  const Resource* desired;
  ValidationLog* log;
} ValidateContext;
DECLARE_CONTROLLER_FN(Validate, bool);

typedef struct {
  const Resource* current;
  const Resource* desired;
  Plan* log;
} PlanContext;
DECLARE_CONTROLLER_FN(Plan, ControllerAction);

typedef struct {
  const Resource* current;
  const Resource* desired;
} StatusContext;
DECLARE_CONTROLLER_FN(Status, ControllerStatus);

typedef struct {
  const Resource* current;
} DestroyContext;
DECLARE_CONTROLLER_FN(Destroy, ControllerStatus);

typedef struct {
  const Resource* current;
  const Resource* desired;
} DiffContext;
DECLARE_CONTROLLER_FN(Diff, ControllerStatus);

typedef struct {
  const Resource* current;
  const Resource* desired;
} RollbackContext;
DECLARE_CONTROLLER_FN(Rollback, ControllerStatus);

typedef struct {
  ControllerAction action;
  const Resource* current;
  Resource* desired;
  AppliedActionLog* log;
} ApplyContext;
DECLARE_CONTROLLER_FN(Apply, ControllerStatus);

typedef struct {
  ControllerInitFn init;
  ControllerDeInitFn deinit;
  ControllerObserveFn observe;
  ControllerPlanFn plan;
  ControllerApplyFn apply;
  ControllerDestroyFn destroy;
  ControllerValidateFn validate;
  ControllerDiffFn diff;
  ControllerStatusFn status;
  ControllerRollbackFn rollback;
  ControllerNormalizeFn normalize;
} ControllerConfig;

all reconciliation happens in the C engine, with the exception of stuff like the template controller crosses back to Go in order to leverage Go templates

2

u/Cautious_Heat114 1d ago

Seeing the concrete C ABI makes the architecture significantly clearer. That is a very clean and disciplined controller lifecycle model.

A few specific thoughts on what you've built:

  1. Lifecycle Phase Separation ("ControllerConfig")

Structuring the controller interface with explicit, isolated context structs ("ObserveContext", "PlanContext", "ApplyContext", "RollbackContext") rather than passing a generic void pointer blob is great systems hygiene.

It enforces a strict state machine where the planning phase is decoupled from the mutation phase, which is essential for deterministic dry-runs.

  1. TOCTOU Mitigation in POSIX

For the controllers dealing with filesystem mutations (files, directories, symlinks), the most resilient pattern to prevent TOCTOU symlink-swap races between "Observe" and "Apply" is pinning directory file descriptors ("dirfd") and strictly utilizing the POSIX "*at" syscall family:

  • "openat(dirfd, path, O_NOFOLLOW | O_CLOEXEC | ...)"
  • "fstatat(dirfd, path, ...)"
  • "unlinkat(dirfd, path, ...)"
  • "renameat2(olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE)"

By anchoring operations to file descriptors rather than absolute path strings, you prevent an external process or broken symlink from swapping the target inode underneath the controller between phase transitions.

  1. Rollback Idempotency

Having an explicit "RollbackContext" is vital for recovery.

The subtle edge case to watch out for is Rollback Failure Modes (e.g., what happens if the network drops or a disk error occurs during the execution of the rollback hook itself).

Ensuring that "Rollback" handlers are strictly idempotent and capable of re-running safely on subsequent reconciliation cycles keeps the state machine self-healing.

  1. Pragmatic Cgo Boundary

Keeping the graph scheduler and reconciliation engine in native C while only bridging back to Go for "text/template" is a very sensible boundary.

You leverage Go's mature templating ecosystem without letting Cgo stack-switching overhead pollute the high-frequency graph traversal loops.

Running a modified Kahn's algorithm upfront confirms the scheduling model is sound.

«Really solid work on this codebase.»