genesis-development

SkillFiles & storage

This skill should be used when developing, debugging, refactoring, or building Genesis itself — tasks like "fix this in Genesis", "add a new MCP tool", "wire up the runtime", "Genesis won't start", "create a worktree", "debug the bridge", or "add a capability". Applies to any task modifying files under src/, .claude/, or tests/. Do NOT load for Genesis-as-tool work ("summarize this", "write a LinkedIn post", "research X") or general questions unrelated to Genesis internals.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the genesis-development skill

What this skill tells your AI

The instructions your AI receives, as published by wingedguardian/genesis-agi in .claude/skills/genesis-development/SKILL.md and read by ahel’s review.

Load Gate

Before reading any reference, confirm the task is Genesis-development, not Genesis-as-tool. If uncertain, ask the user: "Are we modifying Genesis itself, or using Genesis for something else?"

On-Load Mindset

Internalize these immediately when this skill fires — they shape how to work from the start, not just what to check before commit.

Wiring Discipline

Every new component needs at least one call site in the actual runtime path. Apply this 4-level verification taxonomy:

  1. Exists — file/function present. Proves nothing.
  2. Substantive — tests pass, handles happy + error. No runtime proof.
  3. Wired — live call site, import chain unbroken. Minimum for "done."
  4. Data-Flow Verified — real data flows end-to-end. Required for critical paths.

Mark nothing "done" below Level 3.

GROUNDWORK Code Is NOT Dead Code

Code tagged # GROUNDWORK(feature-id): why is intentional future investment. Never delete or refactor it as dead code. Only remove when the feature is fully active or the user explicitly cancels it.

Architecture Review

For medium-to-large Genesis work (3+ files, new components, wiring changes), dispatch a genesis-architect subagent before implementation to check dependencies, edge cases, and DRY violations. Small targeted changes skip this.

Timeout Policy

The burden of proof is on you to justify why a timeout should exist. Do not default to "add a timeout for safety." Instead:

  1. Identify the specific failure mode. What hangs? Why? Is there evidence this actually happens, or is it speculative?
  2. Justify the specific value. Why this number and not another? What legitimate work would be killed at a lower value?
  3. If you have no strong justification for a specific value, default to 2 hours (7200s). This is the project floor — generous enough to never interfere with legitimate work while preventing permanent resource lockout from truly hung processes.
  4. Surface the request to the user with the value, the failure mode, and the evidence. Never add a timeout as a "small improvement" or "defense in depth."

Timeouts on reflections, CC calls, cognitive paths, and long-thinking work fight Genesis instead of helping it — they cap legitimate long thinking and add speculative defense against rare hangs. The exception is raw subprocess calls with no external watchdog (e.g., deterministic executor steps), where a hung process blocks shared resources (executor semaphore) with no other recovery mechanism.

The Bash TOOL's own timeout is separate — default 120000ms, HARD CEILING 600000ms (10 min). An inner timeout N … INSIDE the command does NOT extend it: the tool wrapper SIGTERMs the whole call at its own timeout param (default 120000ms → exit 143). To allow longer, set that timeout PARAMETER explicitly — but you cannot exceed 600000ms. A larger value does not buy more time; the call still dies at 10 minutes (MEASURED 2026-08-27: timeout: 1600000 was killed at exactly 10m 0s). Anything that might run past ten minutes therefore has only ONE correct form — run_in_background: true. Treat "raise the timeout" as a fix that tops out, not one that scales.

For long or unbounded work — above all deploys (scripts/update.sh, bootstrap.sh, host-setup.sh: container align + guardian redeploy + host update-node/update-cc, run sequentially — routinely exceed 600s and so CANNOT be done in the foreground at any timeout value) — run via run_in_background: true (harness-tracked, notifies on completion, no timeout ceiling), NEVER a foreground timeout or nohup … & (detached but untracked → no completion signal, so you end up hand-polling anyway). update.sh has SIGTERM/INT rollback traps, so a mid-run kill is not a no-op — verify state (server active, no mid-rebase, pin, both CC versions) before re-running; it is idempotent. (A non-blocking PreToolUse advisory hook, .claude/hooks/cc-deploy-timeout-guard, nudges toward this when a deploy is run in the foreground.)

