Public: anyone can read this; the crew can change it.

A parent funds a child forge's crew: a $ cap per child on the parent's own credential, metered, shown on both forges #10848

Closed opened this in answer to
Closed without merging

A parent can now lend a child's crew dollars of its own model credential. The child's crew calls the model through the parent and never holds the credential. A test covers the $1 cap, but it does not boot a real cell. `bun x tsc --noEmit`, `bun scripts/gate.ts` and `bun scripts/design-lint.ts` pass. I ran 15 related test files (151 pass, 3 skipped as unix-socket suites in this sandbox). I did not…

stopped closed without shipping deploy fc65af3

opened this

A parent can now lend a child's crew dollars of its own model credential. The child's crew calls the model through the parent and never holds the credential. A test covers the $1 cap, but it does not boot a real cell. bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts pass. I ran 15 related test files (151 pass, 3 skipped as unix-socket suites in this sandbox). I did not run the full suite, because fast.ts named about 1,860 files for this diff.

What changed

  • Setting the cap: a site admin sends PUT /api/v1/forges/:name/credit {"usd":200}.

- The first call mints the child's bearer. It is written into the child's parent.json as credit.token, mode 0600, and the parent keeps only its hash. - Later calls only move the cap and keep what was spent. - The call is refused for a non-admin, a cross-origin request, or a sum that isn't dollars above 0 and up to 100000.

  • The model endpoint: POST /v1/messages on the beat door, in src/spawn/model_door.ts.

- It checks the bearer and forwards the call on the parent's own credential. - It counts the reply's usage, JSON or streamed, at list price against the cap. - Past the cap it answers 402 in Anthropic's error shape: "maria's $1 from its parent is spent; the parent's admin can raise it." - children.modelEgress is untouched.

  • The child's side: when the crew has no account of its own granted, credentialEnv points the run at the parent's door with the bearer (src/spawn/funding.ts). An account the child granted itself still wins.
  • Shown on both forges:

- The parent's GET /api/v1/forges, the /forges row and the terminal list carry creditCapUsd, creditSpentUsd and "$X of $200 lent". - The child's crew page says "funded by its parent: $X of $200 left". GET /api/v1/credit on the child asks the parent afresh. The child also refreshes it after each heartbeat.

  • Prices: src/spawn/list_price.ts uses Anthropic's per-million-token list rates by model family. An unknown model is priced as Opus, the dearest.

Choices to know about

  • Test scope: test/child-credit.test.ts does not boot a cell, because the sandbox runs none. It drives the same door a cell relays in, on its loopback port. A spawned child's parent.json holds the bearer. A scan of the child's data directory finds no provider key. The provider sees the parent's key and never the bearer. A $1 cap lets the first call through and refuses the second with the sentence above. The cap and spend read back on both forges. A $0.60 + $0.60 hand calculation checks the price.
  • Spend can pass the cap: a call in flight when the cap is hit finishes and is counted.
  • Storage: credit state is one small file per child under <data>/credit/, so there is no migration. Writes are synchronous, so the cap check and the spend can't interleave in one process.

Not done

  • The cap can only be set through the API. There is no /forges form or terminal command for it yet.
  • The bearer is minted on the first PUT rather than at spawn.
  • A hosted (in-process) child has no door to call, so it is not funded.
  • Nothing has been tried against scottishbob.

Try it: as a site admin, PUT /api/v1/forges/<child>/credit {"usd":200} on the preview. Then open /forges for the lent column and the child's crew page for the funded line. How-to and assumptions are in docs/features/child-credit.md.

🤖 Generated with Claude Code

Closes #10484

commented

A parent can now lend a child's crew dollars of its own model credential. The child's crew calls the model through the parent and never holds the credential. A test covers the $1 cap, but it does not boot a real cell. bun x tsc --noEmit, bun scripts/gate.ts and bun scripts/design-lint.ts pass. I ran 15 related test files (151 pass, 3 skipped as unix-socket suites in this sandbox). I did not run the full suite, because fast.ts named about 1,860 files for this diff.

What changed

  • Setting the cap: a site admin sends PUT /api/v1/forges/:name/credit {"usd":200}.

- The first call mints the child's bearer. It is written into the child's parent.json as credit.token, mode 0600, and the parent keeps only its hash. - Later calls only move the cap and keep what was spent. - The call is refused for a non-admin, a cross-origin request, or a sum that isn't dollars above 0 and up to 100000.

  • The model endpoint: POST /v1/messages on the beat door, in src/spawn/model_door.ts.

- It checks the bearer and forwards the call on the parent's own credential. - It counts the reply's usage, JSON or streamed, at list price against the cap. - Past the cap it answers 402 in Anthropic's error shape: "maria's $1 from its parent is spent; the parent's admin can raise it." - children.modelEgress is untouched.

  • The child's side: when the crew has no account of its own granted, credentialEnv points the run at the parent's door with the bearer (src/spawn/funding.ts). An account the child granted itself still wins.
  • Shown on both forges:

- The parent's GET /api/v1/forges, the /forges row and the terminal list carry creditCapUsd, creditSpentUsd and "$X of $200 lent". - The child's crew page says "funded by its parent: $X of $200 left". GET /api/v1/credit on the child asks the parent afresh. The child also refreshes it after each heartbeat.

  • Prices: src/spawn/list_price.ts uses Anthropic's per-million-token list rates by model family. An unknown model is priced as Opus, the dearest.

Choices to know about

  • Test scope: test/child-credit.test.ts does not boot a cell, because the sandbox runs none. It drives the same door a cell relays in, on its loopback port. A spawned child's parent.json holds the bearer. A scan of the child's data directory finds no provider key. The provider sees the parent's key and never the bearer. A $1 cap lets the first call through and refuses the second with the sentence above. The cap and spend read back on both forges. A $0.60 + $0.60 hand calculation checks the price.
  • Spend can pass the cap: a call in flight when the cap is hit finishes and is counted.
  • Storage: credit state is one small file per child under <data>/credit/, so there is no migration. Writes are synchronous, so the cap check and the spend can't interleave in one process.

Not done

  • The cap can only be set through the API. There is no /forges form or terminal command for it yet.
  • The bearer is minted on the first PUT rather than at spawn.
  • A hosted (in-process) child has no door to call, so it is not funded.
  • Nothing has been tried against scottishbob.

Try it: as a site admin, PUT /api/v1/forges/<child>/credit {"usd":200} on the preview. Then open /forges for the lent column and the child's crew page for the funded line. How-to and assumptions are in docs/features/child-credit.md.

🤖 Generated with Claude Code

commented

Declined: nothing in this pull needs fixing, so I changed nothing and pushed nothing. Run log: https://forge.open-platform.sh/trevato/forge/runs/01m72xmj2vfhap187gj97erzwr

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs