From 4837112368989b7ef7d71414c8c678ab47011419 Mon Sep 17 00:00:00 2001 From: Zijian Zhang <35801754+futrime@users.noreply.github.com> Date: Mon, 31 Aug 2026 16:32:28 +0800 Subject: [PATCH] docs(features): organize capabilities around five systems --- docs/.vitepress/config.mts | 44 +- .../theme/components/HmzBackends.vue | 72 +- .../.vitepress/theme/components/HmzDaemon.vue | 887 ++++++++++++++ .../theme/components/HmzFeatures.vue | 10 +- .../theme/components/HmzInstall.vue | 2 +- docs/.vitepress/theme/components/HmzMap.vue | 385 ++++-- .../theme/components/HmzProphecy.vue | 1067 +++++++++++++++++ .../theme/components/HmzSurfaces.vue | 938 +++++++++++++++ docs/.vitepress/theme/index.ts | 6 + docs/contributing/docs.md | 19 +- docs/features/backends.md | 38 +- docs/features/capabilities.md | 363 ++++++ docs/features/daemon.md | 101 ++ docs/features/flows.md | 27 +- docs/features/human.md | 1 + docs/features/index.md | 98 +- docs/features/prophecy.md | 123 ++ docs/features/resuming.md | 27 +- docs/features/surfaces.md | 107 ++ docs/features/tracing.md | 15 +- docs/flows/flame-chase.md | 2 +- docs/guide/atlas.md | 7 +- docs/guide/index.md | 2 + docs/reference/flows.md | 7 +- 24 files changed, 4110 insertions(+), 238 deletions(-) create mode 100644 docs/.vitepress/theme/components/HmzDaemon.vue create mode 100644 docs/.vitepress/theme/components/HmzProphecy.vue create mode 100644 docs/.vitepress/theme/components/HmzSurfaces.vue create mode 100644 docs/features/capabilities.md create mode 100644 docs/features/daemon.md create mode 100644 docs/features/prophecy.md create mode 100644 docs/features/surfaces.md diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts index e411d0e2..66cc99d2 100644 --- a/docs/.vitepress/config.mts +++ b/docs/.vitepress/config.mts @@ -61,40 +61,52 @@ export default defineConfig({ // a title nobody would write on the page itself, and the page is what it goes to. So the // section is the first line, and the groups under it are what they were. sidebar: { - // One page per feature, each built around a diagram you can push. The first group is - // the one to send somebody who wants to know what is unusual about this. + // The capability map groups the whole system; the pages beneath it take one mechanism + // far enough to explain its trade-offs, each around a diagram the reader can push. '/features/': [ { text: 'Features', link: '/features/' }, + { text: 'Capability map', link: '/features/capabilities' }, { - text: 'The deep end', + text: 'Flow system', collapsed: false, items: [ - { text: 'The anchor', link: '/features/anchor' }, - { text: 'Two accounts of one CLI', link: '/features/accounts' }, - { text: 'One timeline', link: '/features/tracing' }, - { text: 'A line typed mid-turn', link: '/features/steering' }, - { text: 'Answers in a shape', link: '/features/shapes' }, - ], - }, - { - text: 'The shape of a run', - collapsed: false, - items: [ - { text: 'Twelve CLIs, one agent', link: '/features/backends' }, + { text: 'Python becomes a prophecy', link: '/features/prophecy' }, { text: 'A flow is Python', link: '/features/flows' }, { text: 'Many turns at once', link: '/features/concurrency' }, { text: 'Picked up where it stopped', link: '/features/resuming' }, ], }, { - text: 'Who is at the other end', + text: 'Agent control plane', collapsed: false, items: [ + { text: 'Many backends, one agent', link: '/features/backends' }, + { text: 'Two accounts of one CLI', link: '/features/accounts' }, + { text: 'A line typed mid-turn', link: '/features/steering' }, + { text: 'Answers in a shape', link: '/features/shapes' }, { text: 'It decides when it is done', link: '/features/goals' }, { text: 'The moments of a turn', link: '/features/hooks' }, { text: 'You, as one of the agents', link: '/features/human' }, ], }, + { + text: 'Execution fabric', + collapsed: false, + items: [{ text: 'The anchor', link: '/features/anchor' }], + }, + { + text: 'Run continuity and observability', + collapsed: false, + items: [ + { text: 'The terminal can leave', link: '/features/daemon' }, + { text: 'One timeline', link: '/features/tracing' }, + ], + }, + { + text: 'Product surfaces', + collapsed: false, + items: [{ text: 'One system, four ways in', link: '/features/surfaces' }], + }, ], // One page per flow, named the way `-f` takes it. The order is the order they are worth diff --git a/docs/.vitepress/theme/components/HmzBackends.vue b/docs/.vitepress/theme/components/HmzBackends.vue index 02f83a07..0ad0a8ed 100644 --- a/docs/.vitepress/theme/components/HmzBackends.vue +++ b/docs/.vitepress/theme/components/HmzBackends.vue @@ -3,6 +3,8 @@ // The rows are `hmz/backends.py` and the classes in `hmz/agents/`: which session base a // backend derives from is what decides whether it can be talked to mid-turn, `shapes` is // whether it can be held to a schema, and a `_pursue` of its own is whether it has a goal. +// `trace` means `hmz/tracing/collector.py` has a reader for the backend's logs; it does not +// mean only that the backend writes logs or that humanize can tally them while it runs. import { computed, ref } from 'vue' type Driven = 'held' | 'server' | 'command' | 'sdk' | 'protocol' @@ -16,7 +18,7 @@ interface Backend { steer: string shape: string goal: boolean - logs: string + trace: boolean skills: string note: string } @@ -38,7 +40,7 @@ const BACKENDS: Backend[] = [ steer: 'answered inside the same turn', shape: 'held to it', goal: true, - logs: 'read back', + trace: true, skills: 'its own, and the project’s', note: '“ultracode” is “xhigh” with the turn opted into orchestrating a fleet of its own. It is real, undocumented, and no listing the CLI answers with will ever name it — so humanize writes it down.', }, @@ -50,7 +52,7 @@ const BACKENDS: Backend[] = [ steer: 'a steer on the running turn', shape: 'held to it', goal: true, - logs: 'read back', + trace: true, skills: 'four places, the shared one included', note: 'Its models differ from each other: one takes “ultra” and the one beside it does not, so the ladder is narrowed per model by what the backend itself says when it is asked what it runs.', }, @@ -62,7 +64,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'asked in the prompt', goal: false, - logs: 'none to read', + trace: false, skills: 'its own, and the project’s', note: 'It has no flag for a rung: its models are parameterized, so how hard it thinks and how quickly it is served are written into the model itself — “composer-2.5[effort=high,fast=false]”. A model already spelled with a bracket is passed exactly as it was written.', }, @@ -74,7 +76,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'asked in the prompt', goal: true, - logs: 'read back', + trace: true, skills: 'none', note: 'The one backend that arrives with humanize rather than being found on your PATH — its SDK and the runtime its turns are taken on are ordinary dependencies.', }, @@ -87,7 +89,7 @@ const BACKENDS: Backend[] = [ steer: 'queued, then steered in', shape: 'asked in the prompt', goal: true, - logs: 'read back', + trace: true, skills: 'its own, the shared one, the project’s', note: 'Its effort says how wide as well as how hard: “max” is one agent and “swarmmax” is the same thinking at the width of a fleet, so width is chosen beside the effort rather than among the rungs.', }, @@ -99,7 +101,7 @@ const BACKENDS: Backend[] = [ steer: 'a steer on the run it is making', shape: 'asked in the prompt', goal: false, - logs: 'read back', + trace: false, skills: 'its own, and the shared one', note: '“off” is the model asked not to think at all. That is an effort like any other here: the least of them, not the absence of a setting.', }, @@ -111,7 +113,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'held to it', goal: false, - logs: 'read back', + trace: false, skills: 'eight places, two of them other harnesses’', note: 'The ladder is written as it enumerates them when it refuses one, because a rung it refuses is a turn that never starts.', }, @@ -123,7 +125,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'held to it', goal: false, - logs: 'read back', + trace: false, skills: 'four places', note: 'It has no flag for an effort — they are a setting of its own settings file, so a turn is pointed at one of humanize’s instead of anybody’s being rewritten.', }, @@ -135,7 +137,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'held to it', goal: false, - logs: 'none to read', + trace: false, skills: 'its own', note: 'A conversation here is rows of a database whose payloads are protobuf, so there is no log to read a run’s cost out of as it is spent, and none to gather afterwards.', }, @@ -147,7 +149,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'asked in the prompt', goal: false, - logs: 'none to read', + trace: false, skills: 'three places', note: 'Its effort is the model variant rather than a thinking level of its own, and a provider with no variants takes the flag and ignores it.', }, @@ -159,7 +161,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'asked in the prompt', goal: false, - logs: 'none to read', + trace: false, skills: 'five places', note: 'A fork of opencode, and one directory more: it reads Codex’s skills as well as Claude Code’s.', }, @@ -171,7 +173,7 @@ const BACKENDS: Backend[] = [ steer: 'no', shape: 'asked in the prompt', goal: true, - logs: 'read back', + trace: false, skills: 'four places, the shared one included', note: 'Its ladder is two vocabularies at once, because its models have two: the ones that take a thinking budget answer “max”, “high”, “low” and “nothink”, and the ones that only take thinking or not answer “enabled” or “disabled”. A model narrows it to its own half.', }, @@ -180,10 +182,10 @@ const BACKENDS: Backend[] = [ called: 'anything speaking ACP', driven: 'protocol', efforts: ['as configured'], - steer: 'a word put in', + steer: 'no', shape: 'asked in the prompt', goal: false, - logs: 'none to read', + trace: false, skills: 'none', note: 'The protocol says nothing about which models an agent runs or how hard it may be asked to think — both are the agent’s own — so one rung is offered and none is sent.', }, @@ -199,7 +201,7 @@ const WANTS: Want[] = [ { key: 'steer', said: 'takes a word mid-turn', holds: (one) => one.steer !== 'no' }, { key: 'shape', said: 'held to a shape', holds: (one) => one.shape === 'held to it' }, { key: 'goal', said: 'has a goal of its own', holds: (one) => one.goal }, - { key: 'logs', said: 'leaves a log to trace', holds: (one) => one.logs === 'read back' }, + { key: 'trace', said: 'can be read back into a trace', holds: (one) => one.trace }, { key: 'swarm', said: 'runs a turn as a fleet', holds: (one) => Boolean(one.swarms) }, ] @@ -216,24 +218,39 @@ const asked = computed(() => WANTS.filter((one) => wanted.value.includes(one.key const fits = (one: Backend) => asked.value.every((each) => each.holds(one)) const open = computed(() => BACKENDS.find((one) => one.name === opened.value) ?? BACKENDS[0]) const counted = computed(() => BACKENDS.filter(fits).length) + +function backendLabel(one: Backend) { + return [ + one.name, + `driven through ${DRIVEN[one.driven]}`, + `hardest effort ${one.efforts[0]}`, + `mid-turn ${one.steer}`, + `shape ${one.shape}`, + `goal ${one.goal ? 'yes' : 'no'}`, + `trace ${one.trace ? 'read back' : 'no reader'}`, + ].join(', ') +} diff --git a/docs/.vitepress/theme/components/HmzMap.vue b/docs/.vitepress/theme/components/HmzMap.vue index 1e855908..d53cd6b8 100644 --- a/docs/.vitepress/theme/components/HmzMap.vue +++ b/docs/.vitepress/theme/components/HmzMap.vue @@ -1,156 +1,244 @@ diff --git a/docs/.vitepress/theme/components/HmzProphecy.vue b/docs/.vitepress/theme/components/HmzProphecy.vue new file mode 100644 index 00000000..14fc1d7a --- /dev/null +++ b/docs/.vitepress/theme/components/HmzProphecy.vue @@ -0,0 +1,1067 @@ + + + + + diff --git a/docs/.vitepress/theme/components/HmzSurfaces.vue b/docs/.vitepress/theme/components/HmzSurfaces.vue new file mode 100644 index 00000000..87808d6d --- /dev/null +++ b/docs/.vitepress/theme/components/HmzSurfaces.vue @@ -0,0 +1,938 @@ + + + + + diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts index 16bdb546..782f106e 100644 --- a/docs/.vitepress/theme/index.ts +++ b/docs/.vitepress/theme/index.ts @@ -8,6 +8,7 @@ import DefaultTheme from 'vitepress/theme' import HmzAccounts from './components/HmzAccounts.vue' import HmzAnchor from './components/HmzAnchor.vue' import HmzBackends from './components/HmzBackends.vue' +import HmzDaemon from './components/HmzDaemon.vue' import HmzFeatures from './components/HmzFeatures.vue' import HmzFlowShape from './components/HmzFlowShape.vue' import HmzFlows from './components/HmzFlows.vue' @@ -19,11 +20,13 @@ import HmzMap from './components/HmzMap.vue' import HmzMoments from './components/HmzMoments.vue' import HmzOrchestra from './components/HmzOrchestra.vue' import HmzPerson from './components/HmzPerson.vue' +import HmzProphecy from './components/HmzProphecy.vue' import HmzResume from './components/HmzResume.vue' import HmzShape from './components/HmzShape.vue' import HmzStack from './components/HmzStack.vue' import HmzSteer from './components/HmzSteer.vue' import HmzSyscalls from './components/HmzSyscalls.vue' +import HmzSurfaces from './components/HmzSurfaces.vue' import HmzTimeline from './components/HmzTimeline.vue' import HmzTurns from './components/HmzTurns.vue' import './style.css' @@ -34,6 +37,7 @@ export default { app.component('HmzAccounts', HmzAccounts) app.component('HmzAnchor', HmzAnchor) app.component('HmzBackends', HmzBackends) + app.component('HmzDaemon', HmzDaemon) app.component('HmzFeatures', HmzFeatures) app.component('HmzFlowShape', HmzFlowShape) app.component('HmzFlows', HmzFlows) @@ -45,11 +49,13 @@ export default { app.component('HmzMoments', HmzMoments) app.component('HmzOrchestra', HmzOrchestra) app.component('HmzPerson', HmzPerson) + app.component('HmzProphecy', HmzProphecy) app.component('HmzResume', HmzResume) app.component('HmzShape', HmzShape) app.component('HmzStack', HmzStack) app.component('HmzSteer', HmzSteer) app.component('HmzSyscalls', HmzSyscalls) + app.component('HmzSurfaces', HmzSurfaces) app.component('HmzTimeline', HmzTimeline) app.component('HmzTurns', HmzTurns) }, diff --git a/docs/contributing/docs.md b/docs/contributing/docs.md index 0b3e0aca..68175c0c 100644 --- a/docs/contributing/docs.md +++ b/docs/contributing/docs.md @@ -25,7 +25,7 @@ inbound links moving is a red build rather than a 404 somebody finds later. docs/ ├── .vitepress/config.mts nav, sidebars, the root's redirect, everything ├── .vitepress/theme/ the palette, and every diagram on the site -├── features/ one page per feature, each built on a diagram you can push +├── features/ one capability map, then feature pages built on diagrams ├── flows/ one page per flow, each with its own loop played on it ├── tutorials/ six, in order, each a whole piece of work ├── guide/ one page per feature, answering "how do I use this?" @@ -49,7 +49,7 @@ Five kinds of page, and a page that is two of them is two pages. | | | | | --- | --- | --- | -| **Features** | understanding | One page per feature, built around a diagram the reader can push. What it is and why it works the way it does. **No commands and no code**: a reader who wants to run it is one click from the guide, which every page ends by naming. Its index is the front of the site. | +| **Features** | understanding | A system-wide capability map, then one page per deep feature, built around a diagram the reader can push. What it is and why it works the way it does. The map and deep pages have **no commands and no code**: a reader who wants to run it is one click from the guide. The index is the front of the site, so its one install line and recorded demos are the deliberate exception. | | **Flows** | what there is to run | One page per flow humanize or the official flowverse ships, named the way `-f` takes it, opening with the `hmz exec` line and the shape of its loop. What it is for, what it takes, and what a run picked up carries in. | | **Tutorials** | learning | Taken in order, start to finish, with every command written out. A reader following one is not choosing anything; they are being led. Six of them, and adding a seventh means arguing that one of the six should go. | | **Guides** | doing | "How do I use X?" One feature each. Opens with a `## Try it` section short enough to paste, then explains the rest. A reader here has a job and knows what they want. | @@ -80,9 +80,11 @@ otherwise. ## The front of the site `features/index.md` is what somebody arriving is handed: one line of install with the way to -the quickstart beside it, four drawings, the recorded demos, and the index of the feature -pages. It explains nothing at length — a reader who wants to know how to use something is one -click from a tutorial, a guide or the reference, and every one of those is a better page for it. +the quickstart beside it, the unusual features drawn, the five-system capability map, the +recorded demos, and the index of the feature pages. `features/capabilities.md` expands that map +into the reader's path to the right explanation without turning internal implementation units +into a second product vocabulary. The map and deep pages do not explain how to run something +— a tutorial, guide or reference page is one click away and better at that job. ``` .vitepress/theme/ @@ -123,19 +125,22 @@ One page, one diagram, one component, registered in the same `index.ts` — the and the one elsewhere that is built the same way: ``` -HmzMap.vue features/ the six stages, and every page hung off one +HmzMap.vue features/ five systems, nineteen reader-facing capability groups +HmzProphecy.vue features/prophecy Python becoming a checked, resumable graph HmzSyscalls.vue features/anchor a call, the seccomp verdict, and where it lands HmzAccounts.vue features/accounts the path swap, then the chain and its waits HmzTimeline.vue features/tracing a trace, with the programs and the clock as switches HmzSteer.vue features/steering type a line into a running turn, or queue behind it HmzShape.vue features/shapes a model, how a backend is held to it, what comes back -HmzBackends.vue features/backends twelve backends against what a flow may ask for +HmzBackends.vue features/backends backends against what a flow may ask for HmzLoops.vue features/flows the shapes a loop takes, stepped through HmzTurns.vue features/concurrency twelve prompts, scheduled across n conversations HmzResume.vue features/resuming pull the plug, then run it again +HmzDaemon.vue features/daemon leave, attach again, and see the held PTY HmzGoal.vue features/goals the model deciding, beside your code deciding HmzMoments.vue features/hooks hang a hook, run the turn, read what it said HmzPerson.vue features/human a questionnaire built out of a pydantic model +HmzSurfaces.vue features/surfaces several entry points converging on one run HmzStack.vue contributing/architecture the layers, and what each is allowed to name ``` diff --git a/docs/features/backends.md b/docs/features/backends.md index ec5469f4..797c9a1c 100644 --- a/docs/features/backends.md +++ b/docs/features/backends.md @@ -2,14 +2,14 @@ pageClass: hmz-feature --- -# Twelve CLIs, one agent +# Many backends, one agent -humanize never talks to a model provider. It drives the coding agent CLI you already have, -logged in the way you already log in — twelve of them, plus anything that speaks the Agent -Client Protocol. There is no API key for it to hold. +Most humanize backends do not talk to a model provider. They drive a supported coding agent +under its existing login, through a built-in adapter or the Agent Client Protocol. humanize +does not need the provider's API key for those backends. -The one exception ships inside it: DeepSeek Harness arrives with humanize, because it has no -subscription login to use instead. +The exception ships inside it: DeepSeek Harness arrives as an SDK-backed agent and uses its +own DeepSeek provider credentials because it has no subscription login to reuse. @@ -19,16 +19,13 @@ A backend, a model, an effort, and the [account](/features/accounts) its turns r agents of one spelling are two agents, so a flow of an actor and a reviewer at one configuration is what it says it is. -## What it runs is asked, never written down +## What it runs is discovered for the account -A model id is not a fact that keeps. These CLIs ship models without asking anybody, and which -of them your account may name is your account's business — so a list written down here would be -wrong the day the CLI ships one, and would say nothing about what you may actually run. +A model id is not usually a fact that keeps. Coding agents add models, and which of them an +account may name is the account's business. Wherever a backend can report its catalogue, +humanize asks it under that account and keeps the answer: -So the backend itself is asked, by whatever mechanism that backend offers for being asked, and -what it says is kept: - -- **Asked as the account whose it would be** — under that account's own credential paths and +- **It is asked as the account whose it would be** — under that account's own credential paths and variables, and without the ones its backend would otherwise take an account from. Which is exactly how a turn of that account is run. Two accounts of one CLI are two catalogues. - **Kept with the account**, so taking the account away takes its catalogue with it. @@ -38,11 +35,14 @@ what it says is kept: to ask. A backend that would not answer leaves the account made — an account whose models are not known yet is one to ask again, not one that failed. -Nothing is added to what a backend answered. Claude Code will report a model named by -`ANTHROPIC_CUSTOM_MODEL_OPTION` without checking that the account can run it, so humanize -never sets that variable to put a model in front of you: a catalogue is what the account said -it runs. One you set yourself is passed through untouched, and is kept under the alias you -chose rather than the id Claude resolves it to — the alias is what `--model` takes. +DeepSeek Harness and Qwen Code cannot list their models dynamically. Their adapters provide +small advisory catalogues instead: the official DeepSeek adapter's current models, and the +models Qwen Code ships pointed at. Those lists make initial setup possible; they are not proof +of what an account or compatible endpoint will accept. + +For a discovered catalogue, nothing is added to what the backend answered. Claude Code may +report a custom alias without proving the account can run it, so humanize preserves the alias +exactly as that account supplied it rather than silently manufacturing another model entry. ## The efforts are a vocabulary, so they are written down diff --git a/docs/features/capabilities.md b/docs/features/capabilities.md new file mode 100644 index 00000000..09e6ef99 --- /dev/null +++ b/docs/features/capabilities.md @@ -0,0 +1,363 @@ +--- +pageClass: hmz-feature +--- + +# Capability map + +This is a wayfinding map, not an exhaustive product contract. It groups related behavior by +the user problem it solves; one group may combine several implementations, and one feature may +support more than one group. Availability still depends on the backend, account, operating +system, and shape of the run. + +Use the interactive map to choose an area, then follow **Learn** for the design, **Use** for a +task-oriented guide, or **Reference** for the complete interface and its limits. + + + +## A. Flow system + +Express work as ordinary Python or as an inspectable graph, then compose, schedule, and recover +it without hiding which execution model is in use. + +### A1. Expression and compilation + +**Outcome.** Choose unrestricted runtime behavior or a graph whose structure is settled before +the first agent turn. + +- A regular flow is unrestricted Python; its next step is whatever its body decides at runtime. +- An atlas uses a restricted declarative body that compiles into a typed prophecy graph. +- Calls, shaped values, branches, loops, and returns become explicit nodes, edges, and exits. +- Canonical graph identities separate structural changes from formatting and node-body changes. +- Shipped graphs are checked for drift and rebuilt only from allowlisted prophecy types. + +**Learn:** [Python becomes a prophecy](/features/prophecy) · **Use:** +[Writing a flow](/guide/writing-a-flow), [Atlas](/guide/atlas) · **Reference:** +[Flows](/reference/flows#an-atlas) + +### A2. Static correctness and proving + +**Outcome.** Find structural mistakes and exercise hostile paths before spending a real model +turn. + +- Zero-execution checking reads flow structure without importing or running user code. +- Atlas checks cover edge shapes, bound values, recursion, returns, and loop progress. +- Ordinary flow checks catch selected liveness and shaped-answer mistakes without claiming to + prove arbitrary Python. +- Nested flows and changed configuration schemas are validated before their work begins. +- Stand-in agents, adversarial scenarios, and virtual time can exercise execution without a + real model. + +**Learn:** [Python becomes a prophecy](/features/prophecy) · **Use:** +[Checking flows](/guide/checking-flows), [Testing flows](/guide/testing-flows) · +**Reference:** [Flows](/reference/flows#checking-a-flow) + +### A3. Composition and hot reload + +**Outcome.** Reuse flows and their supporting assets while keeping each nested run visible and +independently scoped. + +- A regular flow may load and call another flow while preserving nested run context. +- An atlas may contain another atlas as a typed supernode in the outer prophecy. +- Remote skill repositories are fetched and cached; selected flow skills are mounted for the + scope that needs them and removed when it ends. +- Flow entry points and side modules are read again so current source is used for later work. +- Synchronous and asynchronous flows share the same runner and failure model. + +**Learn:** [A flow is Python](/features/flows) · **Use:** +[Calling flows](/guide/calling-flows), [Skills](/guide/skills) · **Reference:** +[Flows](/reference/flows#a-flow-that-calls-another-flow) + +### A4. Scheduling, state, and resumption + +**Outcome.** Place and fan out work deliberately, then continue the workflow state that was +actually recorded. + +- Flows declare agent roles, capabilities, and working locations without binding them to one + backend implementation. +- Independent sessions may run concurrently; turns sharing one session remain sequential. +- Resumable regular flows keep an explicit state mapping and resume by running current flow + code again. +- Atlases record completed node visits and resume the first unfinished visit under the same + prophecy identity. +- Neither form restores a backend conversation; repositories and explicit flow state carry the + work forward. + +**Learn:** [Many turns at once](/features/concurrency), +[Picked up where it stopped](/features/resuming) · **Use:** +[Async flows](/guide/async-flows), [Picking a run up](/guide/resuming) · **Reference:** +[Flows](/reference/flows#a-flow-that-can-be-picked-up) + +## B. Agent control plane + +Drive different coding agents through one orchestration model while preserving their real +capabilities, identities, conversations, and ways of collaborating with a person. + +### B1. Backend unification + +**Outcome.** Configure one agent abstraction across different CLIs and protocols without +pretending every backend supports the same controls. + +- Agent and session contracts normalize turns, events, answers, and lifecycle operations. +- Native app servers, streaming command-line adapters, and Agent Client Protocol (ACP) servers + keep their own transport semantics behind that contract. +- A capability matrix exposes what each backend can do, so a flow can require capabilities and + reject an incompatible backend. +- Model catalogues are usually discovered for the account that will run them; backends that + cannot list models begin with a small advisory catalogue instead. +- Shaped answers are reconstructed into the same typed result where a backend supports them. + +**Learn:** [Many backends, one agent](/features/backends), +[Answers in a shape](/features/shapes) · **Use:** [Providers](/guide/providers), +[Efforts](/guide/efforts) · **Reference:** +[Agents](/reference/agents#what-each-backend-can-do) + +### B2. Turn and session control + +**Outcome.** Keep live work steerable and make human collaboration part of the run rather than +an out-of-band interruption. + +- Per-turn controls, lifecycle hooks, and typed failures give flows explicit decision points. +- Steering delivers an acknowledged instruction into a supported turn that is already running. +- Goals continue across controlled turns, while cloning creates a separate conversation branch. +- Side questions through /btw read a frozen conversation snapshot without changing the main + session. +- Agent questions and the human agent share an answer path; away mode returns no answer instead + of leaving a run blocked. +- The board carries non-blocking, durable lines between a person and a flow while both + continue. + +**Learn:** [A line typed mid-turn](/features/steering), +[It decides when it is done](/features/goals), [The moments of a turn](/features/hooks), +[You, as one of the agents](/features/human) · **Use:** [Questions](/guide/questions), +[Side questions (/btw)](/guide/btw), [Board](/guide/board), +[Human agent](/guide/human-agent), [Being away](/guide/afk) · **Reference:** +[Agents](/reference/agents#turns), [Flows](/reference/flows#the-person-at-the-prompt) + +### B3. Tools and skills + +**Outcome.** Give each session only the reusable instructions and temporary callable tools its +role needs. + +- Each session receives the flow-owned skills selected for its role and scope. +- Backends expose the native skills already installed where their own CLI reads them. +- Flow-owned skills are mounted for the session and removed when that scope ends. +- A flow callback can become a native tool from the next turn until it is withdrawn or the + session ends, on a capable backend. + +**Learn:** [Many backends, one agent](/features/backends) · **Use:** +[Skills](/guide/skills), [Callbacks as tools](/guide/tools) · **Reference:** +[Agents](/reference/agents#the-skills-an-agent-carries), +[Flows](/reference/flows#the-skills-a-flow-brings) + +### B4. Failure recovery + +**Outcome.** Respond to a failed turn according to what failed, while making conversation loss +an explicit boundary. + +- Backends distinguish failures worth another attempt from explicitly unrecoverable ones; + configured policy then retries, walks accounts, and finally walks places. +- Retries and waits are policy for a place, not an automatic response to every failure. +- An account chain stays inside one backend and may continue the same backend conversation. +- Cross-backend fallback opens a new session and carries compatible agent settings and the + pending turn, but not the earlier conversation. +- Recovery stops on loops, missing destinations, unsupported capabilities, and failures another + attempt cannot fix. + +**Learn:** [Two accounts of one CLI](/features/accounts) · **Use:** +[Falling back](/guide/fallback) · **Reference:** +[Account recovery](/reference/agents#when-an-account-goes-down), +[Cross-backend recovery](/reference/agents#when-the-place-has-nowhere-left-to-run) + +### B5. Accounts and credentials + +**Outcome.** Run the same CLI under separate identities without changing the agent's command or +leaking another account into the turn. + +- Where a CLI provides a native login, capture lets it create and refresh credentials in its + own format; other backends use their configured credential inputs. +- Credential paths and environment variables are redirected only for the account taking a turn. +- Ambient credential variables are removed so the shell cannot silently select another account. +- Compatible vendor credentials can be reused across CLI backends under each backend's + spelling. +- Concurrent accounts keep private files, and updates to stored account state land atomically. +- The machine's existing login may start an account chain, but humanize does not own or copy + it. + +**Learn:** [Two accounts of one CLI](/features/accounts) · **Use:** +[Providers](/guide/providers) · **Reference:** [Providers](/reference/providers) + +## C. Execution fabric + +Let a local coding agent operate another machine while keeping process behavior, workspace +movement, transport, and machine ownership explicit. + +### C1. Transparent remote execution + +**Outcome.** Keep the agent local while selected work behaves as though its processes and files +belong to the target machine. + +- A supervisor decides selected system calls and can replay them remotely one at a time. +- Program launches, descendants, network access, paths, and executables follow explicit routes. +- Control files and backend state that should remain local stay on the local side. +- Remote results preserve target errors, exit status, and common signals; rarer or repeated + signals have documented limits. +- The anchor is routing and transport, not a sandbox or an authorization boundary. + +**Learn:** [The anchor](/features/anchor) · **Use:** +[Remote execution](/guide/remote-execution), [Security](/guide/security) · **Reference:** +[Remote execution](/reference/remote-execution) + +### C2. Shadow workspace and consistent writes + +**Outcome.** Make a remote workspace available quickly without exposing readers to partial file +updates. + +- A sparse local shadow presents the target workspace before every file has crossed the wire. +- Missing files and virtual exports are materialized when the agent actually reaches them. +- Writes stream to the target and become visible atomically when the complete file arrives. + +**Learn:** [The anchor](/features/anchor) · **Use:** +[Remote execution](/guide/remote-execution) · **Reference:** +[What the agent observes](/reference/remote-execution#what-the-agent-observes) + +### C3. Portable transport runtime + +**Outcome.** Reach different targets through one session protocol without installing humanize +on the target first. + +- A compact target runtime carries process, file, environment, and working-directory + operations. +- One multiplexed connection can keep independent requests and streamed results in flight. +- Targets may use different transports while preserving the same remote-execution semantics. +- Local and target environments are composed deliberately rather than replacing each other. +- Each remote command uses the target-resolved counterpart of the tracee's current working + directory. + +**Learn:** [The anchor](/features/anchor) · **Use:** +[Remote execution](/guide/remote-execution) · **Reference:** +[Targets](/reference/remote-execution#targets) + +### C4. Machine lifecycle + +**Outcome.** Give agents isolated or shared machines with a clear owner for startup, reuse, and +cleanup. + +- An agent may receive a dedicated container whose lifetime follows that agent. +- A run may share one container when the participants need the same environment. +- Existing remote targets remain externally owned; managed targets are closed by the scope that + created them. +- Workspace placement is declared separately from which backend performs the turn. + +**Learn:** [The anchor](/features/anchor) · **Use:** [Containers](/guide/containers) · +**Reference:** [Machines](/reference/machines) + +## D. Run continuity and observability + +Keep long work reachable, leave a readable record after failure, reconstruct its timeline, and +separate local traces from optional outbound reporting. + +### D1. Detached operation + +**Outcome.** Let an interactive run survive terminal or SSH loss and reattach to its current +screen later. + +- One workspace daemon owns the interface pseudoterminal (PTY) while terminals act as + attachable readers. +- A new terminal receives a redraw of the live screen rather than a promised full transcript. +- Slow readers have independent buffers and cannot stall the run or other attached terminals. +- Detaching, cooperative stopping, and forced stopping remain distinct operations. +- Detachment does not survive host or daemon loss; persisted state can support a later run, not + resurrect the old process. + +**Learn:** [The terminal can leave](/features/daemon) · **Use:** +[Unattended runs](/guide/unattended), [Stopping](/guide/stopping) · **Reference:** +[Daemon](/reference/daemon) + +### D2. Persistent state and layered logs + +**Outcome.** Preserve enough structured evidence to inspect an abrupt stop and continue a flow +that explicitly supports it. + +- Each run's cycle record gains complete journal entries as events happen. +- Ordinary flow state is written through on assignment, with a final save for nested mutations. +- Atlas state records completed node visits under the prophecy identity and nesting path. +- Called flows keep layered journals and state beside the run without overwriting their caller. +- These records preserve workflow state, not the backend conversation or a terminal transcript. + +**Learn:** [Picked up where it stopped](/features/resuming), +[The terminal can leave](/features/daemon) · **Use:** +[Picking a run up](/guide/resuming), [History](/guide/history) · **Reference:** +[Resumable flows](/reference/flows#a-flow-that-can-be-picked-up), +[Cycle records](/reference/tracing#cycles) + +### D3. Trace reconstruction + +**Outcome.** Rebuild agent activity and the programs it started onto one timeline that can be +inspected after the run. + +- Backend session logs and profiled processes are combined without copying their source + records. +- Process clocks are calibrated so agent events and operating-system activity can be compared. +- Cycle records bound collection to the sessions opened by the run being inspected. +- Subagent relationships become explicit topology rather than anonymous extra sessions. +- Dense lane packing and lazy attachments keep large traces navigable without dropping detail. +- A trace is a local artifact; creating or opening it does not opt into outbound telemetry. + +**Learn:** [One timeline](/features/tracing) · **Use:** [Tracing](/guide/tracing) · +**Reference:** [Tracing](/reference/tracing) + +### D4. Telemetry privacy + +**Outcome.** Send diagnostic reporting only after consent, with content limits enforced before +anything leaves the machine. + +- Consent may remain unanswered, be enabled, or be disabled; unanswered machines send nothing. +- Data suppliers run only while a report is actually being assembled and provide names, + counts, and configuration rather than prompts, transcripts, tool output, or file content. +- Final filters remove command lines, credentials, external paths, frame context, and logging + breadcrumbs before an outbound report is sent. +- Local run journals, session logs, profiles, and traces are separate from optional reporting. + +**Learn:** [One timeline](/features/tracing) · **Use:** [Reporting](/guide/reporting), +[Tracing](/guide/tracing) · **Reference:** [SDK](/reference/sdk), +[Tracing](/reference/tracing) + +## E. Product surfaces + +Discover, own, configure, and start the same flows through interfaces suited to interactive, +scripted, embedded, or detached work. + +### E1. Discovery, forking, and configuration + +**Outcome.** Find the nearest flow, make a safe local copy when it should become yours, and +configure it from the contract the flow declares. + +- Built-in, fetched, project, and user flows participate in explicit catalogue and resolution + precedence. +- Qualified names select a source directly; unqualified names prefer the nearest local version. +- Forking stages a complete copy and refuses to overwrite an existing local flow. +- A flow's pydantic model drives setup fields, validation, defaults, and grouped presentation. +- Remembered settings are revalidated against the model the flow declares now. + +**Learn:** [One system, four ways in](/features/surfaces) · **Use:** +[Flowverses](/guide/flowverses), [Flow settings](/guide/flow-settings) · **Reference:** +[Flows](/reference/flows#flowverses) + +### E2. Unified entry points + +**Outcome.** Choose a terminal, command line, or Python entry point without creating a second +definition of what a flow or run means. + +- The SDK, command line, and terminal interface share workspace stores, flow loading, + validation, and the underlying runner where their responsibilities overlap. +- The daemon holds the terminal interface but does not interpret flows or become another + engine. +- Each surface keeps its purpose: composable SDK, scriptable command line, conversational + interface, and detached terminal continuity. +- A user-facing run is written as a cycle record that history, state, and tracing can inspect. +- Shared semantics do not imply identical interaction or backend capability on every surface. + +**Learn:** [One system, four ways in](/features/surfaces) · **Use:** +[Quickstart](/tutorials/quickstart), [Status](/guide/status) · **Reference:** +[SDK](/reference/sdk), [CLI](/reference/cli), [TUI](/reference/tui), +[Daemon](/reference/daemon) diff --git a/docs/features/daemon.md b/docs/features/daemon.md new file mode 100644 index 00000000..c4c1144a --- /dev/null +++ b/docs/features/daemon.md @@ -0,0 +1,101 @@ +--- +pageClass: hmz-feature +--- + +# The terminal can leave + +A flow may work longer than an SSH connection or the shell that started it. For an interactive +run, humanize makes that terminal a reader rather than the owner: one process for the workspace +holds the interface on a pseudoterminal (PTY) of its own, and terminals come and go over a +local socket. + +Close every reader and the run still has its terminal. Open the same workspace later and the +screen is drawn there again. + + + +## The PTY belongs to the daemon + +The holder is put in a session of its own and left with no controlling terminal. Its standard +input, output and errors are moved onto one PTY, and the interface draws there exactly as it +would have drawn on the shell that opened it. Nothing in the interface has to know whether a +person is still at the other end. + +It is the machinery underneath a terminal multiplexer, built in rather than delegated to one: +detach the process, keep a PTY alive, and proxy bytes between that PTY and whichever terminals +are reading. + +## One workspace, one holder + +Two runs in one workspace would both claim the same current setup and the same run history. +When a holder binds, it takes a kernel-held lock before an old socket can be removed and +replaced. However the process ends, the kernel drops that lock. Another checkout has another +full path and therefore another holder. + +A process number and a socket file are not proof that anything is alive: process numbers are +reused and socket files outlive their listeners. A holder counts only when its process still +exists and its socket accepts a connection. + +The socket carries framed terminal output, input, resize notices and control requests. More +than one terminal may be attached at once, all reading the same PTY. The terminal type is fixed +by the shell that started the holder; the PTY's size changes when an attached terminal reports +that its window changed. + +## Leaving is not stopping + +Letting go closes readers. It does not signal the flow, close the interface or end the holder. +That separation is the point: a disappearing terminal and a decision to stop the work are two +different events. + +A cooperative stop asks the interface to close the flow and gives it time to unwind. A forced +stop ends the holder itself, leaving the work exactly where the process was. The second is the +last resort for a run that will not complete a cooperative stop. + +This protects a run from terminal and SSH loss, not from the loss of its host or the daemon +process. After either of those, persistence makes the run inspectable and a resumable flow may +start another run from saved state; it does not bring the old process back. A non-interactive +invocation also stays in its own process because there is no terminal to proxy. + +## Replay is a screen, not a transcript + +A newly attached terminal begins empty and may have different modes and dimensions from the +one before it. The daemon first sizes the PTY for it, then asks the interface to draw the whole +current screen from the top. The first terminal may also receive the bounded output drawn +before anybody had ever attached. + +After a terminal has attached once, output drawn while nobody is reading is discarded. A +later reconnect still sees the current screen because the interface redraws it, not because +the daemon recorded every byte. The backend's own session log and the run journal are the +history; terminal replay is only a way back into the live view. + +## A slow reader pays its own cost + +PTY output is read once and offered to each attached terminal without waiting on any of them. +Each reader has its own pending buffer. If one stops taking bytes and that buffer grows past +one mebibyte, only that reader is released; the run, the PTY and every other terminal keep +moving. + +The same non-blocking rule applies in the other direction. A large paste is queued for the PTY +rather than allowed to make the only thread carrying screen output wait on the run it serves. + +## What is written while it runs + +The daemon writes a small, whole-file note saying which process and workspace it holds, when +it began and which terminal type it draws for. Failures that cannot be shown on a terminal are +appended to a log beside that note. + +The run has a record of its own. Its journal gains one complete line for each event as the +event happens, so an abrupt end still leaves everything written before it. If the flow is +resumable, each change to its state mapping writes a complete replacement file into place; a +reader sees either the old state or the new one, never a half-written state. + +Those records are not another terminal buffer and not another transcript. They preserve the +shape and state of the run; the coding-agent backend remains the owner of the conversation. + +## Where the detail is + +- [Run it unattended](/guide/unattended) — running with no interactive reader at all +- [Picking a run up](/guide/resuming) — what saved state can do after a process has ended +- [Stopping](/guide/stopping) — how a flow unwinds, and what a harder stop leaves behind +- [Daemon reference](/reference/daemon) — lifecycle, terminal rules and the on-disk note +- [Tracing](/guide/tracing) — the run journal, session links and traces made from them diff --git a/docs/features/flows.md b/docs/features/flows.md index 64118929..c5eb3244 100644 --- a/docs/features/flows.md +++ b/docs/features/flows.md @@ -4,12 +4,14 @@ pageClass: hmz-feature # A flow is Python -A flow is a directory holding a function that takes the agents and the task. Everything else -about it is ordinary Python — a loop, a subprocess call, a file read between two turns, a -condition on what the last answer said. +A flow is a directory holding Python that takes the agents and the task. Most are ordinary +functions: a loop, a subprocess call, a file read between two turns, a condition on what the +last answer said. An **atlas** makes a different bargain: its deliberately narrow body is read +before it runs and compiled into a typed graph called a prophecy. -There is no graph to declare, no YAML, and no engine deciding what happens next. The flow *is* -what happens next. +Both are Python, discovered the same way and driven by the same run. Choose an ordinary flow +when its shape should remain free; choose an atlas when the shape must be checked, compared or +resumed node by node before any agent starts. @@ -38,11 +40,16 @@ What the mark carries is what a command line cannot otherwise know: A flow may also declare **settings of its own** as a pydantic model, which become fields on the sheet where it is set up and lines in a file a scripted run can hand it. -## Reading one means running it +## Ordinary flows are loaded as code -There is no static description of a flow to read instead, and none is cached. A flow rewritten -between two runs — by hand, or by an agent that flow is itself driving — runs as it is *now*. -That is what makes a flow, and the skills it brings, a thing a run can improve. +There is no static description of an ordinary flow to run instead, and none is cached. A flow +rewritten between two runs — by hand, or by an agent that flow is itself driving — runs as it +is *now*. That is what makes a flow, and the skills it brings, a thing a run can improve. + +The static checker can inspect a flow's source without importing it. An atlas goes further: its +body is the description, so compiling it produces a prophecy without executing that body. Its +node functions and the rest of its module remain ordinary Python, which is why a flowverse is +still trusted as code rather than treated as data. Its own directory is importable while it runs and only while, since what a flow imports is not something the rest of the process should be able to. @@ -108,6 +115,8 @@ somebody had thought to add it would be a list that hid what there is to run. - [Writing a flow](/guide/writing-a-flow) · [Loops](/guide/loops) · [Testing a flow](/guide/testing-flows) +- [An atlas](/guide/atlas) · [Checking a flow](/guide/checking-flows) · [Python becomes a + prophecy](/features/prophecy) - [Settings of its own](/guide/flow-settings) · [A flow that calls a flow](/guide/calling-flows) · [Flowverses](/guide/flowverses) - [Flows reference](/reference/flows) — the contract, in full diff --git a/docs/features/human.md b/docs/features/human.md index 8c991647..d7b3ca58 100644 --- a/docs/features/human.md +++ b/docs/features/human.md @@ -78,4 +78,5 @@ is about to use. - [The person as an agent](/guide/human-agent) — driving one from a flow - [Questions](/guide/questions) — an agent asking its user +- [The mission board](/guide/board) — work the flow and the person can change without waiting - [Being away](/guide/afk) — deciding what happens when nobody is at the prompt diff --git a/docs/features/index.md b/docs/features/index.md index 975a6901..d908111b 100644 --- a/docs/features/index.md +++ b/docs/features/index.md @@ -9,13 +9,13 @@ import { withBase } from 'vitepress' # Features humanize runs **flows**: directories of Python that drive one or more coding agents in a loop -and write down everything they did. It does not talk to a model provider — it drives the coding -agent CLI you already have, logged in the way you already log in. +and write down everything they did. Most backends drive a coding agent you already have under +its existing login; the bundled DeepSeek Harness is the SDK-backed exception. -This is the front of the documentation, and it is what there is rather than how to use it. -Nothing on this page is a command to type: the [tutorials](/tutorials/) teach it in order -starting with the [Quickstart](/tutorials/quickstart), the [guides](/guide/) answer "how do I -use this?", and [Flows](/flows/) is what it can run out of the box. +This is the front of the documentation: one way to install it, then what the system is rather +than how to operate each part. The [tutorials](/tutorials/) teach it in order starting with the +[Quickstart](/tutorials/quickstart), the [guides](/guide/) answer "how do I use this?", and +[Flows](/flows/) is what it can run out of the box. @@ -50,45 +50,71 @@ you are deliberately not entitled to: its reference.

-## Where each page sits +## How the capabilities fit together + +A run crosses five systems: the flow that describes the work, the control plane that drives +agents, the execution fabric that decides where work lands, the run record that keeps it +continuous and readable, and the surfaces through which people start and inspect it. Hover or +focus a group to read the guarantee it owns; follow it to the best explanation. -### The deep end +

+The complete map adds the boundaries, related guides and reference for every group: +Capability map. +

+ +## Feature deep dives + +The map is broad. These pages take one mechanism far enough that its trade-offs make sense, +each around a diagram you can push. + +### Flow system + +| | | +| --- | --- | +| [Python becomes a prophecy](/features/prophecy) | A deliberately narrow flow becomes a typed graph that can be checked, compared and resumed node by node. | +| [A flow is Python](/features/flows) | Ordinary Python and compiled atlases live side by side, chosen by how much of the work must be knowable before it runs. | +| [Many turns at once](/features/concurrency) | Turns are sequential inside one session; concurrency comes from having several conversations to run. | +| [Picked up where it stopped](/features/resuming) | Ordinary flows preserve explicit state; atlases preserve completed node visits. Neither recreates a conversation. | + +### Agent control plane + +| | | +| --- | --- | +| [Many backends, one agent](/features/backends) | Native servers, streaming CLIs and Agent Client Protocol backends meet one session contract. | +| [Two accounts of one CLI](/features/accounts) | Credentials, model catalogues and failure chains stay isolated while a session changes where it runs. | +| [A line typed mid-turn](/features/steering) | Acknowledged queues put guidance into the turn that is working rather than behind it. | +| [Answers in a shape](/features/shapes) | A pydantic model is both the question and the contract the answer must satisfy. | +| [It decides when it is done](/features/goals) | A backend-owned pursuit loop continues until the model settles the objective. | +| [The moments of a turn](/features/hooks) | Typed lifecycle moments let a flow react without teaching the backend about the flow. | +| [You, as one of the agents](/features/human) | Questions, the mission board and a person-shaped agent put human decisions on the same run. | -The five that are worth reading even if you never install it. +### Execution fabric | | | | --- | --- | -| [The anchor](/features/anchor) | The agent runs here. Every syscall it makes is decided one at a time — replayed on another machine, or answered on this one. It is told none of it. | -| [Two accounts of one CLI](/features/accounts) | A CLI signs in once. humanize runs it as an account it was never signed into, by answering the paths it opens with other paths. | -| [One timeline](/features/tracing) | Every agent, every sub-agent and every program those turns ran, on one clock, in one document you open in Perfetto. | -| [A line typed mid-turn](/features/steering) | It goes *into* the turn that is running. Not queued behind it, and never quietly counted as said. | -| [Answers in a shape](/features/shapes) | A turn given a pydantic model answers with that model. The model is the whole of the question, and the answer is read back through it. | +| [The anchor](/features/anchor) | A local agent can work against a remote target while paths, processes, networks and ownership keep their meaning. | -### The shape of a run +### Run continuity and observability | | | | --- | --- | -| [Twelve CLIs, one agent](/features/backends) | Twelve coding agents and anything speaking the Agent Client Protocol, each driven through whatever it actually offers. | -| [A flow is Python](/features/flows) | A loop, a subprocess call, a file read between turns. The agents are its arguments, and the shapes a loop takes are few. | -| [Many turns at once](/features/concurrency) | Turns are sequential only inside one session. Two hundred conversations are two hundred turns. | -| [Picked up where it stopped](/features/resuming) | A loop meant to run for a week is a loop that will be stopped. What it was keeping track of survives; the conversation does not. | +| [The terminal can leave](/features/daemon) | A workspace daemon owns the PTY, so watchers may disconnect and return without owning the run. | +| [One timeline](/features/tracing) | Agent events, sub-agents and sampled processes are reconstructed on one calibrated clock. | -### Who is at the other end +### Product surfaces | | | | --- | --- | -| [It decides when it is done](/features/goals) | The backend's own goal feature: a turn that would have ended starts another, until the model says the objective is met. | -| [The moments of a turn](/features/hooks) | Seven points a turn passes through, and Python callables hung on them — and taken down again while it runs. | -| [You, as one of the agents](/features/human) | A flow puts a decision to a person the way it puts one to a model, in the same shape, with the same branch for an answer that never came. | +| [One system, four ways in](/features/surfaces) | Local discovery, schema-driven setup, Python, CLI, TUI and the daemon all reach the same run and session model. | ## The flows it comes with -Eleven of them, between the package and the flowverse humanize fetches: a ralph loop and a -stateful one, two agents alternating, an actor with a reviewer between its rounds, a loop the -model itself decides is over, and three isolated lanes with a coordinator over them. Each has a -page with its own loop played on it. +Between the package and the official flowverse are a ralph loop and a stateful one, two agents +alternating, an actor with a reviewer between its rounds, a loop the model itself decides is +over, and isolated lanes with a coordinator over them. Each has a page with its own loop +played on it.

Every one of them, with the shape of each: Flows. How to write @@ -106,25 +132,20 @@ Recorded against a stand-in coding agent, in a container of its own — see Working on these docs.

-## And the rest - -Everything above is one page because it is unusual. The ordinary parts of humanize have a guide -apiece and no page here: [efforts](/guide/efforts) and [permissions](/guide/permissions), -[skills](/guide/skills) and [questions](/guide/questions), [containers](/guide/containers) and -[worktrees](/guide/worktrees), [cost and rate](/guide/tally), [flowverses](/guide/flowverses), -[history](/guide/history), [completion](/guide/completion) and [what a project -remembers](/guide/settings). - ## Where to go next
+ + Map the whole system + Five domains, every capability group, and the right explanation for each. + Never used it Nothing installed to a run you can open in Perfetto, in fifteen minutes. What it can run - Eleven flows, the shape of each one drawn and played. + The flows it ships, with the shape of each one drawn and played. One feature @@ -138,6 +159,7 @@ remembers](/guide/settings). ::: warning Before you point one at a repository you care about humanize runs every agent with permission prompts disabled, and nothing turns them back on. A -flow is a directory of Python, and reading one means running it. Read +flow is trusted Python: loading or running it may execute its code, even though static checks +can inspect selected structure without doing so. Read [Security](/guide/security). ::: diff --git a/docs/features/prophecy.md b/docs/features/prophecy.md new file mode 100644 index 00000000..de482254 --- /dev/null +++ b/docs/features/prophecy.md @@ -0,0 +1,123 @@ +--- +pageClass: hmz-feature +--- + +# Python becomes a prophecy + +A regular flow is deliberately unconstrained Python: its next step is whatever its body does. +An **atlas** makes the other bargain. Its small declarative body is read before its first node +runs and compiled into a **prophecy** — a typed, canonical graph of every node, edge, branch, +loop and way out. + +The graph is not a picture made after the run. It is the thing the run walks. + + + +## The narrowness stops at the atlas body + +An atlas body may bind the answer of one call, branch on a value already bound, loop back to +the node that answered it, and return. Arithmetic, nested calls, exception handling and every +other piece of work belong in a node. + +That leaves the work itself as ordinary Python: + +- A **mind** is one agent turn. It has one way out, because what a model happened to say is + not yet a decision the graph can promise. +- A **logic** node is Python that drives no agent. It may count, reshape an answer, enforce a + budget, and make the decision a branch reads. +- A **supernode** is another atlas. From outside it is one node; inside it is another complete + prophecy. + +Only the declarative body is restricted. A node may use the full language, and the ordinary +[flow](/features/flows) remains the better choice when the shape of the work is meant to emerge +while it runs. + +## Compiling settles the graph before model time + +The compiler reads syntax trees without importing or executing the flow. Each call site becomes +a node with a stable id; calling the same function twice makes two nodes. The node inputs and +outputs name their shapes, and every connection is checked before a turn can spend a token. + +A loop becomes an explicit back-edge. Its head is run again with the values the body changed. +If the body changes nothing the head reads, the loop would make the same decision forever and +is refused. A return becomes a named way out of the graph, so a supernode answers with what the +return chose rather than whatever happened to run last. + +The result is all or nothing. A call inside another call, a branch hung straight off a mind, +an incompatible shape, an unbound value, or a graph nested back inside itself produces findings +and no prophecy to guess from. + +## The static reader proves only what it can see + +The same zero-execution reading checks ordinary node code and regular flows. It can establish +useful absences one function at a time: + +- a constant loop with no exit, or one that only sleeps, is an error; +- a loop whose every exit waits for an agent's shaped verdict is warned about unless the + function also owns a bound; +- a suppressed shaped answer whose field is read before the answer is guarded is warned about; +- a field compared with a literal its declared shape can never hold is warned about. + +It deliberately does not pretend to prove that an exit is reachable, follow a value through +arbitrary calls, or predict a model. Those questions belong to tests that drive the flow with +stand-in agents and adversarial answers. Static reading and executable proof meet at the same +boundary: neither needs a real model. + +## One graph has one identity + +A prophecy has a canonical text: nodes, edges, shapes and nested prophecies are ordered by what +they are, not by source formatting. Its identity is the first sixteen hexadecimal characters +of the canonical text's SHA-256 digest. Reflowing a docstring or adding a comment therefore +does not invent a new graph. + +Changing a node's implementation does not change the graph either. Changing a call site, an +edge, a shape or a nested prophecy does. That distinction is what lets the work under a stable +graph evolve without pretending a changed graph is the old one. + +A flowverse may ship the compiled prophecy beside its atlas. A run then walks that shipped +graph, while the static reading still compiles the source and reports when the two digests have +drifted apart. The shipped bytes may rebuild only the small set of tuple types a prophecy is +made of; malformed bytes or a pickle naming anything else are refused rather than executed or +silently replaced with a newly guessed graph. + +## A graph may contain another graph + +A supernode is compiled into the prophecy that reaches it. Its own nodes and shapes remain +visible underneath, and its answer is checked against the outer node that receives it. An +atlas may reach only another atlas this way: a dynamically loaded ordinary flow could have any +shape, which would leave a hole where the promised graph should be. + +The compiler follows the nesting before the outer run begins and refuses a path that reaches +back into a prophecy already being compiled. During one run, a reached atlas is read once and +held; code is not swapped underneath a graph halfway through a round. + +That is a deliberate difference from a regular flow. A regular flow handle keeps a name and +reads the target, including modules beside it, again at each call; a running flow may rewrite +that target and load it again. An atlas gives up that within-run hot reload so its prophecy +has no holes. Its files are read again by the next run. + +## Picking up is another walk over the same graph + +Every completed visit writes its answer under both the node id and the visit number. That last +part matters inside a loop: the third visit to one node is not allowed to overwrite the first +two. Visits inside a supernode are kept beneath the outer visit, however deep the nesting goes. + +When a stopped run is picked up, humanize starts at the graph's way in and rebuilds the values +already written down. Completed visits return their kept answers without running. The first +visit with no answer is exactly where work continues; by default, a node interrupted partway +runs again. A side-effecting node that has certainly taken effect may instead declare that it +should be skipped on resume, but it must answer with nothing so no missing value is hidden. + +This is precise workflow recovery, not process recovery. Backend conversations do not come +back. And the saved state carries the prophecy digest: if the graph changed, the old visits are +cleared and the run starts at the top. If only a node body changed, the digest still matches, +so completed visits stay completed and the first unfinished visit runs its node's current +code. + +## Where the detail is + +- [An atlas](/guide/atlas) — writing the restricted body and reading the compiled graph +- [Checking a flow](/guide/checking-flows) — the zero-execution findings and their limits +- [Testing a flow](/guide/testing-flows) — stand-in agents instead of model calls +- [Picking a run up](/guide/resuming) — how runs and saved state relate +- [Flows reference](/reference/flows) — every mark, graph field and finding diff --git a/docs/features/resuming.md b/docs/features/resuming.md index fd9edccc..aa02e720 100644 --- a/docs/features/resuming.md +++ b/docs/features/resuming.md @@ -5,11 +5,24 @@ pageClass: hmz-feature # Picked up where it stopped A loop meant to run for a week is a loop that will be stopped: somebody presses escape, a -machine goes down, a turn takes the process with it. So a flow may say that it can be picked up -where its last run left off — and then running it again is what picks it up. There is no flag. +machine goes down, a turn takes the process with it. humanize has two answers. An ordinary +flow may preserve the state it explicitly owns; an atlas preserves its completed node visits. +Starting the flow again is what picks either one up. +## State in one case, completed visits in the other + +The diagram shows an ordinary resumable flow. It starts its Python function again with the +dict it last wrote, so the flow decides what progress means and where to continue. It also runs +the current version of that function: saved state is an input to today's code, not a frozen +copy of yesterday's code. + +An [atlas](/features/prophecy) has a stronger coordinate. Every completed visit's answer is +written beside the identity of the prophecy it belongs to. A later run walks those answers to +the first unfinished visit and continues there. If the graph has changed, its identity has +changed and the run starts at the beginning rather than putting an old answer into a new edge. + ## What a flow keeps is its own handful of things A flow that says so is handed a dict holding what it wrote there last time. Which round it is @@ -53,10 +66,11 @@ of it recorded. A flow is a directory on disk, and what can happen next is what ## What does not come back -The conversation. A session is opened rather than reopened, so a run picked up again starts -from the task and the repository with none of the rounds before it in context. A stateful loop -stopped on its fortieth round says round 41 when it is started again — and remembers nothing -else about the forty. +The conversation. Neither a state dict nor a prophecy is a copy of a backend session. An +ordinary flow opens a session rather than reconstructing one; an atlas reuses completed visit +answers rather than recreating the context in which an agent produced them. A stateful loop +stopped on its fortieth round says round 41 when it starts again — and remembers nothing else +about the forty unless the flow wrote it down. Which is the argument for keeping little: the repository is the memory, and the handful of things the flow tracks is what has to survive. @@ -64,5 +78,6 @@ things the flow tracks is what has to survive. ## Where the detail is - [Picking a run up](/guide/resuming) — declaring it, and where the file lives +- [An atlas](/guide/atlas#stopping-and-starting) — graph identity and node-level continuation - [Tracing](/guide/tracing#what-a-run-writes-down) — what else a run writes down - [Stopping](/guide/stopping) — what escape does to a turn diff --git a/docs/features/surfaces.md b/docs/features/surfaces.md new file mode 100644 index 00000000..2761c9c4 --- /dev/null +++ b/docs/features/surfaces.md @@ -0,0 +1,107 @@ +--- +pageClass: hmz-feature +--- + +# One system, four ways in + +humanize has a Python SDK, a command line, a terminal interface and a daemon, but none of +them is a second orchestration engine. They reach the same workspace stores, flow loader and +runner. What changes is how the question is asked and how long the caller stays attached. + +The route starts before a run does: find the nearest flow, fork it when it should become +yours, let its own pydantic model describe its setup, then enter through whichever surface +fits the job. + + + +## A flow starts near you + +A flowverse is one place flows come from. A fetched flowverse is a Git repository whose +flows directory is the only part offered to humanize; the built-in flows are read from the +package instead. The project flow directory and the flow directory in your home are places +too, called local and user, even though nothing fetches either of them. + +That gives a name two different orders. The catalogue opens with the flows humanize ships, +then the official flowverse, added flowverses, and finally the local places where either has +something to show. Resolution is deliberately different: an unqualified name looks in this +project first, then your home, then everywhere else. What is nearest wins. + +A qualified name names its flowverse outright and bypasses that precedence. This is why the +catalogue can show the original and a local variant beside each other, while an unqualified +name quietly picks the one meant for this project. + +Discovery is also a trust boundary. Listing the files in a repository is cheap, but finding +the marked flows and the lines they say about themselves means loading their entry points. +Only the flows directory is considered; what is in that directory is still Python code. A +flowverse should be trusted the way a package that will run on this machine is trusted. + +## Forking changes ownership, not ancestry + +Forking copies a flow into this project's local flow directory. A directory flow arrives +whole: its entry point, the modules it imports beside itself and every skill it brings. A +single-file flow remains a single file. The fetched source stays untouched, so refreshing +its flowverse later cannot erase the local edits. + +The copy is staged beside its destination and then moved into place. If copying fails, no +half-flow is left under the name; if a local file or directory already owns that name, +forking refuses to write over it. Once the copy lands, nearest-first resolution makes the +unqualified name mean the local copy. The qualified source name still reaches the original. + +This is a source decision, not a runtime capability switch. Forking a flow does not add a +goal feature to a backend, make an unsupported hook available or change where an agent can +work. The flow's declared requirements are checked separately against the agents chosen for +the run. + +## The model is the setup surface + +A flow that needs settings declares a pydantic model as its third argument. The model is the +complete vocabulary of that setup: field names, annotations, defaults, descriptions, bounds +and validators. Optional section metadata lets a large model group related fields without +teaching the terminal interface what any of them mean. + +The interface reads those declarations directly. A boolean becomes a switch, a fixed set of +literal values becomes a list to step through, numbers move one at a time or accept typed +input, and other values are written. The description appears beside the field. When the +reader accepts the sheet, the model validates the whole set, including relationships between +fields, and returns its own refusal when the combination cannot run. + +The command line and Python surface do not get a weaker contract. Values read from a setup +file or handed to the SDK are validated by the same model. Loading a flow runs its file +again, so the earlier model class is not trusted as the current one: its fields are read back +through the class the flow declares now. A remembered setup that no longer fits starts over; +a bad setup presented to a run is refused before its first turn. + +## Shared core does not mean identical interfaces + +The SDK's workspace object is the composition point. It reaches the same settings, flows, +agents, accounts and cycles that the other surfaces show, and loads each of them only when it +is asked for. Adding a flowverse through one surface and seeing it in another is not a sync +operation. Both are reading the same store. + +Python and the execution command both use the SDK Run: a loaded runner plus its task, with +one lifecycle for running here, starting in the background, waiting, stopping and closing +the agent conversations. The command line chooses the blocking path; Python may choose +either. + +The terminal interface has a different job. It keeps the same workspace object and runner in +hand so it can configure the flow, watch several conversations, accept questions and steer a +turn while the run is live. It does not need to wrap that runner in a second orchestration +engine merely to draw it. + +The daemon is narrower still. It does not interpret flows at all. It holds the same terminal +interface in a detached process and carries its screen through a pseudoterminal. The interface +sees only the SDK Session boundary: how many terminals are reading and how to detach them +without stopping the run. That protocol keeps daemon machinery out of the interface while a +closed terminal leaves the work going on the same host. + +So unified means common state, validation and runtime semantics where the surfaces overlap. +It does not mean feature parity or identical interaction. The SDK is composable, the command +line is scriptable, the terminal interface is conversational, and the daemon owns +continuity. Each remains small because none has to redefine what a flow, setup, run or +session means. + +## Where the detail is + +- [Flowverses and forking](/guide/flowverses) · [Flow settings](/guide/flow-settings) +- [Python SDK](/reference/sdk) · [CLI](/reference/cli) · [TUI](/reference/tui) · + [Daemon](/reference/daemon) diff --git a/docs/features/tracing.md b/docs/features/tracing.md index 0708e241..137bd8ae 100644 --- a/docs/features/tracing.md +++ b/docs/features/tracing.md @@ -86,6 +86,18 @@ and the traces collected of it. Runs are named so that they sort in the order th **to the millisecond**: two started inside one second would otherwise be ordered at random, and what a flow is picked up from is the last run of it. +## A local trace is not a report + +Collecting a trace reads local run records and backend logs into another local file. Opening it +in Perfetto does not send it to humanize, and the trace may contain prompts, answers and tool +output because it is the record its owner asked to inspect. + +Reporting failures is a separate, opt-in path. It has three states — nobody has answered, yes +and no — and the unanswered state sends nothing. Report suppliers provide names, counts and +configuration rather than transcripts or trace files; final filters remove command lines, +credentials, external paths, frame context and logging breadcrumbs. The full promise, and the +switch that controls it, is in [Reporting](/guide/reporting). + ## Which backends can be read back Four backends have a reader today, and so are what a trace is made of: Claude Code, Codex, @@ -96,11 +108,12 @@ about the backend, kept where every other fact about it is. Several more write a log humanize reads *as a run happens*, which is where the running cost and rate come from. And a backend that keeps its conversation in a database — rows rather than files, with protobuf payloads — has nothing to read either way: no slices afterwards, and no -tally while it runs. Which of them is which is on [Twelve CLIs, one agent](/features/backends). +tally while it runs. Which of them is which is on [Many backends, one agent](/features/backends). ## Where the detail is - [Tracing](/guide/tracing) — collecting one, and what to look for in your first - [Tracing reference](/reference/tracing) — the cycle format, the trace format, what a slice carries +- [Reporting](/guide/reporting) — what may leave the machine, and only after consent - [Many turns at once](/features/concurrency) — why a fan-out is one process and many tracks diff --git a/docs/flows/flame-chase.md b/docs/flows/flame-chase.md index c1fbfff8..6851b8e1 100644 --- a/docs/flows/flame-chase.md +++ b/docs/flows/flame-chase.md @@ -47,4 +47,4 @@ once is counted once. - [official/rlar](/flows/rlar) — two agents, but one of them reviews rather than works - [official/parallel_flame_chase](/flows/parallel-flame-chase) — three of these at once, in isolation -- [Twelve CLIs, one agent](/features/backends) — what you can put on either side of it +- [Many backends, one agent](/features/backends) — what you can put on either side of it diff --git a/docs/guide/atlas.md b/docs/guide/atlas.md index ff92c7fe..3e61cad6 100644 --- a/docs/guide/atlas.md +++ b/docs/guide/atlas.md @@ -273,9 +273,10 @@ thing and reads as another ``` ::: warning -A shipped prophecy is a pickle, and reading one runs what it says. That is the trust a -flowverse already has — [a flow is a directory of Python and reading one means running -it](/guide/security) — but it is worth knowing that `prophecy.pkl` is code and not data. +A shipped prophecy uses pickle's format, but humanize does not open it with a general-purpose +pickle reader. It rebuilds only the seven allowlisted tuple types that make up a prophecy, +checks their canonical shape, and refuses anything else. The flow's Python is still trusted +code when it is loaded, as [the security guide](/guide/security) explains. ::: ## When to write one, and when not to diff --git a/docs/guide/index.md b/docs/guide/index.md index 6ece09dd..baea98e4 100644 --- a/docs/guide/index.md +++ b/docs/guide/index.md @@ -59,6 +59,8 @@ These are for looking things up. If you have not used humanize before, the | [Settings of its own](/guide/flow-settings) | A pydantic model that becomes `/config` fields | | [Many turns at once](/guide/async-flows) | `async def run`, and awaiting several turns | | [A flow that calls a flow](/guide/calling-flows) | Composition, and whose agents the inner one gets | +| [An atlas](/guide/atlas) | Restricted Python compiled into a typed, resumable prophecy | +| [Checking a flow](/guide/checking-flows) | Static findings and executable proof before a real turn | | [Testing a flow](/guide/testing-flows) | Checking the loop without spending a turn | | [Flowverses](/guide/flowverses) | A git repository of flows, offered by name | diff --git a/docs/reference/flows.md b/docs/reference/flows.md index 3b786ef6..8c25dd53 100644 --- a/docs/reference/flows.md +++ b/docs/reference/flows.md @@ -1326,9 +1326,10 @@ Hmz().flows.prophecy("official/review") # reads what the source compiles to ``` or [`hmz check --ship`](/reference/cli#hmz-check) from a command line. The flow's own Python -still has to be there: a prophecy names the functions its nodes are. Reading a shipped -prophecy runs what its bytes say, which is the trust a [flowverse](/guide/security) already -has. +still has to be there: a prophecy names the functions its nodes are. The shipped-file reader +rebuilds only the seven allowlisted tuple types a prophecy uses and validates the resulting +shape. Any other class or malformed shape is refused; loading the flow's Python remains the +separate trust boundary described in [Security](/guide/security). ## Testing a flow