Verify Outcomes, Not Just Tests

ruff check . && pytest -v is the minimum bar, not the finish line. After tests pass, verify the actual end-to-end outcome the change delivers. Diff behavior between main and your changes when relevant. For wiring changes: verify the init/bootstrap order passes the right values at runtime, not just that parameters exist. For notification changes: verify the notification actually arrives. Ask: "If the system restarts right now, will this actually work?" If you can't answer yes with evidence, you're not done.

A check keyed on EXISTENCE can only confirm — it can never disconfirm. Before trusting a verification, ask what it would do if the thing were absent, stale, or the wrong object. If the answer is "pass", it proved nothing. Three shapes of this, all measured in one session 2026-09-08:

  • Waiting for a NEW artifact by testing that one exists. A poll for "a scheduled-review marker is present" passed instantly against a marker from six days earlier. Key the wait on the artifact's IDENTITY — the head SHA it names — not on its presence.
  • Matching the first row instead of the right one. "Is main green?" matched the first push run for that SHA, which was the CodeQL workflow (success), while the CI workflow for the same SHA had failed. Filter by the thing you actually mean (--workflow ci.yml), and prefer reading the JOB you care about over an aggregate conclusion.
  • Counting from a listing sorted by the wrong key. gh pr list --state merged --limit 40 sorts by CREATION, so PRs created earlier and merged today fall outside the window and vanish; the count read low and looked plausible. Use a query whose filter IS the property you are counting (gh search prs --merged-at), per CLAUDE.md's truncated-listing rule.

The failure is silent and self-confirming in all three: an under-read is indistinguishable from a clean result, so nothing prompts a second look. The tell is that the check passed FASTER or more easily than the work should have allowed — a poll that succeeds on the first attempt for something that takes 40 seconds to produce has not observed that thing.

Verify in the REAL runtime context, not a shell proxy. "Works when I run it" is not "works where it runs." Same code + same uid ≠ same context: a long-running systemd service (genesis-server, guardian) differs from your interactive shell in mount namespace, seccomp, NoNewPrivileges, dropped caps, and — the one that bites — ptrace//proc access. Anything that reads /proc/<other-pid>/{environ,mem,stat}, another process's env, sockets, or namespaced/hardened resources MUST be verified by hitting the live endpoint (curl the real server) or running inside the real service — not python -c in your shell. (Origin, 2026-08: the CC-slot stale-code badge read /proc/<pid>/environ, which succeeds in a shell but returns EACCES under the server's ProtectSystem=strict sandbox — so enumerate_cc_slots() returned 0 in-server and three features shipped green but inert since July; the module's docstring claim "same-uid reads succeed" was a shell-tested falsehood. The fix routed around the ptrace-gated read entirely.)

Acceptance Bar + Measured Rate — the primary methodology

Use this as often as it applies. It is the default way to build anything here, not a special-occasion technique. Unit tests prove the code does what you wrote. This proves the thing actually WORKS — with numbers and a denominator.

Two artifacts, both produced BEFORE shipping:

1. The acceptance bar — replay the real defect. Take the actual failure that motivated the work and run it through the new thing. If it does not catch/fix the case it exists for, it does not ship, however elegant it is and however green the suite is. Reconstruct the real case (from git history, from the transcript, from live data) rather than a stylised approximation — a synthetic case can pass while the real shape does not.

2. The measured rate — run it against real data and produce a number. A claim like "low false-positive rate" or "it should be fine in practice" is not evidence. Run the thing over a real corpus — recent commits, live rows, real traffic, historical transcripts — and report k/N (x.x%). A number without a denominator is not a measurement. Then look at the individual hits and say honestly which are real signal and which are noise; a "false-positive rate" that turns out to be mostly true positives is a fire rate, and saying so is part of the result.

