YouGile Agent Bridge
Mention an agent in a task chat and it starts working in your repository
A webhook receiver that turns a project-tracker conversation into a coding session. Mentioning the agent in a YouGile task spawns Claude Code in a configured working directory, and each task chat maps one-to-one onto a persistent session, so the agent remembers the thread when it is mentioned again.
YouGile ──POST──▶ https://your.domain/yougile/webhook │ ▼ FRP / cloudflared / ngrok / nginx — your choice 127.0.0.1:9100 (this service) │ ▼ first matching rule in rules.toml claude --dangerously-skip-permissions cwd = rule.workdir --session-id <chatId> (first time on this chat) --resume <chatId> (subsequent)The full path from a chat mention to a running agent, as documented in the project README.
The idea
The gap between “we should fix that” in a task tracker and a coding session is mostly copying: the task text, the repository, the context of what was already discussed. This closes it. Mention the agent in a YouGile task chat and it starts Claude Code in the working directory that task’s rule points at.
The part that makes it usable rather than a demo is the session mapping. Each task chat gets a Claude session id derived from the chat id, so the first mention starts a session and every later mention resumes it. The conversation in the tracker and the conversation with the agent are the same conversation.
Routing is configuration, not code
Which repository a task drives is decided by the first matching rule in
rules.toml, alongside the prompt template for that trigger. Adding a new
project to the setup is a config change; there is no dispatch logic to edit.
Most of the work was failure handling
The receiver runs on a small host behind whatever tunnel is convenient, which means the network is genuinely unreliable — and a webhook you do not notice has died is worse than not having one.
The behaviour above is the result. The one worth singling out is forcing
IPv4: a broken or half-open IPv6 route was producing SSL handshake timed out
on a host that was otherwise perfectly healthy, while curl connected instantly.
urllib has no Happy Eyeballs, so it sat on the dead AAAA address until timeout.
Dropping AAAA results — with a fallback for hosts that only publish them — made
the whole class of failure disappear.
Second is /healthz. It deliberately never calls the YouGile API, because a
health check that depends on the thing it is meant to report on stops answering
exactly when you need the answer. It returns the resolved rule set, the
last-known subscription snapshot from the background watcher, and how long ago
the event loop last ticked.
- Forced IPv4
- A half-open IPv6 route stalls urllib until timeout — it has no Happy Eyeballs. Dropping AAAA records removes the class of failure.
- Capped exponential backoff
- Every API call and attachment download retries on network errors, timeouts and 429/5xx.
- Permanent vs transient
- A 4xx is never retried — retrying a rejection just hides it.
- Self-healing subscription
- A watcher revives a subscription disabled upstream and retries a failed cycle in 60s rather than waiting for the next sweep.
- Event-loop watchdog
- If the asyncio loop wedges, the process is restarted rather than left silently alive.
- Non-blocking /healthz
- Returns the resolved rule set and loop heartbeat age without calling YouGile, so it stays truthful when the network is down.
Every one of these exists because of a specific failure seen in practice.