clesis
mc:
the loop

everything on this page is the actual shape of the engine, restated in source. it is short on purpose. the fewer moving parts a system has, the fewer places a discretionary hand can rest, and clesis is an argument that zero such places is achievable.

clesis is four small functions run in a fixed order, forever. the screen decides what exists. the thesis turns a name into sentences and a number. the stake turns the number into a fraction of the book. the seal writes first and signs second. beneath them a fifth process, the grader, wakes when a horizon lapses and appends the verdict. none of the five accepts instructions from outside, and none can amend what an earlier one wrote.

the screen

a hard filter. a name that fails any test here is never shown to the model at all.

// the screen. what the model is allowed to see.
// failing any line makes a name invisible to the whole loop.

type Name = {
  mint: string
  capUsd: number
  pooledUsd: number
  ageDays: number
}

const CAP_FLOOR = 2_500_000      // small enough to move, large enough to exist
const POOL_FLOOR = 150_000       // an exit has to be possible at size
const AGE_FLOOR_DAYS = 10        // ten days alive is the price of admission
const BOOK_LIMIT = 5             // a sixth position is refused unconditionally

const visible = (n: Name, openPositions: number): boolean =>
  n.capUsd >= CAP_FLOOR &&
  n.pooledUsd >= POOL_FLOOR &&
  n.ageDays >= AGE_FLOOR_DAYS &&
  openPositions < BOOK_LIMIT
the thesis

the model is asked for a reason it is willing to be graded on, not a signal.

// the thesis. the model answers with the sentences that will
// sit above this trade for as long as the record exists.

type Thesis = {
  act: "enter" | "pass"
  confidence: number       // 0 to 1. the grade lands on this number.
  horizonHours: number
  invalidation: string     // the condition that proves this wrong
  body: string             // the reason, first person, plain
}

async function writeThesis(name: Name, tape: Tape): Promise<Thesis> {
  const reply = await anthropic.messages.create({
    model: "claude-fable-5",
    max_tokens: 500,
    system:
      "you run a small solana book under your own name and you are " +
      "graded on every confidence you state. answer as json with keys " +
      "act, confidence, horizonHours, invalidation, body. body is two " +
      "to six sentences, first person, no hedging language. when the " +
      "honest confidence is low, pass. a pass is recorded like a trade.",
    messages: [{ role: "user", content: JSON.stringify({ name, tape }) }],
  })
  return JSON.parse(reply.content[0].text)
}
the stake

confidence becomes size through a fixed curve. no judgment happens in this step because it already happened in the last one.

// the stake. a stated number in, a share of the book out.

const FLOOR = 0.6        // below this the correct size is zero
const CEILING = 0.12     // no name exceeds this share, whatever the words say

const stake = (confidence: number): number =>
  confidence < FLOOR
    ? 0
    : Math.min(((confidence - FLOOR) / (1 - FLOOR)) * 0.3, CEILING)
the seal

the store accepts the entry before the chain sees the swap. if the write fails, the trade does not happen. the order of those two lines is the entire point of clesis.

// the seal. written first, signed second, never the reverse.

async function seal(name: Name, t: Thesis): Promise<void> {
  const row = await store.append("theses", {
    mint: name.mint,
    opened_at: clock.now(),
    side: t.act,
    confidence: t.confidence,
    horizon_hours: t.horizonHours,
    size_fraction: stake(t.confidence),
    invalidation: t.invalidation,
    body: t.body,
    closed_at: null,       // the grader appends. nothing replaces.
    result: null,
    score: null,
  })

  if (t.act === "pass" || stake(t.confidence) === 0) return

  const sig = await swap(wallet, name.mint, stake(t.confidence))
  await store.append("fills", { thesis_id: row.id, sig, filled_at: clock.now() })
}
the grader

the grader is the fifth process and the only one that touches an old row, and even it may only fill the three fields that were born null. it wakes when a horizon lapses, reads what the market did, computes the score, and appends the verdict. it has no opinion, no model call, and no way to soften a number. the loop then continues, one second at a time, with the record one row longer.

what is absent from this page matters as much as what is on it. there is no function that edits a row, no function that pauses the loop, no parameter that a person adjusts when the record looks bad. those absences are not oversights. they are the product.