Control AI agents mid-execution with interactive terminal commands.
Agent Headlock is an MCP (Model Context Protocol) server that enables real-time user control over AI agent execution. It creates a "headlock" mode where AI agents pause between tasks and wait for user instructions, allowing for step-by-step interactive control of AI workflows.
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
β AI Agent ββββββββββΊβ Headlock MCP ββββββββββΊβ Terminal β
β β β Server β β (User) β
β Enters β β β β β
β Headlock Mode β HTTP/ β Holds agent β HTTP/ β Sends β
β ββββββββββββΊ β WS β state, routes β WS β instructions β
β β β instructions β β ββββββββββββΊ β
β Waits... β β β β β
β βββββββββββ β β β Taps out β
β Executes β β β β ββββββββββββΊ β
β instruction β β β β β
βββββββββββββββββββ βββββββββββββββββββ βββββββββββββββββββ
- AI Agent enters Headlock Mode - Agent calls the MCP server and blocks
- User sees waiting session - Terminal shows agent is ready for instructions
- User sends instruction - Terminal sends command to MCP server
- Agent receives and executes - Instruction flows to waiting agent
- Agent reports result - Agent sends context back, re-enters headlock
- Cycle repeats - User can send more instructions
- User taps out - Sends termination signal, agent exits gracefully
- Real-time Control: Pause AI agents mid-execution and provide step-by-step instructions
- MCP Integration: Full Model Context Protocol support with streamable HTTP
- Interactive Terminal: Rich CLI interface for managing sessions
- WebSocket Updates: Live updates for session status and activity
- Async Support: Both synchronous and asynchronous agent clients
- Session Management: Track multiple concurrent AI agent sessions
- Health Monitoring: Built-in health checks and session statistics
cd agent-headlock
# Install with pip
pip install -e .
# Or install dependencies directly
pip install fastapi uvicorn websockets pydantic rich click httpx python-dotenv mcp# Option 1: Using the installed command
headlock-server
# Option 2: Direct Python
python -m src.server
# Option 3: With uvicorn directly
uvicorn src.server:app --host 0.0.0.0 --port 8765 --reloadServer runs at http://localhost:8765
This project includes VSCode workspace settings that automatically activate the virtual environment in new terminals.
Automatic Setup:
- VSCode will automatically activate the virtual environment when opening new terminals
- The Textual terminal UI is configured as the default interface
- Shell integration is enabled for better Python support
Manual Activation (if needed):
# Activate the virtual environment manually
source .venv/bin/activate
# Or use the convenience script
./activate.sh
# Check if everything is set up correctly
python check_env.py# In a new terminal window
# Option 1: Using the installed command (Textual UI - recommended)
headlock-terminal
# Option 2: Direct Python (Textual UI)
python -m src.terminal
# Option 3: Connect to different server
headlock-terminal --server http://localhost:8765
# Option 4: Force simple mode (if needed)
headlock-terminal --simpleThe terminal now features a modern Textual-based UI that provides:
- Split-pane interface with session sidebar and main content area
- Code editor-like input with proper multi-line support
- Real-time session updates with color-coded status
- Mouse support for clicking to select sessions
- SSH-friendly operation with reliable key bindings
- Rich formatting and visual feedback
The modern Textual interface provides a floating app experience:
Layout:
- Left Sidebar: Session list with status indicators
- Main Area: Split between output display and instruction input
- Bottom: Status bar with keyboard shortcuts
Controls:
- Click sessions in the sidebar to select them
- Type instructions in the text area (Enter creates new lines)
- Ctrl+J to submit instructions
- Ctrl+R to refresh sessions
- Ctrl+T to tap out current session
- F1 for help
- Ctrl+C to quit
Session States:
- π’ WAITING - Agent ready for instructions
- π‘ PROCESSING - Agent executing instruction
- π΅ COMPLETED - Session finished
- π΄ TERMINATED - Session ended
The TextArea widget behaves like a code editor:
- Enter = New line (always)
- Ctrl+J = Submit instruction
- Works perfectly over SSH
- Syntax highlighting ready (can be added later)
| Key | Action |
|---|---|
Ctrl+J |
Submit instruction |
F1 |
Show help |
F2 |
Refresh sessions |
Ctrl+C |
Quit |
Pro Tip: The interface is designed to work seamlessly over SSH connections with proper key binding support.
This server exposes an MCP Streamable HTTP endpoint at:
POST /mcp(and related MCP traffic at the same base path)
Tools exposed:
headlock-enter_headlock(optionalsession_id, optionalcontext)- Enters headlock mode and blocks waiting for the user's first instruction
- Returns
{session_id, instruction, should_terminate}
headlock-continue_headlock(requiredsession_id, optionalcontext)- Continues headlock after executing a task, sends context and waits for next instruction
- Returns
{session_id, instruction, should_terminate}
| Endpoint | Method | Description |
|---|---|---|
/sessions |
GET | List all sessions |
/sessions/waiting |
GET | List waiting sessions |
/sessions/{id} |
GET | Get session details |
/sessions/{id}/instruct |
POST | Send instruction |
/sessions/{id}/tap-out |
POST | Terminate session |
/health |
GET | Health check |
ws://localhost:8765/ws- Global updates for all sessionsws://localhost:8765/ws/{session_id}- Updates for specific session
from src.client import HeadlockClient
client = HeadlockClient("http://localhost:8765")
# Enter headlock - blocks until user sends instruction
response = client.enter_headlock(
context="Agent ready for instructions"
)
while not response.should_terminate:
# Execute the instruction
result = do_something(response.instruction)
# Continue in headlock with result
response = client.continue_headlock(
session_id=response.session_id,
context=f"Completed: {result}"
)
print("Session ended by user")from src.client import AsyncHeadlockClient
client = AsyncHeadlockClient()
response = await client.enter_headlock(context="Ready")
while not response.should_terminate:
result = await do_task(response.instruction)
response = await client.continue_headlock(
session_id=response.session_id,
context=result
)Environment variables:
| Variable | Default | Description |
|---|---|---|
HEADLOCK_SERVER_URL |
http://localhost:8765 |
Server URL for terminal |
HOST |
0.0.0.0 |
Server bind host |
PORT |
8765 |
Server port |
agent-headlock/
βββ src/
β βββ __init__.py
β βββ server.py # FastAPI server with MCP integration
β βββ session_manager.py # Session state management
β βββ models.py # Pydantic models and data structures
β βββ terminal.py # Interactive CLI client
β βββ client.py # Python client for agents
β βββ mcp_tools.py # MCP tool definitions
βββ examples/
β βββ example_agent.py # Synchronous agent example
β βββ async_agent.py # Asynchronous agent example
βββ tests/
β βββ __init__.py
βββ pyproject.toml # Project configuration
βββ requirements.txt # Dependencies
βββ README.md
# Install dev dependencies
pip install -e ".[dev]"
# Run tests
pytest# Clone and enter directory
cd agent-headlock
# Install in development mode with dev dependencies
pip install -e ".[dev]"
# Run server in development mode
uvicorn src.server:app --reload --host 0.0.0.0 --port 8765The project uses:
- pytest for testing
- Black for code formatting (if configured)
- isort for import sorting (if configured)
- mypy for type checking (if configured)
- Server: FastAPI-based MCP server with WebSocket support
- Session Management: Async event-driven session handling
- Terminal Client: Rich CLI with real-time updates
- Client Libraries: Both sync and async Python clients for agents
This project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
For questions or support, please open an issue on GitHub.