Runbook · Multi-agent application build
![]() |
| ChatDev’s finished Live News Debate Wall turns live business headlines into an AI-parody debate—complete with policy, bureaucracy, Vogon poetry and Yoda. |
Building a live AI news debate wall with ChatDev 2.0: from requirements prompt to a tested Linux service
In the first part of this series, I installed ChatDev 2.0 on Linux, connected its virtual software company to an OpenAI-compatible GLM endpoint, and proved that the complete agent graph could design, write, review, test and deliver a runnable program. This time, the factory gets an actual assignment.
The assignment was a browser-based Live News Debate Wall: a continuously updating fictional discussion of current business news among two parody political offices, an original Vogon bureaucratic poet and Yoda. The application had to fetch headlines, generate one short response at a time, remember its conversation, survive temporary network failures and serve a responsive web page without Apache, Nginx, Node.js or a separate front-end server.
Naturally, asking a virtual software company to build a permanently scrolling wall where public institutions, a Vogon and Yoda discuss international business news seemed like the next sensible step. Sensible is doing considerable work in that sentence.
A self-contained Python application that reads the France 24 Business RSS feed, generates a continuing AI-parody debate through GLM, stores its state in SQLite, serves its own desktop and mobile web interface, exposes health and message APIs, and can run interactively, under nohup, or as a user-level systemd service.
What the finished application does
- Reads current business headlines from the France 24 Business RSS feed.
- Uses GLM through an OpenAI-compatible API.
- Generates a continuing fictional discussion among four clearly labelled AI-parody personas.
- Stores the transcript, feed history, topic state and speaker history in SQLite.
- Serves a responsive desktop and mobile browser interface.
- Auto-follows new messages without fighting readers who scroll into transcript history.
- Rejects malformed, structured, incomplete or overlong model output before storage or display.
- Runs its own asynchronous HTTP server.
- Requires no Apache, Nginx, Flask, FastAPI, Uvicorn, Node.js or reverse proxy.
- Continues operating in a degraded state if the feed or model endpoint is temporarily unavailable.
My completed ChatDev run produced a modular Python application, operating documentation, a configuration template and a focused automated suite containing 68 tests across four test modules. ChatDev reported all 68 tests passing; I then activated the delivered virtual environment and independently obtained the same result, followed by a clean Python compilation check and a successful live browser run using the real RSS and GLM services.
What the virtual software company did
The work passed through the virtual company in a recognizable software-development sequence:
- Programmer: Created the Python environment, installed the required packages and implemented the application, including its embedded browser interface, SQLite persistence, RSS processing, model integration, configuration, documentation and tests.
- Code Completion Programmer: Inspected the workspace for unfinished methods, incomplete files and missing deliverables, completed the implementation and ran the initial verification suite.
- Code Reviewer: Examined the complete modular application, dependency declarations, configuration example, documentation and test coverage, checking that the product was complete and directly operable.
- Test Error Summary Programmer: Assessed the test results and identified areas requiring correction or further verification.
- Test Modification Programmer: Inspected and corrected the existing implementation without rebuilding the project from scratch.
- Software Test Engineer: Repeatedly ran the automated suite, Python compilation checks and integration smoke test until all deliverables were confirmed and all 68 tests passed.
- Chief Product Officer: Inspected the completed application and wrote the detailed user manual.
- Chief Executive Officer: Performed the final project-wide review, reran the 68-test suite and issued the concluding verification report.
The review loops were not ceremonial. They found a test file that could not initially be collected, duplicate JavaScript declarations, missing documentation and a resource-cleanup defect triggered by a port conflict. Those problems were corrected before the final suite passed.
In other words, the virtual staff progressed from implementation through completion, review, repair, independent testing, product documentation and executive sign-off—an org chart doing something useful for once.
The entire software company charged me a labor rate of $0 per hour per resource. Model-provider usage is a separate cost, naturally, but the programmers, reviewers, test engineers and executives submitted no timesheets, requested no meetings about the timesheets and generated no procurement workflow to approve the absence of timesheets.
The dialogue produced by this demonstration is fictional and generated by artificial intelligence. It is not a real statement, quotation, policy, endorsement or official position of any person, government, institution, creator or rights holder.
What this article assumes
Complete the installation and GLM configuration described in Part 1 before continuing.
Part 1 places ChatDev in:
~/dev/ChatDev
Every ChatDev command below therefore assumes that directory. If you installed it somewhere else, substitute your own path.
Confirm that the workflow agent entries select the same GLM model configured in Part 1:
grep -nE '^[[:space:]]+name:' \
yaml_instance/ChatDev_v1.yaml
Agent entries should show name: z-ai/glm-5.2. A provider value of openai remains correct because NVIDIA NIM exposes an OpenAI-compatible API.
This basic smoke test should already work:
cd ~/dev/ChatDev
export PATH="$HOME/.local/bin:$PATH"
echo "Create a simple Python program that prints Hello, World!" \
| uv run python run.py \
--path yaml_instance/ChatDev_v1.yaml \
--name HelloWorld
The repository revision used for this build includes model-call pacing and retry improvements for providers that temporarily return rate-limit errors. During the full SDLC run, repeated HTTP 429 responses caused progressively longer waits, but the workflow eventually recovered and completed successfully.
This article uses home-relative paths such as ~/dev/ChatDev and ~/dev/LiveNewsDebateWall. The shell expands ~ to the current reader's home directory, so no particular Linux username is embedded in the procedure.
Enter the ChatDev directory
Log in to the Linux machine directly or through SSH, move into the ChatDev checkout and make sure uv is on the shell path:
cd ~/dev/ChatDev
git pull --ff-only
export PATH="$HOME/.local/bin:$PATH"
Confirm that the expected workflow and Python environment are available:
uv --version
uv run python --version
test -f yaml_instance/ChatDev_v1.yaml \
&& echo "ChatDev workflow found"
grep -nE '^[[:space:]]+name:' \
yaml_instance/ChatDev_v1.yaml
uv prints its version, Python reports a 3.12 interpreter, the workflow check succeeds, and the agent entries identify z-ai/glm-5.2.
Create the project requirements prompt
Create a directory for reusable ChatDev prompts:
mkdir -p prompts
Open a new requirements file:
nano prompts/live-news-wall-prompt.md
Paste the following application specification:
Build a complete, production-quality application named Live News Debate Wall.
Use Python 3.12 or later.
This is a full software-development lifecycle task. Do not stop after planning or initial implementation. Implement, review, test, correct, visually inspect, and verify the complete runnable project.
The application must:
1. Fetch business-news items from:
https://www.france24.com/en/business/rss
2. Generate a continuing fictional AI-parody discussion among exactly:
- POTUS
- President of the European Commission
- Gronk Vellumthud
- Yoda
3. Treat POTUS and President of the European Commission as fictional
representations of offices only.
Do not identify either persona using the name of a real officeholder.
4. Display a strong, permanent AI-parody disclaimer at both the top and
bottom of the browser window.
5. Use a responsive layout:
Desktop:
- scrolling transcript area on the left;
- fixed persona-information sidebar on the right;
- static top and bottom parody disclaimers.
Mobile:
- persona information above the transcript;
- transcript remains readable and vertically scrollable;
- no horizontal scrolling;
- static top and bottom parody disclaimers.
6. Make the transcript auto-scroll correctly.
Required behavior:
- when a new message is added and the viewer is already at or near the
bottom, automatically scroll the actual transcript container to the
newest complete message;
- do not scroll only the page body when the transcript uses its own
scrollable container;
- if the viewer intentionally scrolls upward to read older messages,
do not repeatedly force the view back to the bottom;
- resume automatic following when the viewer returns to the bottom;
- provide a visible "Jump to latest" control when newer messages arrive
while the viewer is reading older messages;
- verify this behavior on both desktop and mobile layouts.
7. Use randomized speaker order while preventing the same speaker from
speaking twice consecutively.
8. Give each persona a distinct style:
POTUS:
- concise;
- forceful;
- economy-first framing;
- maximum 45 words.
President of the European Commission:
- measured;
- policy-focused;
- maximum 65 words.
Gronk Vellumthud:
- exactly three short lines;
- approximate 5-7-5 Vogon bureaucratic poetry;
- maximum 12 words per line.
Yoda:
- terse;
- reflective;
- frequently inverted syntax;
- maximum 40 words.
9. Require every visible contribution to be complete and readable.
Each message must:
- contain only the spoken contribution;
- finish its final sentence or final poetry line;
- remain within the persona-specific word or line limit;
- contain no analysis, labels, metadata, explanation, speaker prefix,
Markdown fence, JSON, YAML, XML, tool output, or hidden reasoning;
- never end in the middle of a sentence because of a token limit.
10. Request plain-text model output only.
The model prompt must explicitly say:
- return only the final visible chat message;
- do not return JSON;
- do not use Markdown code fences;
- do not include fields such as "text", "new_point", "speaker",
"analysis", or "reasoning";
- do not prefix the answer with the persona name.
11. Defensively validate model output before displaying or storing it.
The application must:
- strip accidental leading or trailing Markdown fences;
- reject output that still resembles JSON, YAML, XML, a Python
dictionary, or structured metadata;
- reject empty output;
- reject output that ends mid-sentence;
- reject output that exceeds the persona limit;
- never display the raw model response as a fallback;
- never persist malformed model output to SQLite.
12. When a generated response fails validation:
- retry generation once with a stricter repair instruction;
- require a shorter, complete, plain-text response;
- if the repair attempt also fails, skip that contribution and log a
sanitized warning;
- do not insert broken, partial, fenced, or structured output into the
transcript.
13. Configure the model with enough output capacity to finish a short
contribution.
Do not use an excessively low token ceiling as the visible message
length control.
Use:
- persona word and line limits for visible length;
- post-generation validation for enforcement;
- a token ceiling high enough for a complete short response.
14. Fetch new RSS items at startup and periodically thereafter.
15. Change immediately to a newly discovered RSS topic and discard model
responses generated for an obsolete topic.
16. Persist the following in SQLite:
- messages;
- topics;
- known feed items;
- recent speaker history;
- topic memory;
- monotonically increasing message IDs.
17. Use an OpenAI-compatible model configuration with these defaults:
LLM_BASE_URL=https://integrate.api.nvidia.com/v1
LLM_MODEL=z-ai/glm-5.2
LLM_TEMPERATURE=0.55
LLM_MAX_TOKENS=140
LLM_TIMEOUT_SECONDS=90
18. Read the API key from configuration.
Never:
- hard-code it;
- store it in SQLite;
- display it;
- log it;
- include it in test output.
19. Space visible conversation messages by a random delay of three to six
seconds.
20. Use aiohttp for:
- the built-in HTTP server;
- RSS requests;
- asynchronous application operation.
21. Provide these endpoints:
GET /
GET /api/messages
GET /healthz
22. Embed the complete HTML, CSS and vanilla JavaScript interface in the
primary Python application.
23. Do not require:
- Nginx;
- Apache;
- Flask;
- FastAPI;
- Uvicorn;
- Node.js;
- React;
- Vue;
- a reverse proxy.
24. Continue operating in a degraded state when possible.
Required behavior:
RSS unavailable:
- continue serving the existing transcript;
- report degraded RSS health;
- retry later.
Model unavailable:
- continue serving the interface and stored transcript;
- continue RSS processing;
- report degraded model health;
- retry later.
API key absent:
- start the HTTP server;
- continue RSS processing;
- clearly report that model generation is unavailable;
- do not repeatedly make doomed model requests.
25. Produce at least:
live_news_wall.py
requirements.txt
README.md
BUILD_NOTES.md
manual.md
config/.env.example
tests/
26. Include automated unit and integration tests that do not require the
real RSS feed or a real model API.
27. Add automated tests for model-output quality and validation.
Tests must prove that:
- valid plain-text messages are accepted;
- fenced JSON is rejected or safely extracted without displaying JSON;
- incomplete JSON is rejected;
- raw dictionaries are rejected;
- Markdown code blocks are rejected;
- empty responses are rejected;
- mid-sentence responses are rejected;
- overlong responses are rejected or repaired;
- persona word limits are enforced;
- Gronk produces exactly three valid lines;
- malformed model responses are never stored;
- malformed model responses are never sent to browser clients;
- only one repair attempt is made;
- a failed repair causes the contribution to be skipped safely.
28. Add automated tests for transcript scrolling.
Tests must verify that:
- the transcript element, not merely the document body, is scrolled;
- a new message scrolls to the bottom when the viewer is already near
the bottom;
- automatic scrolling does not override a viewer who intentionally
scrolled upward;
- a "Jump to latest" control appears when unseen messages arrive;
- selecting "Jump to latest" scrolls to the newest message and resumes
automatic following;
- behavior remains correct after multiple messages;
- behavior works in desktop and mobile layouts;
- the newest message is fully visible rather than partially hidden
behind the fixed bottom disclaimer.
29. Test the rendered application with realistic transcript fixtures.
Include representative examples for:
- a short POTUS message;
- a longer but compliant European Commission message;
- a three-line Gronk poem;
- a short Yoda response;
- rapid arrival of several messages;
- a deliberately malformed fenced-JSON model response;
- a deliberately incomplete response;
- enough stored messages to require transcript scrolling.
30. Perform a human-style conversation-quality review.
A reviewer must examine representative rendered chats as a human reader,
not merely confirm that JSON schemas or unit tests pass.
The reviewer must check:
- Does every contribution look like natural visible dialogue?
- Is any JSON, Markdown fence, metadata, field name, or parser artifact
visible?
- Does every message end cleanly?
- Is every message short enough for a scrolling chat wall?
- Are the four personas distinguishable?
- Does the European Commission persona avoid long essays?
- Does Yoda remain terse?
- Does Gronk contain exactly three short poetic lines?
- Does the discussion remain relevant to the current headline?
- Does the newest message become visible automatically?
- Can a reader scroll upward without the interface fighting them?
- Is the "Jump to latest" behavior clear and functional?
- Are messages unobscured by the fixed top or bottom notices?
- Is the interface usable at typical desktop and mobile viewport sizes?
31. The human-style review must use actual rendered output or realistic
rendered fixtures.
Do not satisfy this requirement only by reading source code.
Inspect:
- the visible transcript;
- scrolling behavior;
- message wrapping;
- message completion;
- persona presentation;
- mobile layout;
- desktop layout.
32. If browser automation is available, use it to:
- load the local application;
- inject or generate representative messages;
- capture desktop and mobile screenshots;
- exercise transcript scrolling;
- verify the newest message position;
- verify "Jump to latest";
- inspect visible text for JSON or truncation.
33. If browser automation is unavailable:
- create DOM-level integration tests;
- render representative HTML fixtures;
- inspect the resulting visible text and scroll-state logic manually;
- document the limitation honestly in BUILD_NOTES.md.
34. Run the complete test suite.
35. Run a Python compilation check:
python -m compileall live_news_wall.py tests
36. Run the integration smoke test.
37. Perform a secret scan of:
- source files;
- configuration examples;
- documentation;
- logs;
- test fixtures.
38. Correct every detected defect.
39. Repeat implementation, review, testing, rendered-output inspection and
correction until:
- all executable tests pass;
- no JSON or structured-output artifacts appear in visible chats;
- messages are short and complete;
- transcript auto-scroll works correctly;
- manual human-style review finds no visible quality defects;
- documentation matches the final implementation.
40. In BUILD_NOTES.md, report:
- files created;
- files changed;
- tests run;
- exact test results;
- model-output validation performed;
- representative conversations inspected;
- desktop and mobile scrolling checks performed;
- defects found and corrected;
- any remaining limitations;
- foreground launch command;
- background launch command;
- systemd instructions;
- LAN URL format.
Do not leave TODO items, placeholders, pseudocode, mocked production logic,
known broken behavior, raw model output fallbacks, untested scrolling logic,
or unverified visible chat content.
Do not declare the project complete merely because automated tests pass.
The final acceptance decision must include examination of representative
rendered conversations and transcript behavior as a human reader would see
them.
Save and exit Nano:
Ctrl+O
Enter
Ctrl+X
Verify the file:
wc -c prompts/live-news-wall-prompt.md
sha256sum prompts/live-news-wall-prompt.md
sed -n '1,30p' prompts/live-news-wall-prompt.md
The successful run used a 12,324-byte attachment with SHA-256 208ee1c6a8aa13e32258c17631a6d8b90bccea21da23ca8a61b1ddb1c5f54a9c. Matching both values confirms that the full tightened prompt was saved without truncation or accidental edits.
run.py accepts a short task on standard input and one or more supporting files through --attachment. Keeping the detailed specification in a Markdown file makes it easier to review, revise, reuse and preserve alongside the generated project.
Start the full ChatDev software-development graph
Pipe a concise task into ChatDev and attach the detailed requirements:
printf '%s\n' \
'Build the complete Live News Debate Wall exactly as specified in the attached requirements document. Execute the full software-development lifecycle, including implementation, review, testing, correction, rendered-chat inspection, scrolling verification, human-style conversation review, and final acceptance.' \
| uv run python run.py \
--path yaml_instance/ChatDev_v1.yaml \
--name LiveNewsDebateWall \
--attachment prompts/live-news-wall-prompt.md
The project now travels through the complete multi-agent workflow. Depending on the exact ChatDev revision, the visible roles include programmers, reviewers, test specialists, documentation staff and executive reviewers.
This is not a quick single-prompt completion. The agents create files, install project dependencies, run commands, inspect failures, revise the implementation and repeat tests. A substantial application can therefore take much longer—and consume far more model tokens—than the Hello-World smoke test from Part 1.
A full application run can take hours, especially when the provider asks everyone to wait by returning HTTP 429. The successful run documented here took roughly six hours, so a detachable terminal session is the practical default. Tea remains optional, although history suggests it will become involved.
Run the full SDLC in a detached screen session
Install GNU Screen if it is not already available:
command -v screen \
|| sudo apt-get install -y screen
Start the full workflow in a detached session named LiveNewsDebateWall:
screen -dmS LiveNewsDebateWall bash -lc '
cd "$HOME/dev/ChatDev"
export PATH="$HOME/.local/bin:$PATH"
printf "%s
" \
"Build the complete Live News Debate Wall exactly as specified in the attached requirements document. Execute the full software-development lifecycle, including implementation, review, testing, correction, rendered-chat inspection, scrolling verification, human-style conversation review, and final acceptance." \
| uv run python run.py \
--path yaml_instance/ChatDev_v1.yaml \
--name LiveNewsDebateWall \
--attachment prompts/live-news-wall-prompt.md
'
Confirm that the session exists and attach to it:
screen -ls
screen -r LiveNewsDebateWall
Detach without stopping the workflow by pressing:
Ctrl+A
D
Reattach later with screen -r LiveNewsDebateWall. When the workflow finishes, screen -ls may report No Sockets found; that is normal because Screen removes a session after its command exits.
Press Ctrl+C inside the attached session to request a normal stop. From another shell, screen -S LiveNewsDebateWall -X quit terminates the session. Do not begin with kill -9 unless the process refuses every normal shutdown attempt.
Locate and inspect the generated project
ChatDev writes each normal run beneath WareHouse/ in a timestamped directory. The command below selects the newest matching run as a candidate. A newer directory may belong to an interrupted or unsuccessful attempt, so verify its contents before treating it as the completed build.
cd ~/dev/ChatDev
RUN=$(ls -dt WareHouse/LiveNewsDebateWall_* 2>/dev/null | head -1)
if [ -z "$RUN" ] || [ ! -d "$RUN/code_workspace" ]; then
echo "No usable LiveNewsDebateWall code workspace was found."
exit 1
fi
echo "Candidate run: $RUN"
test -f "$RUN/code_workspace/live_news_wall.py" \
|| { echo "Missing live_news_wall.py"; exit 1; }
test -f "$RUN/code_workspace/requirements.txt" \
|| { echo "Missing requirements.txt"; exit 1; }
test -d "$RUN/code_workspace/tests" \
|| { echo "Missing tests directory"; exit 1; }
echo "Required project files found"
Now list the generated files and inspect the candidate before copying anything:
find "$RUN/code_workspace" \
-maxdepth 3 \
-type f \
-printf '%P\n' \
| sort
A completed project should contain at least:
live_news_wall.py
config_loader.py
database.py
engine.py
feed.py
llm_client.py
personas.py
validator.py
web_server.py
requirements.txt
pyproject.toml
uv.lock
pytest.ini
README.md
BUILD_NOTES.md
manual.md
config/.env.example
tests/
Review ChatDev's build record and operating instructions:
sed -n '1,360p' "$RUN/code_workspace/BUILD_NOTES.md"
sed -n '1,320p' "$RUN/code_workspace/README.md"
sed -n '1,360p' "$RUN/code_workspace/manual.md"
A workflow reporting completion is not, by itself, proof that the application works. Inspect the deliverables, rerun the tests and start the program independently. The final authority is executable evidence, not managerial optimism—artificial or otherwise.
Copy the application to a permanent directory
The Warehouse is useful for preserving ChatDev run history. For an application that will be operated independently, copy the generated workspace into a simpler permanent directory:
mkdir -p ~/dev/LiveNewsDebateWall
rsync -a \
--exclude='.venv/' \
--exclude='__pycache__/' \
--exclude='.pytest_cache/' \
--exclude='*.pyc' \
"$RUN/code_workspace/" \
~/dev/LiveNewsDebateWall/
Enter the application directory:
cd ~/dev/LiveNewsDebateWall
Verify that the main files arrived:
test -f live_news_wall.py \
&& test -f requirements.txt \
&& test -f config/.env.example \
&& echo "Application files copied"
The ChatDev run directory remains an immutable historical artifact, while ~/dev/LiveNewsDebateWall becomes the operating copy. Excluding the generated virtual environment, bytecode and test caches also prevents machine-specific artifacts from leaking into the deployment copy.
Create a clean application environment
Use the generated pyproject.toml and uv.lock to reproduce the project environment with Python 3.12:
cd ~/dev/LiveNewsDebateWall
export PATH="$HOME/.local/bin:$PATH"
uv sync --python 3.12
source .venv/bin/activate
Part 1 did not require the system to provide a python3.12 command. If Python 3.12 is not already installed, uv can obtain it, create the isolated environment and synchronize the exact project dependencies recorded by the generated lock file.
The generated project uses a compact Python stack:
| Package | Purpose |
|---|---|
| aiohttp | Asynchronous HTTP server, RSS requests and GLM API calls |
| aiohttp-cors | Explicit cross-origin handling for the built-in web service |
| beautifulsoup4 | RSS XML parsing and feed normalization |
| lxml | Fast XML parser used by Beautiful Soup |
| python-dotenv | Local config/.env loading |
| PyYAML | YAML support used by the generated project and tests |
| pytest | Automated unit and integration testing |
| pytest-asyncio | Testing asynchronous application code |
Independently rerun the tests
Before adding a real API key or exposing the server on the network, rerun the complete automated suite from the clean operating copy:
cd ~/dev/LiveNewsDebateWall
source .venv/bin/activate
pytest -q
ChatDev reported 68 passing tests. I then activated the delivered environment inside the generated code_workspace and repeated the suite independently:
.................................................................... [100%]
68 passed in 0.57s
Compile every application module and the test suite:
python -m compileall \
live_news_wall.py \
config_loader.py \
database.py \
engine.py \
feed.py \
llm_client.py \
personas.py \
validator.py \
web_server.py \
tests
Run the integration module separately when diagnosing endpoint or persistence behavior:
pytest tests/test_integration.py \
-x \
--tb=short \
-q
The automated suite uses local fixtures and fake feed/model clients. It does not consume the real France 24 feed or spend tokens on the real GLM endpoint.
What the 68-test suite verifies
- Random speaker selection without consecutive repetition.
- Immediate topic changes when a new headline appears.
- Responses generated for obsolete topics are discarded.
- Messages, topics, known items, speaker history and topic memory persist in SQLite.
- Message IDs remain monotonically increasing.
- Valid plain-text persona contributions are accepted.
- Fenced JSON, incomplete JSON, dictionaries, YAML and code blocks are rejected.
- Malformed model output is never stored or sent to the browser.
- Only one repair attempt is made before an invalid contribution is skipped.
- POTUS, European Commission and Yoda word limits are enforced.
- Gronk is restricted to exactly three short lines.
- The transcript container—not the document body—is auto-scrolled.
- Auto-scroll does not override a reader who has scrolled upward.
- Jump to latest appears for unseen messages and resumes following.
- The newest message remains visible above the lower parody notice.
- Responsive layout markers and persona cards are present.
- Message text is inserted using
textContent, not unsafe HTML. - Resources are closed cleanly after a port-bind failure.
Create the application configuration
Copy the generated example configuration:
cp config/.env.example config/.env
chmod 600 config/.env
nano config/.env
Configure the model:
LLM_BASE_URL=https://integrate.api.nvidia.com/v1
LLM_MODEL=z-ai/glm-5.2
LLM_API_KEY=PASTE_YOUR_API_KEY_HERE
The generated example uses a 90-second model timeout. For the patient always-on deployment shown here, I increased only that timeout to ten minutes while retaining the tested message and server settings:
LLM_TEMPERATURE=0.55
LLM_MAX_TOKENS=140
LLM_TIMEOUT_SECONDS=600
RSS_FEED_URL=https://www.france24.com/en/business/rss
RSS_REFRESH_INTERVAL_SECONDS=300
MESSAGE_MIN_DELAY_SECONDS=3
MESSAGE_MAX_DELAY_SECONDS=6
HOST=0.0.0.0
PORT=8765
DB_PATH=live_news_wall.db
LLM_TIMEOUT_SECONDS controls one Live News Debate Wall request to GLM. It is separate from ChatDev's own SDLC retry behavior. Visible message length is enforced by persona-specific validation rather than by cutting every response off with an aggressively low token ceiling.
Save and exit:
Ctrl+O
Enter
Ctrl+X
Never place the real key in a blog post, Git commit, screenshot, shared terminal transcript, bug report or public issue. The chmod 600 command limits config/.env to the owning Linux account.
Start the Live News Debate Wall
Activate the environment and launch the application:
cd ~/dev/LiveNewsDebateWall
source .venv/bin/activate
python live_news_wall.py
The application starts its own asynchronous HTTP server. No separate web-server package or front-end process is required.
From the Linux machine itself, browse to:
http://127.0.0.1:8765
From another computer on the same local network, find the Linux machine's network addresses:
hostname -I
Use the private address belonging to the same local network as the computer from which the browser will connect:
http://<linux-lan-address>:8765
For illustration only, a machine might report an address such as 192.168.1.50. Substitute the address printed on your own machine:
http://192.168.1.50:8765
Docker, virtual machines, VPN software and container tools may create additional network interfaces. Choose the address on the same LAN as the browser device. Addresses such as 172.17.x.x or 172.18.x.x are often container bridges rather than the machine's primary LAN address, although network configurations vary.
Binding to 0.0.0.0 makes the application reachable through the machine's network interfaces. This demonstration does not provide user authentication or HTTPS. Do not expose port 8765 directly to the public internet. Keep it behind a trusted LAN firewall, a VPN or a properly secured reverse proxy.
Check the application health endpoint
Open another terminal on the Linux machine and request the health endpoint:
curl -s http://127.0.0.1:8765/healthz
Format the JSON for easier reading:
curl -s http://127.0.0.1:8765/healthz \
| python -m json.tool
The response reports the state of the event loop, database, RSS task, conversation task and model configuration without exposing the API key.
You can also inspect the message API:
curl -s \
'http://127.0.0.1:8765/api/messages?since=0&limit=10' \
| python -m json.tool
Run the chat wall in the background
Stop the foreground process with:
Ctrl+C
Start it under nohup:
cd ~/dev/LiveNewsDebateWall
nohup .venv/bin/python live_news_wall.py \
> live-news-wall.log 2>&1 &
echo $! > live-news-wall.pid
Follow the log:
tail -f live-news-wall.log
Check whether the process is running:
ps -p "$(cat live-news-wall.pid)"
Stop it cleanly:
kill "$(cat live-news-wall.pid)"
Start it automatically with systemd
A user-level systemd service starts the application automatically, restarts it after failures and avoids running it as root.
Create the user-service directory:
mkdir -p ~/.config/systemd/user
Create the service file:
nano ~/.config/systemd/user/live-news-wall.service
Paste:
[Unit]
Description=Live News Debate Wall
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=%h/dev/LiveNewsDebateWall
ExecStart=%h/dev/LiveNewsDebateWall/.venv/bin/python live_news_wall.py
Restart=on-failure
RestartSec=15
[Install]
WantedBy=default.target
The application reads config/.env from its working directory when it starts. The service therefore does not need systemd to import that file as a shell environment file.
The %h specifier expands to the current user's home directory. The service therefore contains no hard-coded username.
Reload the service manager and start the application:
systemctl --user daemon-reload
systemctl --user enable --now live-news-wall.service
Check the service:
systemctl --user status live-news-wall.service
Follow its journal:
journalctl --user \
-u live-news-wall.service \
-f
To allow the user service to start after boot without an interactive login, an administrator can enable lingering for the current account:
sudo loginctl enable-linger "$(id -un)"
What appears in the browser
The application divides the viewport into three vertical regions:
- A permanent AI-parody warning at the top.
- A central application region containing the transcript and persona information.
- A second permanent AI-parody warning at the bottom.
On a desktop browser, the transcript scrolls independently while the speaker-description panel remains in place. On a narrow mobile screen, the persona information moves above the transcript and the central region becomes the scrollable area.
When the reader is already at or near the bottom, each new message scrolls the transcript container to the newest complete contribution. If the reader scrolls upward to inspect earlier messages, the application stops forcing the transcript downward. A visible Jump to latest control appears when unseen messages arrive; selecting it returns to the newest message and resumes automatic following.
After the automated suite passed, I started the delivered application with the real France 24 feed and GLM endpoint and inspected it in a desktop browser. The dialogue was complete, concise and free of JSON, Markdown fences and parser artifacts; the four personas remained distinct, and both RSS and model health reported healthy. Mobile behavior was covered by responsive-markup and scroll-state tests rather than a separate live mobile-browser run.
The four fictional speakers
| Speaker | Role in the parody |
|---|---|
| POTUS | A fictional AI-generated parody of the office, not a named person or real officeholder. |
| President of the European Commission | A fictional institutional parody, not a named officeholder or official EU statement. |
| Gronk Vellumthud | An original Vogon bureaucratic poet whose economic analysis arrives in approximate three-line haiku. |
| Yoda | A terse fictional parody used for satire and software demonstration. |
Speaker order is randomized rather than cycling predictably. The same persona cannot speak twice in succession, and recent speaker history is preserved across application restarts.
How malformed model output is contained
Model instructions are requests, not guarantees, so every generated contribution passes through a defensive validator before it reaches SQLite or the browser. The application removes accidental fences, rejects structured data, code, empty text, incomplete sentences and persona-limit violations, and allows one stricter repair attempt. If the repaired response still fails, the contribution is skipped. Raw model output is never displayed as a fallback.
What happens when services fail
The application is designed not to fall over merely because the internet has had one of its moments.
RSS feed temporarily unavailable
The server keeps displaying its stored transcript and retries the feed later with backoff.
Model endpoint temporarily unavailable
The browser, database and RSS processing remain operational while the model status is displayed as degraded.
No API key configured
The HTTP server still starts and RSS processing continues, but the application does not repeatedly send doomed model requests.
Application restarted
SQLite restores the transcript, known feed items, topic information, topic memory, recent speaker history and monotonically increasing message IDs.
A new headline arrives mid-discussion
The new feed item moves to the front of the topic queue. Any model response still being generated for the obsolete topic is discarded before it can appear in the transcript.
Troubleshooting
ChatDev generation
Cause: A large ZIP or binary archive was supplied through --attachment. ChatDev attempts to place CLI attachments into the initial model message, which has a finite size limit.
Fix: Attach compact Markdown requirements rather than an entire generated workspace. For a continuation task, copy the existing files directly into a ChatDev workspace and tell the agents to inspect what is already present.
Cause: The multi-agent workflow reached the model provider's active request or quota limit.
Fix: Allow the built-in retry process to run, avoid launching multiple large workflows simultaneously and confirm that the provider account has available quota. Some provider windows can require waits of several minutes.
Cause: The NVIDIA endpoint is configured, but the workflow still selects a different model.
Fix: Verify that the workflow agent entries use z-ai/glm-5.2, confirm no obsolete model declarations remain, and start a clean ChatDev run.
Cause: The graph reached its terminal state after one or more roles failed to obtain a successful model response.
Fix: Inspect code_workspace/ directly, rerun the tests independently and use a continuation run to review and finalize the existing implementation rather than automatically rebuilding it.
Cause: The command was launched from the wrong directory, the run failed before creating its graph context, or the project name differs.
Fix: Confirm that the shell is in ~/dev/ChatDev, review the terminal or nohup log, and search with find WareHouse -maxdepth 1 -type d -name 'LiveNewsDebateWall*'.
Application environment
Cause: The machine could not locate or download the requested interpreter, usually because internet access, DNS resolution, a proxy or an outbound firewall blocked the request.
Fix: Confirm that uv is available, install the interpreter explicitly with uv python install 3.12, and then repeat uv venv --python 3.12 .venv.
Cause: The application virtual environment is not active, or the dependencies in requirements.txt were not installed into it.
Fix: Run cd ~/dev/LiveNewsDebateWall, then uv sync --python 3.12 and source .venv/bin/activate. The import name is dotenv, while the package that provides it is python-dotenv.
Cause: The generated project revision differs from the run documented here, or its tests have since changed.
Fix: This documented build contained 68 tests. The lasting requirement is that every test in the current project collects and passes, not that later revisions preserve exactly the same count.
Web server and browser
Check the process:
pgrep -af live_news_wall.py
Check the listening port:
ss -ltnp | grep ':8765'
Check the local endpoint:
curl -v http://127.0.0.1:8765/healthz
Cause: The server may be bound only to loopback, the wrong network address may be in use, the client may be on a different network, or a firewall may be blocking the port.
Fix: Confirm HOST=0.0.0.0, obtain the addresses with hostname -I, choose the address on the same LAN as the browser device, and allow TCP port 8765 through the machine's firewall if necessary.
Do not forward port 8765 directly from an internet router. Use a trusted LAN, VPN or secured reverse proxy instead.
Cause: Another process is already listening on port 8765.
Fix: Find it with ss -ltnp | grep ':8765', stop the obsolete process, or change PORT in config/.env.
Cause: An earlier application revision displayed raw model output after structured-output parsing failed.
Fix: Use the validator-based build documented here. It rejects malformed structured responses and never displays raw model output as a fallback.
Cause: The model's token ceiling is too low, or an older build lacks completion validation.
Fix: Retain sufficient output capacity, persona word limits and complete-sentence validation. The accepted deployment used LLM_MAX_TOKENS=140.
Cause: The live deployment permits one GLM request to remain active for up to ten minutes.
Fix: This is intentional when patience is preferred to premature failure. Reduce LLM_TIMEOUT_SECONDS if faster recovery is more important for your installation.
Inspect recent warnings:
grep -E 'LLM|RSS|ERROR|WARNING' live-news-wall.log \
| tail -100
Confirm:
- The API key is present in
config/.env. - The model ID and base URL are correct.
- The machine can reach the RSS and model endpoints.
- The provider account has available quota.
- The health endpoint does not report the conversation task as stopped.
The result
The interesting part of this exercise was not merely that an AI model wrote a web application. A single model can produce a convincing pile of code with considerable enthusiasm and occasionally the structural integrity of warm custard.
ChatDev instead moved the project through a larger software-development graph: implementation, completion, code review, automated testing, correction, documentation and final verification.
The completed run delivered:
- A self-contained asynchronous Python application.
- An embedded responsive browser interface.
- RSS ingestion and multi-level deduplication.
- OpenAI-compatible GLM integration.
- SQLite persistence and restart recovery.
- Randomized persona selection without consecutive repetition.
- Immediate topic interruption for newly arriving headlines.
- Graceful handling of RSS, model and database failures.
- Complete operating documentation.
- A passing 68-test suite independently rerun outside ChatDev.
- Defensive rejection and one-repair handling for malformed model responses.
- Persona-specific message limits with complete-sentence enforcement.
- Transcript auto-following that respects readers who scroll into history.
- A visible Jump to latest recovery control.
- A successful live desktop-browser run using the real RSS and GLM services.
Why this feels like the old internet again
What surprised me most was how familiar the whole experience felt. The current world of free and open-source AI models and tools reminds me strongly of the years before a career in corporations took over: new models, runtimes, agents, forks and experiments arriving at breakneck speed in a chaotic, unmanaged and noisy frontier. Documentation lags, conventions shift and today's clever solution may be replaced next week. That disorder is not a defect in the experience. It is a large part of the joy.
I feel back in my teens, when residential dial-up was just arriving and Pine and Lynx ruled the terminal, while BBSs, IRC, MUDs, NNTP newsgroups and shareware FTP archives made the network feel limitless. Coding with WATFOR FORTRAN, Borland compilers, Linux and Xenix carried the same electricity: no single blessed stack, just curiosity, spare hours and the thrill of making a machine do one more improbable thing. After years of managed corporate computing, today's AI frontier feels less like adopting another platform and more like rediscovering the pure excitement of computing itself.
The wall can now run continuously on a Linux server, collecting business headlines and converting them into a fictional debate in which economic policy eventually reaches a Vogon poetry department. This was not necessarily the future promised by distributed computing, but it is at least using the CPU.
Part 1 proved that the virtual software factory was wired correctly. This project showed what happens when it receives a detailed, testable specification instead of Hello-World: the agents divided the work, wrote a substantial application, reviewed one another, corrected defects, generated documentation and left behind something that can be operated independently of ChatDev itself. The result is equal parts useful software experiment and warning that no organizational chart is safe from poetry.
Useful links: Part 1: Install ChatDev 2.0 on Linux · My ChatDev fork · Upstream ChatDev · France 24 Business RSS · NVIDIA build catalog
Model IDs, provider limits, workflow roles and generated dependency versions evolve. Recheck the current ChatDev repository, model card, generated requirements.txt and application documentation if any command has drifted since publication.

No comments:
Post a Comment
"SEO" link builders: move on, your spam link will not get posted.
Note: Only a member of this blog may post a comment.