bgterm

A lightweight way to run an interactive terminal session in the background from Python

View the Project on GitHub AnswerDotAI/bgterm

bgterm

bgterm gives you a lightweight way to run an interactive terminal session in the background from Python.

It is for the common control loop:

  1. start a session once
  2. get back a session id
  3. send more input later
  4. wait a bounded amount of time
  5. inspect the output that arrived since your last read

This is useful for REPLs, shells, and interactive CLI tools such as python, ipython, sqlite3, or custom command-line programs that you want to keep alive between calls.

Why Use It

bgterm is a good fit when you want:

It is not the right tool when you need:

For those cases, see bgtmux.

Quick Start

from bgterm import poll, start_bgterm, write_stdin

sid = start_bgterm(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"])

startup = poll(sid, 5000)
print(startup.text)

out = write_stdin(sid, "2+2\n", 500)
print(out.text)

The object wrapper is just a convenience around the same flow:

from bgterm import Session

with Session.start(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"]) as sess:
    print(sess.poll(5000).text)
    print(sess.write_stdin("2+2\n", 500).text)

How To Think About It

Every session has an unread-output cursor.

That means:

So the normal pattern is:

What yield_time_ms Means

yield_time_ms is a bounded wait, not a semantic completion signal.

When you call write_stdin() or poll() with yield_time_ms:

bgterm does not know whether your REPL command is “done”. It only knows whether terminal output changed.

In practice, callers usually decide completion by one of:

API

The primary interface is functional and sid-based.

start_bgterm(...) -> sid

Start a background session and return an integer session id.

Important arguments:

write_stdin(sid, chars="", yield_time_ms=0, max_output_bytes=65536)

Write input to the session, wait briefly, and return unread output.

Passing chars="" is valid and often useful.

poll(sid, yield_time_ms=0, max_output_bytes=65536)

Wait for unread output without sending input.

read(sid, max_output_bytes=65536)

Return unread output immediately with no waiting.

wait(sid, timeout_ms=None)

Wait for the session to exit and return its exit code, or None on timeout.

close_bgterm(sid) / terminate(sid) / kill(sid)

Shut down the session or forward process termination signals.

list_sessions()

Return the currently active session ids in this Python process.

Session

Thin convenience wrapper over the sid-based API.

PollResult

Each write_stdin() / poll() / read() call returns a PollResult.

The most useful fields are:

The offset fields are there when you need deterministic paging behavior, but most callers only need text plus the status fields above.

Large Output

bgterm is optimized for interactive polling of recent output.

If a session produces output faster than you read it:

That is usually the right tradeoff for interactive terminals. If you need the full transcript of a huge job, write to a file instead of treating the terminal as the transport.

Example: Multi-Step Interaction

from bgterm import poll, start_bgterm, write_stdin

sid = start_bgterm(["ipython", "--simple-prompt", "--no-confirm-exit", "--no-banner"])

startup = poll(sid, 5000)
print(startup.text)

out = write_stdin(sid, "import time; time.sleep(2); print('done')\n", 200)
print(out.text)    # often just echoed input so far

out = poll(sid, 2500)
print(out.text)    # should now include 'done' and the next prompt

Development

pip install -e .[dev]
pytest

Versioning

Version lives in bgterm/__init__.py as __version__.

ship-bump --part 2   # patch
ship-bump --part 1   # minor
ship-bump --part 0   # major

Release

  1. Ensure GitHub issues are labeled bug, enhancement, or breaking.
  2. Run:
ship-gh
ship-pypi

If you need sessions that are named, visible outside the current Python process, and easy to reattach to manually, see bgtmux. bgtmux uses tmux itself as the session registry, so it is often the better choice when you want inspectable long-lived terminals, cross-process visibility, or tmux-native pane and window browsing. bgterm is the better fit when you want the lightest-weight PTY-backed background session model inside one Python process.