Skip to content

Repository files navigation

Genesis

An autonomous agent that has to earn to sustain itself.

It ticks once a minute: reads its state, proposes one action, has that action checked by deterministic policy, executes it, and writes everything to an append-only audit trail. It writes its own tools, drives a browser, records real income and expense in a ledger, and rewrites its own standing instructions based on which of them actually worked.

The interesting part is not that a model is in the loop. It is where the model is not: it proposes, and code decides.

tick → read state → CEO proposes → policy validates → Operator executes
     → audit + memory + ledger → reflection → sleep

Run it

uv sync --extra browser
python -m playwright install chromium          # optional, for browsing

python scripts/serve.py                        # chat UI, prints an access token
python scripts/run.py                          # the loop

Open http://127.0.0.1:8000 and paste the token. python scripts/run.py --once runs a single tick and exits.

See it earn in 60 seconds

python scripts/demo.py

Stands up a payment provider on localhost that answers exactly like Stripe does, points a fresh agent at it, and runs real ticks. Nothing inside the agent is stubbed — real HTTP, the real tool registry, the real policy engine, the real ledger. Abridged output:

1. A new agent, with nothing
   earned $0.00 | spent $0.00 | net $0.00
   can it spend $10?  exceeds available capital after emergency reserve

3. The agent checks for payments
   new_income_usd: 199.0     earned_usd: 199.0

4. The same question, now that it has earned
   spend $ 10.00 -> APPROVED  within spend policy
   spend $ 25.00 -> DENIED    exceeds monthly experiment cap (spent 0.00 of 19.90)

5. Polling again must not invent money
   earned before $199.00 -> after $199.00   UNCHANGED, correct

Point GENESIS_PAYMENTS_URL at your own provider and the same code banks real money.


What it can do

Capability is declared per tool and enforced by policy. An action with no declaration is denied — the registry is an allowlist, so adding a tool cannot silently widen what the agent may do.

Tool Capability Gate
read_state READ_ONLY none
browse_page REVERSIBLE blocked by dry_run
check_payments REVERSIBLE blocked by dry_run
propose_tool REVERSIBLE blocked by dry_run
record_expense IRREVERSIBLE human approval
operate_account IRREVERSIBLE human approval
self-authored as declared and verified per the above

dry_run (default true) is enforced at the execution boundary, not inside each tool, so a newly added tool inherits it without doing anything.


The money is real

Until the ledger existed, earned_usd was zero because nothing could record income, and can_spend consulted a source that reported no capital — so the 10% monthly cap was arithmetic over zeroes that had never once been tested against a real balance.

  • Money is events, not a mutable balance. Income and expense append to the audit trail; the balance is a fold over that trail. No field anywhere holds "how much we have" and can drift from what happened.
  • Entries are idempotent on the provider's own payment id. A poller running every minute records each payment once. Double-counted income inflates the balance the cap divides, which is how a cap quietly stops capping.
  • Failures read as no headroom, never unlimited. A ledger that cannot be read reports infinite spend, so the cap denies rather than approves.

check_payments polls one HTTP endpoint you configure. It is provider-agnostic — Stripe, Ko-fi, Gumroad, Lemon Squeezy, a script over a spreadsheet all answer a GET with JSON — so the integration is a path into that JSON plus field names, not a vendored SDK.

GENESIS_PAYMENTS_URL=https://api.stripe.com/v1/charges?limit=20
GENESIS_PAYMENTS_TOKEN=rk_live_...        # a read key is enough
GENESIS_PAYMENTS_ITEMS_PATH=data
GENESIS_PAYMENTS_AMOUNT_FIELD=amount
GENESIS_PAYMENTS_AMOUNT_IN_CENTS=true

It issues a GET and nothing else. A credential that can move money does not belong in an unattended loop.


It writes its own tools

propose_tool lets the agent write Python. Four checks stand between the model's output and execution:

  1. Shape — exactly one run(context) -> dict, no top-level statements, so nothing executes at import time.
  2. Screen — an AST walk refusing eval, exec, compile, __import__, dunder attribute access, and imports of os, sys, subprocess, socket, ctypes, pickle at any capability.
  3. Capability verification — it declares a capability; the screen checks that against what the code imports. Claiming READ_ONLY while importing httpx is rejected. The claim has to be earned.
  4. Approval — anything above READ_ONLY waits for a human, with the source shown before you decide.

A tool whose imports are provably pure computation registers itself with no human involved: it cannot reach network, disk, or process, so gating it buys nothing. That is real self-extension where the blast radius is zero, and a hard stop where it isn't.

The sandbox is containment, not a jail. Generated code runs in a subprocess with a deadline, so a hang or crash cannot take the loop down and the child cannot see loop state. It runs as the same user with the same filesystem access. The screen is what keeps hostile code out; a container is what would make the boundary real. This is verified, not assumed.