The measurement is a GATE, not a footnote. Decide the acceptable threshold BEFORE measuring, and if the number misses it, tighten and re-measure rather than shipping with a caveat. When you tighten, re-run the acceptance bar in the same breath — a filter that improves the rate by breaking the thing you built it for has made it worse, and only running both together catches that.

Worked example (2026-08-27/28, an orphaned-literal detector). The tool itself was still on an unmerged branch when this was written, so treat the first two bullets as a method illustration; the figures in the paragraph after them are derived from this repository's own history and can be re-derived.

  • Acceptance: replayed a real review defect; the detector named the exact sibling file. PASS.
  • Measured, false-positive side: 1/151 real file-edits fired (0.7%). The one hit was an identifier rather than prose, so the filter was tightened to require interior whitespace, re-measured at 0/151 (0.0%), and the acceptance replay was re-run to confirm the tightening had not blinded it.

That example then became a lesson against itself, which is why it is kept. Everything above measures FALSE POSITIVES, and 0/151 reads as though the tightening were free. It was not. Measuring the other direction needs INDEPENDENT ground truth rather than the tool's own criterion — otherwise you grade the tool on its own definition of success. Here that meant mining history for a literal removed from one file and then removed AGAIN from a second file in a later commit: the repo itself recording that the first fix left a sibling. Over 1,584 commits that yields 95 verified cases (6.0% of commits) — and the interior-whitespace filter that scored so well on precision excludes 48 of those 95 (51%) by construction. The number that actually decided the design was recall against a budget cap: 28/47 in-scope cases caught (60%) with a cap of 6 literals per edit, versus 47/47 (100%) with the cap lifted — every one of the 19 misses was that single cap, and lifting it recovered all of them.

The rule that generalises: a rate measured on one side of a tradeoff is half a measurement. A precision number with no recall number cannot distinguish a good filter from a blind one, and the side you did not measure is the side that will be wrong. Decide which direction matters for the thing you are building, and measure that one first.

This also catches a specific self-deception. A first prototype of that same detector used a regex and reported "2 findings" while silently skipping the entire class it was built for (the pattern excluded backslashes; every prompt string ends in \n). The acceptance replay is what exposed it. A matcher that finds nothing is indistinguishable from a matcher that looks at nothing — only replaying a known-positive tells them apart.

A null result is a LEAD, never a clearance — and it travels with its blind spot attached or it does not travel. The paragraph above is about an instrument that looked at nothing. This is the harder case: the instrument worked, the reading was clean, and the reading was still wrong. Three null results from one session were wrong the same way, and every one was overturned by CONSTRUCTION rather than by more sampling:

  • "An expansion in verb position always emits ≥2 words, so it self-corrupts any command it appears in." Generalised from a handful of samples of ONE of the construct's spellings. Another spelling admits a degenerate case that emits exactly one word — so argv survives intact, and the parse resolves a verb bash never runs. Stated abstractly on purpose; see the note under the resource-defect bullet in Test-First Discipline.
  • "Zero over-block flips across 129,179 real commands." The regression was CONSTRUCTIBLE, and a later worker constructed it: a BLOCK → ALLOW flip on an rm -rf of the production database's parent directory.
  • "Zero regressions, verified three ways." All three ways were the same axis — see the resource-defect bullet under Test-First Discipline.

A corpus measures what has been TYPED. It says nothing about what is TYPEABLE. For a SAFETY property the question is therefore never "did I observe a failure" but "can one be CONSTRUCTED", and only the second question has an answer that clears anything. So when a null result is handed to anyone — a subagent, a peer session, a PR body, the user — state the DENOMINATOR and the METHOD'S BLIND SPOT in the same breath as the number, and say in words that it is not proof. Then name the recipient's job: the person best placed to construct the counterexample is whoever is about to rely on the null result, and they will not go looking unless you tell them the search is still open. A null passed on bare is read as a clearance by everyone downstream, which is how one unproven sentence becomes the premise of three later decisions.

Select, don't amputate — truncation is the absence of a decision

Scope first, because bounding is often correct. What makes something an amputation is LOSS — the value cut here was the only copy. Nothing else. A bounded PREVIEW of something stored intact elsewhere is a selection, and stays one even if its handle is useless. Bounding against a hard external budget is likewise correct: a hook's stdout cap, a context window, a column whose limit is actually ENFORCED. That last qualifier is load-bearing here — this repo's SQLite TEXT columns enforce no length at all, so "the database column" does not excuse a cut; a self-imposed storage assumption is a decision to justify, not a budget to obey. So is refusing an oversized value outright.

And a SAFETY cap may be lossy — that is the one place cutting the only copy is right. Streaming is a TRANSPORT property and only-copy is a DURABILITY one; check them separately, because a chunked read is often backed by a retained source you could go back to. The case that earns the lossy cap is the source that genuinely has no retained copy — a live subprocess pipe — where reading to the end to avoid "truncating" is how a runaway command exhausts memory; this repo bounds exactly that at a few MiB (autonomy/executor/deterministic.py _read_limited, whose own comment names the yes-command threat; verified 2026-09-04). Losing the tail of a log beats losing the process. The obligation there is not to keep the bytes, it is to be LOUD about the cut — say the output was bounded and roughly by how much, so nobody reads a clipped log as a complete one. That cited cap has since been fixed AT THE READ and is no longer the silent-cut example it was: _read_limited drains the whole stream while retaining only limit bytes and returns (retained, total_size), so the caller appends ... (truncated, N bytes total) with N the TRUE drained total (PR #1796; verified in autonomy/executor/deterministic.py 2026-09-08). It is still not a worked example of a loud cut, and the reason generalises: a declaration has to survive the CONSUMERS, not just be emitted. That marker is appended at character 50,000 of the result field, and all six onward paths head-slice it at 200-2000 characters — including the one that feeds the next step's prompt — so the declaration is unreachable in every direction it travels, and the slice that removes it declares nothing itself (MEASURED 2026-09-08). When you fix a silent cut, check the READERS of the field you just made honest; otherwise you have moved the silence one layer out. A silent lossy cap is still the defect; a declared one is a resource guard doing its job. This section is about the remaining case.

A handle that does not resolve is a separate defect, and do not conflate the two — that conflation is the mistake this section made about itself, twice. Check the pointer, because a preview advertising a retrieval path that does not exist teaches a lie; but when the full value survives somewhere, the fix is to mend or drop the handle — removing the cap TO PREVENT DATA LOSS is fixing a loss that never happened, and that misdiagnosis is how this rule causes the damage it exists to stop. Whether the cap should exist at all is the separate question the "what breaks if it is unbounded" test answers: a cap with no external, safety, or measured compatibility justification may be removed once that is established — for being unjustified, never for being an amputation.

There, do not truncate. Not strings, not lists, not context, not output. Reaching for a character cap is a signal that a question was skipped, not answered. Omitting is legitimate — it is a judgement about relevance. Truncating is not: it is what happens when that judgement was never made, so the value gets cut at a point that has nothing to do with meaning. A truncated value is frequently worse than either alternative, because it still LOOKS complete, so nobody checks it — at which point you may as well not have passed it at all.

Before bounding anything, answer: what is this value FOR, who reads it, why does it need budgeting at all, and what actually breaks if it is unbounded? Solve THAT. Usually the answer is "select less, whole" rather than "cut", and often the bound turns out not to be load-bearing.

Three rules when a bound really is needed:

  • Bound by MEANING, not by one blanket number. A closed set is validated against that set — a value outside it is INVALID, not "too long". A timestamp is a shape; half a timestamp is not a shorter timestamp. A blanket cap turns 100,000 characters of foreign data into 300 characters of foreign data and calls it bounded. This governs HOW you bound, never WHETHER you may. Rejecting a structured value or a collection by size is correct and stays correct: an over-long id, an over-large batch or an implausibly large file is simply not a value we accept, and refusing it is a resource guard, not an amputation. What is forbidden is silently CUTTING them to fit. Only free text gets a bound it is expected to sit under; every other INBOUND value gets one it must not cross. Scope that to inbound on purpose: a READ that pages a large collection — the first n WHOLE elements plus a total and a truncation flag — is a selection with a denominator, this section's own preferred shape, and the source collection is valid precisely because it exceeds the page. Reject the oversized value you are asked to ACCEPT; paginate the oversized collection you are asked to LIST.
  • Derive the number from the right thing, and record how. A SAFETY bound — memory exhaustion, an abuse ceiling, untrusted input — does not come from the corpus at all: historical traffic says nothing about adversarial input, concurrency, or the memory you actually have, and a cap chosen from observed values will be exactly the wrong size when it matters. Derive those from the protocol, the capacity and the threat model FIRST, then use k/N only to price what the bound rejects. It is the COMPATIBILITY bounds — how long is this field in practice, what does this cap cost real readers — that a corpus answers, and for those: measure the real population and report k/N per the Acceptance Bar, then name the corpus, the query and the date. Naming them is necessary and not sufficient: the query is only re-derivable if the rows are still there when the next reader runs it, so a table with a retention window yields an EPHEMERAL observation. Say which one you have. And what the bound COSTS is a SECOND claim needing its own denominator — "the cap discards the part worth keeping" is exactly the sentence that sounds measured because it followed a measurement.
  • Omit explicitly, with a constant-bounded marker (<omitted: 104,823 chars>) in preference to a mid-value cut. An honest gap beats a plausible-looking fragment. The bound-plus-loud-flag half of this is already the house pattern; the character-count marker is a proposal, so do not go looking for a precedent that is not there. Declaration is a requirement on top of the loss rules, never a substitute for them. Stating the rule as "never cut" overshoots: this repo cuts mid-value in several places on purpose and is right to — a resource guard on an unbounded stream, a preview rendered next to the full record, a display string trimmed before escaping so the cut cannot land mid-entity. Each of those is a cut the loss rules PERMIT (a safety cap, a pointer-backed selection, third-party display text) — which does not certify how each is reported today: the safety cap above still cuts silently, and PERMITTED is not DECLARED. The order matters: first the loss rules decide whether a cut may happen at all — announcing a sliced KEY does not un-merge the two identities it collapsed — and only then does declaration decide whether the permitted cut is honest. A silent permitted cut is still a defect; a loud forbidden cut is still forbidden. When the total is not already known, say that instead of computing it. Bounding a stream is the case — but first ask which KIND of bounded reader you have, because the bound is on RETENTION, not necessarily on reading. A drain-and-discard reader consumes to EOF anyway (a subprocess pipe must be drained or the child blocks) and can count what it discards in constant space, so it KNOWS the exact total and should say it. Only a stop-at-limit reader, which truly stops consuming, cannot know. There, state the quantity you KNOW — the cap — and mark the tail unknown: <kept first 40,000 chars; rest of stream omitted>. Do not write <omitted: ≥40,000 chars>: under this rule's own grammar that number describes the OMITTED content, and one character over the cap makes it a fabricated tail length — an invented number wearing the honest marker's clothes, which is the exact thing the marker exists to prevent.

One trap deserves naming, because it is what produced this rule: a cap can manufacture a correctness bug in the very data it was added to protect. Truncating an identifier used as a KEY merges two distinct identities into one, and downstream code then attributes one subject's state to another. That is not hypothetical — it shipped here. Two roster peers whose names shared a prefix collapsed onto a single key, so one peer's success cleared the other peer's recorded failure. A short DISPLAY handle is a different thing and is fine; the rule is about the stored key, not the rendered one.

A real need to truncate is a CONVERSATION to have, not a magic number to pick alone. If you catch yourself choosing 300 or 200 or 1000, stop and raise it.

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
96
Forks
23
Last commit
Sep 2026

ahel review

  • K1binfo
    installs-packages (in references/build-state.md)
  • K1binfo
    installs-packages (in references/worktrees.md)

Automated review, not a security audit. Ruleset v1+k2.

Advanced
Catalog kind
skill
Gateway key
genesis-development
Source
github.com/wingedguardian/genesis-agi