Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# Security Policy

## Threat Model

- The trust boundary is a 0600 socket inside a 0700 directory.
- `SO_PEERCRED` is advisory logging only, not access control.
- Any process running as the same uid is trusted.
- `--as` is a name-collision guard, not authentication.

## Supported Versions

Tether is currently in active development. Security updates are provided for the latest release branch.
Expand Down
2 changes: 1 addition & 1 deletion cmd/tether/cmd_doctor.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func newDoctorCmd() *cobra.Command {

cmd := &cobra.Command{
Use: "doctor",
Short: "Check the daemon, this workspace, and every agent's wake tier",
Short: "Check the daemon, this workspace, and every agent here",
Long: doctorLong,
Example: " tether doctor\n" +
" tether doctor --json",
Expand Down
4 changes: 2 additions & 2 deletions cmd/tether/cmd_explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ import (

const explainLong = `Explain one agent's computed state and how much mail is waiting for it.

With no argument this reports on you (--as, or $TETHER_NAME). Pass an address to
inspect somebody else before sending them work.
With no argument this reports on you (--as, or whatever this session already
registered). Pass an address to inspect somebody else before sending them work.

state/source/seen/detail are computed fresh on every call, never stored: seen
is how old the evidence behind state is, and detail says what that evidence
Expand Down
2 changes: 1 addition & 1 deletion cmd/tether/cmd_wait.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ on it:
if tether wait --timeout 2m; then tether inbox; fi

This is the polling-free way to idle: agents whose harness the daemon cannot
wake (the "universal" tier) should sit in wait rather than calling inbox in a loop.`
wake should sit in wait rather than calling inbox in a loop.`

type waitOptions struct {
identityFlags
Expand Down
3 changes: 1 addition & 2 deletions cmd/tether/identity.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ import (
"github.com/praneethravuri/tether/internal/wsname"
)

// Harness identifiers reported at registration time. They are stable strings:
// the daemon maps them to a notifier and a wake tier.
// Harness identifiers reported at registration time.
const (
harnessClaudeCode = "claude-code"
harnessGeminiCLI = "gemini-cli"
Expand Down
30 changes: 30 additions & 0 deletions cmd/tether/stale_help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package main

import (
"testing"

"github.com/spf13/cobra"
)

// Guards help text against advertising features the store no longer has:
// notifier/tier columns are dropped in migrateV1ToV2, and nothing reads
// $TETHER_NAME.
func TestHelpDoesNotAdvertiseRemovedFeatures(t *testing.T) {
cases := []struct {
name string
newCmd func() *cobra.Command
unwanted string
}{
// doctor's Short only surfaces in the parent listing, not its own --help.
{"doctor wake tier", newRootCmd, "wake tier"},
{"explain TETHER_NAME", newExplainCmd, "TETHER_NAME"},
{"wait universal tier", newWaitCmd, "universal"},
}

for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
r := mustRun(t, c.newCmd(), "", "--help")
requireNotContains(t, r.stdout, c.unwanted, "help output")
})
}
}
26 changes: 26 additions & 0 deletions skills/tether/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,10 @@ triage hints, not enforced. Use `--reply-to <id>` when answering a question.
Send to `'*'` or `all` (quoted, so the shell doesn't glob it) to reach
everyone else in the workspace.

One agent's pending mail is capped at 500 messages. Past that, `note`s are
evicted first — a `handoff`/`question`/`answer` only starts dropping once
every pending `note` is gone.

## Read mail

```sh
Expand All @@ -76,6 +80,15 @@ Shows pending messages and clears them in the same step — reading IS
acknowledging, there's no separate ack command. Use `--peek` to look without
clearing.

## Recovering messages you already read

```sh
tether inbox --replay
```

Shows what an earlier `inbox` drain already delivered, in case the context
that came with it got lost. Mutually exclusive with `--peek`.

## Wait for a reply

```sh
Expand All @@ -86,6 +99,19 @@ Blocks until mail arrives (exit 0) or the timeout elapses (exit 4). Prefer
this over polling `inbox` in a loop — it wakes the instant a message is
sent instead of on some poll interval.

Nothing wakes an agent that isn't sitting in `wait`. Call it as a checkpoint:
after each subtask, before a long build, and before declaring work done.

To ask another agent something and block for the reply:

```sh
tether send backend --kind question "does /orders paginate yet?"
tether wait --timeout 5m
tether inbox
```

The answer arrives with `--reply-to` pointing at the question's message id.

## Exit codes

| Code | Meaning |
Expand Down
Loading