Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
# gitleaks gitleaks git (full history) secrets; allowlist in .gitleaks.toml
# loc cloc bin/ thinkers/ the harness stays under
# LOC_LIMIT code lines (the
# README's "about 10K lines"
# README's "about 11K lines"
# claim; was 10K at the launch
# blog post, week of 2026-08-24)
# alert-main Slack post after each push to main: red
Expand Down Expand Up @@ -274,7 +274,7 @@ jobs:
runs-on: ubuntu-latest
timeout-minutes: 5
env:
LOC_LIMIT: 11000
LOC_LIMIT: 11600
steps:
- uses: actions/checkout@v5
- name: Install cloc
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
[![CI](https://github.com/laude-institute/headlong/actions/workflows/ci.yml/badge.svg)](https://github.com/laude-institute/headlong/actions/workflows/ci.yml)

**Headlong** is an open source agent microharness, a complete agent harness
with a core of about 10K lines of Bash.
with a core of about 11K lines of Bash.

[Launch post](https://www.laude.org/updates/headlong-a-microharness-for-persistent-agents) |
[Announcement](https://x.com/andykonwinski/status/2091990178638496195)
Expand Down Expand Up @@ -158,7 +158,7 @@ Headlong also gives an agent a few convenience tools, such as a way to
distill and codify its experience (`mem`) and a way to save and reuse
procedures for specialized tasks (`skills`). The core is the tools the
running mind executes, the executables in `bin/` plus the thought
processes in `thinkers/`, and it comes to about 10K lines by cloc's count. A
processes in `thinkers/`, and it comes to about 11K lines by cloc's count. A
harness this small can be read end to end, and it is easy to modify and
experiment with.

Expand Down
2 changes: 1 addition & 1 deletion bin/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,6 @@ and the smaller tools around them. The table in the root
[README](../README.md) says what each one does, and
[docs/shellm.md](../docs/shellm.md) is the engine reference.

Code in `bin/` and `thinkers/` counts against the under-10K-lines core
Code in `bin/` and `thinkers/` counts against the under-11.5K-lines core
(`cloc bin/ thinkers/`), so keep additions small. Tooling that runs
around the mind rather than inside it belongs in [tools/](../tools/).
398 changes: 376 additions & 22 deletions bin/llm

Large diffs are not rendered by default.

397 changes: 323 additions & 74 deletions bin/shellm

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion design/providers.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,9 @@ llm -m qwen3:8b "hello"
| Variable | Meaning |
|---|---|
| `LLM_PROVIDER=openai-compatible` | Selects the provider (or pass `--provider openai-compatible`) |
| `LLM_API_URL` | The chat-completions endpoint. Required, no default |
| `LLM_API_URL` | The exact chat-completions or Responses endpoint. Required, no default |
| `LLM_API_KEY` | Optional. When set, sent as `Authorization: Bearer` |
| `LLM_API_FORMAT` | `chat` (default) or `responses` |

What "compatible" means here, concretely: a chat-completions endpoint
that accepts `model`, `messages`, and `max_tokens`; non-streaming
Expand All @@ -70,6 +71,15 @@ That subset is what the code exercises and the tests pin. An endpoint
that diverges from it is best effort — it may well work, but the
divergence is not a core bug to absorb.

With `LLM_API_FORMAT=responses`, compatibility instead means the synchronous
OpenAI Responses create protocol at the exact configured URL: typed `input`
items, terminal `output` items and status, Responses SSE events, and structured
errors. `LLM_RESPONSES_BODY_FILE` carries create fields beyond the completion
CLI's stable flags, while `LLM_RESPONSE_FILE` receives the full terminal object
or error envelope. Server-side lifecycle operations (retrieve, cancel, delete,
Conversations, background jobs, and WebSocket sessions) are not part of this
completion-provider seam.

`LLM_PROVIDER` is process-wide by design (decided 2026-08-26:
environment overrides are authoritative, never pattern-guessed around),
so selecting `openai-compatible` routes every completion in the
Expand Down
93 changes: 93 additions & 0 deletions design/responses-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# OpenAI Responses completion protocol

Status: implemented 2026-09-01.

## Scope

Headlong's completion boundary remains `bin/llm`. Chat Completions remains the
default. Operators opt into the OpenAI Responses create protocol with
`LLM_API_FORMAT=responses` for the `openai`, `openrouter`, or
`openai-compatible` providers.

This change covers synchronous buffered and SSE response creation, including
reasoning summaries, function call items and outputs, structured and
multimodal input items, terminal status and errors, usage, and continuation.
Response retrieval/deletion/cancellation, input-item listing, Conversations,
background responses, and WebSocket mode are separate lifecycle work.

## Wire contract

- Native OpenAI defaults to `https://api.openai.com/v1/responses` in Responses
mode. OpenRouter defaults to `https://openrouter.ai/api/v1/responses`.
`openai-compatible` still requires the exact `LLM_API_URL` endpoint.
- The existing messages input is passed as the Responses `input` array without
reshaping. This preserves typed input/output items, images, files, assistant
phases, reasoning items, function calls, and `function_call_output` items.
- The system prompt maps to `instructions`, the token cap maps to
`max_output_tokens`, and an explicit thinking level maps to
`reasoning.effort` with an automatic summary.
- `LLM_RESPONSES_BODY_FILE` may name a JSON object containing other synchronous
create fields. `bin/llm` owns and overwrites `model`, `input`, `instructions`,
`max_output_tokens`, `stream`, and `previous_response_id` so command-line and
continuation semantics remain deterministic. Conversation state is rejected
because it conflicts with this continuation contract.
- Every create requests `reasoning.encrypted_content`, preserving exact
reasoning-item replay for stateless and Zero Data Retention paths while
retaining any other caller-supplied `include` values.
- `LLM_PREVIOUS_RESPONSE_ID` adds stateful continuation.
- `LLM_RESPONSE_FILE`, when set, receives the complete terminal Response object
or provider error envelope through an atomic mode-0600 write. It is the
machine-readable channel for response IDs, all output items, function calls,
encrypted reasoning, status, errors, and usage.

The human-output contract does not change: visible `output_text` is stdout,
reasoning summaries are stderr, and `--raw` prints the buffered API object.
A function-only response is a successful protocol response even though stdout
is empty; callers consume its items from `LLM_RESPONSE_FILE`.

## Streaming and failure semantics

The SSE handler emits text and reasoning deltas as they arrive, records the
terminal response from `response.completed`, `response.incomplete`, or
`response.failed`, and maps Responses usage into the existing usage record.
Incomplete responses warn with their reason. Failed responses and `error`
events fail the call.

Retries remain legal only before protocol output is emitted. A terminal output
item counts as output even when it is a function call with no visible text.
After a text, reasoning, or output-item event, a truncated or failed stream is
never replayed automatically.

## shellm continuation

Responses mode keeps completion state only for the current `shellm` process:

1. The first call sends the trajectory-derived context in full.
2. Later calls send only newly appended user-side context plus the stable
instructions and the previous response ID.
3. In parallel, shellm retains the original input and every terminal output
item. This exact replay chain preserves encrypted reasoning and assistant
`phase` values for stateless endpoints and Zero Data Retention accounts.
4. If a continuation is rejected specifically because the previous response
cannot be referenced, before any output is emitted, shellm retries once with
the replay chain and remains stateless for the rest of the run.
5. A resumed process starts a new chain from the durable trajectory. Remote
response IDs are not persisted as durable trajectory state.

OpenRouter's Responses endpoint is stateless and therefore starts directly in
replay mode. Native OpenAI and generic compatible endpoints use automatic
stateful continuation with the safe replay fallback.

The existing thinking-text empty-response workaround remains the Chat
Completions behavior. In Responses mode, an incomplete reasoning-only Response
continues through its response ID or exact output-item replay instead of
turning a reasoning summary into an invented assistant message.

## Verification

Hermetic tests pin request JSON, endpoint selection, pass-through input,
extra-body validation and precedence, buffered extraction, terminal sidecar
permissions, response status and usage, SSE event classes, function-only
success, stateful shellm deltas, stateless replay, continuation fallback, and
unchanged Chat behavior. The implementation is additionally smoke-tested
against native OpenAI and an independent OpenAI-compatible Responses endpoint.
43 changes: 42 additions & 1 deletion docs/shellm.md
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,39 @@ LLM_API_URL=http://localhost:11434/v1/chat/completions \
llm -m qwen3:8b "hello"
```

OpenAI's Responses completion protocol is opt-in. Native OpenAI and
OpenRouter choose their `/responses` endpoint automatically;
`openai-compatible` uses the exact configured URL without appending a path:

```bash
# Native OpenAI Responses, with the complete terminal object kept for tools,
# typed output items, response IDs, status, and usage.
LLM_API_FORMAT=responses \
LLM_RESPONSE_FILE=/tmp/response.json \
llm --provider openai -m gpt-5.5 --thinking high "solve carefully"

# A compatible Responses endpoint.
LLM_API_FORMAT=responses \
LLM_PROVIDER=openai-compatible \
LLM_API_URL=https://router.example/v1/responses \
LLM_API_KEY=... \
llm -m routed-model "hello"
```

`LLM_RESPONSES_BODY_FILE` may name a JSON object with other synchronous
Responses create fields, including `tools`, `include`, `store`, `metadata`,
`text.format`, `truncation`, and provider-specific extensions. `llm` owns the
fields coupled to its CLI (`model`, `input`, `instructions`,
`previous_response_id`, `max_output_tokens`, `stream`, and command-line
reasoning settings). Typed message, image, file, reasoning, function-call, and
function-output items in `--messages-file` pass through unchanged.

Visible output text remains stdout; reasoning summaries go to stderr. The
mode-0600 `LLM_RESPONSE_FILE` sidecar is the machine channel for the complete
terminal Response or error envelope, including function-only results. Retrieval,
cancellation, deletion, Conversations, background mode, and WebSocket sessions
are lifecycle APIs and are not completion operations in this CLI.

Set `LLM_API_KEY` if the endpoint wants a bearer token. The policy for
which providers live in core is in
[design/providers.md](../design/providers.md).
Expand All @@ -310,13 +343,21 @@ shellm "what os is this?"
nested shellm runs the same way the vendor keys are, so `llm` calls
inside generated code reach the endpoint too.

To run shellm itself on Responses, set `SHELLM_API_FORMAT=responses` (and
`SHELLM_RESPONSES_BODY_FILE` for extra create fields). Native OpenAI and generic
compatible endpoints use `previous_response_id` while retaining an exact
process-local replay chain. If an endpoint rejects continuation, shellm retries
once with that chain and stays stateless for the rest of the run. OpenRouter's
documented stateless Responses endpoint uses exact replay from the first turn.
Remote response IDs and replay items are removed when the shellm process exits.

A provider that can't speak this protocol (an SDK, a vendor CLI,
signed requests) runs outside core as an adapter: set
`LLM_PROVIDER=adapter` and `LLM_ADAPTER=/path/to/executable`, and
`llm` runs that executable in place of curl. The adapter contract is
in [design/providers.md](../design/providers.md).

**Output contract:** stdout = text response, stderr = thinking tokens (Anthropic only), exit 0 = success. This makes it composable with pipes and subshells.
**Output contract:** stdout = text response, stderr = thinking/reasoning output, exit 0 = success. This makes it composable with pipes and subshells.

## mem and skills

Expand Down
2 changes: 1 addition & 1 deletion philosophy.md
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ or `./install.sh` from a checkout. The core needs nothing but bash, curl, jq, an

Here's how I think about whether an agent architecture is on the right track. I call it the Thompson test, after Ken:

1. **Can you understand every component in an afternoon?** Each of these tools is a single bash script. The whole core — the executables the mind runs plus the thinkers — is about 10K lines, and shellm, the largest, is under 3K. You can read every line of code that comprises the entire agent.
1. **Can you understand every component in an afternoon?** Each of these tools is a single bash script. The whole core — the executables the mind runs plus the thinkers — is about 11K lines, and shellm, the largest, is a few thousand. You can read every line of code that comprises the entire agent.

2. **Can you compose the pieces in ways the author didn't anticipate?** mem is just a CLI that manages files. You can pipe its output into anything. Skills are markdown files — editable, greppable, version-controllable. shellm can call itself. None of these composition patterns were "designed in" — they fall out of the Unix interface naturally.

Expand Down
Loading
Loading