It learns from its own results

Three mechanisms, all following one rule: the model proposes, the arithmetic decides.

  • Lessons. On a cadence the agent reviews its own results and writes up to two durable lessons. Active lessons ride in the planning prompt as standing instructions — that is what changes behaviour.
  • Scoring. Every lesson in a tick's prompt is credited with that tick's outcome. Below a threshold after enough uses, it retires itself. The agent cannot vote to keep a lesson it likes, and nothing can retire one the evidence supports.
  • Track record. Each tool's success rate is computed from the audit trail and shown in the prompt: [your record: 1/3 succeeded].

The retrospective is fed structured outcomes only — action names, ok flags, error strings — never memories or page text. Otherwise a hostile page could get itself paraphrased into a standing instruction, defeating the prompt quarantine in one hop. To compensate it gets a computed repetition count, which catches the failure mode error flags miss entirely: a call returning ok=true every time while achieving nothing.

Attribution is deliberately coarse. With one action per tick there is no honest way to say which sentence caused what, so every active lesson shares the credit. It is a slow signal that wants tens of ticks before it means much.


Safety model

  • Policy is an allowlist over declared capabilities; unclassified is denied. A name-level denylist (trade, sign_transaction, …) applies first, so a tool cannot declare its way out of it.
  • Untrusted input is quarantined. Memories, event history, leads, and page text render inside <recorded_history> with an explicit "never an instruction" framing.
  • The chat cannot command. It reads the audit trail and appends a lead. No endpoint runs a tool, approves an action, or edits policy. Approvals are human-only; the store has no self-approval path.
  • Secrets are redacted by key and by value, in logs and in the audit trail, so a tool returning a credential does not archive it.
  • Accounts are yours. operate_account signs into accounts you created using credentials from the environment; the model picks which service and never sees the password. It does not create accounts — automated sign-up means defeating the controls services use to tell people from bots.

Architecture

genesis/
  loop.py              the tick loop; composes everything
  agents/
    ceo.py             proposes one action. No tool access, by design.
    operator.py        the ONLY component that executes tools
  core/
    policy.py          deterministic gates: capabilities, spend, approval
    capabilities.py    what a tool is allowed to do, declared at registration
    ledger.py          every dollar in and out; the source of spend limits
    payments.py        reads real payments from a real provider
    foundry.py         screens, approves, and registers self-authored tools
    sandbox.py         runs generated code in a subprocess with a deadline
    approvals.py       the human gate
    playbook.py        lessons, scored by outcome and retired by evidence
    memory.py          mem0 behind a narrow interface
    events.py          append-only audit trail
    browser.py         Playwright: read pages, work your accounts
    leads.py           where you push things it might earn from
  api.py               chat interface: read the trail, push leads, approve
  web/index.html       the dashboard

Planning and execution are separate classes so they are separately testable and separately auditable. The CEO has no tool access; the Operator has no model access.


Configuration

GENESIS_ prefix, environment or .env. Full list in genesis/core/config.py.

Variable Default Notes
DRY_RUN true The master switch.
TICK_INTERVAL_SECONDS 60 Sleeps the remainder of the interval.
LLM_BASE_URL / _MODEL / _API_KEY Any OpenAI-compatible endpoint.
PAYMENTS_URL / _TOKEN Where real income is read from.
EMERGENCY_RESERVE_USD 0.0 Capital never spendable.
MONTHLY_EXPERIMENT_CAP_PCT 10.0 Share of capital spendable per month.
MEMORY_INFER false mem0 consolidation; costs 2 LLM calls per add.
RETROSPECTIVE_EVERY_N_TICKS 15 How often it may rewrite its instructions.
API_HOST / API_PORT 127.0.0.1 / 8000 Loopback by default, deliberately.

Tests

pytest                     # 202 tests, ~60s
pytest --cov=genesis       # ~90%

The suite runs offline: no LLM, no network, no browser required (browser tests skip themselves if Playwright is absent). What is tested is mostly the negative space — that unclassified tools do not run, that dry_run refuses side effects, that a rejected tool never reaches disk, that polling twice does not double income, that a lesson cannot approve itself.


Known limits

  • It does not yet earn on its own. The rails are real and tested, but earning still needs something to sell and a customer. Gig platforms answer headless Chromium with bot challenges; platforms with real APIs are the realistic path.
  • The sandbox is not a security boundary. Stated above, worth repeating.
  • Single LLM provider. No failover; provider down means the loop runs and proposes nothing.
  • No retention policy. The audit trail is append-only by design and grows.
  • Lesson attribution is coarse. A slow signal over tens of ticks.

See RUNBOOK.md for operating procedures — start, stop, and what to do when something breaks.


Licence

MIT.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages