πŸ”„ Topic

Building a local ChatGPT-to-OpenCode relay that keeps the workflow simple without letting stale replies, duplicate loops, or leaked worker output control the next step.


🎯 Goal

Let a conversation guide one local coding worker at a time, while preserving task identity, redacting outgoing content, and stopping retries from repeating work.


πŸ›  What I Did

I built a simple relay mode for the local agent bridge: take the latest instruction from a chosen ChatGPT conversation, send it to an OpenCode worker, return the worker’s handover, and repeat only when a new reply exists.

The simple version deliberately retained a few non-negotiable controls:

  • one loop process at a time through a PID lock;
  • one conversation pinned for the duration of a run;
  • a hash marking the last consumed instruction;
  • a freshness check so the loop cannot consume its own previous post;
  • redaction before worker output returns to the browser;
  • a fresh OpenCode session when the selected conversation changes.

The key learning was that simplicity should remove unnecessary ceremony, not the controls that prevent a loop from becoming self-referential or running two tasks against one local model at once.


πŸ”— Key Cybersecurity Connections

This is integrity and replay protection for an automation workflow. A repeated instruction, an old handover, or a changed browser target can all cause an agent to act on the wrong state.


πŸ” Investigation Questions

  • Can a second loop start while one is active?
  • What proves an instruction is newer than the last consumed one?
  • Does changing conversations carry old task context forward?
  • Can worker output contain secrets or internal details?
  • What happens if posting the handover fails?

🚨 Detection Opportunities

Useful events include:

  • refused second-loop start
  • stale or self-authored reply rejected
  • conversation identity changed during a run
  • outgoing text modified by the redactor
  • worker completed but browser handover failed

Example:

event=agent_bridge_instruction_rejected
reason=not_fresher_than_last_consumed
action=stop_loop_and_check_conversation_target

🧭 MITRE ATT&CK Techniques

Relevant defensive context:

  • T1059 β€” Command and Scripting Interpreter, because the worker is executing local development tasks
  • T1552 β€” Unsecured Credentials, because output needs a redaction boundary before it leaves the local worker

πŸ—Ί Visual Investigation Diagram

Selected conversation
    ↓
Freshness + identity check
    ↓
One local worker round
    ↓
Redact handover
    ↓
Record consumed state
    ↓
Await genuinely newer instruction

⚠ Challenges

The first instinct was to make the relay clever with lots of scoring and envelope rules. Live use showed that the core loop was useful, while excessive control layers introduced their own failure modes.


πŸ“š What I Learned

I learned to separate a safety boundary from workflow decoration. Conversation identity, replay prevention, one active process, and output redaction are boundaries. A rigid response format is often just friction.


➑ Next Steps

  • Keep loop state small and inspectable
  • Test crash recovery around β€œconsumed” and β€œposted” transitions
  • Keep the worker tied to one task context at a time

🧠 Reflection

Automation loops are easiest to trust when they make their state explicit: what conversation, what round, what instruction, and what was actually sent back.


🧩 Lessons Learned

What worked

Keeping the loop simple while retaining hard identity and freshness checks.

What broke

Assuming a local model server could safely handle several competing loops.

Why it broke

The worker and its GPU context were shared resources without a concurrency boundary.

Fix / takeaway

One task loop, one pinned target, and explicit consumed-state tracking.


πŸ“ˆ Skill Progression Context

This moved my local-agent work closer to reliable orchestration: not just asking a model to continue, but proving which piece of work it is continuing.


πŸ˜„ TL;DR

The useful agent loop is simpleβ€”but it still needs a lock, a target, a freshness check, and a redaction boundary.