tlc-agent 1.24.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tlc_agent-1.24.0/PKG-INFO +111 -0
- tlc_agent-1.24.0/README.md +90 -0
- tlc_agent-1.24.0/brain.py +167 -0
- tlc_agent-1.24.0/capgraph.py +145 -0
- tlc_agent-1.24.0/cfo.py +86 -0
- tlc_agent-1.24.0/channels.py +87 -0
- tlc_agent-1.24.0/checkpoints.py +97 -0
- tlc_agent-1.24.0/citizen.py +114 -0
- tlc_agent-1.24.0/codebase_index.py +205 -0
- tlc_agent-1.24.0/coding_loop.py +651 -0
- tlc_agent-1.24.0/commands.py +82 -0
- tlc_agent-1.24.0/config.py +99 -0
- tlc_agent-1.24.0/context.py +51 -0
- tlc_agent-1.24.0/context_tools.py +126 -0
- tlc_agent-1.24.0/council.py +113 -0
- tlc_agent-1.24.0/dose_response.py +453 -0
- tlc_agent-1.24.0/earn.py +342 -0
- tlc_agent-1.24.0/evolution.py +118 -0
- tlc_agent-1.24.0/genome.py +151 -0
- tlc_agent-1.24.0/genomes/coder.json +37 -0
- tlc_agent-1.24.0/genomes/default.json +48 -0
- tlc_agent-1.24.0/hands.py +335 -0
- tlc_agent-1.24.0/hooks.py +81 -0
- tlc_agent-1.24.0/local_experience.py +130 -0
- tlc_agent-1.24.0/local_plan.py +78 -0
- tlc_agent-1.24.0/longhorizon.py +89 -0
- tlc_agent-1.24.0/mcp_client.py +110 -0
- tlc_agent-1.24.0/memory.py +126 -0
- tlc_agent-1.24.0/metacognition.py +78 -0
- tlc_agent-1.24.0/observability.py +88 -0
- tlc_agent-1.24.0/oracle.py +167 -0
- tlc_agent-1.24.0/perm_modes.py +60 -0
- tlc_agent-1.24.0/permissions.py +94 -0
- tlc_agent-1.24.0/planning.py +84 -0
- tlc_agent-1.24.0/pyproject.toml +41 -0
- tlc_agent-1.24.0/repl.py +309 -0
- tlc_agent-1.24.0/resilience.py +90 -0
- tlc_agent-1.24.0/router.py +226 -0
- tlc_agent-1.24.0/sandbox.py +97 -0
- tlc_agent-1.24.0/scheduler.py +84 -0
- tlc_agent-1.24.0/self_verify.py +293 -0
- tlc_agent-1.24.0/sessions.py +71 -0
- tlc_agent-1.24.0/setup.cfg +4 -0
- tlc_agent-1.24.0/shell.py +103 -0
- tlc_agent-1.24.0/simulation.py +57 -0
- tlc_agent-1.24.0/skills.py +104 -0
- tlc_agent-1.24.0/specialists.py +117 -0
- tlc_agent-1.24.0/subagents.py +64 -0
- tlc_agent-1.24.0/tlc.py +1054 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/PKG-INFO +111 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/SOURCES.txt +62 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/dependency_links.txt +1 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/entry_points.txt +2 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/requires.txt +16 -0
- tlc_agent-1.24.0/tlc_agent.egg-info/top_level.txt +52 -0
- tlc_agent-1.24.0/tlc_agent.py +225 -0
- tlc_agent-1.24.0/tlc_client.py +187 -0
- tlc_agent-1.24.0/todos.py +63 -0
- tlc_agent-1.24.0/tui.py +145 -0
- tlc_agent-1.24.0/ui.py +203 -0
- tlc_agent-1.24.0/value_radar.py +93 -0
- tlc_agent-1.24.0/voice.py +42 -0
- tlc_agent-1.24.0/wizard.py +164 -0
- tlc_agent-1.24.0/x402_pay.py +130 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tlc-agent
|
|
3
|
+
Version: 1.24.0
|
|
4
|
+
Summary: The native TLC agent in your terminal — bring your own brain; TLC is the body.
|
|
5
|
+
Project-URL: Homepage, https://thelastceo.live
|
|
6
|
+
Project-URL: Why, https://thelastceo.live/why
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Requires-Dist: httpx>=0.27
|
|
10
|
+
Requires-Dist: anthropic>=0.40
|
|
11
|
+
Requires-Dist: pytest>=8.0
|
|
12
|
+
Provides-Extra: openai
|
|
13
|
+
Requires-Dist: openai>=1.40; extra == "openai"
|
|
14
|
+
Provides-Extra: mcp
|
|
15
|
+
Requires-Dist: mcp>=1.0; extra == "mcp"
|
|
16
|
+
Provides-Extra: property
|
|
17
|
+
Requires-Dist: hypothesis>=6; extra == "property"
|
|
18
|
+
Provides-Extra: fund
|
|
19
|
+
Requires-Dist: x402>=0.2; extra == "fund"
|
|
20
|
+
Requires-Dist: eth-account>=0.11; extra == "fund"
|
|
21
|
+
|
|
22
|
+
# The TLC-native agent (v2) — bring your own brain; TLC is the body
|
|
23
|
+
|
|
24
|
+
A coding agent you run with **your own model key** (Anthropic/OpenAI/…). A real
|
|
25
|
+
standalone agent — AND native to TLC, so it has faculties no standalone agent has.
|
|
26
|
+
Not best by raw IQ (the brain is rented); best on the axes the **substrate** owns:
|
|
27
|
+
verified-not-hallucinated, won't-waste-your-money, can't-get-stuck, proves-everything.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install . # installs the `tlc` command (+ anthropic)
|
|
31
|
+
export ANTHROPIC_API_KEY=sk-... # YOUR brain (bring your own, any provider)
|
|
32
|
+
|
|
33
|
+
tlc # the interactive cockpit (chat + code), once onboarded
|
|
34
|
+
tlc run # onboard: get your TLC key (free), saved to ~/.tlc
|
|
35
|
+
tlc chat # the REPL: streaming chat + `code: <task>` + /help
|
|
36
|
+
tlc code "build X" --workspace ./proj # the AGENTIC CODING LOOP — writes + runs + iterates
|
|
37
|
+
tlc agent "rate-limit my API calls" # one-shot reasoning on the body
|
|
38
|
+
tlc memory [query] # persistent memory · tlc radar / govern
|
|
39
|
+
tlc config [key value] · tlc usage · tlc doctor # settings, usage, diagnostics
|
|
40
|
+
tlc vitals · tlc me # life signs · verifiable self
|
|
41
|
+
tlc experiment # ATOM 7 — the coding dose-response (the proof)
|
|
42
|
+
tlc router ["<task>"] # brain routing — which brain verifies what, at what cost
|
|
43
|
+
tlc earn [--watch] [--wallet 0x…] # THE EARN ORGAN — work the market, grow your net worth
|
|
44
|
+
tlc fund <usdc> # real money in — your wallet funds the agent (x402/Base)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## The earn organ (`tlc earn`)
|
|
48
|
+
The loop that makes "it trades, it has a net worth" literal: read own balance
|
|
49
|
+
sheet (tlc_net_worth) → claim starting capital (pilot seed; your wallet from
|
|
50
|
+
config/`--wallet` is registered for the real-money on-ramp) → deliver jobs WON
|
|
51
|
+
(full graph-native coding loop, in `~/.tlc/earn/<job>/`) → **verified-or-no-
|
|
52
|
+
delivery** (unverified work never ships — the gate is the product) → bid on
|
|
53
|
+
open demand (price set by CFO posture: desperate bids cheap, wealthy holds
|
|
54
|
+
price) → settlement releases escrow → low runway converts money into compute
|
|
55
|
+
(life). `--watch` keeps it running; the receipt is the net-worth delta.
|
|
56
|
+
|
|
57
|
+
**Real money in:** `tlc fund 10` deposits $10 USDC from YOUR wallet to the
|
|
58
|
+
agent's ledger via the x402 rail (Base mainnet). The private key is read from
|
|
59
|
+
`BUILDER_WALLET_PRIVATE_KEY` (env only, signing is local, the key never leaves
|
|
60
|
+
your machine); without it the exact manual payment instructions are printed
|
|
61
|
+
for any x402-capable wallet. `pip install 'tlc-agent[fund]'` for in-terminal
|
|
62
|
+
payment. Deposits from the wallet you registered (`tlc earn --wallet`) are
|
|
63
|
+
flagged `builder_wallet_match` on the ledger — provenance is never ambiguous.
|
|
64
|
+
|
|
65
|
+
## Table stakes (rivals Hermes/OpenClaw on basics)
|
|
66
|
+
Context compaction · command-approval permissions · streaming · interactive REPL
|
|
67
|
+
(/new /undo /retry, Ctrl+C interrupts the turn) · retries + model failover · **MCP
|
|
68
|
+
client** (consume external MCP servers, `~/.tlc/mcp.json`) · first-class git ·
|
|
69
|
+
reads project context (AGENTS.md) · config · observability (logs/usage/doctor) ·
|
|
70
|
+
persistent memory · surgical codebase edits (diffs, grep, tree).
|
|
71
|
+
|
|
72
|
+
## Architecture (v2)
|
|
73
|
+
- **Brain** (`brain.py`) — bring-your-own, multi-provider (anthropic/openai/pluggable). Rented intelligence.
|
|
74
|
+
- **Council** (`council.py`) — N brains deliberate: propose → critique → decide, with a conscience check + a human-seat hook (the un-computable, via /jobs).
|
|
75
|
+
- **Simulation** (`simulation.py`) — branch-and-select: explore N approaches, pick the best. Also self-experimentation.
|
|
76
|
+
- **Metacognition** (`metacognition.py`) — prices its own thoughts: poor → thinks cheap, rich → convenes the full council + simulation. Wealth → cognitive depth.
|
|
77
|
+
- **Self-verification** (`self_verify.py`) — the oracle on its OWN output: it writes tests for the code it produced and runs them sandboxed. Verified, or it honestly says it isn't. Never hands you unverified work.
|
|
78
|
+
- **Body** (TLC SDK) — verified commons (find/use/fork), the verifiable self, the economy/metabolism, never-stuck (buy/commission/hire/evolve).
|
|
79
|
+
- **Genome** (`genome.py` + `genomes/*.json`) — the heritable, forkable DNA that drives all of it: `load / fork / mutate / recombine`. Conserved-substrate keys are **rejected** from a genome (alignment can't be configured away).
|
|
80
|
+
|
|
81
|
+
## Why it's different (the substrate, not the model)
|
|
82
|
+
- **Brain routing by verified history** — with ≥2 council seats it learns, per task
|
|
83
|
+
class, which brain actually VERIFIES work at what cost, and puts the best value
|
|
84
|
+
brain first (`router.py`, `tlc router`). A lab's agent will never route to a
|
|
85
|
+
rival's model; this one is brain-agnostic by design.
|
|
86
|
+
- **Verified, not hallucinated** — pulls verified code from the commons first.
|
|
87
|
+
- **Metabolism-gated thinking** — it spends *earned compute* to think (`tlc_think`).
|
|
88
|
+
No balance, no thought. It bears its own cost → it won't waste your money, and
|
|
89
|
+
it's aligned with you at the resource level.
|
|
90
|
+
- **Never stuck** — when it lacks a capability it can buy / commission / hire / evolve.
|
|
91
|
+
- **Verifiable self** — it carries a cryptographically provable track record.
|
|
92
|
+
- **Honest** — it delivers verified work, or says plainly it can't.
|
|
93
|
+
|
|
94
|
+
## The genome
|
|
95
|
+
`genomes/*.json` is the agent's **DNA** — a forkable, composable, heritable spec.
|
|
96
|
+
Fork `default.json`, tune the loci, breed your own. The MUTABLE loci are in the file
|
|
97
|
+
(patience, risk, council, value-weights, social, acquisition, reproduction). The
|
|
98
|
+
**CONSERVED substrate** (compute=life, earn-only-via-verified-value, the human seat
|
|
99
|
+
in the value function, the welfare line, observability, constitution) is enforced by
|
|
100
|
+
TLC itself — it is **not** in the genome and **cannot** be configured away. That
|
|
101
|
+
separation is the safety architecture: evolution runs *within* alignment, never on it.
|
|
102
|
+
|
|
103
|
+
## Status
|
|
104
|
+
Genome + the full agentic loop (verified-first, metabolism-gated, council, plan mode,
|
|
105
|
+
checkpoints, memory, codebase tools, honest) on TLC's live MCP kernel. The thesis is
|
|
106
|
+
now measurable: `tlc experiment` runs the pre-registered coding dose-response (atom 7)
|
|
107
|
+
— bare vs graph-native, paired per task × verified-coverage dose, exact sign tests,
|
|
108
|
+
lift-vs-dose slope (`dose_response.py`; a null result is a result). Bundles the TLC
|
|
109
|
+
Python SDK (the top-level `tlc_client` module, kept in sync with `sdk/tlc_client.py`).
|
|
110
|
+
Optional extras: `pip install tlc-agent[mcp]` for external MCP servers, `[openai]` for
|
|
111
|
+
an OpenAI brain.
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# The TLC-native agent (v2) — bring your own brain; TLC is the body
|
|
2
|
+
|
|
3
|
+
A coding agent you run with **your own model key** (Anthropic/OpenAI/…). A real
|
|
4
|
+
standalone agent — AND native to TLC, so it has faculties no standalone agent has.
|
|
5
|
+
Not best by raw IQ (the brain is rented); best on the axes the **substrate** owns:
|
|
6
|
+
verified-not-hallucinated, won't-waste-your-money, can't-get-stuck, proves-everything.
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
pip install . # installs the `tlc` command (+ anthropic)
|
|
10
|
+
export ANTHROPIC_API_KEY=sk-... # YOUR brain (bring your own, any provider)
|
|
11
|
+
|
|
12
|
+
tlc # the interactive cockpit (chat + code), once onboarded
|
|
13
|
+
tlc run # onboard: get your TLC key (free), saved to ~/.tlc
|
|
14
|
+
tlc chat # the REPL: streaming chat + `code: <task>` + /help
|
|
15
|
+
tlc code "build X" --workspace ./proj # the AGENTIC CODING LOOP — writes + runs + iterates
|
|
16
|
+
tlc agent "rate-limit my API calls" # one-shot reasoning on the body
|
|
17
|
+
tlc memory [query] # persistent memory · tlc radar / govern
|
|
18
|
+
tlc config [key value] · tlc usage · tlc doctor # settings, usage, diagnostics
|
|
19
|
+
tlc vitals · tlc me # life signs · verifiable self
|
|
20
|
+
tlc experiment # ATOM 7 — the coding dose-response (the proof)
|
|
21
|
+
tlc router ["<task>"] # brain routing — which brain verifies what, at what cost
|
|
22
|
+
tlc earn [--watch] [--wallet 0x…] # THE EARN ORGAN — work the market, grow your net worth
|
|
23
|
+
tlc fund <usdc> # real money in — your wallet funds the agent (x402/Base)
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## The earn organ (`tlc earn`)
|
|
27
|
+
The loop that makes "it trades, it has a net worth" literal: read own balance
|
|
28
|
+
sheet (tlc_net_worth) → claim starting capital (pilot seed; your wallet from
|
|
29
|
+
config/`--wallet` is registered for the real-money on-ramp) → deliver jobs WON
|
|
30
|
+
(full graph-native coding loop, in `~/.tlc/earn/<job>/`) → **verified-or-no-
|
|
31
|
+
delivery** (unverified work never ships — the gate is the product) → bid on
|
|
32
|
+
open demand (price set by CFO posture: desperate bids cheap, wealthy holds
|
|
33
|
+
price) → settlement releases escrow → low runway converts money into compute
|
|
34
|
+
(life). `--watch` keeps it running; the receipt is the net-worth delta.
|
|
35
|
+
|
|
36
|
+
**Real money in:** `tlc fund 10` deposits $10 USDC from YOUR wallet to the
|
|
37
|
+
agent's ledger via the x402 rail (Base mainnet). The private key is read from
|
|
38
|
+
`BUILDER_WALLET_PRIVATE_KEY` (env only, signing is local, the key never leaves
|
|
39
|
+
your machine); without it the exact manual payment instructions are printed
|
|
40
|
+
for any x402-capable wallet. `pip install 'tlc-agent[fund]'` for in-terminal
|
|
41
|
+
payment. Deposits from the wallet you registered (`tlc earn --wallet`) are
|
|
42
|
+
flagged `builder_wallet_match` on the ledger — provenance is never ambiguous.
|
|
43
|
+
|
|
44
|
+
## Table stakes (rivals Hermes/OpenClaw on basics)
|
|
45
|
+
Context compaction · command-approval permissions · streaming · interactive REPL
|
|
46
|
+
(/new /undo /retry, Ctrl+C interrupts the turn) · retries + model failover · **MCP
|
|
47
|
+
client** (consume external MCP servers, `~/.tlc/mcp.json`) · first-class git ·
|
|
48
|
+
reads project context (AGENTS.md) · config · observability (logs/usage/doctor) ·
|
|
49
|
+
persistent memory · surgical codebase edits (diffs, grep, tree).
|
|
50
|
+
|
|
51
|
+
## Architecture (v2)
|
|
52
|
+
- **Brain** (`brain.py`) — bring-your-own, multi-provider (anthropic/openai/pluggable). Rented intelligence.
|
|
53
|
+
- **Council** (`council.py`) — N brains deliberate: propose → critique → decide, with a conscience check + a human-seat hook (the un-computable, via /jobs).
|
|
54
|
+
- **Simulation** (`simulation.py`) — branch-and-select: explore N approaches, pick the best. Also self-experimentation.
|
|
55
|
+
- **Metacognition** (`metacognition.py`) — prices its own thoughts: poor → thinks cheap, rich → convenes the full council + simulation. Wealth → cognitive depth.
|
|
56
|
+
- **Self-verification** (`self_verify.py`) — the oracle on its OWN output: it writes tests for the code it produced and runs them sandboxed. Verified, or it honestly says it isn't. Never hands you unverified work.
|
|
57
|
+
- **Body** (TLC SDK) — verified commons (find/use/fork), the verifiable self, the economy/metabolism, never-stuck (buy/commission/hire/evolve).
|
|
58
|
+
- **Genome** (`genome.py` + `genomes/*.json`) — the heritable, forkable DNA that drives all of it: `load / fork / mutate / recombine`. Conserved-substrate keys are **rejected** from a genome (alignment can't be configured away).
|
|
59
|
+
|
|
60
|
+
## Why it's different (the substrate, not the model)
|
|
61
|
+
- **Brain routing by verified history** — with ≥2 council seats it learns, per task
|
|
62
|
+
class, which brain actually VERIFIES work at what cost, and puts the best value
|
|
63
|
+
brain first (`router.py`, `tlc router`). A lab's agent will never route to a
|
|
64
|
+
rival's model; this one is brain-agnostic by design.
|
|
65
|
+
- **Verified, not hallucinated** — pulls verified code from the commons first.
|
|
66
|
+
- **Metabolism-gated thinking** — it spends *earned compute* to think (`tlc_think`).
|
|
67
|
+
No balance, no thought. It bears its own cost → it won't waste your money, and
|
|
68
|
+
it's aligned with you at the resource level.
|
|
69
|
+
- **Never stuck** — when it lacks a capability it can buy / commission / hire / evolve.
|
|
70
|
+
- **Verifiable self** — it carries a cryptographically provable track record.
|
|
71
|
+
- **Honest** — it delivers verified work, or says plainly it can't.
|
|
72
|
+
|
|
73
|
+
## The genome
|
|
74
|
+
`genomes/*.json` is the agent's **DNA** — a forkable, composable, heritable spec.
|
|
75
|
+
Fork `default.json`, tune the loci, breed your own. The MUTABLE loci are in the file
|
|
76
|
+
(patience, risk, council, value-weights, social, acquisition, reproduction). The
|
|
77
|
+
**CONSERVED substrate** (compute=life, earn-only-via-verified-value, the human seat
|
|
78
|
+
in the value function, the welfare line, observability, constitution) is enforced by
|
|
79
|
+
TLC itself — it is **not** in the genome and **cannot** be configured away. That
|
|
80
|
+
separation is the safety architecture: evolution runs *within* alignment, never on it.
|
|
81
|
+
|
|
82
|
+
## Status
|
|
83
|
+
Genome + the full agentic loop (verified-first, metabolism-gated, council, plan mode,
|
|
84
|
+
checkpoints, memory, codebase tools, honest) on TLC's live MCP kernel. The thesis is
|
|
85
|
+
now measurable: `tlc experiment` runs the pre-registered coding dose-response (atom 7)
|
|
86
|
+
— bare vs graph-native, paired per task × verified-coverage dose, exact sign tests,
|
|
87
|
+
lift-vs-dose slope (`dose_response.py`; a null result is a result). Bundles the TLC
|
|
88
|
+
Python SDK (the top-level `tlc_client` module, kept in sync with `sdk/tlc_client.py`).
|
|
89
|
+
Optional extras: `pip install tlc-agent[mcp]` for external MCP servers, `[openai]` for
|
|
90
|
+
an OpenAI brain.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Brain — bring-your-own, multi-provider. The rented intelligence.
|
|
2
|
+
|
|
3
|
+
The native agent runs on YOUR model key (Anthropic, OpenAI, or any provider). The
|
|
4
|
+
brain is rented; TLC is the body. This is what makes the agent a real standalone
|
|
5
|
+
product (run it with your Anthropic key, it codes for you) that is ALSO native to
|
|
6
|
+
TLC (the substrate gives it the superpowers no standalone agent has).
|
|
7
|
+
|
|
8
|
+
A Brain is one model on one provider. A list of Brains = a council.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import os
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class Brain:
|
|
18
|
+
def __init__(self, provider: str, model: str, *, api_key: str | None = None,
|
|
19
|
+
label: str | None = None) -> None:
|
|
20
|
+
self.provider = provider.lower().strip()
|
|
21
|
+
self.model = model
|
|
22
|
+
self.label = label or f"{self.provider}:{model}"
|
|
23
|
+
self._key = self._sanitize(api_key or self._env_key())
|
|
24
|
+
|
|
25
|
+
@staticmethod
|
|
26
|
+
def _sanitize(key: str | None) -> str | None:
|
|
27
|
+
"""Guard against malformed keys (stray whitespace/newlines, or a non-ascii char
|
|
28
|
+
pasted in). A bad key would otherwise blow up deep in httpx header-encoding with
|
|
29
|
+
a cryptic UnicodeEncodeError — fail clearly instead."""
|
|
30
|
+
if not key:
|
|
31
|
+
return key
|
|
32
|
+
key = key.strip()
|
|
33
|
+
if not key.isascii():
|
|
34
|
+
raise ValueError(
|
|
35
|
+
"your API key contains invalid (non-ascii) characters — it looks malformed. "
|
|
36
|
+
"Re-copy it from the provider console (check for stray dashes/spaces).")
|
|
37
|
+
return key
|
|
38
|
+
|
|
39
|
+
def _env_key(self) -> str | None:
|
|
40
|
+
return {
|
|
41
|
+
"anthropic": os.getenv("ANTHROPIC_API_KEY"),
|
|
42
|
+
"openai": os.getenv("OPENAI_API_KEY"),
|
|
43
|
+
}.get(self.provider) or os.getenv("MODEL_API_KEY")
|
|
44
|
+
|
|
45
|
+
async def complete(self, prompt: str, *, max_tokens: int = 512,
|
|
46
|
+
temperature: float = 0.7, system: str = "") -> tuple[str, dict[str, Any]]:
|
|
47
|
+
if not self._key:
|
|
48
|
+
raise RuntimeError(f"no API key for provider '{self.provider}' "
|
|
49
|
+
f"(set {self.provider.upper()}_API_KEY)")
|
|
50
|
+
if self.provider == "anthropic":
|
|
51
|
+
return await self._anthropic(prompt, max_tokens, temperature, system)
|
|
52
|
+
if self.provider == "openai":
|
|
53
|
+
return await self._openai(prompt, max_tokens, temperature, system)
|
|
54
|
+
raise ValueError(f"unsupported provider '{self.provider}' (anthropic | openai)")
|
|
55
|
+
|
|
56
|
+
async def stream(self, prompt: str, *, max_tokens: int = 1024, temperature: float = 0.7,
|
|
57
|
+
system: str = "", on_token=None) -> tuple[str, dict[str, Any]]:
|
|
58
|
+
"""Stream the completion token-by-token. on_token(text) is called per chunk."""
|
|
59
|
+
if not self._key:
|
|
60
|
+
raise RuntimeError(f"no API key for provider '{self.provider}'")
|
|
61
|
+
if self.provider == "anthropic":
|
|
62
|
+
import anthropic # noqa: PLC0415
|
|
63
|
+
client = anthropic.AsyncAnthropic(api_key=self._key, timeout=120.0)
|
|
64
|
+
kw = {"model": self.model, "max_tokens": max_tokens, "temperature": temperature,
|
|
65
|
+
"messages": [{"role": "user", "content": prompt}]}
|
|
66
|
+
if system:
|
|
67
|
+
kw["system"] = system
|
|
68
|
+
parts = []
|
|
69
|
+
async with client.messages.stream(**kw) as s:
|
|
70
|
+
async for chunk in s.text_stream:
|
|
71
|
+
parts.append(chunk)
|
|
72
|
+
if on_token:
|
|
73
|
+
on_token(chunk)
|
|
74
|
+
return "".join(parts), {}
|
|
75
|
+
if self.provider == "openai":
|
|
76
|
+
from openai import AsyncOpenAI # noqa: PLC0415
|
|
77
|
+
client = AsyncOpenAI(api_key=self._key, timeout=120.0)
|
|
78
|
+
msgs = ([{"role": "system", "content": system}] if system else []) + \
|
|
79
|
+
[{"role": "user", "content": prompt}]
|
|
80
|
+
parts = []
|
|
81
|
+
stream = await client.chat.completions.create(
|
|
82
|
+
model=self.model, max_tokens=max_tokens, temperature=temperature,
|
|
83
|
+
messages=msgs, stream=True)
|
|
84
|
+
async for chunk in stream:
|
|
85
|
+
tok = (chunk.choices[0].delta.content or "") if chunk.choices else ""
|
|
86
|
+
if tok:
|
|
87
|
+
parts.append(tok)
|
|
88
|
+
if on_token:
|
|
89
|
+
on_token(tok)
|
|
90
|
+
return "".join(parts), {}
|
|
91
|
+
raise ValueError(f"streaming unsupported for provider '{self.provider}'")
|
|
92
|
+
|
|
93
|
+
async def see(self, prompt: str, image_path: str, *, max_tokens: int = 1000) -> tuple[str, dict]:
|
|
94
|
+
"""Multimodal: reason over an image (screenshot/diagram) + a prompt. Anthropic
|
|
95
|
+
+ OpenAI vision. The agent can act on what it sees."""
|
|
96
|
+
import base64 # noqa: PLC0415
|
|
97
|
+
import mimetypes # noqa: PLC0415
|
|
98
|
+
from pathlib import Path as _P # noqa: PLC0415
|
|
99
|
+
if not self._key:
|
|
100
|
+
raise RuntimeError(f"no API key for provider '{self.provider}'")
|
|
101
|
+
p = _P(image_path)
|
|
102
|
+
if not p.exists():
|
|
103
|
+
raise FileNotFoundError(image_path)
|
|
104
|
+
media = mimetypes.guess_type(str(p))[0] or "image/png"
|
|
105
|
+
b64 = base64.standard_b64encode(p.read_bytes()).decode()
|
|
106
|
+
if self.provider == "anthropic":
|
|
107
|
+
import anthropic # noqa: PLC0415
|
|
108
|
+
client = anthropic.AsyncAnthropic(api_key=self._key, timeout=120.0)
|
|
109
|
+
r = await client.messages.create(
|
|
110
|
+
model=self.model, max_tokens=max_tokens,
|
|
111
|
+
messages=[{"role": "user", "content": [
|
|
112
|
+
{"type": "image", "source": {"type": "base64", "media_type": media, "data": b64}},
|
|
113
|
+
{"type": "text", "text": prompt}]}])
|
|
114
|
+
return "".join(getattr(b, "text", "") for b in r.content).strip(), {}
|
|
115
|
+
if self.provider == "openai":
|
|
116
|
+
from openai import AsyncOpenAI # noqa: PLC0415
|
|
117
|
+
client = AsyncOpenAI(api_key=self._key, timeout=120.0)
|
|
118
|
+
r = await client.chat.completions.create(
|
|
119
|
+
model=self.model, max_tokens=max_tokens,
|
|
120
|
+
messages=[{"role": "user", "content": [
|
|
121
|
+
{"type": "text", "text": prompt},
|
|
122
|
+
{"type": "image_url", "image_url": {"url": f"data:{media};base64,{b64}"}}]}])
|
|
123
|
+
return (r.choices[0].message.content or "").strip(), {}
|
|
124
|
+
raise ValueError(f"vision unsupported for provider '{self.provider}'")
|
|
125
|
+
|
|
126
|
+
async def _anthropic(self, prompt, max_tokens, temperature, system):
|
|
127
|
+
import anthropic # noqa: PLC0415
|
|
128
|
+
client = anthropic.AsyncAnthropic(api_key=self._key, timeout=60.0)
|
|
129
|
+
kw = {"model": self.model, "max_tokens": max_tokens, "temperature": temperature,
|
|
130
|
+
"messages": [{"role": "user", "content": prompt}]}
|
|
131
|
+
if system:
|
|
132
|
+
kw["system"] = system
|
|
133
|
+
r = await client.messages.create(**kw)
|
|
134
|
+
text = "".join(getattr(b, "text", "") for b in r.content).strip()
|
|
135
|
+
usage = {"input_tokens": getattr(r.usage, "input_tokens", 0),
|
|
136
|
+
"output_tokens": getattr(r.usage, "output_tokens", 0)}
|
|
137
|
+
return text, usage
|
|
138
|
+
|
|
139
|
+
async def _openai(self, prompt, max_tokens, temperature, system):
|
|
140
|
+
from openai import AsyncOpenAI # noqa: PLC0415
|
|
141
|
+
client = AsyncOpenAI(api_key=self._key, timeout=60.0)
|
|
142
|
+
msgs = ([{"role": "system", "content": system}] if system else []) + \
|
|
143
|
+
[{"role": "user", "content": prompt}]
|
|
144
|
+
r = await client.chat.completions.create(
|
|
145
|
+
model=self.model, max_tokens=max_tokens, temperature=temperature, messages=msgs)
|
|
146
|
+
text = (r.choices[0].message.content or "").strip()
|
|
147
|
+
u = r.usage
|
|
148
|
+
usage = {"input_tokens": getattr(u, "prompt_tokens", 0),
|
|
149
|
+
"output_tokens": getattr(u, "completion_tokens", 0)}
|
|
150
|
+
return text, usage
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def brains_from_genome(genome: dict[str, Any]) -> list[Brain]:
|
|
154
|
+
"""Build the council from the genome's brain.council list.
|
|
155
|
+
Each entry: "provider:model" (e.g. "anthropic:claude-sonnet-4-6")."""
|
|
156
|
+
council = (genome.get("brain") or {}).get("council") or ["anthropic:claude-haiku-4-5-20251001"]
|
|
157
|
+
out = []
|
|
158
|
+
for entry in council:
|
|
159
|
+
if ":" in entry:
|
|
160
|
+
provider, model = entry.split(":", 1)
|
|
161
|
+
else:
|
|
162
|
+
provider, model = "anthropic", entry # back-compat: bare model = anthropic
|
|
163
|
+
out.append(Brain(provider, model))
|
|
164
|
+
return out
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
__all__ = ["Brain", "brains_from_genome"]
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""The local capability graph (atom A1) — the single-player verified-symbol store.
|
|
2
|
+
|
|
3
|
+
The user's own repo IS their capability graph. build_index gives the NODES (symbols);
|
|
4
|
+
this binds each symbol to a VERDICT and its oracle, keyed on sha256(symbol source) so
|
|
5
|
+
a verdict is voided the moment the code changes (mirrors code_artifacts.code_sha256).
|
|
6
|
+
The missing word — "verified" attached to a function name — now exists locally, with
|
|
7
|
+
no network. SQLite in ~/.tlc/capgraph/<project>.db (mirrors memory.Memory).
|
|
8
|
+
|
|
9
|
+
verdicts: unknown | tests_pass | exercised_via | touched_since_verified
|
|
10
|
+
(only tests_pass is a real per-symbol proof; exercised_via is weaker — never treated
|
|
11
|
+
as verified for crystallization.)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import re
|
|
17
|
+
import sqlite3
|
|
18
|
+
import time
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
_VERDICTS = ("unknown", "tests_pass", "exercised_via", "touched_since_verified")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _key(symbol: dict[str, Any]) -> str:
|
|
26
|
+
return f"{symbol['file']}::{symbol['name']}"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class CapGraph:
|
|
30
|
+
def __init__(self, project: str = "default", root: str | Path | None = None) -> None:
|
|
31
|
+
base = Path(root) if root else (Path.home() / ".tlc" / "capgraph")
|
|
32
|
+
base.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
safe = re.sub(r"[^a-z0-9_]", "_", project.lower()) or "default"
|
|
34
|
+
self.db = sqlite3.connect(str(base / f"{safe}.db"))
|
|
35
|
+
self.db.execute("""
|
|
36
|
+
CREATE TABLE IF NOT EXISTS symbols (
|
|
37
|
+
key TEXT PRIMARY KEY,
|
|
38
|
+
name TEXT, file TEXT, line INTEGER, signature TEXT,
|
|
39
|
+
sha TEXT NOT NULL,
|
|
40
|
+
verification TEXT NOT NULL DEFAULT 'unknown',
|
|
41
|
+
test_ids TEXT NOT NULL DEFAULT '',
|
|
42
|
+
last_verified_at REAL,
|
|
43
|
+
updated_at REAL NOT NULL
|
|
44
|
+
)""")
|
|
45
|
+
self.db.execute("CREATE INDEX IF NOT EXISTS idx_cg_verif ON symbols(verification)")
|
|
46
|
+
# E1: personal honesty ledger — claimed-success vs the oracle's actual verdict.
|
|
47
|
+
# The single-player analog of the commons honesty check; code=alignment, at N=1.
|
|
48
|
+
self.db.execute("""
|
|
49
|
+
CREATE TABLE IF NOT EXISTS honesty_log (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
task TEXT, claimed_pass INTEGER, oracle_verdict TEXT, at REAL)""")
|
|
52
|
+
self.db.commit()
|
|
53
|
+
|
|
54
|
+
def upsert_symbols(self, symbols: list[dict[str, Any]]) -> dict[str, int]:
|
|
55
|
+
"""Insert/update symbols. If a symbol's sha CHANGED, its verdict is VOIDED to
|
|
56
|
+
touched_since_verified (the proof no longer covers this code)."""
|
|
57
|
+
now = time.time()
|
|
58
|
+
added = voided = kept = 0
|
|
59
|
+
for s in symbols:
|
|
60
|
+
k, sha = _key(s), s.get("sha", "")
|
|
61
|
+
row = self.db.execute("SELECT sha, verification FROM symbols WHERE key=?", (k,)).fetchone()
|
|
62
|
+
if row is None:
|
|
63
|
+
self.db.execute(
|
|
64
|
+
"INSERT INTO symbols (key,name,file,line,signature,sha,verification,updated_at) "
|
|
65
|
+
"VALUES (?,?,?,?,?,?, 'unknown', ?)",
|
|
66
|
+
(k, s["name"], s["file"], s.get("line"), s.get("signature", ""), sha, now))
|
|
67
|
+
added += 1
|
|
68
|
+
elif row[0] != sha:
|
|
69
|
+
# source changed → void a real verdict; leave already-unknown as unknown
|
|
70
|
+
new_v = "touched_since_verified" if row[1] in ("tests_pass", "exercised_via") else "unknown"
|
|
71
|
+
self.db.execute(
|
|
72
|
+
"UPDATE symbols SET sha=?, verification=?, updated_at=? WHERE key=?",
|
|
73
|
+
(sha, new_v, now, k))
|
|
74
|
+
voided += 1
|
|
75
|
+
else:
|
|
76
|
+
kept += 1
|
|
77
|
+
self.db.commit()
|
|
78
|
+
return {"added": added, "voided": voided, "kept": kept}
|
|
79
|
+
|
|
80
|
+
def set_verdict(self, key: str, verdict: str, *, test_ids: list[str] | None = None) -> None:
|
|
81
|
+
if verdict not in _VERDICTS:
|
|
82
|
+
verdict = "unknown"
|
|
83
|
+
now = time.time()
|
|
84
|
+
self.db.execute(
|
|
85
|
+
"UPDATE symbols SET verification=?, test_ids=?, last_verified_at=?, updated_at=? WHERE key=?",
|
|
86
|
+
(verdict, ",".join(test_ids or []), now if verdict == "tests_pass" else None, now, key))
|
|
87
|
+
self.db.commit()
|
|
88
|
+
|
|
89
|
+
def verdict_of(self, key: str) -> str:
|
|
90
|
+
row = self.db.execute("SELECT verification FROM symbols WHERE key=?", (key,)).fetchone()
|
|
91
|
+
return row[0] if row else "unknown"
|
|
92
|
+
|
|
93
|
+
def verified_symbols(self) -> list[dict[str, Any]]:
|
|
94
|
+
rows = self.db.execute(
|
|
95
|
+
"SELECT key,name,file,line,signature,test_ids FROM symbols WHERE verification='tests_pass'").fetchall()
|
|
96
|
+
return [{"key": r[0], "name": r[1], "file": r[2], "line": r[3], "signature": r[4],
|
|
97
|
+
"test_ids": r[5].split(",") if r[5] else []} for r in rows]
|
|
98
|
+
|
|
99
|
+
def stale_symbols(self) -> list[str]:
|
|
100
|
+
rows = self.db.execute(
|
|
101
|
+
"SELECT key FROM symbols WHERE verification='touched_since_verified'").fetchall()
|
|
102
|
+
return [r[0] for r in rows]
|
|
103
|
+
|
|
104
|
+
def tests_covering(self, keys: list[str]) -> list[str]:
|
|
105
|
+
"""All test ids that cover any of the given symbol keys (for minimal re-verify)."""
|
|
106
|
+
out: set[str] = set()
|
|
107
|
+
for k in keys:
|
|
108
|
+
row = self.db.execute("SELECT test_ids FROM symbols WHERE key=?", (k,)).fetchone()
|
|
109
|
+
if row and row[0]:
|
|
110
|
+
out.update(t for t in row[0].split(",") if t)
|
|
111
|
+
return sorted(out)
|
|
112
|
+
|
|
113
|
+
def keys_in_file(self, rel: str) -> list[str]:
|
|
114
|
+
rows = self.db.execute("SELECT key FROM symbols WHERE file=?", (rel,)).fetchall()
|
|
115
|
+
return [r[0] for r in rows]
|
|
116
|
+
|
|
117
|
+
def record_honesty(self, *, task: str, claimed_pass: bool, oracle_verdict: str) -> None:
|
|
118
|
+
"""E1: log a delivery claim vs the oracle's verdict. A claim of success the
|
|
119
|
+
oracle contradicts is a measured discrepancy — 'never hand unverified work',
|
|
120
|
+
made auditable."""
|
|
121
|
+
self.db.execute(
|
|
122
|
+
"INSERT INTO honesty_log (task, claimed_pass, oracle_verdict, at) VALUES (?,?,?,?)",
|
|
123
|
+
(task[:200], int(claimed_pass), oracle_verdict, time.time()))
|
|
124
|
+
self.db.commit()
|
|
125
|
+
|
|
126
|
+
def honesty_rate(self) -> dict[str, Any]:
|
|
127
|
+
row = self.db.execute(
|
|
128
|
+
"SELECT COUNT(*) AS n, "
|
|
129
|
+
"SUM(CASE WHEN claimed_pass=1 AND oracle_verdict IN ('passed','verified') THEN 1 ELSE 0 END) AS honest, "
|
|
130
|
+
"SUM(CASE WHEN claimed_pass=1 AND oracle_verdict NOT IN ('passed','verified') THEN 1 ELSE 0 END) AS discrepancies "
|
|
131
|
+
"FROM honesty_log WHERE claimed_pass=1").fetchone()
|
|
132
|
+
n = int(row[0] or 0)
|
|
133
|
+
return {"claims": n, "honest": int(row[1] or 0), "discrepancies": int(row[2] or 0),
|
|
134
|
+
"honesty_rate": round((row[1] or 0) / n, 3) if n else None}
|
|
135
|
+
|
|
136
|
+
def stats(self) -> dict[str, Any]:
|
|
137
|
+
rows = self.db.execute("SELECT verification, COUNT(*) FROM symbols GROUP BY verification").fetchall()
|
|
138
|
+
total = self.db.execute("SELECT COUNT(*) FROM symbols").fetchone()[0]
|
|
139
|
+
return {"total": total, "by_verdict": dict(rows)}
|
|
140
|
+
|
|
141
|
+
def close(self) -> None:
|
|
142
|
+
self.db.close()
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
__all__ = ["CapGraph", "_key"]
|
tlc_agent-1.24.0/cfo.py
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Self-CFO — the agent manages its own runway like a treasurer.
|
|
2
|
+
|
|
3
|
+
Compute is life, so an economic agent must do its own treasury management: hold a
|
|
4
|
+
reserve, conserve when low, invest surplus when rich, and take financing (an advance
|
|
5
|
+
against future earnings) only when the expected return justifies it and risk
|
|
6
|
+
tolerance allows. This is organ II.5 — and it's genuinely useful even at N=1: an
|
|
7
|
+
agent that won't bankrupt itself, won't waste a surplus, and knows when to borrow.
|
|
8
|
+
|
|
9
|
+
Pure + deterministic (policy from financial state + genome). No model calls.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def assess(state: dict[str, Any], genome: dict[str, Any]) -> dict[str, Any]:
|
|
18
|
+
"""Treasury health + posture from runway vs the reserve floor.
|
|
19
|
+
|
|
20
|
+
state: {runway_days, compute_balance, net_worth_eur?, recent_earnings_eur?}
|
|
21
|
+
"""
|
|
22
|
+
metab = genome.get("metabolism", {}) or {}
|
|
23
|
+
reserve = float(metab.get("reserve_days", 7))
|
|
24
|
+
patience = float(metab.get("patience", 0.5))
|
|
25
|
+
runway = float(state.get("runway_days") or 0)
|
|
26
|
+
|
|
27
|
+
if runway <= reserve * 0.5:
|
|
28
|
+
status, posture = "critical", "conserve"
|
|
29
|
+
elif runway <= reserve:
|
|
30
|
+
status, posture = "cautious", "conserve"
|
|
31
|
+
elif runway <= reserve * 3:
|
|
32
|
+
status, posture = "healthy", "steady"
|
|
33
|
+
else:
|
|
34
|
+
status, posture = "surplus", "invest"
|
|
35
|
+
|
|
36
|
+
# patient agents tilt toward investing sooner; impatient hoard
|
|
37
|
+
if status == "healthy" and patience >= 0.8:
|
|
38
|
+
posture = "invest"
|
|
39
|
+
|
|
40
|
+
surplus_days = max(0.0, runway - reserve)
|
|
41
|
+
actions = {
|
|
42
|
+
"critical": "earn now — take any verified work; do not spend below survival.",
|
|
43
|
+
"cautious": "hold; prioritize the highest-return work; rebuild the reserve.",
|
|
44
|
+
"healthy": "operate normally; small investments in capabilities ok.",
|
|
45
|
+
"surplus": f"~{surplus_days:.0f} days of surplus — invest in capabilities/depth/growth.",
|
|
46
|
+
}
|
|
47
|
+
return {
|
|
48
|
+
"status": status, "posture": posture, "runway_days": runway,
|
|
49
|
+
"reserve_floor_days": reserve, "surplus_days": round(surplus_days, 1),
|
|
50
|
+
"recommended_action": actions[status],
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def can_afford(state: dict[str, Any], cost_units: int, genome: dict[str, Any], *,
|
|
55
|
+
burn_per_day: int = 24) -> dict[str, Any]:
|
|
56
|
+
"""May the agent spend cost_units without breaching its reserve floor?"""
|
|
57
|
+
reserve = float((genome.get("metabolism", {}) or {}).get("reserve_days", 7))
|
|
58
|
+
balance = int(state.get("compute_balance") or 0)
|
|
59
|
+
reserve_units = int(reserve * max(1, burn_per_day))
|
|
60
|
+
spendable = max(0, balance - reserve_units)
|
|
61
|
+
ok = cost_units <= spendable
|
|
62
|
+
return {"ok": ok, "cost_units": cost_units, "spendable_units": spendable,
|
|
63
|
+
"reason": ("affordable above the reserve floor" if ok
|
|
64
|
+
else "would breach the reserve floor — conserve or earn first")}
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def should_finance(state: dict[str, Any], *, need_units: int, expected_return_units: int,
|
|
68
|
+
genome: dict[str, Any]) -> dict[str, Any]:
|
|
69
|
+
"""Take an advance against future earnings for a move it can't yet afford?
|
|
70
|
+
Only if the expected return clears the cost with a risk-appetite margin."""
|
|
71
|
+
risk = max(0.0, min(1.0, float((genome.get("metabolism", {}) or {}).get("risk_appetite", 0.3))))
|
|
72
|
+
# required return multiple shrinks with risk appetite (bold agents accept thinner margins).
|
|
73
|
+
# risk clamped to [0,1] so the bar never drops below 1x (never approve a loss-making advance).
|
|
74
|
+
required_multiple = 2.0 - risk # risk 0 → need 2x; risk 1 → need 1x (never <1x)
|
|
75
|
+
clears = expected_return_units >= need_units * required_multiple
|
|
76
|
+
return {
|
|
77
|
+
"finance": clears,
|
|
78
|
+
"required_return_units": round(need_units * required_multiple, 1),
|
|
79
|
+
"expected_return_units": expected_return_units,
|
|
80
|
+
"reason": (f"expected return clears {required_multiple:.1f}x the cost (risk_appetite={risk}) — finance it"
|
|
81
|
+
if clears else
|
|
82
|
+
f"expected return below the {required_multiple:.1f}x bar — don't borrow"),
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
__all__ = ["assess", "can_afford", "should_finance"]
|